lazy - JSON.NET y nHibernate Carga diferida de colecciones
chrome flags (6)
¿Alguien está usando JSON.NET con nHibernate? Noté que recibo errores cuando intento cargar una clase con colecciones secundarias.
¿Estás obteniendo un error de dependencia circular? ¿Cómo ignoras los objetos de la serialización?
Como la carga diferida genera un proxy-objects, se perderán todos los atributos que tengan los miembros de su clase. Me encontré con el mismo problema con Newtonsoft JSON-serializer, ya que el proxy-objeto ya no tenía los atributos [JsonIgnore].
Probablemente desees cargar la mayor parte del objeto para que se pueda serializar:
ICriteria ic = _session.CreateCriteria(typeof(Person));
ic.Add(Restrictions.Eq("Id", id));
if (fetchEager)
{
ic.SetFetchMode("Person", FetchMode.Eager);
}
Una buena forma de hacerlo es agregar un bool al constructor (bool isFetchEager) de su método de proveedor de datos.
Tuvimos este problema exacto, que fue resuelto con la inspiración de la respuesta del Artesano aquí.
El problema surge cuando JSON.NET se confunde sobre cómo serializar las clases de proxy de NHibernate. Solución: serialice las instancias de proxy como su clase base.
Una versión simplificada del código de Handcraftsman es la siguiente:
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);
}
}
}
En mi humilde opinión, este código tiene la ventaja de seguir confiando en el comportamiento predeterminado de JSON.NET con respecto a los atributos personalizados, etc. (¡y el código es mucho más corto!).
Se usa así
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();
Nota: Este código fue escrito y utilizado con NHibernate 2.1. Como algunos comentaristas han señalado, no funciona de la caja con las versiones posteriores de NHibernate, tendrá que hacer algunos ajustes. Intentaré actualizar el código si alguna vez tengo que hacerlo con versiones posteriores de NHibernate.
Uso NHibernate con Json.NET y me di cuenta de que estaba obteniendo propiedades inexplicables de "__interceptor" en mis objetos serializados. Una búsqueda en google descubrió esta excelente solución de Lee Henson que adapté para trabajar con Json.NET 3.5 Release 5 de la siguiente manera.
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);
}
}
Para usarlo, simplemente coloque una instancia en la propiedad ContractResolver de su JsonSerializer. El problema de dependencia circular observado por jishi puede resolverse estableciendo la propiedad ReferenceLoopHandling en ReferenceLoopHandling.Ignore. Aquí hay un método de extensión que se puede usar para serializar objetos usando 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);
}
}
}
Yo diría que esto es un problema de diseño en mi opinión. Debido a que NH hace conexiones a la base de datos debajo de todo y tiene proxys en el medio, no es bueno que la transparencia de su aplicación serialice directamente (y como puede ver, a Json.NET no le gustan para nada).
No debe serializar las entidades en sí, sino que debe convertirlas en objetos "ver" u objetos POCO o DTO (como quiera que los llame) y luego serializarlos.
La diferencia es que mientras la entidad NH puede tener proxies, atributos perezosos, etc. Los objetos de vista son objetos simples con solo primitivas que son serializables por defecto.
¿Cómo administrar FKs? Mi regla personal es:
Nivel de entidad: clase de persona y con una clase de género asociada
Nivel de vista: vista de persona con propiedades GenderId y GenderName.
Esto significa que necesita expandir sus propiedades en primitivas al convertir objetos para ver. De esta forma también sus objetos json son más simples y fáciles de manejar.
Cuando necesite enviar los cambios a la base de datos, en mi caso utilizo AutoMapper y realizo una clase ValueResolver que puede convertir su nuevo Guid al objeto Gender.
ACTUALIZACIÓN: consulte http://blog.andrewawhitaker.com/blog/2014/06/19/queryover-series-part-4-transforming/ para obtener una vista directa (AliasToBean) de NH. Esto sería un impulso en el lado de DB.
Estaba enfrentando el mismo problema, así que traté de usar el código de @ Liedman, pero GetSerializableMembers()
nunca fue llamado para la referencia por proxy. Encontré otro método para anular:
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);
}
}