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 5 people

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

Tags:

.NET Framework | Hibernate | WCF

Related posts

Comments

4/2/2008 4:54:49 PM

Per

This looks pretty interesting. Are there any concrete source code examples available?

Per us

6/3/2008 12:37:08 PM

N

Magic!!! I have been trying to xml serialize all day today but the nHibernate kept messing everything...so now I manage to seralize.
However is there a way to do similar thing with NetDataContractSerializer as the collections are deserialized as arrays. I looked at the blog you recommended but I it doesnt completely help me - I get an exception: ...INHibernateProxy1' does not have DataContractAttribute attribute and therefore cannot support IExtensibleDataObject. .
I cannot really know ahead the known type and i was wondering if there is a similar way as the IDataContractSurrogate that can be used for NetDataContractSerializer .

Many thanks!
N

N gb

6/3/2008 12:47:44 PM

Per

Have a look at davybrion.com/.../ it contains some examples uing the UseNetDataContractSerializer attribute.

Per us

6/4/2008 6:32:49 AM

N

Hi Per,
Thanks for the reference. I am not really using WCF - just its objects for serialize/deserialize. I use the sample and the serialize works OK but when I deserialize I get the following exception:

System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. ---> NHibernate.LazyInitializationException: Could not initialize proxy - no Session..

Any ideas?

Many thanks Smile
N

N

6/5/2008 1:15:52 PM

Per

It looks like an attempt is made to fetch more data from the dB but you're 'outside' an NHibernate session.
Have you tried doing the same thing within a session?
I assume you get this error on the client side, or is it at the server side?
In any event, my understanding is that you need to be within an NHibernate session when 'querying' NH proxies (or an exception will be thrown). You could 'theoretically' disable lazy loading and by doing so no proxy will have to be serialized. You should also verify that you have references to NHibernate and Castyle.DynamicProxy on both sides.

http:/www.canoniccorp.com

Per us

6/6/2008 11:05:54 AM

N

I realize that there is an attempt to fetch data from the database however I thought their might be another way to sort it. When I use the example above using IDataContractSurrogate and DataContractSerializer the serializing and deserailizing works OK without any session exception and I hoped there will be similar way to use NetDataContractSerializer - however I cannot get it to work and I am not really sure why I got the exception when I used NetDataContractSerializer but did not get when using DataContractSerializer - I serialize/deserialize the same object (and I have to use lazy="true" otherwose I will have circular initializtion).
Any ideas would be helpful!!
BTW - i dont really use WCF - there is no client/server - I just use WCF serialization objects as i am using nHibernate.
Thx
N

N gb

6/9/2008 10:31:14 PM

Tim Vasil

Hey N,

I haven't used NetDataContractSerializer, but I can foresee it being a problem with NHibernate objects, since you're effectively going to be serializing the proxy wrappers. Upon deserialization, the proxies are not going to be in a valid state (since they'll be uninitialized and won't point to a valid NHibernate session). I think you're choices are either use the plain old DataContractSerializer (with any special type handing in the GetDeserializedObject method of the data contract surrogate), or eliminate your use of proxy classes.

You say you use lazy="true" because you'll otherwise have "circular initialization." I don't understand this. NHibernate can handle circular references even if you're not using lazy initialization, so if you're getting an error with lazy="false" you probably have done something wrong in your mapping file.

Good luck!

Tim Vasil

6/10/2008 5:04:21 AM

N

Thanks you! I think I will have to stick to DataContractSerializer..
Thanks Smile

N gb

6/11/2008 8:32:24 AM

N

Thinking of that... Can you can you eliminate the use of proxies for an instance of a class? And if yes, how?
Thanks Smile

N gb

7/7/2008 2:48:19 PM

Robert

I tried updating this for the nHibernate 2.0 trunk, but I keep getting stack overflow exceptions with the code below. Any glowing errors?

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();
obj = ((INHibernateProxy)obj).HibernateLazyInitializer.GetImplementation(); //<!------- HERE
}

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

return obj;
}

Robert us

8/8/2008 3:23:34 PM

Naravin Kim

I'm followed you code and did not see any reference to NHibernateProxyHelper(). Where did you get NHibernateProxyHelper from?
Thanks.
Nk.

Naravin Kim us

8/11/2008 10:45:14 AM

timvasil

Naravin,

NHibernateProxyHelper is a class that ships with NHibernate. You can find it in the NHibernate.Proxy namespace.

timvasil us

8/11/2008 4:51:01 PM

Naravin.Kim@eds.com

Thanks Tim. Since I'm new to Hibernate, if NHibernate is part of .net Frameword 3.5x or from this Org - http://www.hibernate.org? Thanks.
Nk.

Naravin.Kim@eds.com us

8/15/2008 2:33:50 AM

Ajith Khan

Implemented it... But iam getting an exception "The communication object, System.ServiceModel.Channels.ServiceChannel, cannot be used for communication because it is in the Faulted state."

Ajith Khan in

12/16/2008 8:44:54 PM

Jim

How would I do this when using WCF with ASP.NET, since I can't create the DataContractSerializer with the custom surrogate myself?

Jim us

12/17/2008 4:26:43 PM

Tim Vasil

Jim,

This point might be of some assistance: blogs.msdn.com/.../561188.aspx

Good luck!
Tim

Tim Vasil

Add comment


(Will show your Gravatar icon)  

  Country flag





Live preview

1/6/2009 3:06:03 PM

 

About the author

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

E-mail me Send mail

Search

Calendar

<<  January 2009  >>
MoTuWeThFrSaSu
2930311234
567891011
12131415161718
19202122232425
2627282930311
2345678

View posts in large calendar

Recent comments