Come aggirare il problema Circular Reference con JSON ed Entity

12

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

Sto usando il primo metodo del codice per creare il mio database. Quando si esegue il primo metodo del codice, una relazione uno a molti (genitore / figlio) richiede al figlio di avere un riferimento al genitore. (Esempio codice mio essere un refuso 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 "genitore" tramite JsonResult, viene generato un errore di riferimento circolare perché "figlio" ha una proprietà di classe parent.

Ho provato l'attributo ScriptIgnore ma perdo la capacità di guardare gli oggetti figli. Avrò bisogno di visualizzare le informazioni in una vista figlio genitore ad un certo punto.

Ho provato a creare classi base per entrambi i genitori e i figli che non hanno un riferimento circolare. Sfortunatamente quando provo a inviare baseParent e baseChild questi vengono letti dal parser JSON come classi derivate (sono abbastanza sicuro che questo concetto mi sfugge).

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

L'unica soluzione che ho trovato è quella di creare modelli "View". 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.

NOTA: sto postando qui perché penso che questa sia una discussione più degna. Potrei sfruttare un modello di design diverso per risolvere 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 avere una bella applicazione MVC che sfrutti pesantemente JSON per comunicare con il server e visualizzare i dati. Pur mantenendo un modello coerente su più livelli (o nel modo migliore che riesco a trovare).

    
posta DanScan 19.02.2014 - 23:28
fonte

5 risposte

5

Vedo due argomenti distinti nella tua domanda:

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

Riguardo ai 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 è di mantenere solo il composito - > componente della composizione e scartare il componente "back navigation" - > composito, quindi nell'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;}
}

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

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

Ma poi dovrai scartarlo di nuovo prima di inviarlo 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 di usare le entità EF come entità del modello di vista.

È probabile che il primo EF utilizzi i proxy dinamici della tua classe per implementare comportamenti come il rilevamento di modifiche o il caricamento lento, devi disabilitare quelli se desideri serializzare le entità EF.

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

Quindi, se vuoi che la tua 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 una vista specifica modello.

    
risposta data 04.03.2014 - 16:59
fonte
2

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

Per fare ciò, puoi impostare un DataContractSerializer e impostare DataContractSerializer.PreserveObjectReferences proprietà a "false" nel costruttore della classe del modello di dati. Questo specifica che i riferimenti all'oggetto non devono essere conservati nella 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 preleva un oggetto a cui fanno riferimento oggetti figlio, gli oggetti figli non verranno serializzati.

Vedi anche DataContractsSerializer classe.

    
risposta data 06.05.2014 - 13:04
fonte
1

Serializzatore JSON che si occupa di riferimenti circolari

Ecco un esempio personalizzato Jackson JSONSerializer che si occupa di riferimenti circolari serializzando la prima occorrenza e memorizzando un * reference alla prima occorrenza su tutte le occorrenze successive.

Gestione di riferimenti circolari durante la serializzazione oggetti con Jackson

Snippet parziale pertinente dall'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();
    }
}
    
risposta data 04.03.2014 - 19:07
fonte
0

The one solution I have come up with is to create "View" Models. I create simple versions of the database models that do not include the reference to the parent class. These view models each have method to return the Database Version and a constructor that takes the database model as a parameter (viewmodel.name = databasemodel.name). This method seems forced although it works.

L'invio del minimo di dati è l'unica risposta corretta. Quando invii i dati dal database, di solito non ha senso inviare ogni singola colonna con tutte le associazioni. I consumatori non dovrebbero avere bisogno di gestire le associazioni e le strutture di database, cioè i database. Questo non solo consentirà di risparmiare larghezza di banda, ma è anche molto più facile da mantenere, leggere e consumare. Interrogare i dati e quindi modellarli per ciò che è effettivamente necessario per inviare eq. il minimo indispensabile.

    
risposta data 05.07.2014 - 18:00
fonte
-2

.Include(x => x.TableName ) non restituisce relazioni (dalla tabella principale alla tabella dipendente) o restituisce solo una riga di dati, FIX QUI:

link

Inoltre, in Startup.cs assicurati di averlo in cima:

using Microsoft.EntityFrameworkCore; 
using Newtonsoft.Json; 
using Project_Name_Here.Models;
    
risposta data 30.03.2017 - 23:25
fonte

Leggi altre domande sui tag