32

Is anybody using JSON.NET with nHibernate? I notice that I am getting errors when I try to load a class with child collections.

Xm7X
  • 861
  • 1
  • 11
  • 23
user32326
  • 437
  • 1
  • 6
  • 6

9 Answers9

45

I was facing the same problem so I tried to use @Liedman's code but the GetSerializableMembers() was never get called for the proxied reference. I found another method to override:

  public class NHibernateContractResolver : DefaultContractResolver
  {
      protected override JsonContract CreateContract(Type objectType)
      {
          if (typeof(NHibernate.Proxy.INHibernateProxy).IsAssignableFrom(objectType))
              return base.CreateContract(objectType.BaseType);
          else
              return base.CreateContract(objectType);
      }
  }
TheCloudlessSky
  • 18,608
  • 15
  • 75
  • 116
Alireza Sabouri
  • 1,426
  • 1
  • 13
  • 21
  • 3
    +1 - This seems to be the only working version right now with NH 3.3 and JSON.NET 4.5.7. – TheCloudlessSky Jul 16 '12 at 14:00
  • 1
    It does work if I do this however, return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ContractResolver = new NHibernateContractResolver() }); – PandaWood Mar 06 '14 at 03:39
24

We had this exact problem, which was solved with inspiration from Handcraftsman's response here.

The problem arises from JSON.NET being confused about how to serialize NHibernate's proxy classes. Solution: serialize the proxy instances like their base class.

A simplified version of Handcraftsman's code goes like this:

public class NHibernateContractResolver : DefaultContractResolver {
    protected override List<MemberInfo> GetSerializableMembers(Type objectType) {
        if (typeof(INHibernateProxy).IsAssignableFrom(objectType)) {
            return base.GetSerializableMembers(objectType.BaseType);
        } else {
            return base.GetSerializableMembers(objectType);
        }
    }
}

IMHO, this code has the advantage of still relying on JSON.NET's default behaviour regarding custom attributes, etc. (and the code is a lot shorter!).

It is used like this

        var serializer = new JsonSerializer{
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            ContractResolver = new NHibernateContractResolver()
        };
        StringWriter stringWriter = new StringWriter();
        JsonWriter jsonWriter = new Newtonsoft.Json.JsonTextWriter(stringWriter);                
        serializer.Serialize(jsonWriter, objectToSerialize);
        string serializedObject = stringWriter.ToString();

Note: This code was written for and used with NHibernate 2.1. As some commenters have pointed out, it doesn't work out of the box with later versions of NHibernate, you will have to make some adjustments. I will try to update the code if I ever have to do it with later versions of NHibernate.

Liedman
  • 10,099
  • 4
  • 34
  • 36
  • 1
    Liedman's solution doesn't work anymore since the type passed in is Interface so objectType.BaseType returns null and crashes – Marko Kovačić Jul 25 '11 at 09:14
  • 1
    In using this solution for NHibernate 3.2.0.4000 and Json.NET 4.0.4, I found it necessary to combine this with sos00's solution. Also, I am encountering proxy objects that do NOT implement NHibernate.Proxy.INHibernateProxy, but DO implement NHibernate.Proxy.DynamicProxy.IProxy. I have modified the code to check for both. I have not found it necessary to use ReferenceLoopHandling.Ignore. Hope this helps somebody else--I know I spent too much time on this! – Chris Nielsen Nov 29 '11 at 16:36
  • @ChrisNielsen: Thanks, I updated the answer with a note regarding compatibility with later versions of NHibernate. – Liedman Nov 30 '11 at 09:28
  • Made this as an extension method... Works like a charm :) – Joel Apr 30 '13 at 17:12
18

I use NHibernate with Json.NET and noticed that I was getting inexplicable "__interceptors" properties in my serialized objects. A google search turned up this excellent solution by Lee Henson which I adapted to work with Json.NET 3.5 Release 5 as follows.

public class NHibernateContractResolver : DefaultContractResolver
{
  private static readonly MemberInfo[] NHibernateProxyInterfaceMembers = typeof(INHibernateProxy).GetMembers();

  protected override List<MemberInfo> GetSerializableMembers(Type objectType)
  {
    var members = base.GetSerializableMembers(objectType);

    members.RemoveAll(memberInfo =>
                      (IsMemberPartOfNHibernateProxyInterface(memberInfo)) ||
                      (IsMemberDynamicProxyMixin(memberInfo)) ||
                      (IsMemberMarkedWithIgnoreAttribute(memberInfo, objectType)) ||
                      (IsMemberInheritedFromProxySuperclass(memberInfo, objectType)));

    var actualMemberInfos = new List<MemberInfo>();

    foreach (var memberInfo in members)
    {
      var infos = memberInfo.DeclaringType.BaseType.GetMember(memberInfo.Name);
      actualMemberInfos.Add(infos.Length == 0 ? memberInfo : infos[0]);
    }

    return actualMemberInfos;
  }

  private static bool IsMemberDynamicProxyMixin(MemberInfo memberInfo)
  {
    return memberInfo.Name == "__interceptors";
  }

  private static bool IsMemberInheritedFromProxySuperclass(MemberInfo memberInfo, Type objectType)
  {
    return memberInfo.DeclaringType.Assembly == typeof(INHibernateProxy).Assembly;
  }

  private static bool IsMemberMarkedWithIgnoreAttribute(MemberInfo memberInfo, Type objectType)
  {
    var infos = typeof(INHibernateProxy).IsAssignableFrom(objectType)
                  ? objectType.BaseType.GetMember(memberInfo.Name)
                  : objectType.GetMember(memberInfo.Name);

    return infos[0].GetCustomAttributes(typeof(JsonIgnoreAttribute), true).Length > 0;
  }

  private static bool IsMemberPartOfNHibernateProxyInterface(MemberInfo memberInfo)
  {
    return Array.Exists(NHibernateProxyInterfaceMembers, mi => memberInfo.Name == mi.Name);
  }
}

To use it just put an instance in the ContractResolver property of your JsonSerializer. The circular dependency problem noted by jishi can be resolved by setting the ReferenceLoopHandling property to ReferenceLoopHandling.Ignore . Here's an extension method that can be used to serialize objects using Json.Net

  public static void SerializeToJsonFile<T>(this T itemToSerialize, string filePath)
  {
    using (StreamWriter streamWriter = new StreamWriter(filePath))
    {
      using (JsonWriter jsonWriter = new JsonTextWriter(streamWriter))
      {
        jsonWriter.Formatting = Formatting.Indented;
        JsonSerializer serializer = new JsonSerializer
          {
            NullValueHandling = NullValueHandling.Ignore,
            ReferenceLoopHandling = ReferenceLoopHandling.Ignore,
            ContractResolver = new NHibernateContractResolver(),
          };
        serializer.Serialize(jsonWriter, itemToSerialize);
      }
    }
  }
Handcraftsman
  • 6,863
  • 2
  • 40
  • 33
  • 2
    Thanks for this code, it works great! And saved me from using a StatelessSession. – Daniel T. Dec 17 '09 at 01:33
  • This stopped working in JSON.NET 3.5 release 7, but still works fine in 3.5 release 5. – zcrar70 Apr 29 '10 at 12:04
  • they seem to have updated the AssemblyVersion as of release 6, meaning that if you previously included this file in your solution, the version would mismatch, and throw some sort of securityexception. Could that be the case? – jishi Oct 07 '10 at 14:11
3

Are you getting a circular dependancy-error? How do you ignore objects from serialization?

Since lazy loading generates a proxy-objects, any attributes your class-members have will be lost. I ran into the same issue with Newtonsoft JSON-serializer, since the proxy-object didn't have the [JsonIgnore] attributes anymore.

jishi
  • 24,126
  • 6
  • 49
  • 75
  • See mine and Handcraftsman's replies, they contain a solution for exactly this problem. – Liedman Sep 20 '10 at 08:55
  • 2
    Kind of retarded to down vote an answer that was written 1,5 years before yours? – jishi Sep 21 '10 at 13:03
  • Yes that's exactly the issue I was running into. I hadn't been ignoring any objects from serialization when I was getting the errors though. I think I need to go back and read the docs properly! – user32326 Nov 16 '08 at 13:55
  • It's not retarded, if the answer were now unhelpful or obsolete. You may want to update it, or at least provide specific context (i.e. JS/NH version numbers you refer to?) so people finding it not-helpful with later versions won't be compelled to downvote. As a side note, I almost marked your answer as not-an-answer because I read it uncarefully and at first I thought it to be a follow-up question. You could make it a bit more obvious for lazy readers like me :) Maybe, just maybe, the downvoter also would need to re-read it, but did not. – quetzalcoatl Nov 02 '17 at 10:30
3

You will probably want to eager load most of the object so that it can be serialized:

        ICriteria ic = _session.CreateCriteria(typeof(Person));

        ic.Add(Restrictions.Eq("Id", id));

        if (fetchEager)
        {
            ic.SetFetchMode("Person", FetchMode.Eager);
        }

A nice way to do this is to add a bool to the constructor (bool isFetchEager) of your data provider method.

David P
  • 3,604
  • 3
  • 37
  • 54
1

I'd say this is a design problem in my opinion. Because NH makes connections to the database underneath all and has proxies in the middle, it is not good for the transparency of your application to serialize them directly (and as you can see Json.NET does not like them at all).

You should not serialize the entities themselves, but you should convert them into "view" objects or POCO or DTO objects (whatever you want to call them) and then serialize these.

The difference is that while NH entity may have proxies, lazy attributes, etc. View objects are simple objects with only primitives which are serializable by default.

How to manage FKs? My personal rule is:

Entity level: Person class and with a Gender class associated

View level: Person view with GenderId and GenderName properties.

This means that you need to expand your properties into primitives when converting to view objects. This way also your json objects are simpler and easier to handle.

When you need to push the changes to the DB, in my case I use AutoMapper and do a ValueResolver class which can convert your new Guid to the Gender object.

UPDATE: Check http://blog.andrewawhitaker.com/blog/2014/06/19/queryover-series-part-4-transforming/ for a way to get the view directly (AliasToBean) from NH. This would be a boost in the DB side.

Kat Lim Ruiz
  • 2,425
  • 2
  • 26
  • 32
0

The problem can happen when NHibernate wraps the nested collection properties in a PersistentGenericBag<> type.

The GetSerializableMembers and CreateContract overrides cannot detect that these nested collection properties are "proxied". One way to resolve this is to override the CreateProperty method. The trick is to get the value from the property using reflection and test whether the type is of PersistentGenericBag. This method also has the ability to filter any properties that generated exceptions.

    public class NHibernateContractResolver : DefaultContractResolver
    {
        protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
        {
            JsonProperty property = base.CreateProperty(member, memberSerialization);

            property.ShouldSerialize = instance =>
            {
                try
                {
                    PropertyInfo prop = (PropertyInfo)member;
                    if (prop.CanRead)
                    {
                        var value = prop.GetValue(instance, null);
                        if (value != null && typeof(NHibernate.Collection.Generic.PersistentGenericBag<>).IsSubclassOfRawGeneric(value.GetType()))
                            return false;

                        return true;
                    }
                }
                catch
                { }
                return false;
            };

            return property;
        }
    }

The IsSubclassOfRawGeneric extension used above:

public static class TypeExtensions
{
    public static bool IsSubclassOfRawGeneric(this Type generic, Type? toCheck)
    {
        while (toCheck != null && toCheck != typeof(object))
        {
            var cur = toCheck.IsGenericType ? toCheck.GetGenericTypeDefinition() : toCheck;
            if (generic == cur)
            {
                return true;
            }
            toCheck = toCheck?.BaseType;
        }
        return false;
    }
}
jBelanger
  • 1,526
  • 18
  • 11
0

If you serialize objects that contain NHibernate proxy classes you might end up downloading the whole database, because once the property is accessed NHibernate would trigger a request to the database. I've just implemented a Unit of Work for NHibernate: NHUnit that fixes two of the most annoying issues from NHibernate: proxy classes and cartesian product when using fetch.

How would you use this?

var customer = await _dbContext.Customers.Get(customerId) //returns a wrapper to configure the query
           .Include(c => c.Addresses.Single().Country, //include Addresses and Country
                    c => c.PhoneNumbers.Single().PhoneNumberType) //include all PhoneNumbers with PhoneNumberType
           .Unproxy() //instructs the framework to strip all the proxy classes when the Value is returned
           .Deferred() //instructs the framework to delay execution (future)
           .ValueAsync(token); //this is where all deferred queries get executed

The above code is basically configuring a query: return a customer by id with multiple child objects which should be executed with other queries (futures) and the returned result should be stripped of NHibernate proxies. The query gets executed when ValueAsync is called. NHUnit determines if it should do join with the main query, create new future queries or make use of batch fetch.

There is a simple example project on Github to show you how to use NHUnit package. If others are interested in this project I will invest more time to make it better.

0

This is what I use:

  1. Have a marker interface and inherit it on your entities, e.g. in my case empty IEntity.

We will use the marker interface to detect NHibernate entity types in the contract resolver.

   public class CustomerEntity : IEntity {    ...   }
  1. Create a custom contract resolver for JSON.NET

        public class NHibernateProxyJsonValueProvider : IValueProvider {
    
         private readonly IValueProvider _valueProvider;
    
         public NHibernateProxyJsonValueProvider(IValueProvider valueProvider)
         {
             _valueProvider = valueProvider;
         }
    
         public void SetValue(object target, object value)
         { 
             _valueProvider.SetValue(target, value); 
         }
    
         private static (bool isProxy, bool isInitialized) GetProxy(object proxy)
         {
             // this is pretty much what NHibernateUtil.IsInitialized() does.
             switch (proxy)
             {
                 case INHibernateProxy hibernateProxy:
                     return (true, !hibernateProxy.HibernateLazyInitializer.IsUninitialized);
                 case ILazyInitializedCollection initializedCollection:
                     return (true, initializedCollection.WasInitialized);
                 case IPersistentCollection persistentCollection:
                     return (true, persistentCollection.WasInitialized);
                 default:
                     return (false, false);
             }
         }
    
         public object GetValue(object target)
         { 
             object value = _valueProvider.GetValue(target);
             (bool isProxy, bool isInitialized) = GetProxy(value);
             if (isProxy)
             {
                 if (isInitialized)
                 {
                     return value;
                 }
    
                 if (value is IEnumerable)
                 {
                     return Enumerable.Empty<object>();
                 }
    
                 return null;
             }
    
             return value;
         }  
    }
    
    public class NHibernateContractResolver : CamelCasePropertyNamesContractResolver {
    
         protected override JsonContract CreateContract(Type objectType)
         {
             if (objectType.IsAssignableTo(typeof(IEntity)))
             {
                 return base.CreateObjectContract(objectType);
             }
    
             return base.CreateContract(objectType);
         } 
    
         protected override JsonProperty CreateProperty(MemberInfo member, MemberSerialization memberSerialization)
         {
             JsonProperty property = base.CreateProperty(member, memberSerialization);
    
             property.ValueProvider = new NHibernateProxyJsonValueProvider(property.ValueProvider);
    
             return property;
         }  
     }
    
  • Normal uninitialized lazy loaded properties will result in null in the json output.
  • Collection uninitialized lazy loaded properties will result in an [] empty array in json.

So for a lazy loaded property to appear in the json output you need to eagerly load it in the query or in code before serialization.

Usage:

JsonConvert.SerializeObject(entityToSerialize, new JsonSerializerSettings() {
  ContractResolver = new NHibernateContractResolver()
});

Or globally in in ASP.NET Core Startup class

   services.AddNewtonsoftJson(options =>
            { 
                options.SerializerSettings.ContractResolver = new NHibernateContractResolver();  
            });

Using:

  • NET 5.0
  • NHibernate 5.3.8
  • JSON.NET latest via ASP.NET Core
krdx
  • 1,315
  • 15
  • 22