Using serializable values

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 Exceptions, complete with any nested InnerExceptions.

Getting ready

Complete the Getting ready instructions at the beginning of this chapter.

How to do it…

  1. Add a new folder named SerializableValues to the MappingRecipes project.
  2. Add a new class named 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; }
    }
  3. Add an embedded mapping named 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>
  4. Add a class named 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);
          }
        }
      }
    }
  5. Run the application and start the SerializableValues recipe.

How it works…

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.

There's more…

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.102.159