So umgehen Sie das Circular Reference-Problem mit JSON und Entity


13

Ich habe mit der Erstellung einer Website experimentiert, die MVC mit JSON für meine Präsentationsebene und das Entity Framework für Datenmodell / Datenbank nutzt. Mein Problem tritt beim Serialisieren meiner Modellobjekte in JSON auf.

Ich verwende die erste Codemethode, um meine Datenbank zu erstellen. Bei der ersten Codemethode erfordert eine Eins-zu-Viele-Beziehung (Eltern / Kind), dass das Kind einen Verweis auf das Elternteil hat. (Beispielcode Ich bin ein Tippfehler, aber Sie bekommen das Bild)

class parent
{
   public List<child> Children{get;set;}
   public int Id{get;set;}

}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId")]
    public parent MyParent{get;set;}
    public string name{get;set;}
 }

Bei der Rückgabe eines "parent" -Objekts über ein JsonResult wird ein Zirkelverweisfehler ausgegeben, da "child" eine Eigenschaft der Klasse parent hat.

Ich habe das ScriptIgnore-Attribut ausprobiert, aber ich verliere die Fähigkeit, die untergeordneten Objekte zu betrachten. Ich muss irgendwann Informationen in einer übergeordneten untergeordneten Ansicht anzeigen.

Ich habe versucht, Basisklassen für Eltern und Kind zu erstellen, die keinen Zirkelbezug haben. Leider werden beim Versuch, baseParent und baseChild zu senden, diese vom JSON-Parser als abgeleitete Klassen gelesen (ich bin ziemlich sicher, dass mir dieses Konzept entgeht).

Base.baseParent basep = (Base.baseParent)parent;
return Json(basep, JsonRequestBehavior.AllowGet);

Die einzige Lösung, die ich gefunden habe, ist die Erstellung von "View" -Modellen. Ich erstelle einfache Versionen der Datenbankmodelle, die keinen Verweis auf die übergeordnete Klasse enthalten. Diese Ansichtsmodelle verfügen jeweils über eine Methode zum Zurückgeben der Datenbankversion und einen Konstruktor, der das Datenbankmodell als Parameter verwendet (viewmodel.name = databasemodel.name). Diese Methode scheint erzwungen, obwohl es funktioniert.

HINWEIS: Ich poste hier, weil ich denke, dass dies mehr Diskussion wert ist. Ich könnte ein anderes Entwurfsmuster verwenden, um dieses Problem zu lösen, oder es könnte so einfach sein, wie ein anderes Attribut für mein Modell zu verwenden. Bei meiner Suche habe ich keine gute Methode gefunden, um dieses Problem zu lösen.

Mein Endziel wäre eine schöne MVC-Anwendung, die JSON in hohem Maße für die Kommunikation mit dem Server und die Anzeige von Daten nutzt. Während ein konsistentes Modell über mehrere Ebenen hinweg beibehalten wird (oder so gut ich kann).

Antworten:


6

Ich sehe zwei unterschiedliche Themen in Ihrer Frage:

  • Wie verwalte ich Zirkelverweise bei der Serialisierung nach JSON?
  • Wie sicher ist es, EF-Entitäten in Ihren Ansichten als Modellentitäten zu verwenden?

In Bezug auf Zirkelverweise muss ich leider sagen, dass es keine einfache Lösung gibt. Erstens, da JSON nicht zum Darstellen von Zirkelverweisen verwendet werden kann, der folgende Code:

var aParent = {Children : []}, aChild  = {Parent : aParent};
aParent.Children.push(aChild);
JSON.stringify(aParent);

Ergebnisse in: TypeError: Converting circular structure to JSON

Sie haben nur die Wahl, nur den Composite-> Komponententeil der Komposition beizubehalten und die "Back Navigation" -Komponente -> Composite zu verwerfen. Beispiel:

class parent
{
    public List<child> Children{get;set;}
    public int Id{get;set;}
}
class child
{
    public int ParentId{get;set;}
    [ForeignKey("ParentId"), ScriptIgnore]
    public parent MyParent{get;set;}
    public string name{get;set;}
}

Nichts hindert Sie daran, diese Navigationseigenschaft auf Ihrer Clientseite mithilfe von jQuery neu zu erstellen:

$.each(parent.Children, function(i, child) {
  child.Parent = parent;  
})

Dann müssen Sie es jedoch erneut verwerfen, bevor Sie es an den Server zurücksenden, da JSON.stringify den Zirkelverweis nicht serialisieren kann:

$.each(parent.Children, function(i, child) {
  delete child.Parent;  
})

Jetzt gibt es das Problem, EF-Entitäten als Ansichtsmodell-Entitäten zu verwenden.

Zunächst verwendet EF wahrscheinlich Dynamic Proxies Ihrer Klasse, um Verhaltensweisen wie Änderungserkennung oder verzögertes Laden zu implementieren. Sie müssen diese deaktivieren, wenn Sie die EF-Entitäten serialisieren möchten.

Darüber hinaus kann die Verwendung von EF-Entitäten in der Benutzeroberfläche gefährdet sein, da der gesamte Standardordner jedes Feld von der Anforderung auf Entitätsfelder abbildet, einschließlich der Felder, die der Benutzer nicht festlegen soll.

Wenn Sie also möchten, dass Ihre MVC-App richtig gestaltet wird, würde ich empfehlen, ein dediziertes Ansichtsmodell zu verwenden, um zu verhindern, dass die "Eingeweide" Ihres internen Geschäftsmodells für den Kunden sichtbar werden. Daher würde ich Ihnen ein bestimmtes Ansichtsmodell empfehlen.


Gibt es einen ausgefallenen Weg mit objektorientierten Techniken, um den Zirkelbezug und das EF-Problem zu umgehen?
DanScan

Gibt es eine ausgefallene Möglichkeit, mit objektorientierten Techniken sowohl die Zirkelreferenz als auch das EF-Problem zu umgehen? Genauso wie BaseObject von entityObject und viewObject geerbt wird. Somit hätte entityObject die Zirkelreferenz, viewObject aber nicht die Zirkelreferenz. Ich habe dies umgangen, indem ich viewObject aus entityObject (viewObject.name = entityObject.name) erstellt habe, aber dies scheint Zeitverschwendung zu sein. Wie kann ich dieses Problem umgehen?
DanScan

Sie Sie sehr . Ihre Erklärung war sehr klar und leicht zu verstehen.
Nick

2

Eine einfachere Alternative zum Versuch, die Objekte zu serialisieren, besteht darin, die Serialisierung von übergeordneten / untergeordneten Objekten zu deaktivieren. Stattdessen können Sie einen separaten Anruf tätigen, um die zugeordneten übergeordneten / untergeordneten Objekte nach Bedarf abzurufen. Dies ist möglicherweise nicht ideal für Ihre Anwendung, aber eine Option.

Dazu können Sie einen DataContractSerializer einrichten und die DataContractSerializer.PreserveObjectReferences- Eigenschaft im Konstruktor Ihrer Datenmodellklasse auf 'false' setzen. Dies gibt an, dass die Objektreferenzen beim Serialisieren der HTTP-Antworten nicht beibehalten werden sollen.

Beispiele:

Json-Format:

var json = GlobalConfiguration.Configuration.Formatters.JsonFormatter;
json.SerializerSettings.PreserveReferencesHandling = 
    Newtonsoft.Json.PreserveReferencesHandling.None;

XML-Format:

var xml = GlobalConfiguration.Configuration.Formatters.XmlFormatter;
var dcs = new DataContractSerializer(typeof(Employee), null, int.MaxValue, 
    false, /* preserveObjectReferences: */ false, null);
xml.SetSerializer<Employee>(dcs);

Dies bedeutet, dass beim Abrufen eines Elements, auf das untergeordnete Objekte verwiesen werden, die untergeordneten Objekte nicht serialisiert werden.

Siehe auch die DataContractsSerializer- Klasse.


1

JSON-Serializer, der sich mit Zirkelreferenzen befasst

Hier ein Beispiel für einen benutzerdefinierten Jackson JSONSerializer, der sich mit Zirkelverweisen befasst, indem er das erste Vorkommen serialisiert und referencebei allen nachfolgenden Vorkommen ein * für das erste Vorkommen speichert .

Umgang mit Zirkelreferenzen beim Serialisieren von Objekten mit Jackson

Relevantes Teil-Snippet aus dem obigen Artikel:

private final Set<ObjectName> seen;

/**
 * Serialize an ObjectName with all its attributes or only its String representation if it is a circular reference.
 * @param on ObjectName to serialize
 * @param jgen JsonGenerator to build the output
 * @param provider SerializerProvider
 * @throws IOException
 * @throws JsonProcessingException
 */
@Override
public void serialize(@Nonnull final ObjectName on, @Nonnull final JsonGenerator jgen, @Nonnull final SerializerProvider provider) throws IOException, JsonProcessingException
{
    if (this.seen.contains(on))
    {
        jgen.writeString(on.toString());
    }
    else
    {
        this.seen.add(on);
        jgen.writeStartObject();
        final List<MBeanAttributeInfo> ais = this.getAttributeInfos(on);
        for (final MBeanAttributeInfo ai : ais)
        {
            final Object attribute = this.getAttribute(on, ai.getName());
            jgen.writeObjectField(ai.getName(), attribute);
        }
        jgen.writeEndObject();
    }
}

0

Die einzige Lösung, die ich gefunden habe, ist die Erstellung von "Ansichts" -Modellen. Ich erstelle einfache Versionen der Datenbankmodelle, die keinen Verweis auf die übergeordnete Klasse enthalten. Diese Ansichtsmodelle verfügen jeweils über eine Methode zum Zurückgeben der Datenbankversion und einen Konstruktor, der das Datenbankmodell als Parameter verwendet (viewmodel.name = databasemodel.name). Diese Methode scheint erzwungen, obwohl es funktioniert.

Das Nötigste an Daten auszusenden, ist die einzig richtige Antwort. Wenn Sie Daten aus der Datenbank senden, ist es normalerweise nicht sinnvoll, jede einzelne Spalte mit allen Zuordnungen zu senden. Die Verbraucher sollten sich nicht mit Datenbankzuordnungen und -strukturen befassen müssen, dh mit Datenbanken. Dies spart nicht nur Bandbreite, sondern ist auch viel einfacher zu warten, zu lesen und zu konsumieren. Fragen Sie die Daten ab und modellieren Sie sie dann für das, was Sie tatsächlich benötigen, um Gl. das bloße Minimum.


Wenn Sie über Big Data sprechen, ist mehr Verarbeitungszeit erforderlich, da Sie jetzt alles zweimal transformieren müssen.
David van Dugteren

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.