Beim Serialisieren eines Objekts vom Typ 'SubSonic.Schema .DatabaseColumn' wurde ein Zirkelverweis erkannt.

170

Ich versuche, eine einfache JSON-Rückgabe durchzuführen, habe jedoch folgende Probleme.

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

Ich erhalte ein HTTP 500 mit der Ausnahme, wie im Titel dieser Frage gezeigt. Ich habe es auch versucht

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

Das gab das gleiche Problem.

Ist das ein Fehler oder meine Implementierung?

Jon
quelle
1
Schau dir dieses an. Es gibt eine Lösung mit dem ScriptIgnoreAttribut. stackoverflow.com/questions/1193857/subsonic-3-0-0-2-structs-tt
freddoo
Dies war die beste Lösung für mich; Ich hatte Spiel> Turnier> Spiel> Turnier> Spiel usw. Ich habe ein ScriptIgnoreAttribut auf die Tournament.Game-Eigenschaft gesetzt und es hat gut funktioniert :)
eth0
Wenn jemand eine "automatisierte" (nicht bewährte) Lösung für dieses Problem wünscht, für die kein zusätzlicher Code erforderlich ist,
lesen Sie die folgende Qualitätssicherung

Antworten:

175

Es scheint, dass Ihre Objekthierarchie Zirkelverweise enthält, die vom JSON-Serializer nicht unterstützt werden. Benötigen Sie alle Spalten? Sie können nur die Eigenschaften auswählen, die Sie in der Ansicht benötigen:

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

Dadurch wird Ihr JSON-Objekt leichter und verständlicher. Wenn Sie viele Eigenschaften haben, kann AutoMapper verwendet werden, um automatisch zwischen DTO-Objekten und Ansichtsobjekten zuzuordnen .

Darin Dimitrov
quelle
Ich denke, vielleicht funktioniert die Auswahl derjenigen, die ich möchte. Ich denke, der Zirkelverweis ist, weil ich in Event IQueryable <Category> habe, was wiederum ein IQueryable <Event> haben wird
Jon
7
Automapper ist keine Garantie dafür, dass Sie dieses Problem nicht bekommen. Ich bin hierher gekommen, um eine Antwort zu suchen, und ich benutze tatsächlich Automapper.
Kapitän Kenpachi
1
Sehen Sie die Antwort von @ClayKaboom, da sie erklärt, warum es kreisförmig sein könnte
PandaWood
106

Ich hatte das gleiche Problem und gelöst durch using Newtonsoft.Json;

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

return Content(list, "application/json");
ddfnfal
quelle
3
Dieser Inline-Code hat bei mir gut funktioniert. Das gleiche Zeug in der globalen Konfiguration, wie es von kravits88 erwähnt wird, funktioniert bei mir nicht. AUCH die Methodensignatur sollte aktualisiert werden, um ContentResult für diesen Code zurückzugeben.
BiLaL
6
Dies sollte als die beste Antwort markiert werden, da es Fälle abdeckt, in denen Sie nicht stundenlang Ihre Objekte in andere Darstellungen konvertieren können, wie in der als akzeptiert gekennzeichneten Antwort.
Renan
56

Dies geschieht tatsächlich, weil die komplexen Objekte dazu führen, dass das resultierende JSON-Objekt ausfällt. Und es schlägt fehl, weil beim Zuordnen des Objekts die Kinder zugeordnet werden, die ihre Eltern zuordnen, wodurch ein Zirkelverweis entsteht. Json würde unendlich viel Zeit für die Serialisierung benötigen, um das Problem mit der Ausnahme zu vermeiden.

Die Entity Framework-Zuordnung führt ebenfalls zu demselben Verhalten. Die Lösung besteht darin, alle unerwünschten Eigenschaften zu verwerfen.

Wenn Sie nur die endgültige Antwort erläutern, lautet der gesamte Code:

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

Dies kann auch der Fall sein, wenn Sie die Objekte nicht in einer ResultEigenschaft haben möchten :

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
           );
}
ClayKaboom
quelle
1
+1 für klare und leicht verständliche Dinge, danke @Clay. Ich mag Ihre Erklärung zu den Konzepten hinter dem Fehler.
Ajay2707
14

Zusammenfassend gibt es dafür 4 Lösungen:

Lösung 1: Deaktivieren Sie ProxyCreation für den DBContext und stellen Sie ihn am Ende wieder her.

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

Lösung 2: Verwenden von JsonConvert durch Festlegen von ReferenceLoopHandling zum Ignorieren der Serializer-Einstellungen.

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

Die folgenden zwei Lösungen sind gleich, aber die Verwendung eines Modells ist besser, da es stark typisiert ist.

Lösung 3: Geben Sie ein Modell zurück, das nur die erforderlichen Eigenschaften enthält.

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

Lösung 4: Geben Sie ein neues dynamisches Objekt zurück, das nur die erforderlichen Eigenschaften enthält.

    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);
        }
    }
Amro
quelle
7

JSON ist wie XML und verschiedene andere Formate ein baumbasiertes Serialisierungsformat. Es wird dich nicht lieben, wenn du Zirkelverweise in deinen Objekten hast, wie der "Baum" wäre:

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

Es gibt oft Möglichkeiten, die Navigation entlang eines bestimmten Pfades zu deaktivieren. Mit können XmlSerializerSie beispielsweise die übergeordnete Eigenschaft als markieren XmlIgnore. Ich weiß nicht, ob dies mit dem fraglichen json-Serializer möglich ist oder ob DatabaseColumngeeignete Marker vorhanden sind ( sehr unwahrscheinlich, da auf jede Serialisierungs-API verwiesen werden müsste).

Marc Gravell
quelle
4

Dies liegt an der neuen DbContext T4-Vorlage, die zum Generieren der EntityFramework-Entitäten verwendet wird. Um die Änderungsverfolgung durchführen zu können, verwenden diese Vorlagen das Proxy-Muster, indem Sie Ihre netten POCOs damit umschließen. Dies verursacht dann die Probleme bei der Serialisierung mit dem JavaScriptSerializer.

Dann sind die 2 Lösungen:

  1. Entweder serialisieren Sie einfach und geben die Eigenschaften zurück, die Sie auf dem Client benötigen
  2. Sie können die automatische Generierung von Proxys deaktivieren, indem Sie sie in der Kontextkonfiguration festlegen

    context.Configuration.ProxyCreationEnabled = false;

Sehr gut erklärt im folgenden Artikel.

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

Nilesh
quelle
4

Verwenden von Newtonsoft.Json: Fügen Sie in Ihrer Global.asax Application_Start-Methode diese Zeile hinzu:

GlobalConfiguration.Configuration.Formatters.JsonFormatter.SerializerSettings.ReferenceLoopHandling = Newtonsoft.Json.ReferenceLoopHandling.Ignore;
kravits88
quelle
1
Sieht anscheinend sehr einfach aus, hat aber bei mir nicht funktioniert
BiLaL
4

Hinzufügen [JsonIgnore]zu virtuellen Eigenschaften in Ihrem Modell.

MorenajeRD
quelle
4

Vermeiden Sie es, das Tabellenobjekt direkt zu konvertieren. Wenn Beziehungen zwischen anderen Tabellen festgelegt werden, wird dieser Fehler möglicherweise ausgelöst. Sie können vielmehr eine Modellklasse erstellen, dem Klassenobjekt Werte zuweisen und es dann serialisieren.

Unais.NI
quelle
3

Die bereitgestellten Antworten sind gut, aber ich denke, sie können durch Hinzufügen einer "architektonischen" Perspektive verbessert werden.

Ermittlung

MVC's Controller.JsonDie Funktion erledigt den Job, ist jedoch in diesem Fall sehr schlecht darin, einen relevanten Fehler bereitzustellen. Bei Verwendung von Newtonsoft.Json.JsonConvert.SerializeObjectgibt der Fehler genau an, welche Eigenschaft die Zirkelreferenz auslöst. Dies ist besonders nützlich, wenn komplexere Objekthierarchien serialisiert werden.

Richtige Architektur

Man sollte niemals versuchen, Datenmodelle (z. B. EF-Modelle) zu serialisieren, da die Navigationseigenschaften von ORM der Weg zum Untergang sind, wenn es um Serialisierung geht. Der Datenfluss sollte wie folgt sein:

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

Servicemodelle können aus Datenmodellen unter Verwendung von Auto-Mappern (z . B. Automapper ) abgerufen werden . Dies garantiert zwar nicht das Fehlen von Zirkelverweisen, aber das richtige Design sollte dies tun: Servicemodelle sollten genau das enthalten, was der Servicekonsument benötigt (dh die Eigenschaften).

In diesen seltenen Fällen kann der Service, wenn der Client eine Hierarchie mit demselben Objekttyp auf verschiedenen Ebenen anfordert, eine lineare Struktur mit Eltern-Kind-Beziehung erstellen (wobei nur Bezeichner und keine Referenzen verwendet werden).

Moderne Anwendungen vermeiden es, komplexe Datenstrukturen gleichzeitig zu laden, und Servicemodelle sollten schlank sein. Z.B:

  1. Zugriff auf ein Ereignis - nur Headerdaten (Kennung, Name, Datum usw.) werden geladen -> Servicemodell (JSON), das nur Headerdaten enthält
  2. Liste der verwalteten Teilnehmer - Greifen Sie auf ein Popup zu und laden Sie die Liste -> Servicemodell (JSON), das nur die Teilnehmerliste enthält, verzögert
Alexei
quelle
1

Ich verwende das Update, weil Knockout in MVC5-Ansichten verwendet wird.

Auf Aktion

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

Funktion

   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_;  
    }
A. Kosecik
quelle
0

Sie können die Eigenschaften feststellen, die den Zirkelverweis verursachen. Dann können Sie so etwas tun wie:

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

   return object
}
Bassel
quelle
-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);
}
Ynnoboy
quelle
Dies beantwortet nicht die Frage
Däne I
-1

Eine einfachere Alternative zur Lösung dieses Problems besteht darin, eine Zeichenfolge zurückzugeben und diese Zeichenfolge mit JavaScriptSerializer an json zu formatieren.

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

Es ist wichtig, dass der Teil "Auswählen" die Eigenschaften auswählt, die Sie in Ihrer Ansicht haben möchten. Einige Objekte haben eine Referenz für das übergeordnete Objekt. Wenn Sie die Attribute nicht auswählen, wird möglicherweise der Zirkelverweis angezeigt, wenn Sie nur die Tabellen als Ganzes verwenden.

Mach das nicht:

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

Tun Sie dies stattdessen, wenn Sie nicht die gesamte Tabelle möchten:

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

Dies hilft beim Rendern einer Ansicht mit weniger Daten, nur mit den von Ihnen benötigten Attributen, und beschleunigt die Ausführung Ihres Webs.

Sterling Diaz
quelle