有人将 JSON.NET 与 nHibernate 一起使用吗?我注意到当我尝试加载带有子集合的类时出现错误。
9 回答
我遇到了同样的问题,所以我尝试使用@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);
}
}
我们遇到了这个确切的问题,这是在 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 来更新代码,我会尝试更新。
我将 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);
}
}
}
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.
您是否收到循环依赖错误?你如何从序列化中忽略对象?
由于延迟加载会生成代理对象,因此您的类成员所拥有的任何属性都将丢失。我在使用 Newtonsoft JSON-serializer 时遇到了同样的问题,因为代理对象不再具有 [JsonIgnore] 属性。
我会说这是我认为的设计问题。因为 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) 的方法。这将是数据库方面的一个推动力。
当 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;
}
}
如果您序列化包含 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 包。如果其他人对这个项目感兴趣,我会投入更多的时间让它变得更好。
这就是我使用的:
- 有一个标记接口并在您的实体上继承它,例如在我的情况下为 empty
IEntity
。
我们将使用标记接口来检测合约解析器中的 NHibernate 实体类型。
public class CustomerEntity : IEntity { ... }
为 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; } }
- 正常未初始化的延迟加载属性将导致
null
json 输出。 - 集合未初始化的延迟加载属性将导致
[]
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