JSON.NET和nHibernate Lazy加载集合

时间:2008-11-13 10:13:06

标签: nhibernate json.net

有人在使用JSON.NET和nHibernate吗?我注意到当我尝试加载带有子集合的类时,我遇到了错误。

9 个答案:

答案 0 :(得分:42)

我遇到了同样的问题,所以我尝试使用@Liedman的代码,但GetSerializableMembers()永远不会被调用代理引用。 我发现了另一种覆盖方法:

  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);
      }
  }

答案 1 :(得分:24)

我们遇到了这个确切的问题,这是在Handcraftsman的回应中得到了解决的。

问题源于JSON.NET对如何序列化NHibernate的代理类感到困惑。解决方案:将代理实例序列化为基类。

Handcraftsman代码的简化版本如下:

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);
        }
    }
}

恕我直言,这段代码的优势仍然是依赖于JSON.NET关于自定义属性等的默认行为(并且代码更短!)。

像这样使用

        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();

注意:此代码是为NHibernate 2.1编写并使用的。正如一些评论者指出的那样,它不能与更新版本的NHibernate一起开箱即用,你将不得不做一些调整。如果我必须使用更新版本的NHibernate,我会尝试更新代码。

答案 2 :(得分:18)

我将NHibernate与Json.NET一起使用,并注意到我在序列化对象中遇到了无法理解的“__interceptors”属性。 Lee Henson进行了一次谷歌搜索this excellent solution,我改编自Json.NET 3.5 Release 5,如下所示。

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);
  }
}

要使用它,只需在JsonSerializer的ContractResolver属性中放置一个实例。可以通过将ReferenceLoopHandling属性设置为ReferenceLoopHandling.Ignore来解决jishi指出的循环依赖问题。这是一个扩展方法,可用于使用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);
      }
    }
  }

答案 3 :(得分:3)

您是否收到循环依赖错误?如何忽略序列化中的对象?

由于延迟加载会生成代理对象,因此类成员拥有的任何属性都将丢失。我遇到了与Newtonsoft JSON-serializer相同的问题,因为代理对象不再具有[JsonIgnore]属性。

答案 4 :(得分:3)

您可能希望加载大部分对象,以便对其进行序列化:

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

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

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

一个很好的方法是在数据提供者方法的构造函数(bool isFetchEager)中添加一个bool。

答案 5 :(得分:1)

我认为这是我认为的设计问题。因为NH在所有数据库下建立了连接并且在中间有代理,所以直接序列化它们对应用程序的透明性是不利的(并且你可以看到Json.NET根本不喜欢它们)。

您不应该自己序列化实体,但是您应该将它们转换为&#34; view&#34;对象或POCO或DTO对象(无论你想要什么),然后序列化它们。

不同之处在于,NH实体可能具有代理,延迟属性等。视图对象是简单对象,只有基元,默认情况下可序列化。

如何管理FK? 我个人的规则是:

实体级别:人员类和关联的性别类

查看级别:具有GenderId和GenderName属性的人员视图。

这意味着您需要在转换为视图对象时将属性扩展为基元。这样,您的json对象也更简单,更容易处理。

当您需要将更改推送到数据库时,在我的情况下,我使用AutoMapper并执行ValueResolver类,它可以将您的新Guid转换为Gender对象。

更新:检查http://blog.andrewawhitaker.com/blog/2014/06/19/queryover-series-part-4-transforming/是否可以直接从NH获取视图(AliasToBean)。这将是数据库方面的一个推动力。

答案 6 :(得分:0)

当NHibernate将嵌套的集合属性包装为PersistentGenericBag <>类型时,可能会发生此问题。

GetSerializableMembers和CreateContract替代无法检测到这些嵌套的集合属性被“代理”。解决此问题的一种方法是重写CreateProperty方法。诀窍是使用反射从属性中获取值并测试类型是否为PersistentGenericBag。此方法还可以过滤生成异常的所有属性。

    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;
        }
    }

上面使用的IsSubclassOfRawGeneric扩展名:

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;
    }
}

答案 7 :(得分:0)

如果您序列化包含 NHibernate 代理类的对象,您可能最终会下载整个数据库,因为一旦访问该属性,NHibernate 将触发对数据库的请求。 我刚刚为 NHibernate 实现了一个工作单元:NHUnit,它解决了 NHibernate 中两个最烦人的问题:使用 fetch 时的代理类和笛卡尔积。

你会如何使用它?

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

上面的代码基本上是配置一个查询:通过 id 返回一个带有多个子对象的客户,这些子对象应该与其他查询(期货)一起执行,并且返回的结果应该去除 NHibernate 代理。调用 ValueAsync 时执行查询。 NHUnit 决定是否应该加入主查询、创建新的未来查询或使用批量提取。

有一个 simple example project on Github 向您展示如何使用 NHUnit 包。如果其他人对这个项目感兴趣,我会投入更多时间让它变得更好。

答案 8 :(得分:0)

这是我使用的:

  1. 有一个标记接口并在您的实体上继承它,例如在我的情况下为空 IEntity

我们将使用标记接口来检测合同解析器中的 NHibernate 实体类型。

   public class CustomerEntity : IEntity {    ...   }
  1. 为 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;
         }  
     }
    
  • 正常未初始化的延迟加载属性将导致 json 输出中的 null
  • 集合未初始化的延迟加载属性将导致 json 中的 [] 空数组。

因此,要使延迟加载的属性出现在 json 输出中,您需要在序列化之前在查询或代码中急切加载它。

用法:

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

或者全局在 ASP.NET Core Startup 类中

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

使用:

  • NET 5.0
  • NHibernate 5.3.8
  • JSON.NET 最新通过 ASP.NET Core