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.

Tags:

.NET Framework | Hibernate | WCF

Comments (28) -

4/2/2008 4:54:49 PM

Per

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

Per United States

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 United Kingdom

6/3/2008 12:47:44 PM

Per

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

Per United States

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 United States

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 United Kingdom

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 United Kingdom

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 United Kingdom

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 United States

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 United States

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 United States

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 United States

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 India

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 United States

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

1/14/2009 4:43:44 AM

Asger

@Robert: I have the exact same problem with that version. And I can't find any info about it. I was wondering if has either been fixed in newer versions, or if you've come up with a another way of getting the implementation for the proxy?

Asger Denmark

1/21/2009 7:02:33 AM

Bartosz Pierzchlewicz

@Robert and Asger
for the nHibernate 2.0  you could try this:

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)
        ILazyInitializer init = ((INHibernateProxy) obj).HibernateLazyInitializer;

        if (init.IsUninitialized)
          obj = null;
        else
          obj = init.GetImplementation();
      }

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

      return obj;
    }

But, there is other problem. It seems that when you return 'null' in this method WCF assumes that its type is System.Object!
I get InvalidCastException on client side.

On message level,when my Supplier is null it looks like this:
I get:

<b:Supplier i:type="c:anyType" xmlns:c="http://www.w3.org/2001/XMLSchema"></b:Supplier>

I suppose to get:
<b:Supplier i:nil="true"></b:Supplier>

Anyone has idea how to fix it?

Bartosz Pierzchlewicz Poland

1/22/2009 4:55:30 AM

Bartosz Pierzchlewicz

Here you have description of the problem, but still no resolution.
For me it is a bug.

social.msdn.microsoft.com/.../

Bartosz Pierzchlewicz Poland

2/10/2009 3:16:09 AM

Asger

Hi Bartosz
Thank you very much!
/Asger

Asger Denmark

2/10/2009 3:20:40 AM

Bartosz Pierzchlewicz

I have issued this error to Microsoft at: connect.microsoft.com/.../ViewFeedback.aspx
I dont't think they fix it soon.

Bartosz Pierzchlewicz Poland

2/13/2009 5:07:06 AM

Asger

Hi...

Have any body any experience with this and the NH any-association. I'm getting pretty strange results... sometimes it serializes the same instance twice, as if it cannot properly compare the instances when they are coming from the any-association?

Anybody?

/Asger

Asger Denmark

2/16/2009 10:40:47 AM

Asger

Hi Tim

First of all, thanks for the article!
As I've written here above, I get some strange results. One thing a can see is that in the implementation of GetDataContractType(Type type), I never seem to get into any of the conditions. It is because the type parameter sent in, is the type of the DataMember property, and I can not see why that should ever be of type IPersistentCollection or INHibernateProxy... or I'm I mistaken?

/Asger

Asger Denmark

2/20/2009 4:53:20 AM

TBoe

That's seems to be exactly what I'm looking for. I've been struggling through getting lazily oaded NHibernate collections sent over the wire for quite a while. Looks like the NHibernateDataContractSurrogate will help me out. Just one question: How can I tell WCF to use a DataContactSerializer with the NHibernateDataContractSurrogate passed in to its constructor like described in your article? I couldn't figure out a way of ding this. WCF by default always used the DataContactSerializer with the default constructit and I donÄt see a way for me to pass the NHibernateDataContractSurrogate argument in.

Any help will be very much appreciated.
Thnx.

TBoe Germany

2/20/2009 5:02:09 AM

Asger

@TBoe:
Check this out: blogs.msdn.com/.../561188.aspx

And be aware of this part if you reference the same instances many times:

"persistentCollection.ForceInitialization();"

Asger Denmark

4/20/2009 3:03:06 AM

Miki Watts

I also ran into the stack overflow problem, from what I can figure out, it's caused when you have type A that contains a reference to type B, and type B also contains a reference to type A.

WCF usually can handle this fine, but something about the lazy loading causes it to not recognize the cyclic references.

Did anyone find a solution to this issue?

I've also posted here: stackoverflow.com/.../wcf-serialization-and-nhibernate-lazy-loading

Miki Watts Israel

5/26/2009 4:41:21 AM

jods

@Bartosz Pierzchlewicz:
I've validated and rated your feedback on MS connect.

I've hit the same limitation and I've added some comments, including a potential workaround MS could implement. Could you re-open the issue so that it gets their attention? Hopefully we'll get a fix in .NET 4

connect.microsoft.com/.../ViewFeedback.aspx

jods Switzerland

8/19/2009 8:11:29 AM

Bartosz Pierzchlewicz

@jods - issue was closed by Microsoft, and marked as 'resolved'.
I have information from Polish MVP that this is standard procedure...
They will porobably fix this, but I don't think that it will be in .NET 4.0

Bartosz Pierzchlewicz

Bartosz Pierzchlewicz Poland

Add comment




  Country flag
biuquote
  • Comment
  • Preview
Loading


Search

Calendar

«  August 2014  »
SuMoTuWeThFrSa
272829303112
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar

Recent comments

Archive