it-swarm.com.de

Beim Serialisieren des Objekts in JSON wurde eine Ausnahmebedingung für einen Zirkelverweis festgestellt

Wie in this post erwähnt, wird beim Serialisieren eines Entity Framework-Proxys ein Json-Serialisierungsfehler angezeigt:

Beim Serialisieren eines Objekts vom Typ "System.Data.Entity.DynamicProxies.PurchaseOrder_446B939192F161CDBC740067F174F7A6059B0F9C0EEE68CD3EBBD63CF9AF5BD0" wurde ein Zirkelverweis festgestellt.

Der Unterschied ist jedoch, dass ich keine Zirkelverweise in meinen Entitäten habe und nur in unserer Produktionsumgebung vorkomme. Vor Ort funktioniert alles gut ...

Meine Entitäten:

public interface IEntity
{
    Guid UniqueId { get; }
    int Id { get; }
} 

public class Entity : IEntity
{
    public int Id { get; set; }
    public Guid UniqueId { get; set; }
}

public class PurchaseOrder : Entity
{
    public string Username { get; set; }
    public string Company { get; set; }

    public string SupplierId { get; set; }
    public string SupplierName { get; set; }

    public virtual ICollection<PurchaseOrderLine> Lines { get; set; }
}

public class PurchaseOrderLine : Entity
{
    public string Code { get; set; }
    public string Name { get; set; }
    public decimal Quantity { get; set; }
}

Die GetCurrent-Aktion auf meinem PurchaseOrderController löst die Ausnahme aus:

public class PurchaseOrderController : Controller
{
    private readonly IUnitOfWork _unitOfWork;

    public PurchaseOrderController(IUnitOfWork unitOfWork)
    {
        _unitOfWork = unitOfWork;
    }

    public JsonResult GetCurrent()
    {
        return Json(EnsurePurchaseOrder(), JsonRequestBehavior.AllowGet);
    }

    private PurchaseOrder EnsurePurchaseOrder()
    {
        var company = RouteData.GetRequiredString("company");
        var repository = _unitOfWork.GetRepository<PurchaseOrder>();

        var purchaseOrder = repository
                .Include(p => p.Lines)
                .FirstOrDefault
                (
                    p => p.Company == company && 
                         p.Username == User.Identity.Name
                );

        if (purchaseOrder == null)
        {
            purchaseOrder = repository.Create();
            purchaseOrder.UniqueId = Guid.NewGuid();
            purchaseOrder.Company = company;
            purchaseOrder.Username = User.Identity.Name;
            _unitOfWork.SaveChanges();
        }

        return purchaseOrder;
    }
}
34
Mathijs

Ihre POCO-Entitäten sind perfekt serialisierbar. Ihr Problem ist, dass die dynamischen Proxies, die die EF-Laufzeitumgebung für Sie erstellt, normalerweise keine sind. Sie können den context.Configuration.ProxyCreationEnabled auf false setzen, aber dann verlieren Sie das langsame Laden. Ich empfehle Ihnen dringend, Json.NET zu verwenden, der die Serialisierung für EF-Entitäten unterstützt:

ADO.NET Entity Framework-Unterstützung wurde versehentlich in Json.NET hinzugefügt

Populäres Hochleistungs-JSON-Framework für .NET

Option 1 (empfohlen)

Versuchen Sie, die Proxy-Objekterstellung auf Ihrem DbContext zu deaktivieren .

DbContext.Configuration.ProxyCreationEnabled = false;

Normalerweise liegt dieses Szenario vor, weil die Anwendung POCO-Objekte verwendet (entweder T4 Generated oder Code-First). Das Problem tritt auf, wenn Entity Framework Änderungen in Ihrem Objekt nachverfolgen möchte, die nicht in POCO-Objekte integriert sind. Um dies zu beheben, erstellt EF Proxy-Objekte, denen die Attribute in den POCO-Objekten fehlen und die nicht serialisiert werden können.

Die Gründe, warum ich diesen Ansatz empfehle; Die Verwendung einer Website bedeutet, dass Sie wahrscheinlich keine Änderungsnachverfolgung (Stateful) für Entity Framework-Objekte benötigen. Dadurch werden Speicher und CPU-Speicher frei, da die Änderungsnachverfolgung deaktiviert ist und alle Ihre Objekte auf dieselbe Weise konsistent arbeiten.

Option 2 

Verwenden Sie einen Serialisierer (z. B. JSON.Net , der bereits in ASP.Net 4 enthalten ist), der die Anpassung der Objekte ermöglicht.

Die Gründe, aus denen ich diesen Ansatz nicht empfehle, sind die serielle Proxy-Objekte als other -Objekttypen. Dies bedeutet, dass Sie von der Logik abhängig sind, um ein Ergebnis stromabwärts zu liefern. Das Objekt ändern bedeutet, die Logik zu ändern. In einem ASP.Net MVC-Projekt (einer beliebigen Version) müssen Sie nicht nur eine Ansicht ändern, sondern Sie müssen noch etwas ändern, das außerhalb desjenigen, der die Logik zuerst geschrieben hat, nicht ohne weiteres bekannt ist.

Option 3 (Entity Framework 5.x +)

Verwenden Sie .AsNoTracking () , wodurch die Proxy-Objekte für die jeweilige Abfrage deaktiviert werden. Wenn Sie Änderungsnachverfolgung verwenden müssen, ermöglicht dies eine Zwischenlösung von Nice für Lösung Nr. 1.

42
Erik Philips

Ich habe unzählige Stunden damit verbracht, die verschiedenen Lösungen zu testen, die ich im Web gefunden hatte, darunter:

  • [JsonIgnore]
  • Interne Getter
  • Deaktivieren von LazyLoadingEnabled und ProxyCreationEnabled
  • ReferenceLoopHandling auf "ignorieren" setzen
  • Bei Bedarf explizites Laden verwenden

All dies erwies sich letztendlich als fruchtlos für mich. Das Ignorieren einer Eigenschaft half einer Abfrage, verletzte jedoch 3 andere. Es fühlte sich an wie das Programmieren, das einem Whack-a-Mole entspricht. 

Der Kontext meines Problems bestand darin, dass die Daten, die von meiner Anwendung ausgehen, JSON sein mussten. Kein Weg vorbei. Einfügungen und Updates sind offensichtlich weniger problematisch. Die Auswahl von Daten, die in einer normalisierten Datenbank (und in meinem Fall einschließlich eines Versionsverlaufs) zur Serialisierung gespeichert werden, ist jedoch ein Albtraum.

Die Lösung:

Geben Sie die benötigten Daten (Eigenschaften) als anonyme Objekte zurück.

Ein Codebeispiel:

In diesem Fall brauchte ich die 3 neuesten Tickets, basierend auf "Termin". Benötigt aber auch mehrere Eigenschaften in verwandten Entitäten.

var tickets =
     context.TicketDetails
    .Where(t => t.DateScheduled >= DateTime.Now)
    .OrderBy(t => t.DateScheduled)
    .Take(3)
    .Include(t => t.Ticket)
    .Include(t => t.Ticket.Feature)
    .Include(t => t.Ticket.Feature.Property)
    .AsEnumerable()
    .Select(
        t =>
        new {
            ID = t.Ticket.ID,
            Address = t.Ticket.Feature.Property.Address,
            Subject = t.Ticket.Subject,
            DateScheduled = String.Format("{0:MMMM dd, yyyy}", t.DateScheduled)
    }
);

Und voila, keine sich selbst referenzierenden Schleifen. 

Mir ist klar, dass diese Situation nicht in allen Fällen angemessen ist, da sich Entitäten und Objekte ändern können. Es ist aber sicherlich eine Überlegung wert, wenn alles andere versagt.

11
pimbrouwers

Welche Klassen auch immer die Referenz einer anderen Klasse haben, fügen Sie einfach ein solches Attribut hinzu

[Newtonsoft.Json.JsonIgnoreAttribute]
public virtual ICollection<PurchaseOrderLine> Lines { get; set; }

Jetzt läuft alles reibungslos

1
Ali Adravi

Fügen Sie in Ihrer DbContext-Klasse diese Codezeile hinzu:

this.Configuration.ProxyCreationEnabled = false;

Zum Beispiel:

public partial class EmpDBEntities : DbContext
{
    public EmpDBEntities()
        : base("name=EmpDBEntities")
    {
        this.Configuration.ProxyCreationEnabled = false;
    }

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        throw new UnintentionalCodeFirstException();
    }

    public virtual DbSet<Department> Departments { get; set; }
    public virtual DbSet<Employee> Employees { get; set; }
}
1
Abdi

Ich hatte den gleichen Fehler, jedoch habe ich ihn sowohl auf dem Produktionsserver als auch lokal gesehen. Das Ändern der DbContext-Konfiguration hat mein Problem nicht ganz gelöst. Eine andere Lösung wurde mir mit der vorgestellt

[IgnoreDataMember]

attribut für DB-Entitätsverweise. Lesen Sie den Beitrag hier, wenn dies für Ihr Problem relevanter erscheint.

ASP.NET-Web-API-serialisierter JSON-Fehler: "Selbstreferenzierende Schleife"

1
Freestyle076

Ich hatte das gleiche Problem und löste es, indem Json.NET in den Projekterweiterungen im Referenz-Manager deaktiviert wurde.

(siehe das Bild http://i.stack.imgur.com/RqbXZ.png )

Ich musste auch die Datei project.csproj ändern, um den korrekten Pfad für die neue Version zuzuordnen:

<Reference Include="Newtonsoft.Json">
  <HintPath>..\packages\Newtonsoft.Json.6.0.5\lib\net45\Newtonsoft.Json.dll</HintPath>
</Reference>

und musste noch die web.config konfigurieren

  <dependentAssembly>
    <assemblyIdentity name="Newtonsoft.Json" publicKeyToken="30ad4fe6b2a6aeed" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-6.0.0.0" newVersion="6.0.0.0" />
  </dependentAssembly>

Beachten Sie, dass ich in der Datei web.config gezwungen war, auf die ältere Version (6.0.0.0) zu verweisen, obwohl die installierte Version 6.0.5 war.

Ich hoffe es hilft!

0
Marco

Der Zirkelverweis geschieht, weil Sie das Objekt eifrig laden.

Sie haben 3 Methoden:

  • Deaktivieren Sie den Ladevorgang beim Laden Ihrer Abfrage (Linq oder Lambda) DbContext.Configuration.ProxyCreationEnabled = false;
  • Trennen Sie die Objekte (= keine eifrige Ladefunktionalität und kein Proxy)
    • Repository.Detach (entityObject)
    • DbContext.Entry (entityObject) .EntityState = EntityState.Detached
  • Klonen Sie die Eigenschaften
    • Sie können etwas wie AutoMapper verwenden, um das Objekt zu klonen. Verwenden Sie die ICloneable-Schnittstelle nicht, da auch die ProxyProperties im Objekt geklont werden, damit dies nicht funktioniert.
  • Wenn Sie eine API erstellen, versuchen Sie, ein separates Projekt mit einer anderen Konfiguration zu verwenden (ohne Proxies).

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

0
NicoJuicy

Ich hatte das gleiche Problem, was ich getan habe, ist nur Spalte gegangen, um zu sehen In meinem Fall nur 2.

 List<SubCategory> lstSubCategory = GetSubCateroy() // list from repo

 var subCategoryToReturn = lstSubCategory.Select(S => new { Id  = S.Id, Name = S.Name }); 

return this.Json(subCategoryToReturn , JsonRequestBehavior.AllowGet);
0
BJ Patel