2011/05/15

NHibernate: Serializing Delegates

Mike Hadlow discovered very interesting delegate feature for me. They are serializable! In outher words they can be stored in a database, e.g. with NHibernate.

Entity that represents a persistable entity could look like this:
public class PersistableDelegate
{
    public virtual Delegate Delegate { get; set; }
    public virtual string Name { get; set; }
    public virtual int Id { get; set; }

    private byte[] SerializedDelegate {
        get {
            if (Delegate == null)
                return null;

            var formatter = new BinaryFormatter();
            using (var stream = new MemoryStream()) {
                formatter.Serialize(stream, this.Delegate);
                return stream.ToArray();
        }

        set {
            if (value != null) {
                var formatter = new BinaryFormatter();
                using (var stream = new MemoryStream(value))
                    Delegate = (Delegate)formatter.Deserialize(stream);
            }
            else
                Delegate = null;
        }
    }
}

Your model can manipulate with Delegate property. SerializedDelegate property provides access to serialized data of the delegate for NHibernate – it is implementation detail and is marked as private. Getter of this property returns the delegate serialized to an array and the setter reconstructs the delegate from provided data.

FNH mapping is simple as breath then:
public class PersistableDelegateMap : ClassMap<PersistableDelegate> {
    public PersistableDelegateMap() {
        Id(x => x.Id);
        Map(x => x.Name);
        Map(Reveal.Member<PersistableDelegate>("SerializedDelegate"))
            .Length(int.MaxValue);
    }
}
Reveal class makes it possible to map private property.

Corresponding database table for MS SQL is:
CREATE TABLE [dbo].[PersistenceAction](
    [Id] [int] IDENTITY(1,1) NOT NULL,
    [Name] [nvarchar](255) NULL,
    [SerializedDelegate] [image] NULL,
)

Now it is possible to store a delegate in database:
var action = new PersistableDelegate();
action.Delegate = (Action)(() => { Console.WriteLine("Hello from persisted delegate!"); });
action.Name = "First test";

session.Save(action);
session.Flush();

and load back:
var action = session.Query<PersistableDelegate>().Where(x => x.Name == "First test").FirstOrDefault();
action.Delegate.DynamicInvoke();

I am not sure yet how can be this useful. There are definitely many limitations, e.g. lambda expression closed over local variable is not serializable. In any case such feature is very promising...

No comments:

Post a Comment