È stato rilevato un riferimento circolare durante la serializzazione di un oggetto di tipo "SubSonic.Schema .DatabaseColumn".


170

Sto provando a fare un semplice ritorno JSON, ma sto riscontrando dei problemi:

public JsonResult GetEventData()
{
    var data = Event.Find(x => x.ID != 0);
    return Json(data);
}

Ottengo un HTTP 500 con l'eccezione, come mostrato nel titolo di questa domanda. Ho anche provato

var data = Event.All().ToList()

Ciò ha dato lo stesso problema.

Si tratta di un bug o della mia implementazione?


1
Guarda questo. C'è una soluzione che utilizza l' ScriptIgnoreattributo. stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo

Questa è stata la soluzione migliore per me; Ho avuto Gioco> Torneo> Gioco> Torneo> Gioco, ecc. Ho inserito un ScriptIgnoreattributo nella proprietà Tournament.Game e ha funzionato bene :)
eth0

Nel caso in cui qualcuno desideri una soluzione "automatizzata" (non delle migliori pratiche) per questo problema che non richiede alcun codice aggiuntivo, dai un'occhiata a questo QA: non serializzare i riferimenti di classe Entity Framework in JSON (libreria ServiceStack.Text)
nikib3ro,

Risposte:


175

Sembra che ci siano riferimenti circolari nella gerarchia degli oggetti che non è supportato dal serializzatore JSON. Hai bisogno di tutte le colonne? È possibile selezionare solo le proprietà necessarie nella vista:

return Json(new 
{  
    PropertyINeed1 = data.PropertyINeed1,
    PropertyINeed2 = data.PropertyINeed2
});

Ciò renderà il tuo oggetto JSON più leggero e più facile da capire. Se si dispone di molte proprietà, AutoMapper può essere utilizzato per mappare automaticamente tra oggetti DTO e Visualizza oggetti.


Penso che forse la selezione di quelli che voglio possa funzionare Penso che il riferimento circolare sia perché in Event ho IQueryable <Category> che a sua volta avrà un IQueryable <Event>
Jon

7
Automapper non garantisce che questo problema non si presenti. Sono venuto qui in cerca di una risposta e sto usando automapper.
Capitano Kenpachi,

1
Vedi la risposta di @ClayKaboom perché spiega perché potrebbe essere circolare
PandaWood

106

Ho avuto lo stesso problema e risolto using Newtonsoft.Json;

var list = JsonConvert.SerializeObject(model,
    Formatting.None,
    new JsonSerializerSettings() {
        ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore
});

return Content(list, "application/json");

3
Questo codice incorporato ha funzionato bene per me. Le stesse cose nella configurazione globale menzionate da kravits88 non funzionano per me. Inoltre, è necessario aggiornare la firma del metodo per restituire ContentResult per questo codice.
BiLaL,

6
Questa dovrebbe essere contrassegnata come la migliore risposta, in quanto copre i casi in cui non puoi passare ore a convertire i tuoi oggetti in altre rappresentazioni come nella risposta contrassegnata come accettata.
Renan,

56

Questo in realtà accade perché gli oggetti complessi sono ciò che fa fallire l'oggetto json risultante. E fallisce perché quando l'oggetto è mappato mappa i bambini, che mappano i loro genitori, facendo accadere un riferimento circolare. Json impiegherebbe un tempo infinito per serializzarlo, quindi previene il problema con l'eccezione.

Anche il mapping di Entity Framework produce lo stesso comportamento e la soluzione è eliminare tutte le proprietà indesiderate.

Spiegando solo la risposta finale, l'intero codice sarebbe:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           new {
                Result = (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
               }
           , JsonRequestBehavior.AllowGet
           );
}

Potrebbe anche essere il seguente nel caso in cui non si desideri oggetti all'interno di una Resultproprietà:

public JsonResult getJson()
{
    DataContext db = new DataContext ();

    return this.Json(
           (from obj in db.Things select new {Id = obj.Id, Name = obj.Name})
           , JsonRequestBehavior.AllowGet
           );
}

1
+1 per cose chiare e di facile comprensione, grazie a @Clay. Mi piace la tua spiegazione sui concetti alla base dell'errore.
Ajay2707,

14

Per riassumere, ci sono 4 soluzioni a questo:

Soluzione 1: disattivare ProxyCreation per DBContext e ripristinarlo alla fine.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        bool proxyCreation = db.Configuration.ProxyCreationEnabled;
        try
        {
            //set ProxyCreation to false
            db.Configuration.ProxyCreationEnabled = false;

            var data = db.Products.ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
        finally
        {
            //restore ProxyCreation to its original state
            db.Configuration.ProxyCreationEnabled = proxyCreation;
        }
    }

Soluzione 2: utilizzo di JsonConvert impostando ReferenceLoopHandling per ignorare le impostazioni del serializzatore.

    //using using Newtonsoft.Json;

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.ToList();

            JsonSerializerSettings jss = new JsonSerializerSettings { ReferenceLoopHandling = ReferenceLoopHandling.Ignore };
            var result = JsonConvert.SerializeObject(data, Formatting.Indented, jss);

            return Json(result, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

Seguire due soluzioni sono le stesse, ma usare un modello è meglio perché è fortemente tipizzato.

Soluzione 3: restituire un modello che include solo le proprietà necessarie.

    private DBEntities db = new DBEntities();//dbcontext

    public class ProductModel
    {
        public int Product_ID { get; set;}

        public string Product_Name { get; set;}

        public double Product_Price { get; set;}
    }

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new ProductModel
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

Soluzione 4: restituire un nuovo oggetto dinamico che include solo le proprietà necessarie.

    private DBEntities db = new DBEntities();//dbcontext

    public ActionResult Index()
    {
        try
        {
            var data = db.Products.Select(p => new
                                                {
                                                    Product_ID = p.Product_ID,
                                                    Product_Name = p.Product_Name,
                                                    Product_Price = p.Product_Price
                                                }).ToList();

            return Json(data, JsonRequestBehavior.AllowGet);
        }
        catch (Exception ex)
        {
            Response.StatusCode = (int)HttpStatusCode.BadRequest;
            return Json(ex.Message);
        }
    }

7

JSON, come xml e vari altri formati, è un formato di serializzazione basato su alberi. Non ti amerà se hai riferimenti circolari nei tuoi oggetti, come "l'albero" sarebbe:

root B => child A => parent B => child A => parent B => ...

Esistono spesso modi per disabilitare la navigazione lungo un determinato percorso; ad esempio, con XmlSerializerte potresti contrassegnare la proprietà parent come XmlIgnore. Non so se questo sia possibile con il serializzatore json in questione, né se DatabaseColumnabbia marcatori adatti ( molto improbabile, poiché dovrebbe fare riferimento a ogni API di serializzazione)


4

È a causa del nuovo modello DbContext T4 utilizzato per generare le entità EntityFramework. Per poter eseguire il rilevamento delle modifiche, questo modello utilizza il modello Proxy, avvolgendo i tuoi simpatici POCO con essi. Ciò causa quindi i problemi durante la serializzazione con JavaScriptSerializer.

Quindi le 2 soluzioni sono:

  1. O semplicemente serializzare e restituire le proprietà necessarie sul client
  2. È possibile disattivare la generazione automatica di proxy impostandola sulla configurazione del contesto

    context.Configuration.ProxyCreationEnabled = false;

Molto ben spiegato nell'articolo di seguito.

http://juristr.com/blog/2011/08/javascriptserializer-circular-reference/


4

Utilizzando Newtonsoft.Json: nel tuo metodo Application_Start Global.asax aggiungi questa riga:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;

1
Apparentemente sembra molto diretto ma non ha funzionato per me
BiLaL

4

aggiungi [JsonIgnore]alle proprietà virtuali nel tuo modello.


4

Evitare di convertire direttamente l'oggetto tabella. Se le relazioni sono impostate tra altre tabelle, potrebbe generare questo errore. Piuttosto, è possibile creare una classe modello, assegnare valori all'oggetto classe e quindi serializzarla.


3

Le risposte fornite sono buone, ma penso che possano essere migliorate aggiungendo una prospettiva "architettonica".

Indagine

MVC's Controller.Jsonla funzione sta facendo il lavoro, ma è molto scarsa nel fornire un errore rilevante in questo caso. Utilizzando Newtonsoft.Json.JsonConvert.SerializeObject, l'errore specifica esattamente qual è la proprietà che sta attivando il riferimento circolare. Ciò è particolarmente utile quando si serializzano gerarchie di oggetti più complesse.

Architettura corretta

Non si dovrebbe mai provare a serializzare i modelli di dati (ad esempio i modelli EF), poiché le proprietà di navigazione di ORM sono la strada verso la perdizione quando si tratta di serializzazione. Il flusso di dati dovrebbe essere il seguente:

Database -> data models -> service models -> JSON string 

I modelli di servizio possono essere ottenuti da modelli di dati utilizzando i mappatori automatici (ad es. Automapper ). Sebbene ciò non garantisca la mancanza di riferimenti circolari, una progettazione adeguata dovrebbe farlo: i modelli di servizio dovrebbero contenere esattamente ciò che il consumatore richiede il servizio (ovvero le proprietà).

In quei rari casi, quando il client richiede una gerarchia che coinvolge lo stesso tipo di oggetto su livelli diversi, il servizio può creare una struttura lineare con relazione parent-> child (usando solo identificatori, non riferimenti).

Le applicazioni moderne tendono ad evitare di caricare strutture di dati complesse contemporaneamente e i modelli di servizio dovrebbero essere snelli. Per esempio:

  1. accedere a un evento - vengono caricati solo i dati di intestazione (identificativo, nome, data ecc.) -> modello di servizio (JSON) contenente solo i dati di intestazione
  2. elenco dei partecipanti gestiti - accedi a un popup e carica l'elenco pigri -> modello di servizio (JSON) contenente solo l'elenco dei partecipanti

1

Sto usando la correzione, perché utilizzo Knockout nelle viste MVC5.

In azione

return Json(ModelHelper.GetJsonModel<Core_User>(viewModel));

funzione

   public static TEntity GetJsonModel<TEntity>(TEntity Entity) where TEntity : class
    {
        TEntity Entity_ = Activator.CreateInstance(typeof(TEntity)) as TEntity;
        foreach (var item in Entity.GetType().GetProperties())
        {
            if (item.PropertyType.ToString().IndexOf("Generic.ICollection") == -1 && item.PropertyType.ToString().IndexOf("SaymenCore.DAL.") == -1)
                item.SetValue(Entity_, Entity.GetPropValue(item.Name));
        }
        return Entity_;  
    }

0

È possibile notare le proprietà che causano il riferimento circolare. Quindi puoi fare qualcosa del tipo:

private Object DeCircular(Object object)
{
   // Set properties that cause the circular reference to null

   return object
}

-1
//first: Create a class as your view model

public class EventViewModel 
{
 public int Id{get;set}
 public string Property1{get;set;}
 public string Property2{get;set;}
}
//then from your method
[HttpGet]
public async Task<ActionResult> GetEvent()
{
 var events = await db.Event.Find(x => x.ID != 0);
 List<EventViewModel> model = events.Select(event => new EventViewModel(){
 Id = event.Id,
 Property1 = event.Property1,
 Property1 = event.Property2
}).ToList();
 return Json(new{ data = model }, JsonRequestBehavior.AllowGet);
}

Questo non risponde alla domanda
Dane I,

-1

Un'alternativa più semplice per risolvere questo problema è restituire una stringa e formattare quella stringa in json con JavaScriptSerializer.

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

È importante la parte "Seleziona", che sceglie le proprietà desiderate nella vista. Alcuni oggetti hanno un riferimento per il genitore. Se non si scelgono gli attributi, potrebbe apparire il riferimento circolare, se si prendono semplicemente le tabelle nel loro insieme.

Non farlo:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.toList();
   return j.Serialize(entityList );
}

Fallo invece se non vuoi l'intero tavolo:

public string GetEntityInJson()
{
   JavaScriptSerializer j = new JavaScriptSerializer();
   var entityList = dataContext.Entitites.Select(x => new { ID = x.ID, AnotherAttribute = x.AnotherAttribute });
   return j.Serialize(entityList );
}

Ciò consente di eseguire il rendering di una vista con meno dati, solo con gli attributi di cui hai bisogno, e rende il tuo Web più veloce.

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.