EF und Repository-Muster - endet mit mehreren DbContexte in einem Controller - irgendwelche Probleme (Leistung, Datenintegrität)?

8

Die meisten meiner Kenntnisse über ASP.NET MVC 3 entstammen dem Buch Pro ASP.NET MVC 3 Framework von Adam Freeman und Steven Senderson. Für meine Testanwendung habe ich versucht, an ihren Beispielen festzuhalten. Ich verwende das Repository-Muster plus Ninject und Moq, was bedeutet, dass Komponententests ziemlich gut funktionieren (d. H. Ohne dass Daten aus der Datenbank abgerufen werden müssen).

Im Buch werden Repositories wie folgt verwendet:

%Vor%

Und hier ist der DbContext, der dazu gehört:

%Vor%

Bitte beachten Sie: Um die Dinge in diesem extrahierten Beispiel einfach zu halten, habe ich hier die Schnittstelle ITestChildRepository weggelassen, die Ninject dann verwenden würde.

In anderen Quellen habe ich einen allgemeineren Ansatz für das Repository gesehen, bei dem ein einziges Repository für die gesamte Anwendung ausreicht. Offensichtlich in meinem Fall habe ich eine ziemlich große Liste von Repositories in meiner Anwendung - im Grunde eine für jede Entität in meinem Domain-Modell. Ich bin mir nicht sicher über die Vor- und Nachteile der beiden Ansätze - ich bin einfach dem Buch gefolgt, um auf der sicheren Seite zu sein.

Um endlich zu meiner Frage zu kommen: Jedes Repository hat seinen eigenen DbContext - private EFDbContext context = new EFDbContext(); . Kann ich mit mehreren DbContexts innerhalb einer Anfrage enden? Und würde dies zu einem erheblichen Leistungsaufwand führen? Wie wäre es mit einem Konfliktpotenzial zwischen den Kontexten und möglichen Konsequenzen für die Datenintegrität?

Hier ist ein Beispiel, in dem ich mehr als ein Repository in einem Controller gefunden habe.

Meine beiden Datenbanktabellen sind mit einer Fremdschlüsselbeziehung verknüpft. Meine Domänenmodellklassen:

%Vor%

Die Webanwendung enthält eine Seite, auf der der Benutzer ein neues TestChild erstellen kann. Darauf befindet sich eine Selectbox, die eine Liste der verfügbaren TestParents enthält. So sieht mein Controller aus:

%Vor%

Es ist nicht genug, ein EFDbTestChildRepository verfügbar zu haben, aber ich brauche auch ein EFDbTestParentRepository. Beide sind privaten Variablen des Controllers zugeordnet - und voila, mir scheint, dass zwei DbContexte erstellt wurden. Oder ist das nicht korrekt?

Um das Problem zu vermeiden, habe ich versucht, mit EFDbTestChildRepository zu den TestParents zu gelangen. Aber das bringt natürlich nur diejenigen zum Vorschein, die bereits an mindestens ein TestChild angeschlossen sind - also nicht das, was ich will.

Hier ist der Code für das Ansichtsmodell:

%Vor%

Bitte lassen Sie es mich wissen, wenn ich vergessen habe, einen relevanten Code hinzuzufügen. Vielen Dank für Ihren Rat!

    
ralfonso 04.02.2012, 10:29
quelle

3 Antworten

5

Es wird kein Performance-Problem geben (es sei denn, wir sprechen über Nanosekunden, die Instantiierung eines Kontexts ist sehr billig) und Sie werden Ihre Datenintegrität nicht beschädigt haben (bevor das passiert, erhalten Sie Ausnahmen).

>

Aber der Ansatz ist sehr begrenzt und wird nur in sehr einfachen Situationen funktionieren. Mehrere Kontexte führen in vielen Szenarien zu Problemen. Beispiel: Angenommen, Sie möchten ein neues untergeordnetes Element für ein vorhandenes übergeordnetes Element erstellen und versuchen es mit dem folgenden Code:

%Vor%

Dieser einfache Code funktioniert nicht, da parent bereits an den Kontext innerhalb von parentRepo angehängt ist, aber childrenRepo.SaveTestChild versucht, ihn an den Kontext innerhalb von childrenRepo anzuhängen, was eine Ausnahme wegen einer Entität verursacht darf nicht an einen anderen Kontext angehängt werden. (Hier ist eigentlich eine Problemumgehung, weil Sie die FK-Eigenschaft festlegen könnten, anstatt die parent : child.TestParentID = 1 zu laden. Aber ohne eine FK-Eigenschaft wäre das ein Problem.)

Wie löst man ein solches Problem?

Ein Ansatz könnte sein, die EFDbTestChildRepository um eine neue Eigenschaft zu erweitern:

%Vor%

Im obigen Beispielcode könnten Sie dann nur ein Repository verwenden und der Code würde funktionieren. Aber wie Sie sehen, passt der Name "EFDbTest Kind Repository" nicht mehr wirklich zum Zweck des neuen Repositories. Es sollte jetzt "EFDbTest ParentAndChild Repository" sein.

Ich würde dies den Aggregatstamm -Ansatz nennen, was bedeutet, dass Sie ein Repository nicht nur für eine Entität, sondern für einige Entitäten erstellen, die eng miteinander verwandt sind und Navigationseigenschaften zwischen ihnen haben / p>

Eine alternative Lösung besteht darin, den Kontext in die Repositorys zu injizieren (anstatt ihn in den Repositories zu erstellen), um sicherzustellen, dass jedes Repository denselben Kontext verwendet. (Der Kontext wird oft in eine IUnitOfWork -Schnittstelle abstrahiert.) Beispiel:

%Vor%

Dies gibt Ihnen einen einzelnen Kontext pro Controller, den Sie in mehreren Repositories verwenden können.

Der nächste Schritt könnte sein, einen einzelnen Kontext pro Anfrage durch Dependency-Injection zu erstellen, wie ...

%Vor%

... und dann Konfigurieren des IOC-Containers zum Erstellen einer einzelnen Kontextinstanz, die möglicherweise in mehrere Controller injiziert wird.

    
Slauma 04.02.2012 13:58
quelle
2
  

Kann ich riskieren, dass mehrere DbContexte innerhalb einer Anfrage landen?

Ja. Jede Instanz eines Repository wird seine eigenen DbContexts-Instanzen instanziieren. Je nach Größe und Verwendung der Anwendung ist dies möglicherweise kein Problem, obwohl es sich nicht um einen sehr skalierbaren Ansatz handelt. Es gibt mehrere Möglichkeiten, dies zu umgehen. In meinen Webprojekten füge ich die DbContext (s) der Context.Item-Sammlung der Anfrage hinzu, so dass sie für alle Klassen verfügbar ist, die sie benötigen. Ich benutze Autofac (ähnlich Ninject), um zu kontrollieren, welche DbContexte in bestimmten Szenarien erstellt werden und wie sie gespeichert werden, z. Ich habe einen anderen 'Session Manager' für einen WCF-Kontext als den für einen Http-Kontext.

  

Und würde das zu erheblichen Leistungseinbußen führen?

Ja, aber auch nicht massiv, wenn die Anwendung relativ klein ist. Während es wächst, können Sie den Overhead bemerken.

  

Wie wäre es mit einem Konfliktpotenzial zwischen den Kontexten und anderen?   Konsequenzen für die Datenintegrität?

Einer der Gründe für die Verwendung eines ORM ist, dass Änderungen im DbContext beibehalten werden können. Wenn Sie mehrere Kontextinstanzen pro Anforderung instanziieren, verlieren Sie diesen Vorteil. Sie würden keine Konflikte oder Auswirkungen der Integrität an sich bemerken, wenn Sie nicht viele Updates asynchron verarbeiten.

    
Digbyswift 04.02.2012 10:50
quelle
2

Wie versprochen, poste ich meine Lösung.

Ich bin auf Ihre Frage gestoßen, weil ich Probleme mit dem Speicher des IIS-Anwendungspools hatte, der über Grenzen hinausging, und mehrere DBContexte zu haben, war einer meiner Verdächtigen. Im Nachhinein ist es fair zu sagen, dass es andere Ursachen für meine Probleme gab. Es hat mich jedoch herausgefordert, ein besseres schichtbasiertes Design für mein Repository zu finden.

Ich fand dieses ausgezeichnete Blog: Korrekte Verwendung von Repository und Unit Of Work-Mustern in ASP.NET MVC führte mich in die richtige Richtung . Das Redesign basiert auf dem UnitOfWork-Muster. Es ermöglicht mir, nur einen Konstruktorparameter für alle meine Controller zu haben, anstatt "niemals endende Konstruktorparameter". Und danach war ich in der Lage, auch proaktives Caching einzuführen, was einen Großteil der zuvor erwähnten Probleme löste.

Jetzt habe ich nur diese Klassen:

  • IUnitOfWork
  • EFUnitOfWork
  • IGenericRepository
  • EFGenericRepository

Weitere Informationen und die Implementierung dieser Klassen finden Sie im entsprechenden Blog. Um nur ein Beispiel zu nennen: IUnitOfWork enthält Repository-Definitionen für alle benötigten Entitäten, wie:

%Vor%

Die Dependency Injection (DI) ist nur eine Aussage (ich benutze Ninject):

%Vor%

Die Controller-Konstruktoren sind wartbar:

%Vor%

Innerhalb des Controllers kann ich bei Bedarf mit _context auf alle Repositories zugreifen. Der nette Teil davon ist, dass es nur einen einzigen Commit () - Aufruf benötigt, um geänderte Daten für alle Repositories zu speichern:

%Vor%     
keun 11.02.2014 12:46
quelle