32 votes

Chargement paresseux des collections dans JSON.NET et nHibernate

Quelqu'un utilise-t-il JSON.NET avec nHibernate ? Je remarque que j'obtiens des erreurs lorsque j'essaie de charger une classe avec des collections enfant.

2 votes

Pouvez-vous nous donner des détails sur les erreurs que vous rencontrez ?

0 votes

J'obtenais le message "La méthode ou l'opération n'est pas implémentée" et le correctif de Liedman a fonctionné pour moi.

45voto

sos00 Points 725

J'étais confronté au même problème et j'ai essayé d'utiliser le code de @Liedman mais la GetSerializableMembers() n'a jamais été appelé pour la référence mandatée. J'ai trouvé une autre méthode à remplacer :

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

3 votes

+1 - Cela semble être la seule version qui fonctionne actuellement avec NH 3.3 et JSON.NET 4.5.7.

1 votes

Cela fonctionne si je fais ceci : return JsonConvert.SerializeObject(obj, new JsonSerializerSettings { ContractResolver = new NHibernateContractResolver() }) ;

24voto

Liedman Points 3144

Nous avons eu exactement ce problème, qui a été résolu en s'inspirant de la réponse de Handcraftsman ici.

Le problème vient du fait que JSON.NET ne sait pas comment sérialiser les classes proxy de NHibernate. Solution : sérialiser les instances de proxy comme leur classe de base.

Une version simplifiée du code du bricoleur se présente comme suit :

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

À mon avis, ce code présente l'avantage de continuer à s'appuyer sur le comportement par défaut de JSON.NET en ce qui concerne les attributs personnalisés, etc. (et le code est beaucoup plus court !).

Il est utilisé comme suit

        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 : Ce code a été écrit pour et utilisé avec NHibernate 2.1. Comme certains commentateurs l'ont souligné, il ne fonctionne pas d'emblée avec les versions ultérieures de NHibernate, vous devrez procéder à quelques ajustements. J'essaierai de mettre à jour le code si jamais je dois le faire avec des versions ultérieures de NHibernate.

1 votes

La solution de Liedman ne fonctionne plus puisque le type passé est une interface, donc objectType.BaseType renvoie null et se plante.

1 votes

En utilisant cette solution pour NHibernate 3.2.0.4000 et Json.NET 4.0.4, j'ai trouvé nécessaire de la combiner avec la solution de sos00. En outre, je rencontre des objets proxy qui n'implémentent PAS NHibernate.Proxy.INHibernateProxy, mais qui implémentent NHibernate.Proxy.DynamicProxy.IProxy. J'ai modifié le code pour vérifier les deux. Je n'ai pas trouvé nécessaire d'utiliser ReferenceLoopHandling.Ignore. J'espère que cela aidera quelqu'un d'autre - je sais que j'ai passé trop de temps sur ce sujet !

0 votes

@ChrisNielsen : Merci, j'ai mis à jour la réponse avec une note concernant la compatibilité avec les versions ultérieures de NHibernate.

18voto

Handcraftsman Points 3166

J'utilise NHibernate avec Json.NET et j'ai remarqué que j'obtenais des propriétés "__interceptors" inexplicables dans mes objets sérialisés. Une recherche sur Google m'a permis de trouver cette excellente solution par Lee Henson que j'ai adapté pour fonctionner avec Json.NET 3.5 Release 5 comme suit.

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

Pour l'utiliser, il suffit de placer une instance dans la propriété ContractResolver de votre JsonSerializer. Le problème de dépendance circulaire signalé par jishi peut être résolu en fixant la propriété ReferenceLoopHandling à ReferenceLoopHandling.Ignore . Voici une méthode d'extension qui peut être utilisée pour sérialiser des objets à l'aide de 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);
      }
    }
  }

2 votes

Merci pour ce code, il fonctionne très bien ! Et m'a évité d'utiliser une StatelessSession.

0 votes

Cela a cessé de fonctionner dans la version 7 de JSON.NET 3.5, mais fonctionne toujours dans la version 5 de 3.5.

0 votes

Il semble qu'ils aient mis à jour l'AssemblyVersion à partir de la version 6, ce qui signifie que si vous incluiez précédemment ce fichier dans votre solution, la version ne correspondrait pas et entraînerait une sorte d'exception de sécurité. Cela pourrait-il être le cas ?

3voto

jishi Points 10442

Vous obtenez une erreur de dépendance circulaire ? Comment ignorer les objets de la sérialisation ?

Comme le chargement paresseux génère un proxy-objet, tous les attributs de vos membres de classe seront perdus. J'ai rencontré le même problème avec le sérialiseur JSON de Newtonsoft, puisque le proxy-objet n'avait plus les attributs [JsonIgnore].

0 votes

Oui, c'est exactement le problème que je rencontrais. Je n'avais pas ignoré d'objets de la sérialisation lorsque j'ai eu les erreurs. Je pense que je dois retourner en arrière et lire la documentation correctement !

0 votes

Voir mes réponses et celles de Handcraftsman, elles contiennent une solution pour exactement ce problème.

2 votes

C'est un peu débile de voter contre une réponse qui a été écrite 1,5 ans avant la vôtre ?

3voto

David P Points 2430

Vous voudrez probablement charger rapidement la majeure partie de l'objet afin qu'il puisse être sérialisé :

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

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

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

Une bonne façon de procéder consiste à ajouter un bool au constructeur (bool isFetchEager) de la méthode de votre fournisseur de données.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X