it-swarm.com.de

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

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

Ich verwende die Code First-Methode, um meine Datenbank zu erstellen. Bei der ersten Code-Methode erfordert eine Eins-zu-Viele-Beziehung (Eltern/Kind), dass das Kind einen Verweis auf das Elternteil hat. (Beispielcode kann ein Tippfehler sein, 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 "übergeordneten" Objekts über ein JsonResult wird ein Zirkelreferenzfehler ausgelöst, da "untergeordnetes Objekt" die Eigenschaft der übergeordneten Klasse hat.

Ich habe das ScriptIgnore-Attribut ausprobiert, aber ich kann die untergeordneten Objekte nicht mehr anzeigen. Ich muss irgendwann Informationen in einer übergeordneten untergeordneten Ansicht anzeigen.

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

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

Die einzige Lösung, die ich mir ausgedacht habe, ist das Erstellen von "Ansicht" -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 = databaseasemodel.name). Diese Methode scheint erzwungen, obwohl sie funktioniert.

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

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

13
DanScan

Ich sehe zwei verschiedene Themen in Ihrer Frage:

  • Wie verwalte ich Zirkelverweise bei der Serialisierung in 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 zur Darstellung von Zirkelverweisen verwendet werden kann, folgender Code:

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

Ergebnisse in: TypeError: Converting circular structure to JSON

Die einzige Möglichkeit, die Sie haben, besteht darin, nur den Verbund -> Bestandteil der Komposition beizubehalten und die Komponente "Rücknavigation" -> Verbund zu verwerfen. In Ihrem Beispiel also:

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 neu zusammenzusetzen, hier mit jQuery:

$.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 Ihre Ansichtsmodell-Entitäten zu verwenden.

Zuerst verwendet EF wahrscheinlich dynamische 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 aus der Anforderung Entitätsfeldern zuordnet, einschließlich derjenigen, die der Benutzer nicht festlegen soll.

Wenn Sie also möchten, dass Ihre MVC-App ordnungsgemäß gestaltet wird, würde ich empfehlen, ein dediziertes Ansichtsmodell zu verwenden, um zu verhindern, dass der "Mut" Ihres internen Geschäftsmodells dem Kunden ausgesetzt wird. Daher würde ich Ihnen ein bestimmtes Ansichtsmodell empfehlen.

7
Julien Ch.

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 Aufruf durchführen, um die zugehörigen übergeordneten/untergeordneten Objekte nach Bedarf abzurufen. Dies ist möglicherweise nicht ideal für Ihre Anwendung, aber es ist eine Option.

Zu diesem Zweck können Sie einen DataContractSerializer einrichten und die Eigenschaft DataContractSerializer.PreserveObjectReferences 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 Klasse DataContractsSerializer .

2

JSON-Serializer, der sich mit Rundschreiben befasst

Hier ist ein Beispiel für ein benutzerdefiniertes Jackson JSONSerializer, das sich mit Zirkelverweisen befasst, indem das erste Vorkommen serialisiert und ein * reference für das erste Vorkommen bei allen nachfolgenden Vorkommen gespeichert wird.

mgang mit Zirkelverweisen beim Serialisieren von Objekten mit Jackson

Relevanter Teilausschnitt 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();
    }
}
1
user7519

Die einzige Lösung, die ich mir ausgedacht habe, ist das Erstellen von "Ansicht" -Modellen. Ich erstelle einfache Versionen der Datenbankmodelle, die keinen Verweis auf die übergeordnete Klasse enthalten. Diese Ansichtsmodelle verfügen jeweils über eine Methode zur Rückgabe der Datenbankversion und einen Konstruktor, der das Datenbankmodell als Parameter verwendet (viewmodel.name = databaseasodem.name). Diese Methode scheint erzwungen, obwohl sie funktioniert.

Das Versenden eines Minimums an Daten 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 verbrauchen. 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.

0
Dante