Come aggirare il problema dei riferimenti circolari con JSON ed Entity


13

Ho sperimentato la creazione di un sito Web che sfrutta MVC con JSON per il mio livello di presentazione e il framework Entity per il modello di dati / database. My Issue entra in gioco con la serializzazione degli oggetti Model in JSON.

Sto usando il primo metodo di codice per creare il mio database. Quando si esegue il primo metodo con codice, una relazione una a molte (padre / figlio) richiede che il figlio abbia un riferimento al padre. (Esempio di codice my be a typo ma ottieni l'immagine)

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

Quando si restituisce un oggetto "parent" tramite un JsonResult viene generato un errore di riferimento circolare perché "child" ha una proprietà di parent parent.

Ho provato l'attributo ScriptIgnore ma perdo la possibilità di guardare gli oggetti figlio. A un certo punto dovrò visualizzare le informazioni in una vista figlio principale.

Ho provato a creare classi di base sia per i genitori che per i figli che non hanno un riferimento circolare. Sfortunatamente quando provo a inviare baseParent e baseChild, questi vengono letti da JSON Parser come classi derivate (sono abbastanza sicuro che questo concetto mi stia sfuggendo).

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

L'unica soluzione che ho escogitato è quella di creare modelli "Visualizza". Creo versioni semplici dei modelli di database che non includono il riferimento alla classe genitore. Ciascuno di questi modelli di vista ha un metodo per restituire la versione del database e un costruttore che accetta il modello di database come parametro (viewmodel.name = databasemodel.name). Questo metodo sembra forzato sebbene funzioni.

NOTA: sto postando qui perché penso che questo sia più degno di discussione. Potrei sfruttare un diverso modello di progettazione per superare questo problema o potrebbe essere semplice come utilizzare un attributo diverso sul mio modello. Nella mia ricerca non ho visto un buon metodo per superare questo problema.

Il mio obiettivo finale sarebbe quello di avere una bella applicazione MVC che sfrutta pesantemente JSON per comunicare con il server e visualizzare i dati. Pur mantenendo un modello coerente tra i livelli (o il meglio che posso trovare).

Risposte:


6

Vedo due argomenti distinti nella tua domanda:

  • Come gestire i riferimenti circolari durante la serializzazione su JSON?
  • Quanto è sicuro utilizzare entità EF come entità modello nelle viste?

Per quanto riguarda i riferimenti circolari, mi dispiace dire che non esiste una soluzione semplice. Innanzitutto perché JSON non può essere utilizzato per rappresentare riferimenti circolari, il seguente codice:

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

Risultati in: TypeError: Converting circular structure to JSON

L'unica scelta che hai è quella di mantenere solo il componente composito -> componente della composizione e scartare il componente "back navigation" -> composito, quindi nel tuo esempio:

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

Nulla ti impedisce di ricomporre questa proprietà di navigazione sul lato client, qui usando jQuery:

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

Ma dovrai eliminarlo di nuovo prima di rispedirlo al server, poiché JSON.stringify non sarà in grado di serializzare il riferimento circolare:

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

Ora c'è il problema dell'utilizzo di entità EF come entità del modello di vista.

Il primo EF è probabile che utilizzi i proxy dinamici della tua classe per implementare comportamenti come il rilevamento delle modifiche o il caricamento lento, devi disabilitarli se vuoi serializzare le entità EF.

Inoltre, l'utilizzo di entità EF nell'interfaccia utente può essere a rischio poiché tutti i raccoglitori predefiniti mapperanno ogni campo dalla richiesta ai campi entità, compresi quelli che non si desidera impostare dall'utente.

Pertanto, se desideri che l'app MVC sia progettata correttamente, ti consiglio di utilizzare un modello di visualizzazione dedicato per evitare che le "viscere" del tuo modello di business interno vengano esposte al client, quindi ti consiglierei un modello di visualizzazione specifico.


Esiste un modo elegante con tecniche orientate agli oggetti che posso aggirare sia il riferimento circolare sia il problema EF.
DanScan,

Esiste un modo elegante con tecniche orientate agli oggetti che posso aggirare sia il riferimento circolare sia la questione EF? Come BaseObject è ereditato da entityObject e da viewObject. Quindi entityObject avrebbe il riferimento circolare ma viewObject non avrebbe il riferimento circolare. Ho risolto questo problema costruendo viewObject da entityObject (viewObject.name = entityObject.name) ma questo sembra essere una perdita di tempo. Come posso aggirare questo problema?
DanScan,

Loro tu molto . La tua spiegazione è stata molto chiara e di facile comprensione.
Nick

2

Un'alternativa più semplice al tentativo di serializzare gli oggetti sarebbe quella di disabilitare la serializzazione degli oggetti padre / figlio. Invece, è possibile effettuare una chiamata separata per recuperare gli oggetti padre / figlio associati come e quando sono necessari. Questo potrebbe non essere l'ideale per la tua applicazione, ma è un'opzione.

Per fare ciò, è possibile impostare un DataContractSerializer e impostare la proprietà DataContractSerializer.PreserveObjectReferences su 'false' nel costruttore della classe del modello di dati. Questo specifica che i riferimenti agli oggetti non devono essere conservati durante la serializzazione delle risposte HTTP.

Esempi:

Formato Json:

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

Formato XML:

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

Ciò significa che se si recupera un elemento a cui fanno riferimento oggetti figlio, gli oggetti figlio non verranno serializzati.

Vedi anche la classe DataContractsSerializer .


1

Serializzatore JSON che si occupa di riferimenti circolari

Ecco un esempio Jackson personalizzato JSONSerializerche si occupa di riferimenti circolari serializzando la prima occorrenza e memorizzando un * referencealla prima occorrenza in tutte le occorrenze successive.

Trattare con riferimenti circolari durante la serializzazione di oggetti con Jackson

Snippet parziale pertinente dell'articolo precedente:

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

L'unica soluzione che ho escogitato è quella di creare modelli "Visualizza". Creo versioni semplici dei modelli di database che non includono il riferimento alla classe genitore. Questi modelli di vista hanno ciascuno un metodo per restituire la versione del database e un costruttore che accetta il modello del database come parametro (viewmodel.name = databasemodel.name). Questo metodo sembra forzato sebbene funzioni.

L'invio del minimo indispensabile di dati è l'unica risposta corretta. Quando si inviano dati dal database, di solito non ha senso inviare ogni singola colonna con tutte le associazioni. I consumatori non dovrebbero avere bisogno di trattare con associazioni e strutture di database, vale a dire per i database. Questo non solo salverà la larghezza di banda, ma è anche molto più facile da mantenere, leggere e consumare. Interroga i dati e poi modellali per ciò di cui hai effettivamente bisogno per inviare l'eq. il minimo indispensabile.


Richiede più tempo di elaborazione quando si parla di big data, poiché ora è necessario trasformare tutto due volte.
David van Dugteren,

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.