web-dev-qa-db-fra.com

Exception de référence Json et Circular

J'ai un objet qui a une référence circulaire à un autre objet. Compte tenu de la relation entre ces objets, c'est la bonne conception.

Pour illustrer

Machine => Customer => Machine

Comme prévu, je rencontre un problème lorsque j'essaie d'utiliser Json pour sérialiser un ordinateur ou un objet client. Ce que je ne sais pas, c'est comment résoudre ce problème car je ne veux pas rompre la relation entre les objets Machine et Client. Quelles sont les options pour résoudre ce problème?

Modifier

J'utilise actuellement la méthode Json fournie par la classe de base du contrôleur . Donc, la sérialisation que je fais est aussi fondamentale que:

Json(machineForm);
39
ahsteele

Mettre à jour:

N'essayez pas d'utiliser NonSerializedAttribute, car JavaScriptSerializer l'ignore apparemment.

Au lieu de cela, utilisez la ScriptIgnoreAttribute dans System.Web.Script.Serialization.

public class Machine
{
    public string Customer { get; set; }

    // Other members
    // ...
}

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

    // Other members
    // ...
}

Ainsi, lorsque vous jetez un Machine dans la méthode Json, il parcourra la relation de Machine à Customer mais n'essaiera pas de revenir de Customer à Machine.

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

56
Aaronaught

Je réponds à cela malgré son âge, car c’est le 3ème résultat (actuellement) de Google pour "json.encode circular reference" et bien que je ne sois pas d’accord avec les réponses (complètement) ci-dessus, en ce que l’utilisation de ScriptIgnoreAttribute suppose que vous Nulle part dans votre code ne voudra traverser la relation dans certains sens pour certains JSON. Je ne crois pas qu'il soit nécessaire de verrouiller votre modèle à cause d'un cas d'utilisation.

Cela m'a inspiré pour utiliser cette solution simple.

Puisque 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, utilisez une requête LINQ dans votre vue pour aplatir les données en supprimant les données incriminées. référence circulaire pour le JSON particulier que vous voulez comme ceci:

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

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

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

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

Exemple:

//Retrieve Items with Json:
public JsonResult Search(string 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);
}
10
Thomas

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

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<string, object> JsonFlatten(object data, int maxLevel, int currLevel) {
        var result = new Dictionary<string, object>();
        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<string, object> 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);
    }
4
GvS

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

La définition de la valeur false devrait éviter le problème de «référence circulaire». Cependant, vous devrez explicitement charger les propriétés de navigation que vous souhaitez inclure. 

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

2
txl

Depuis, à ma connaissance, vous ne pouvez pas sérialiser les références d’objet, mais seulement les copies que vous pourriez essayer d’utiliser un peu comme un bidouillage qui ressemble à ceci:

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

Vous devez décider quel est l'objet "racine". Disons que la machine est la racine, alors le client est un sous-objet de la machine. Lorsque vous sérialisez la machine, le client est sérialisé en tant que sous-objet dans le JSON et, lorsque le client est sérialisé, ilNOTsérialise son renvoi arrière sur la machine. Lorsque votre code désérialise la machine, il désérialise le sous-objet client de la machine et rétablit la référence arrière du client sur la machine.

La plupart des bibliothèques de sérialisation fournissent une sorte de crochet pour modifier la manière dont la désérialisation est effectuée pour chaque classe. Vous devez utiliser ce crochet pour modifier la désérialisation de la classe de machines afin de rétablir la référence arrière dans le client de la machine. La nature exacte de ce hook dépend de la bibliothèque JSON que vous utilisez.

0
Nat

Ce que j'ai fait est un peu radical, mais je n'ai pas besoin de la propriété, ce qui cause la vilaine erreur de référence circulaire, alors 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 pourrez utiliser ultérieurement pour restaurer la valeur d'origine.

0
DiSaSteR

J'ai eu le même problème cette semaine et je ne pouvais pas utiliser de types anonymes car je devais implémenter une interface demandant un List<MyType>. Après avoir créé un diagramme montrant toutes les relations avec la navigabilité, j'ai découvert que MyType avait une relation bidirectionnelle avec MyObject qui a provoqué cette référence circulaire, puisqu'elles se sont mutuellement sauvées.

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

0
Br2