Json et exception de référence circulaire

J’ai un object qui a une référence circulaire à un autre object. Compte tenu de la relation entre ces objects, c’est le bon design.

Pour illustrer

Machine => Customer => Machine 

Comme prévu, je rencontre un problème lorsque j’essaie d’utiliser Json pour sérialiser une machine ou un object client. Ce dont je ne suis pas sûr, c’est comment résoudre ce problème car je ne veux pas rompre la relation entre les objects Machine et Client. Quelles sont les options pour résoudre ce problème?

modifier

Actuellement, j’utilise la méthode Json fournie par la classe de base Controller . Donc, la sérialisation que je fais est aussi simple que:

 Json(machineForm); 

Mettre à jour:

N’essayez pas d’utiliser NonSerializedAtsortingbute , car JavaScriptSerializer ignore apparemment.

Au lieu de cela, utilisez le ScriptIgnoreAtsortingbute dans System.Web.Script.Serialization .

 public class Machine { public ssortingng Customer { get; set; } // Other members // ... } public class Customer { [ScriptIgnore] public Machine Machine { get; set; } // Parent reference? // Other members // ... } 

De cette façon, lorsque vous lancez une Machine dans la méthode Json , celle-ci traverse la relation entre Machine et Customer mais n’essaye pas de revenir du Customer à la Machine .

La relation est toujours là pour que votre code fonctionne comme il le Json , mais JavaScriptSerializer (utilisé par la méthode Json ) l’ignorera.

Je réponds à cela malgré son âge car c’est le 3ème résultat (actuellement) de Google pour “référence circulaire json.encode” et bien que je ne sois pas d’accord avec les réponses (complètement) ci-dessus, l’utilisation de ScriptIgnoreAtsortingbute suppose que vous N’importe où dans votre code ne voudra pas traverser la relation dans l’autre sens pour certains JSON. Je ne crois pas au locking de votre modèle à cause d’un cas d’utilisation.

Cela m’a inspiré à utiliser cette solution simple.

Étant donné que vous travaillez dans une vue dans MVC, vous avez le modèle et vous souhaitez simplement affecter le modèle à ViewData.Model dans votre contrôleur, continuez et utilisez une requête LINQ dans votre vue pour aplatir correctement les données en supprimant le problème. référence circulaire pour le JSON particulier que vous voulez comme ceci:

 var jsonMachines = from m in machineForm select new { mX, mY, // other Machine properties you desire Customer = new { m.Customer.Id, m.Customer.Name, // other Customer properties you desire }}; return Json(jsonMachines); 

Ou si la machine -> relation client est 1 .. * -> *, puis essayez:

 var jsonMachines = from m in machineForm select new { mX, mY, // other machine properties you desire Customers = new List( (from c in m.Customers select new Customer() { Id = c.Id, Name = c.Name, // Other Customer properties you desire }).Cast()) }; return Json(jsonMachines); 

Sur la base de la réponse de txl, vous devez désactiver le chargement paresseux et la création de proxy et vous pouvez utiliser les méthodes habituelles pour obtenir vos données.

Exemple:

 //Resortingeve Items with Json: public JsonResult Search(ssortingng id = "") { db.Configuration.LazyLoadingEnabled = false; db.Configuration.ProxyCreationEnabled = false; var res = db.Table.Where(a => a.Name.Contains(id)).Take(8); return Json(res, JsonRequestBehavior.AllowGet); } 

Utilisez pour avoir le même problème. J’ai créé une méthode d’extension simple, qui “aplatit” les objects L2E dans un IDictionary. Un IDictionary est sérialisé correctement par JavaScriptSerializer. Le Json résultant est identique à la sérialisation directe de l’object.

Comme je limite le niveau de sérialisation, les références circulaires sont évitées. Il n’inclura pas non plus 1-> n tables liées (Entitysets).

  private static IDictionary JsonFlatten(object data, int maxLevel, int currLevel) { var result = new Dictionary(); var myType = data.GetType(); var myAssembly = myType.Assembly; var props = myType.GetProperties(); foreach (var prop in props) { // Remove EntityKey etc. if (prop.Name.StartsWith("Entity")) { continue; } if (prop.Name.EndsWith("Reference")) { continue; } // Do not include lookups to linked tables Type typeOfProp = prop.PropertyType; if (typeOfProp.Name.StartsWith("EntityCollection")) { continue; } // If the type is from my assembly == custom type // include it, but flattened if (typeOfProp.Assembly == myAssembly) { if (currLevel < maxLevel) { result.Add(prop.Name, JsonFlatten(prop.GetValue(data, null), maxLevel, currLevel + 1)); } } else { result.Add(prop.Name, prop.GetValue(data, null)); } } return result; } public static IDictionary JsonFlatten(this Controller controller, object data, int maxLevel = 2) { return JsonFlatten(data, maxLevel, 1); } 

Ma méthode d’action ressemble à ceci:

  public JsonResult AsJson(int id) { var data = Find(id); var result = this.JsonFlatten(data); return Json(result, JsonRequestBehavior.AllowGet); } 

Dans Entity Framework version 4 , une option est disponible: ObjectContextOptions.LazyLoadingEnabled

Si vous le définissez sur false, vous éviterez le problème de la «référence circulaire». Cependant, vous devrez charger explicitement les propriétés de navigation à inclure.

voir: http://msdn.microsoft.com/en-us/library/bb896272.aspx

Puisque, à ma connaissance, vous ne pouvez pas sérialiser des références d’object, mais seulement des copies, vous pouvez essayer d’utiliser un peu d’un hack sale qui ressemble à ceci:

  1. Le client doit sérialiser sa référence de machine en tant qu’identifiant de la machine
  2. Lorsque vous désérialisez le code json, vous pouvez exécuter une fonction simple qui transforme ces identifiants en références appropriées.

Vous devez décider quel est l’object “root”. Disons que la machine est la racine, alors le client est un sous-object de la machine. Lorsque vous sérialisez une machine, elle sérialise le client en tant que sous-object dans le JSON et, lorsque le client est sérialisé, il ne sérialise pas sa référence arrière sur la machine. Lorsque votre code désérialise la machine, il désérialise le sous-object client de la machine et rétablit la référence arrière du client à la machine.

La plupart des bibliothèques de sérialisation fournissent une sorte de hook pour modifier la façon dont la désérialisation est effectuée pour chaque classe. Vous devez utiliser ce hook pour modifier la désérialisation de la classe de machine afin de rétablir la référence arrière chez le client de la machine. Ce que dépend exactement ce hook dépend de la bibliothèque JSON que vous utilisez.

J’ai eu le même problème cette semaine, et je ne pouvais pas utiliser les types anonymes car je devais implémenter une interface demandant une List . Après avoir MyType un diagramme montrant toutes les relations avec la navigabilité, j’ai découvert que MyType avait une relation bidirectionnelle avec MyObject qui provoquait cette référence circulaire, puisqu’ils se sauvegardaient tous les deux.

Après avoir décidé que MyObject n’avait pas vraiment besoin de connaître MyType , et donc en faire une relation unidirectionnelle, ce problème a été résolu.

Ce que j’ai fait est un peu radical, mais je n’ai pas besoin de la propriété, ce qui crée une erreur de référence circulaire désagréable, donc je l’ai définie sur null avant la sérialisation.

 SessionTickets result = GetTicketsSession(); foreach(var r in result.Tickets) { r.TicketTypes = null; //those two were creating the problem r.SelectedTicketType = null; } return Json(result); 

Si vous avez vraiment besoin de vos propriétés, vous pouvez créer un modèle de vue qui ne contient pas de références circulaires, mais conserve peut-être un identifiant de l’élément important, que vous pouvez utiliser ultérieurement pour restaurer la valeur d’origine.