Entity Framework und Verbindungspooling

268

Ich habe vor kurzem begonnen, Entity Framework 4.0 in meiner .NET 4.0-Anwendung zu verwenden, und bin neugierig auf einige Dinge im Zusammenhang mit dem Pooling.

  1. Wie ich weiß, wird das Verbindungspooling vom ADO.NET-Datenprovider verwaltet, in meinem Fall von MS SQL Server. Gilt dies, wenn Sie einen neuen Entity-Kontext ( ObjectContext) instanziieren , dh den parameterlosen new MyDatabaseModelEntities()?

  2. Was sind die Vor- und Nachteile von a) Erstellen eines globalen Entitätskontexts für die Anwendung (dh einer statischen Instanz) oder b) Erstellen und Offenlegen eines Entitätskontexts für jede gegebene Operation / Methode mit einem usingBlock.

  3. Gibt es weitere Empfehlungen, Best Practices oder gängige Ansätze für bestimmte Szenarien, die ich kennen sollte?

Noldorin
quelle

Antworten:

369
  1. Das Verbindungspooling wird wie in jeder anderen ADO.NET-Anwendung behandelt. Die Entitätsverbindung verwendet weiterhin die herkömmliche Datenbankverbindung mit der herkömmlichen Verbindungszeichenfolge. Ich glaube, Sie können das Verbindungspooling in der Verbindungszeichenfolge deaktivieren, wenn Sie es nicht verwenden möchten. (Lesen Sie mehr über SQL Server Connection Pooling (ADO.NET). )
  2. Verwenden Sie niemals den globalen Kontext. ObjectContext implementiert intern mehrere Muster, einschließlich Identity Map und Unit of Work. Die Verwendung des globalen Kontexts ist je nach Anwendungstyp unterschiedlich.
  3. Verwenden Sie für Webanwendungen einen einzelnen Kontext pro Anforderung. Verwenden Sie für Webdienste einen einzelnen Kontext pro Anruf. Verwenden Sie in WinForms oder WPF-Anwendungen einen einzelnen Kontext pro Formular oder pro Präsentator. Es kann einige spezielle Anforderungen geben, die die Verwendung dieses Ansatzes nicht zulassen, aber in den meisten Situationen ist dies ausreichend.

Wenn Sie wissen möchten, welche Auswirkungen der Einzelobjektkontext für die WPF / WinForm-Anwendung hat, lesen Sie diesen Artikel . Es geht um NHibernate Session, aber die Idee ist dieselbe.

Bearbeiten:

Wenn Sie EF verwenden, wird jede Entität standardmäßig nur einmal pro Kontext geladen. Die erste Abfrage erstellt eine Entitätsinstanz und speichert sie intern. Jede nachfolgende Abfrage, für die eine Entität mit demselben Schlüssel erforderlich ist, gibt diese gespeicherte Instanz zurück. Wenn sich die Werte im Datenspeicher geändert haben, erhalten Sie die Entität weiterhin mit Werten aus der ersten Abfrage. Dies wird als Identitätskartenmuster bezeichnet . Sie können den Objektkontext zwingen, die Entität neu zu laden, aber es wird eine einzelne gemeinsam genutzte Instanz neu geladen.

An der Entität vorgenommene Änderungen werden erst beibehalten, wenn Sie SaveChangesden Kontext aufrufen . Sie können Änderungen an mehreren Entitäten vornehmen und diese gleichzeitig speichern. Dies wird als Arbeitseinheitsmuster bezeichnet . Sie können nicht selektiv angeben, welche geänderte angehängte Entität Sie speichern möchten.

Kombinieren Sie diese beiden Muster und Sie werden einige interessante Effekte sehen. Sie haben nur eine Instanz einer Entität für die gesamte Anwendung. Alle Änderungen an der Entität wirken sich auf die gesamte Anwendung aus, auch wenn die Änderungen noch nicht beibehalten (festgeschrieben) wurden. In den meisten Fällen ist dies nicht das, was Sie wollen. Angenommen, Sie haben ein Bearbeitungsformular in der WPF-Anwendung. Sie arbeiten mit der Entität und entscheiden sich, die komplexe Bearbeitung abzubrechen (Ändern von Werten, Hinzufügen verwandter Entitäten, Entfernen anderer verwandter Entitäten usw.). Die Entität wurde jedoch bereits im gemeinsam genutzten Kontext geändert. Was wirst du machen? Hinweis: Ich kenne keine CancelChanges oder UndoChanges ObjectContext.

Ich denke, wir müssen das Serverszenario nicht diskutieren. Durch die einfache Freigabe einer einzelnen Entität für mehrere HTTP-Anforderungen oder Webdienstaufrufe wird Ihre Anwendung unbrauchbar. Jede Anforderung kann nur SaveChangesTeildaten aus einer anderen Anforderung auslösen und speichern, da Sie eine einzelne Arbeitseinheit unter allen teilen. Dies hat auch ein anderes Problem: Der Kontext und jede Manipulation mit Entitäten im Kontext oder einer vom Kontext verwendeten Datenbankverbindung ist nicht threadsicher.

Selbst für eine schreibgeschützte Anwendung ist ein globaler Kontext keine gute Wahl, da Sie wahrscheinlich jedes Mal, wenn Sie die Anwendung abfragen, neue Daten benötigen.

Ladislav Mrnka
quelle
Danke für deine Antwort. Vielleicht könnten Sie näher erläutern, warum es schlecht ist, einen einzigen globalen Kontext zu verwenden? Es macht den parallelen Zugriff sicher schwieriger, aber was noch ...?
Noldorin
Ok, das ist jetzt viel klarer, danke. Nur um zu bestätigen, dass ein globaler Kontext niemals wirklich angemessen ist, kann ein einzelner Kontext für einen "Bearbeitungsdialog" oder dergleichen der richtige Weg sein? In anderen Situationen wie Webdiensten und ASP.NET sind Kontexte innerhalb von Methoden nur sinnvoller. Über richtig?
Noldorin
Ich nahm Ihren Rat an und entfernte den Singelton. Jetzt bekomme ich einen weiteren Fehler: stackoverflow.com/questions/14795899/…
Elad Benda
Ich verstehe, dass die Implementierung des Arbeitseinheitsmusters und die Kapselung von DbContext Geschäftslogik- und Datenbankoperationen trennen sollten. Ich kann nicht verstehen, wie das Arbeitseinheitsmuster implementiert und TransactionScope nur für einige Vorgänge verwendet wird.
Rudolf Dvoracek
4
@ RudolfDvoracek: Leicht. TransactionScopegehört nicht zur Arbeitseinheit, sondern zu Ihrer Geschäftslogik, da die Logik selbst die Transaktion definiert. Die Arbeitseinheit definiert nur, was zusammen beibehalten werden soll, während der Transaktionsbereich es Ihnen ermöglicht, die Persistenz der Arbeitseinheit innerhalb derselben Transaktion mehrmals zu verwenden.
Ladislav Mrnka
70

Laut Daniel Simmons:

Erstellen Sie für jede Servicemethode eine neue ObjectContext-Instanz in einer Using-Anweisung, damit sie entsorgt wird, bevor die Methode zurückgegeben wird. Dieser Schritt ist entscheidend für die Skalierbarkeit Ihres Dienstes. Es stellt sicher, dass Datenbankverbindungen nicht über Dienstaufrufe hinweg offen bleiben und dass der temporäre Status, der von einer bestimmten Operation verwendet wird, Müll ist, der gesammelt wird, wenn diese Operation beendet ist. Das Entity Framework speichert automatisch Metadaten und andere Informationen zwischen, die es in der App-Domäne benötigt, und ADO.NET bündelt Datenbankverbindungen, sodass das erneute Erstellen des Kontexts jedes Mal ein schneller Vorgang ist.

Dies ist aus seinem umfassenden Artikel hier:

http://msdn.microsoft.com/en-us/magazine/ee335715.aspx

Ich glaube, dieser Rat gilt auch für HTTP-Anfragen und gilt daher für ASP.NET. Eine Stateful-Fat-Client-Anwendung wie eine WPF-Anwendung ist möglicherweise der einzige Fall für einen "gemeinsam genutzten" Kontext.

Dave Swersky
quelle
Danke, das ist dort ein sehr informatives Zitat. Ich frage mich jedoch immer noch, ob ein gemeinsamer (globaler) Kontext selbst für eine Client-WPF-App oder dergleichen geeignet wäre. Gibt es auch in diesem Fall einen Vorteil?
Noldorin
Ein globaler Kontext in einer WPF-App hätte keinen Vorteil, aber wahrscheinlich auch keinen signifikanten Nachteil. Wenn Sie einen globalen Kontext implementieren, müssen Sie bei hohen Anforderungsraten möglicherweise die Datenbankverbindungen manuell verwalten (explizites Schließen der Verbindung).
Dave Swersky
1
Richtig; Im Grunde kann ich also nie wirklich etwas falsch machen, wenn ich mehrere temporäre Kontexte verwende (vorausgesetzt, ich weiß, dass Verbindungspooling stattfindet)? ... Wenn Sie einen einzelnen globalen Kontext verwenden, kann die Verbindung theoretisch nicht zu einem zufälligen Zeitpunkt unterbrochen werden?
Noldorin
1
@ Nolodrin: Ich glaube nicht, dass die Verbindung "zufällig" unterbrochen wird ... das Risiko besteht darin, dass Verbindungen zu lange offen gehalten werden und den Verbindungspool überlasten.
Dave Swersky
1
ObjectContext / DbContext-Implementierung IDisposable, sollte daher für die kürzeste angemessene Zeit geöffnet sein, ist meine Ansicht.
nicodemus13
12

Gemäß EF6-Dokumentation (auch 4,5): https://msdn.microsoft.com/en-us/data/hh949853#9

9.3 Kontext pro Anfrage

Die Kontexte von Entity Framework sollen als kurzlebige Instanzen verwendet werden, um ein optimales Leistungserlebnis zu bieten . Es wird erwartet, dass Kontexte nur von kurzer Dauer sind und verworfen werden. Daher wurden sie so implementiert, dass sie sehr leicht sind und Metadaten nach Möglichkeit wiederverwenden. In Web-Szenarien ist es wichtig, dies zu berücksichtigen und nicht länger als die Dauer einer einzelnen Anforderung einen Kontext zu haben. In Nicht-Web-Szenarien sollte der Kontext auf der Grundlage Ihres Verständnisses der verschiedenen Ebenen des Caching im Entity Framework verworfen werden. Im Allgemeinen sollte vermieden werden, dass während der gesamten Lebensdauer der Anwendung eine Kontextinstanz sowie Kontexte pro Thread und statische Kontexte vorhanden sind.

Raj Rao
quelle
2
Ich weiß, dass diese Antwort schon eine Weile hier ist, aber ich muss sagen, das hat mir eine Menge Kopfschmerzen erspart. Bei der Verwendung von EF mit Oracle wurde der Fehler "Pooled Connection" angezeigt, und es konnte nicht herausgefunden werden, warum. Ich hatte den dbContext als Klassenvariable eingerichtet und ihn bei der Erstellung instanziiert. Das Ändern, um den Kontext nach Bedarf zu erstellen, behebt alle Übel meiner Welt. Danke dir!
Fletchius
1

Der folgende Code hat dazu beigetragen, dass mein Objekt mit neuen Datenbankwerten aktualisiert wurde. Der Befehl Entry (object) .Reload () zwingt das Objekt, Datenbankwerte abzurufen

GM_MEMBERS member = DatabaseObjectContext.GM_MEMBERS.FirstOrDefault(p => p.Username == username && p.ApplicationName == this.ApplicationName);
DatabaseObjectContext.Entry(member).Reload();
HGMamaci
quelle
sowie dies für Sammlungen (VB-Code):CType(myContext, IObjectContextAdapter).ObjectContext.Refresh(RefreshMode.StoreWins,myCustomers)
Ivan Ferrer Villa