32

有人将 JSON.NET 与 nHibernate 一起使用吗?我注意到当我尝试加载带有子集合的类时出现错误。

4

9 回答 9

45

我遇到了同样的问题,所以我尝试使用@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);
      }
  }
于 2011-05-08T09:50:10.410 回答
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 来更新代码,我会尝试更新。

于 2010-03-17T10:19:08.267 回答
18

我将 NHibernate 与 Json.NET 一起使用,并注意到我在序列化对象中得到了莫名其妙的“__interceptors”属性。谷歌搜索发现了Lee Henson 提出的这个出色的解决方案,我将其改编为与 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 属性中放置一个实例。jishi 提到的循环依赖问题可以通过将 ReferenceLoopHandling 属性设置为 ReferenceLoopHandling.Ignore 来解决。这是一个扩展方法,可用于使用 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);
      }
    }
  }
于 2009-10-30T20:57:43.363 回答
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.

于 2008-11-17T00:45:03.943 回答
3

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

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

于 2008-11-13T13:45:55.243 回答
1

我会说这是我认为的设计问题。因为 NH 在所有底层都与数据库建立连接,并且在中间有代理,所以直接序列化它们不利于应用程序的透明性(正如你所见,Json.NET 根本不喜欢它们)。

您不应该序列化实体本身,而是应该将它们转换为“视图”对象或 POCO 或 DTO 对象(无论您想如何称呼它们),然后对它们进行序列化。

不同之处在于,虽然 NH 实体可能有代理、惰性属性等。视图对象是简单的对象,只有默认情况下可序列化的原语。

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

实体级别:Person 类并与 Gender 类相关联

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

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

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

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

于 2015-04-06T22:29:51.740 回答
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;
    }
}
于 2020-12-18T16:33:48.123 回答
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确定它是否应该加入主查询、创建新的未来查询或使用批量获取。

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

于 2021-01-18T11:13:42.550 回答
0

这就是我使用的:

  1. 有一个标记接口并在您的实体上继承它,例如在我的情况下为 empty 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;
         }  
     }
    
  • 正常未初始化的延迟加载属性将导致nulljson 输出。
  • 集合未初始化的延迟加载属性将导致[]json 中的数组为空。

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

用法:

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

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

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

使用:

  • 净 5.0
  • NHibernate 5.3.8
  • JSON.NET 最新通过 ASP.NET Core
于 2021-04-26T21:53:03.060 回答