EF 4.1 – Code zuerst – JSON Circular Reference Serialization Error

Ich bekomme einen Circular Reference Serialization Error, obwohl ich nach meinem Wissen keine Zirkelverweise habe. Ich erhalte eine Reihe von Bestellungen aus der database und sende sie als JSON an den Client. Der gesamte Code ist unten abgebildet.

Dies ist der Fehler:

Error

Beim Serialisieren eines Objekts vom Typ ‘System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812’ wurde ein Zirkelverweis erkannt. Beschreibung: Bei der Ausführung der aktuellen Webanforderung ist eine nicht behandelte Ausnahme aufgetreten. Bitte überprüfen Sie die Stack-Trace für weitere Informationen über den Fehler und wo es aus dem Code stammt.

Ausnahmedetails: System.InvalidOperationException: Beim Serialisieren eines Objekts vom Typ ‘System.Data.Entity.DynamicProxies.Order_83CECF2AA4DE38232F9077D4B26941AB96BC61230419EA8AC42C9100E6072812’ wurde ein Zirkelverweis erkannt.

Quellerrors:

Während der Ausführung der aktuellen Webanforderung wurde eine nicht behandelte Ausnahme generiert. Informationen über den Ursprung und den Ort der Ausnahme können anhand der folgenden Ausnahme-Stack-Trace identifiziert werden.

Meine classn sind wie folgt:

Auftrag

public class Order { [Key] public int OrderId { get; set; } public int PatientId { get; set; } public virtual Patient Patient { get; set; } public int CertificationPeriodId { get; set; } public virtual CertificationPeriod CertificationPeriod { get; set; } public int AgencyId { get; set; } public virtual Agency Agency { get; set; } public int PrimaryDiagnosisId { get; set; } public virtual Diagnosis PrimaryDiagnosis { get; set; } public int ApprovalStatusId { get; set; } public virtual OrderApprovalStatus ApprovalStatus { get; set; } public int ApproverId { get; set; } public virtual User Approver { get; set; } public int SubmitterId { get; set; } public virtual User Submitter { get; set; } public DateTime ApprovalDate { get; set; } public DateTime SubmittedDate { get; set; } public Boolean IsDeprecated { get; set; } } 

Geduldig

 public class Patient { [Key] public int PatientId { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string MiddleInitial { get; set; } public bool IsMale; public DateTime DateOfBirth { get; set; } public int PatientAddressId { get; set; } public Address PatientAddress { get; set; } public bool IsDeprecated { get; set; } } 

Zertifizierungszeitraum

 public class CertificationPeriod { [Key] public int CertificationPeriodId { get; set; } public DateTime startDate { get; set; } public DateTime endDate { get; set; } public bool isDeprecated { get; set; } } 

Agentur

 public class Agency { [Key] public int AgencyId { get; set; } public string Name { get; set; } public int PatientAddressId { get; set; } public virtual Address Address { get; set; } } 

Diagnose

 public class Diagnosis { [Key] public int DiagnosisId { get; set; } public string Icd9Code { get; set; } public string Description { get; set; } public DateTime DateOfDiagnosis { get; set; } public string Onset { get; set; } public string Details { get; set; } } 

OrderApprovalStatus

 public class OrderApprovalStatus { [Key] public int OrderApprovalStatusId { get; set; } public string Status { get; set; } } 

Benutzer

 public class User { [Key] public int UserId { get; set; } public string Login { get; set; } public string FirstName { get; set; } public string LastName { get; set; } public string NPI { get; set; } public string Email { get; set; } } 

HINWEIS: ADRESSKLASSE IST NEW ADDITION WÄHREND EDIT

Adresse

 public class Address { [Key] public int AddressId { get; set; } public string StreetAddress { get; set; } public string City { get; set; } public string State { get; set; } public string Zip { get; set; } public string Phone { get; set; } public string Title { get; set; } public string Label { get; set; } } 

Der Code, der die Serialisierung ausführt, ist hier:

Auszug aus OrderController

  public ActionResult GetAll() { return Json(ppEFContext.Orders, JsonRequestBehavior.AllowGet); } 

Vielen Dank

Sie könnten versuchen, das virtual Schlüsselwort aus allen Navigationseigenschaften zu entfernen, um das verzögerte Laden und die Erstellung von Proxys zu deaktivieren, und stattdessen das eifrige Laden verwenden, um das erforderliche Objektdiagramm explizit zu laden:

 public ActionResult GetAll() { return Json(ppEFContext.Orders .Include(o => o.Patient) .Include(o => o.Patient.PatientAddress) .Include(o => o.CertificationPeriod) .Include(o => o.Agency) .Include(o => o.Agency.Address) .Include(o => o.PrimaryDiagnosis) .Include(o => o.ApprovalStatus) .Include(o => o.Approver) .Include(o => o.Submitter), JsonRequestBehavior.AllowGet); } 

In Bezug auf Ihren vorherigen Beitrag sieht es so aus, als ob Ihre Anwendung sowieso nicht auf Lazy-Laden angewiesen ist, weil Sie dort die virtuellen Eigenschaften eingeführt haben, um das Objektdiagramm träge zu laden, was jetzt möglicherweise das Problem der Serialisierung verursacht.

Bearbeiten

Es ist nicht notwendig, das virtual Schlüsselwort aus den Navigationseigenschaften zu entfernen (wodurch das Lazy-Laden für das Modell völlig unmöglich wird). Es genügt, die Proxy-Erstellung (die auch das verzögerte Laden deaktiviert) für die speziellen Umstände zu deaktivieren, unter denen Proxies stören, wie die Serialisierung:

 ppEFContext.Configuration.ProxyCreationEnabled = false; 

Dies deaktiviert die Proxy-Erstellung nur für die spezifische Kontextinstanz ppEFContext .

(Ich habe gerade gesehen, @WillC hat es bereits hier erwähnt. Upvote für diese Bearbeitung bitte zu seiner Antwort.)

Wenn Sie wissen, dass Sie aus einem bestimmten Kontext serialisieren müssen, können Sie die Proxy-Erstellung für diese bestimmte Abfrage wie unten beschrieben deaktivieren. Das funktionierte für mich und war besser, als meine Modellklassen zu überarbeiten.

 using (var context = new MeContext()) { context.Configuration.ProxyCreationEnabled = false; return context.cars.Where(w => w.Brand == "Ferrari") } 

Dieser Ansatz entfernt den Proxy-Objekttyp für diese bestimmte Instanz des Kontexts, so dass die zurückgegebenen Objekte die tatsächliche class sind und daher die Serialisierung kein Problem darstellt.

zB:

 {Models.car} 

Anstatt von

 {System.Data.Entity.DynamicProxies.car_231710A36F27E54BC6CE99BB50E0FE3B6BD4462EC‌​A19695CD1BABB79605296EB} 

Das Problem besteht darin, dass Sie ein von einem Entitätsframework generiertes Proxyobjekt serialisieren. Leider hat dies einige Probleme, wenn es mit dem JSON-Serializer verwendet wird. Sie könnten Ihre Entitäten aus Gründen der JSON-Kompatibilität speziellen POCO-classn zuordnen.

Es gibt ein Attribut, das Entity Framework-Objekten hinzugefügt werden kann

 [ScriptIgnore] 

Dies führt dazu, dass der Code keine Zirkelreferenzen ausführt.

Ich denke, sie haben das in der neuesten Version behoben.

Sehen Sie sich die Hilfedokumente im Abschnitt ” Serialisierung und Deserialisierung von JSON -> Serialisierung und Einbehaltung von Objektverweisen ” an.

Setzen Sie diese Einstellung beim Initialisieren des JSON.Net Serializers:

 PreserveReferencesHandling = PreserveReferencesHandling.Objects; 

Ein Beispiel wäre das:

 var serializerSettings = new JsonSerializerSettings { PreserveReferencesHandling = PreserveReferencesHandling.Objects }; string json = JsonConvert.SerializeObject(people, Formatting.Indented, serializerSettings); 

Ich habe überprüft, dass dies mit meiner ersten Code-Lösung und einem Zirkelverweis in den Navigationseigenschaften funktioniert. Wenn Sie sich den resultierenden JSON anschauen, sollte er überall die Eigenschaften “$ id” und “$ ref” haben.

Eine alternative Lösung wäre, anonyme Typen als Ergebnis einer LINQ-Abfrage zu verwenden.

In meinem Projekt verwende ich häufig Lazy Loading, und es war nicht das Richtige, es zu deaktivieren.

Eine alternative Lösung, wenn nur einige Werte von Objekten benötigt werden, ist eine anonyme class zu erstellen und zurückzugeben, wie im folgenden Beispiel:

 public JsonResult AjaxFindByName(string term) { var customers = context.Customers .Where(c => c.Name.ToUpper().Contains(term.ToUpper())).Take(10) .AsEnumerable() .Select(c => new { value = c.Name, SSN = String.Format(@"{0:000\-00\-0000}", c.SSN), CustomerID = c.CustomerID }); return Json(customers, JsonRequestBehavior.AllowGet); } 

Die Zirkelreferenz geschieht, weil Sie das Objekt eifrig laden.

Sie haben ein paar Methoden:

  • Schalten Sie das Eager-Laden aus, wenn Sie Ihre Query laden (linq oder lambda) DbContext.Configuration.ProxyCreationEnabled = false;
  • Entfernen Sie das virtuelle Schlüsselwort aus dem Domänenmodell
  • Trennen Sie die Objekte (= keine Euer Ladefunktionalität und kein Proxy)
    • Repository.Detach (Entitätsobjekt)
    • DbContext.Entry (entityObject) .EntityState = EntityState.Detached
  • Klonen Sie die Eigenschaften
    • Sie könnten etwas wie AutoMapper verwenden, um das Objekt zu klonen, verwenden Sie nicht die ICloneable-Schnittstelle, da es auch die ProxyProperties im Objekt klont, so dass es nicht funktioniert.
  • Falls Sie eine API erstellen, verwenden Sie ein separates Projekt mit einer anderen Konfiguration (die keine Proxies zurückgibt).

PS. Proxies ist das Objekt, das von EF erstellt wird, wenn Sie es aus dem Entity Framework laden. Kurz gesagt: Es enthält die ursprünglichen Werte und die aktualisierten Werte, damit sie später aktualisiert werden können. Es geht um andere Dinge 😉

Für diejenigen, die die Proxy-EF / Linq2SQL-classn verwenden, bestand meine Lösung darin, einfach die Elternreferenz für meine untergeordneten Entitäten zu entfernen.

In meinem Modell habe ich also die Beziehung ausgewählt und die Elternreferenz so geändert, dass sie intern statt öffentlich ist.

Vielleicht keine ideale Lösung für alle, aber für mich gearbeitet.

Sie können das virtual Schlüsselwort entfernen:

public virtual Patient Patient { get; set; } public virtual Patient Patient { get; set; } -> public Patient Patient { get; set; } public Patient Patient { get; set; }

Beachten Sie, dass beim Entfernen des virtuellen Schlüsselworts das verzögerte Laden deaktiviert wird.