WCF serialization with NHibernate object graphs

by timvasil 2/5/2008 11:05:00 AM

Say you have a complex object graph with circular references and you need to send it across the wire as XML.  The good ol' XmlSerializer is not going to help you; you need a more powerful too like WCF's DataContractSerializer.  By slapping [DataContract] onto the classes in your object graph and [DataMember] onto the non-transient properties and fields of these classes, you're golden.  WCF serializes cyclical object graphs without complaint.  Well, almost.

If you're using Hibernate's lazy-initialization feature, you'll see a problem:  .NET will compain about unknown types (proxy wrappers) that need to be registered as well-known types.  Unfortunately the types aren't known until run-time.  Sure, you could traverse the object graph at runtime, figure out these proxy types, and pass them into a DataContractSerializer costructor, but that's far from elegant.  Even if you make it over that hurdle, .NET will complain about Hibernate's persistent set implementations too; you'll have to add those types too.  If you're using generics, that could quickly balloon to a lot of types.

Fortunately there's a straightforward solution:  IDataContractSurrogate.  WCF provides this interface specifically to get around sticky situations like this.  You can use it to unwrap and initialize proxies, and to replace Hibernate's persistent collections with built-in well-known collections. Here's how it works:

public class HibernateDataContractSurrogate : IDataContractSurrogate
{
    public HibernateDataContractSurrogate()
    {
    }

    public Type GetDataContractType(Type type)
    {
        // Serialize proxies as the base type
        if (typeof(INHibernateProxy).IsAssignableFrom(type))
        {
            type = type.GetType().BaseType;
        }

        // Serialize persistent collections as the collection interface type
        if (typeof(IPersistentCollection).IsAssignableFrom(type))
        {
            foreach (Type collInterface in type.GetInterfaces())
            {
                if (collInterface.IsGenericType)
                {
                    type = collInterface;
                    break;
                }
                else if (!collInterface.Equals(typeof(IPersistentCollection)))
                {
                    type = collInterface;
                }
            }
        }

        return type;
    }

    public object GetObjectToSerialize(object obj, Type targetType)
    {
        // Serialize proxies as the base type
        if (obj is INHibernateProxy)
        {
            // Getting the implementation of the proxy forces an initialization of the proxied object (if not yet initialized)
            obj = NHibernateProxyHelper.GetLazyInitializer((INHibernateProxy)obj).GetImplementation();
        }

        // Serialize persistent collections as the collection interface type
        if (obj is IPersistentCollection)
        {
            IPersistentCollection persistentCollection = (IPersistentCollection)obj;
            persistentCollection.ForceInitialization();
            obj = persistentCollection.Entries(); // This returns the "wrapped" collection
        }

        return obj;
    }

    public object GetDeserializedObject(object obj, Type targetType)
    {
        return obj;
    }

    public object GetCustomDataToExport(MemberInfo memberInfo, Type dataContractType)
    {
        return null;
    }

    public object GetCustomDataToExport(Type clrType, Type dataContractType)
    {
        return null;
    }

    public void GetKnownCustomDataTypes(Collection<Type> customDataTypes)
    {
    }

    public Type GetReferencedTypeOnImport(string typeName, string typeNamespace, object customData)
    {
        return null;
    }

    public CodeTypeDeclaration ProcessImportedType(CodeTypeDeclaration typeDeclaration, CodeCompileUnit compileUnit)
    {
        return typeDeclaration;
    }
}

To use this surrogate, simply pass in an instance to the DataContractSerializer constructor:

DataContractSerializer serializer = new DataContractSerializer(typeof(rootObj.GetType()), null, int.MaxValue, false, true, new HibernateDataContractSurrogate());
MemoryStream ms = new MemoryStream();
serializer.WriteObject(ms, rootObj);

Notes:

  • With this technique, you avoid having to translate your Hibernate objects into DTOs before serialization.
  • There's an alternative:  you can use NetDataContractSerializer, however that would assume you've got NHibernate and Castyle.DynamicProxy at the other end of the wire.  It also requires a bit more legwork, as described here.
  • WCF doesn't know about Iesi sets, so you're still on the hook for adding attributes such as [WellKnown(typeof(HasedSet<type>))] to classes that use sets.

Currently rated 5.0 by 11 people

  • Currently 5/5 Stars.
  • 1
  • 2
  • 3
  • 4
  • 5

Tags:

.NET Framework | Hibernate | WCF

 

About the author

Tim Vasil Tim Vasil
I'm a software engineer living in Cambridge, MA.

E-mail me Send mail

Search

Calendar

<<  September 2010  >>
MoTuWeThFrSaSu
303112345
6789101112
13141516171819
20212223242526
27282930123
45678910

View posts in large calendar

Recent comments