2010-01-04 10 views
37

Ich habe ein Objekt, das einen Zirkelbezug zu einem anderen Objekt hat. Angesichts der Beziehung zwischen diesen Objekten ist dies das richtige Design.Json und Circular Reference Exception

Um zu Illustrieren

Machine => Customer => Machine 

Wie erwartet wird ich in ein Problem laufen, wenn ich versuche Json zu verwenden, um eine Maschine oder ein Kundenobjekt serialisiert werden. Was ich unsicher bin, ist, wie dieses Problem gelöst werden kann, da ich die Beziehung zwischen den Maschinen- und Kundenobjekten nicht unterbrechen möchte. Welche Möglichkeiten gibt es, dieses Problem zu lösen?

bearbeiten

Derzeit verwende ich Json method provided by the Controller base class. So ist die Serialisierung ich tue, ist so einfach wie:

Json(machineForm); 

Antwort

51

Update:

Versuchen Sie nicht, NonSerializedAttribute zu verwenden, da die JavaScriptSerializer offenbar ignoriert.

Verwenden Sie stattdessen ScriptIgnoreAttribute in 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 
    // ... 
} 

Auf diese Weise, wenn Sie eine Machine in die Json Methode werfen, wird es die Beziehung von Machine zu Customer durchqueren, sondern versuchen, nicht wieder von Customer zu Machine gehen.

Die Beziehung ist immer noch da für Ihren Code zu tun, wie es Ihnen gefällt, aber die JavaScriptSerializer (von der Json-Methode verwendet) wird es ignorieren.

+0

Ich muss das eine Chance geben, aber leider kommt dies von einem Domain/Core-Objekt und ich bin mir nicht sicher, ob ich einen Verweis auf System.Web.Script.Serialization für dieses Projekt einführen möchte. Ich kapsle diese Kernobjekte in einem Modell, von dem ich denke, dass ich 'ScriptIgnore' verwenden könnte, aber an diesem Punkt konnte ich einfach den Zirkelverweis entfernen. Diese Lösung bietet eine ähnliche Lösung wie die anonymen Typen http://stackoverflow.com/questions/372955/best-way-to-filter-domain-objects-for-json-output-in-a--asp-net- -mvc-application/372977 # 372977 – ahsteele

+0

Das klappt auch. Sie sollten mit Ihren Fragen/Kommentaren spezifisch sein - zu sagen, dass Sie den Zirkelbezug nicht entfernen können/wollen, ist anders als zu sagen, dass Sie im Modell überhaupt nichts ändern können/wollen. Ich gebe häufig anonyme Typen an "Json" weiter, das einzige Problem, das ich hier sehe, ist, dass wenn Sie mehr als eine Methode haben, die eine JSON-Maschine zurückgibt, müssen Sie daran denken, sie jedes Mal zu "filtern". Es ist Schokolade vs. Vanille in meinen Augen - sie sind beide eine gute Wahl. – Aaronaught

+0

@Aaron verstanden. Ich bin mir nicht sicher, ob ich nicht sicher bin/werde, ob es sich richtig anfühlt, den Verweis auf das Core/Domain-Projekt hinzuzufügen, nur um diese Lösung für eine bestimmte UI-Situation bereitzustellen. Ich mochte die Lösung, die das NonSerializedAttribute als seinen im Namespace System verwendet. Es scheint ein Manko zu sein, dass der JavaScriptSerializer es ignoriert. – ahsteele

1

Da meines Wissens, können Sie nicht Objektreferenzen serialisiert, sondern nur Kopien könnten Sie versuchen, ein bisschen wie ein schmutziger Hack beschäftigt, die ungefähr so ​​geht:

  1. Kunde sollte seine Maschinenreferenz als die ID des Geräts serialisiert
  2. Wenn Sie den Code json deserialisieren können Sie dann eine einfache Funktion auf ihn laufen, die diese IDs in der richtigen Referenzen verwandelt.
+0

Sicher, aber das liefert die Maschineninformationen nicht an die abfragende Seite und würde zusätzliche Abfragen benötigen, um die Maschineneigenschaften zu erhalten. – ahsteele

0

Sie müssen entscheiden, welches das "root" -Objekt ist. Sagen wir der Maschine ist die Wurzel, dann ist der Kunde ein Unterobjekt der Maschine. Wenn Sie die Maschine serialisieren, serialisiert sie den Kunden als Unterobjekt im JSON, und wenn der Kunde serialisiert ist, wird er NICHT serialisieren, es ist eine Rückreferenz auf die Maschine. Wenn Ihr Code die Maschine deserialisiert, wird das Kundenunterobjekt der Maschine deserialisiert und die Rückverweisung vom Kunden auf die Maschine wiederhergestellt.

Die meisten Serialisierungsbibliotheken bieten eine Art Hook, um zu ändern, wie die Deserialisierung für jede Klasse durchgeführt wird. Sie müssten diesen Hook verwenden, um die Deserialisierung für die Maschinenklasse zu ändern und die Rückreferenz im Kunden der Maschine wiederherzustellen. Was genau dieser Hook ist, hängt von der JSON-Bibliothek ab, die Sie verwenden.

4

Verwenden Sie, um das gleiche Problem zu haben. Ich habe eine einfache Erweiterungsmethode erstellt, die L2E-Objekte in ein IDictionary "flacht".Ein IDictionary wird vom JavaScriptSerializer korrekt serialisiert. Das resultierende Json ist das gleiche wie das Objekt direkt serialisieren.

Da ich die Ebene der Serialisierung begrenzen, werden zirkuläre Referenzen vermieden. Es enthält auch keine verknüpften Tabellen (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); 
    } 

Meine Aktion Methode sieht wie folgt aus:

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

Ich beantworte diese trotz seines Alters, weil es das dritte Ergebnis (derzeit) von Google für „json.encode zirkuläre Referenz“ ist und obwohl Ich stimme den obigen Antworten (vollständig) nicht zu, da bei der Verwendung von ScriptIgnoreAttribute davon ausgegangen wird, dass Sie nirgends in Ihrem Code die Beziehung für JSON in die andere Richtung verschieben möchten. Ich glaube nicht, dass Sie Ihr Modell wegen eines Anwendungsfalls sperren.

Es hat mich inspiriert, diese einfache Lösung zu verwenden.

Da Sie in einer Ansicht in MVC arbeiten, haben Sie das Modell und möchten das Modell einfach dem ViewData.Model in Ihrem Controller zuweisen. Gehen Sie voran und verwenden Sie eine LINQ-Abfrage in Ihrer Ansicht, um die Daten zu reduzieren schön Entfernen der zirkulären Verweis säumige für die bestimmte JSON möchten Sie wie folgt aus:

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

oder wenn die Maschine -> Customer Relationship 1 .. * -> * dann versuchen:

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

In der Entity Framework version 4, gibt es eine Option: ObjectContextOp tions.LazyLoadingEnabled

Wenn Sie es auf false setzen, sollte das Problem der "Zirkelreferenz" vermieden werden. Sie müssen jedoch explizit die Navigationseigenschaften laden, die Sie einschließen möchten.

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

+0

In einigen Fällen müssen Sie möglicherweise auch die Proxy-Generierung deaktivieren. –

+0

Hinzugefügt ein Beispiel unten: http://Stackoverflow.com/a/17068227/605586 – Thomas

9

Basierend auf txl Antwort Sie müssen deaktivieren verzögertes Laden und Proxy-Erstellung und Sie können die normalen Methoden verwenden, um Ihre Daten zu bekommen.

Beispiel:

//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); 
} 
+0

Danke - das ist meine Lieblings-Lösung in dieser Frage, weil es unnötige Framework-Berechnung statt mehr Code verwendet, um "zurück" in eine Untergruppe von Daten nach der unnötigen Arbeit ausgeführt. – Stephen

+0

HFS Ich habe 3 Tage gebraucht, um das zu finden, aber vielen Dank für die Veröffentlichung dieser Lösung !!!! – user3320597

0

Ich habe das gleiche Problem in dieser Woche auch habe, und kann nicht anonyme Typen verwenden, weil ich für ein List<MyType> ersucht wird, eine Schnittstelle zu implementieren benötigt. Nach dem Erstellen eines Diagramms, das alle Beziehungen mit der Navigationsfähigkeit zeigt, fand ich heraus, dass MyType eine bidirektionale Beziehung mit MyObject hatte, die diese zirkuläre Referenz verursachte, da beide sich gegenseitig speicherten.

Nach der Entscheidung, dass MyObject nicht wirklich MyType wissen musste, und damit eine unidirektionale Beziehung zu machen, wurde dieses Problem gelöst.

0

Was ich getan habe, ist ein bisschen radikal, aber ich brauche nicht die Eigenschaft, die den bösen Rundschreiben-Referenz-verursachenden Fehler macht, also habe ich es vor der Serialisierung auf Null gesetzt.

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

Wenn Sie wirklich Ihre Eigenschaften benötigen, können Sie ein Ansichtsmodell erstellen, die nicht zirkuläre Referenzen halten, behalten aber vielleicht etwas Id des wichtigen Elements, das Sie später für die Wiederherstellung des ursprünglichen Wertes benutzen können.