2010-07-16 20 views
28

Da ich neu im Entity Framework bin, bin ich eher daran interessiert, wie ich mit dieser Reihe von Problemen fortfahren kann. Bei dem Projekt, an dem ich gerade arbeite, ist die gesamte Website stark in das EF-Modell integriert. Der Zugriff auf den EF-Kontext wurde zunächst mit einem Bootstrapper Dependency Injection gesteuert. Aus betrieblichen Gründen konnten wir keine DI-Bibliothek verwenden. Ich entfernte das und verwendete ein Modell einzelner Instanzen des Kontextobjekts, wo dies erforderlich war. Ich habe die folgende Ausnahme erhalten:.NET Entity Framework und Transaktionen

Der Typ 'XXX' wurde mehr als einmal zugeordnet.

Wir kamen zu dem Schluss, dass die verschiedenen Instanzen des Kontexts dieses Problem verursachten. Ich abstrahierte dann das Kontextobjekt in eine einzelne statische Instanz, auf die von jedem Thread/jeder Seite zugegriffen wurde. Ich bekomme jetzt eine von mehreren Ausnahmen über Transaktionen:

Neue Transaktion ist nicht zulässig, da andere Threads in der Sitzung ausgeführt werden.

Der Transaktionsvorgang kann nicht ausgeführt werden, da ausstehende Anforderungen an dieser Transaktion ausgeführt werden.

ExecuteReader erfordert, dass der Befehl eine Transaktion ausführt, wenn die dem Befehl zugewiesene -Verbindung in einer ausstehenden lokalen Transaktion ist. Die Transaction-Eigenschaft des Befehls wurde nicht initialisiert.

Die letzte dieser Ausnahmen trat bei einer Ladeoperation auf. Ich habe nicht versucht, den Kontextzustand in dem DB in dem fehlgeschlagenen Thread zu speichern. Es gab jedoch einen anderen Thread, der eine solche Operation ausführte.

Diese Ausnahmen sind bestenfalls intermittierend, aber es ist mir gelungen, die Site in einen Status zu versetzen, in dem neue Verbindungen aufgrund einer Transaktionssperre abgelehnt wurden. Leider kann ich die Ausnahmedetails nicht finden.

Ich denke, meine erste Frage ist, sollte das EF-Modell aus einer statischen Einzelinstanz verwendet werden? Ist es auch möglich, die Notwendigkeit von Transaktionen in EF zu entfernen? Ich habe versucht, ohne Erfolg ein TransactionScope Objekt mit ...

Um ehrlich zu sein habe ich viel bin hier fest und kann nicht verstehen, warum (was sein soll) ziemlich einfache Operationen, wie ein Problem verursachen ...

+0

Verbunden: http://stackoverflow.com/questions/10585478/one-dbcontext-per-web-request-why – Steven

+0

Es ist schade, dass Sie nicht einen IOC Bootstrapper verwenden können, weil die Lösung mit [Ninject] (http wäre eine "gemeinsame" Instanz an die _request scope_ zu binden //www.ninject.org/), wie andere vorgeschlagen haben:. 'kernel.Bind >(), um > () .InRequestScope(); '- der wichtige Teil **' InRequestScope' ** – drzaus

Antwort

50

Erstellen einer globalen ObjectContext in einer Webanwendung ist sehr schlecht. Die Klasse ObjectContext ist nicht Thread-sicher. Es basiert auf dem Konzept der unit of work und bedeutet, dass Sie es verwenden, um einen einzelnen Anwendungsfall zu betreiben: also für eine Geschäftstransaktion. Es soll eine einzige Anfrage bearbeiten.

Die Ausnahme, die Sie erhalten, passiert, weil Sie für jede Anforderung eine neue Transaktion erstellen, aber versuchen Sie, dieselbe ObjectContext zu verwenden. Du hast Glück, dass der ObjectContext dies erkennt und eine Ausnahme auslöst, denn jetzt hast du herausgefunden, dass das nicht funktioniert.

Bitte denken Sie darüber nach, warum das nicht funktionieren kann. Die ObjectContext enthält einen lokalen Cache von Entitäten in Ihrer Datenbank. Sie können einige Änderungen vornehmen und diese Änderungen schließlich an die Datenbank senden. Wenn Sie eine einzelne statische ObjectContext mit mehreren Benutzern verwenden, die SaveChanges auf diesem Objekt aufrufen, wie soll es wissen, was genau begangen werden sollte und was nicht?Da es nicht weiß, speichert es alle Änderungen, aber zu diesem Zeitpunkt kann noch ein anderer Benutzer Änderungen vornehmen. Wenn Sie Glück haben, schlägt entweder EF oder Ihre Datenbank fehl, weil sich die Entitäten in einem ungültigen Zustand befinden. Wenn Sie unglückliche Objekte sind, die sich in einem ungültigen Zustand befinden, werden sie erfolgreich in der Datenbank gespeichert und Sie werden vielleicht Wochen später feststellen, dass Ihre Datenbank voller Mist ist. Die Lösung für Ihr Problem ist create at least one ObjectContext per request. Während theoretisch ein Objektkontext in der Benutzersitzung zwischengespeichert werden könnte, ist dies ebenfalls eine schlechte Idee, da ObjectContext typischerweise zu lange leben wird und veraltete Daten enthalten wird (weil sein interner Cache nicht automatisch aktualisiert wird).

UPDATE: so schlecht ist, eine einzelne Instanz haben für die komplette Web-Anwendung

Beachten Sie auch, dass pro Thema ein ObjectContext mit. ASP.NET verwendet einen Threadpool, was bedeutet, dass während der Lebensdauer einer Webanwendung eine begrenzte Anzahl von Threads erstellt wird. Dies bedeutet im Wesentlichen, dass die Instanzen ObjectContext in diesem Fall noch für die Lebensdauer der Anwendung leben, was die gleichen Probleme mit der Veralterung der Daten verursacht.

Sie könnten denken, dass ein DbContext pro Thread tatsächlich Thread-sicher ist, aber das ist normalerweise nicht der Fall, da ASP.NET ein asynchrones Modell hat, das Anfragen an einen anderen Thread als wo es gestartet wurde (und Die neuesten Versionen von MVC und Web API erlauben sogar eine beliebige Anzahl von Threads, die eine einzelne Anfrage in sequentieller Reihenfolge behandeln. Dies bedeutet, dass der Thread, der eine Anfrage gestartet und die ObjectContext erstellt hat, verfügbar werden kann, um eine weitere Anfrage zu bearbeiten, lange bevor diese erste Anfrage beendet wurde. Die Objekte, die in dieser Anforderung verwendet werden (z. B. eine Webseite, ein Controller oder eine beliebige Geschäftsklasse), verweisen jedoch möglicherweise immer noch auf diese ObjectContext. Da die neue Webanforderung in demselben Thread ausgeführt wird, erhält sie dieselbe ObjectContext Instanz wie die alte Anforderung. Dies führt wiederum zu rennen Bedingungen in Ihrer Anwendung und verursachen die gleichen Thread-Sicherheitsprobleme wie was eine globale ObjectContext Instanz verursacht.

+1

Wohlbefinden, in der Tat ist das, was ich dachte, ich tat ... ich würde nie normalerweise statische Daten Kontextobjekte verwendet, sondern wurde immer verzweifelter. Es stellte sich heraus, dass ich einen anderen Fehler hatte, bei dem ich einzelne Instanzen des Kontextes hatte.Sie befanden sich jedoch in einem statischen Objekt, was die Ursache zu sein scheint. 2 Stunden meines Lebens kommen nicht zurück ... Danke – sicknote

+19

Nur zwei Stunden? Dann bist du ein glücklicher Mann :-) – Steven

+1

Und was ist die beste Option, um diese Art von Problemen zu begegnen? – Romias

4

Wie Sie auf die „Seite“ beziehen sich in Sie i Frage nehme an, dies ist eine Web-Anwendung. Statische Elemente gibt es nur einmal für die gesamte Anwendung. Wenn Sie ein Singleton-Muster mit einer einzelnen Kontextinstanz in der gesamten Anwendung verwenden, werden alle Arten von Anforderungen in allen Arten von Zuständen über die gesamte Anwendung verteilt sein.

wird eine einzelne statische Kontextinstanz nicht funktionieren, aber mehrere Kontext-Instanzen pro Thread als auch lästig sein, da Sie nicht und Spiel Kontexte mischen. Was Sie brauchen, ist ein einzelner Kontext pro Thread. Wir haben dies in unserer Anwendung unter Verwendung eines Abhängigkeitsinjektionstypmusters getan. Unsere BLL und DAL-Klassen nehmen einen Kontext als Parameter in den Methoden, da man so etwas wie unten tun können:

using (TransactionScope ts = new TransactionScope()) 
{ 
    using (ObjectContext oContext = new ObjectContext("MyConnection")) 
    { 
     oBLLClass.Update(oEntity, oContext); 
    } 
} 

Wenn Sie rufen andere BLL/DAL Methoden in Ihrem Update (oder was auch immer Methode, die Sie gewählt haben) Sie geben einfach den gleichen Kontext um. Auf diese Weise sind Aktualisierungen/Einfügungen/Löschungen atomar. Alles innerhalb einer einzigen Methode verwendet dieselbe Kontextinstanz, diese Instanz wird jedoch nicht von anderen Threads verwendet.