At some point you may need to store objects in the database that are either impossible to map using NHibernate (the object may be of a type you have no control over) or the resulting relational model would simply be too complex. As long as the objects are of a type that is serializable (using BinaryFormatter
), NHibernate can save and retrieve them transparently.
In this recipe we'll show a crude NHibernate based error log, which stores raised Exception
s, complete with any nested InnerException
s.
SerializableValues
to the MappingRecipes
project.Error
to the folder:public class Error { public virtual Guid Id { get; set; } public virtual DateTime ErrorDateTime { get; set; } public virtual Exception Exception { get; set; } }
Error.hbm.xml
to the folder:<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="MappingRecipes" namespace="MappingRecipes.SerializableValues"> <class name="Error"> <id name="Id"> <generator class="guid.comb"/> </id> <property name="ErrorDateTime" /> <property name="Exception" type="Serializable"/> </class> </hibernate-mapping>
Recipe
to the folder:using System; using NH4CookbookHelpers; using NHibernate; namespace MappingRecipes.SerializableValues { public class Recipe : HbmMappingRecipe { protected override void AddInitialData( ISessionFactory sessionFactory) { try { throw new ApplicationException( "Something happened", new NullReferenceException("Something was null") ); } catch (Exception ex) { LogError(ex, sessionFactory); } } private void LogError(Exception exception, ISessionFactory sessionFactory) { using (var session= sessionFactory .OpenStatelessSession()) { using (var tx = session.BeginTransaction()) { session.Insert(new Error { ErrorDateTime = DateTime.Now, Exception = exception }); tx.Commit(); } } } public override void RunQueries(ISession session) { var error = session.QueryOver<Error>() .SingleOrDefault(); if (error.Exception != null) { ShowException(error.Exception); Console.WriteLine("Stack trace:" + error.Exception.StackTrace); } } private void ShowException(Exception exception) { Console.WriteLine("Type: {0}, Message: {1}", exception.Message,exception.GetType()); if (exception.InnerException != null) { ShowException(exception.InnerException); } } } }
SerializableValues
recipe.As long as an object can be serialized to a type which itself can be stored in the database, NHibernate can handle it.
In our example, we mapped the Exception
property of the Error
class as we do any other property. We added one little thing, though, with the attribute type="Serializable"
. This tells NHibernate to use the type NHibernate.Type.SerializableType
when persisting the object.
SerializableType
stores its values using a binary column type in the database (the actual type will vary between DBMS:es) and it uses a BinaryFormatter
to serialize and deserialize the mapped properties value to and from a byte array.
We expose our Exception
property as a System.Exception
. We could also have chosen object
or dynamic
, since the BinaryFormatter
includes the type of the serialized object in the byte array data. It's therefore possible to map a property which can hold any type of serializable value and you can change and update values to different types, whenever you want.
SerializableType
is an implementation of an NHibernate IType
, but you can also create a similar serializing and deserializing type, by implementing the IUserType
interface. Such a type could store and retrieve complex values using JSON, BSON, Google protocol buffers or any other serialization technique which can convert an object into a string or a byte array.
The basics for creating a JSON serializing type, using the popular JSON.NET
library, could be summed up in these two methods:
public object NullSafeGet(IDataReader rs, string[] names, object owner) { if (names.Length != 1) throw new InvalidOperationException("Invalid column count"); var val = rs[names[0]] as string; if (val != null && !string.IsNullOrWhiteSpace(val)) { return JsonConvert.DeserializeObject<T>(val); } return null; } public void NullSafeSet(IDbCommand cmd, object value, int index) { var parameter = (DbParameter)cmd.Parameters[index]; if (value == null) { parameter.Value = DBNull.Value; } else { parameter.Value = JsonConvert.SerializeObject(value); } }
There's more to it than that, but that sums up the basics. For more on creating custom types, read the first two recipes in Chapter 8, Extending NHibernate.
3.133.123.34