446 Stimmen

Ein DbContext pro Webanfrage... warum?

Ich habe eine Menge Artikel gelesen, in denen erklärt wird, wie man die Entity Frameworks DbContext so dass nur eine pro HTTP-Webanforderung unter Verwendung verschiedener DI-Frameworks erstellt und verwendet wird.

Warum ist das überhaupt eine gute Idee? Welche Vorteile bringt dieser Ansatz mit sich? Gibt es bestimmte Situationen, in denen dies eine gute Idee wäre? Gibt es Dinge, die man mit dieser Technik tun kann, die man bei der Instanziierung nicht tun kann? DbContext s pro Repository-Methodenaufruf?

627voto

Steven Punkte 157957

HINWEIS: Diese Antwort spricht über die Entity Framework's DbContext aber es ist auf jede Art von Unit of Work-Implementierung anwendbar, wie z.B. LINQ to SQL's DataContext und NHibernate's ISession .

Zunächst möchte ich mich Ian anschließen: Eine einzige DbContext für die gesamte Anwendung ist eine schlechte Idee. Die einzige Situation, in der dies sinnvoll ist, ist eine Single-Thread-Anwendung und eine Datenbank, die nur von dieser einen Anwendungsinstanz verwendet wird. Die DbContext nicht thread-sicher ist und da die DbContext Daten zwischenspeichert, werden sie bald veraltet sein. Dies führt zu allen möglichen Problemen, wenn mehrere Benutzer/Anwendungen gleichzeitig mit dieser Datenbank arbeiten (was natürlich sehr häufig vorkommt). Aber ich nehme an, Sie wissen das bereits und wollen nur wissen, warum Sie nicht einfach eine neue Instanz (d.h. mit einem transienten Lebensstil) der DbContext für jeden, der es braucht. (für weitere Informationen darüber, warum ein einzelner DbContext -oder sogar auf Kontext pro Thread- ist schlecht, lesen 本答 ).

Zunächst möchte ich sagen, dass die Registrierung einer DbContext als transient funktionieren könnte, aber normalerweise möchte man eine einzige Instanz einer solchen Arbeitseinheit innerhalb eines bestimmten Bereichs haben. In einer Web-Anwendung kann es praktisch sein, einen solchen Bereich an den Grenzen einer Web-Anfrage zu definieren, also einen Per Web Request Lifestyle. Dies ermöglicht es Ihnen, eine ganze Reihe von Objekten im selben Kontext zu betreiben. Mit anderen Worten: Sie arbeiten innerhalb desselben Geschäftsvorfalls.

Wenn Sie nicht das Ziel haben, dass eine Reihe von Operationen im gleichen Kontext ablaufen, dann ist der transiente Lebensstil in Ordnung, aber es gibt ein paar Dinge zu beachten:

  • Da jedes Objekt seine eigene Instanz erhält, muss jede Klasse, die den Zustand des Systems ändert, die _context.SaveChanges() (sonst würden Änderungen verloren gehen). Dies kann Ihren Code verkomplizieren und fügt dem Code eine zweite Verantwortung hinzu (die Verantwortung für die Kontrolle des Kontexts) und ist ein Verstoß gegen die Prinzip der einzigen Verantwortung .
  • Sie müssen sicherstellen, dass Entitäten [geladen und gespeichert von einem DbContext ] verlassen niemals den Geltungsbereich einer solchen Klasse, da sie nicht in der Kontextinstanz einer anderen Klasse verwendet werden können. Dies kann Ihren Code enorm verkomplizieren, denn wenn Sie diese Entitäten benötigen, müssen Sie sie erneut per id laden, was auch zu Leistungsproblemen führen kann.
  • Seit DbContext implementiert IDisposable Wenn Sie eine neue Instanz erstellen, möchten Sie wahrscheinlich trotzdem alle erstellten Instanzen entsorgen. Wenn Sie dies tun wollen, haben Sie grundsätzlich zwei Möglichkeiten. Sie müssen sie in der gleichen Methode entsorgen, direkt nach dem Aufruf von context.SaveChanges() aber in diesem Fall übernimmt die Geschäftslogik das Eigentum an einem Objekt, das von außen weitergegeben wird. Die zweite Möglichkeit besteht darin, alle erstellten Instanzen am Rande der HTTP-Anforderung zu entsorgen, aber in diesem Fall benötigen Sie immer noch eine Art von Scoping, damit der Container weiß, wann diese Instanzen entsorgt werden müssen.

Eine weitere Möglichkeit ist pas einspritzen DbContext überhaupt nicht. Stattdessen injizieren Sie eine DbContextFactory die in der Lage ist, eine neue Instanz zu erstellen (in der Vergangenheit habe ich diesen Ansatz verwendet). Auf diese Weise steuert die Geschäftslogik den Kontext explizit. Das könnte dann so aussehen:

public void SomeOperation()
{
    using (var context = this.contextFactory.CreateNew())
    {
        var entities = this.otherDependency.Operate(
            context, "some value");

        context.Entities.InsertOnSubmit(entities);

        context.SaveChanges();
    }
}

Der Vorteil dabei ist, dass Sie die Lebensdauer der DbContext und es ist einfach, dies einzurichten. Außerdem können Sie einen einzigen Kontext in einem bestimmten Bereich verwenden, was eindeutige Vorteile hat, z. B. die Ausführung von Code in einem einzigen Geschäftsvorgang und die Möglichkeit, Entitäten weiterzugeben, da sie aus demselben Kontext stammen. DbContext .

Der Nachteil besteht darin, dass Sie die Informationen weitergeben müssen. DbContext von Methode zu Methode (dies wird als Methodeninjektion bezeichnet). Beachten Sie, dass diese Lösung in gewissem Sinne dasselbe ist wie der "scoped"-Ansatz, aber jetzt wird der Umfang im Anwendungscode selbst kontrolliert (und möglicherweise viele Male wiederholt). Es ist die Anwendung, die für das Erstellen und Entsorgen der Arbeitseinheit verantwortlich ist. Da die DbContext erstellt wird, nachdem der Abhängigkeitsgraph erstellt wurde, ist Constructor Injection nicht mehr möglich, und Sie müssen auf Method Injection zurückgreifen, wenn Sie den Kontext von einer Klasse an die andere weitergeben müssen.

Methodeninjektion ist nicht so schlimm, aber wenn die Geschäftslogik komplexer wird und mehr Klassen involviert sind, müssen Sie sie von Methode zu Methode und von Klasse zu Klasse weitergeben, was den Code sehr verkomplizieren kann (ich habe das in der Vergangenheit gesehen). Für eine einfache Anwendung ist dieser Ansatz jedoch genau richtig.

Wegen der Nachteile, die dieser Fabrik-Ansatz für größere Systeme hat, kann ein anderer Ansatz nützlich sein, und zwar der, bei dem man den Container oder den Infrastrukturcode / Zusammensetzung Wurzel die Arbeitseinheit zu verwalten. Das ist der Stil, um den es in Ihrer Frage geht.

Indem Sie dies dem Container und/oder der Infrastruktur überlassen, wird Ihr Anwendungscode nicht dadurch belastet, dass Sie eine UoW-Instanz erstellen, (optional) übertragen und entsorgen müssen, wodurch die Geschäftslogik einfach und sauber bleibt (nur eine einzige Verantwortung). Dieser Ansatz birgt einige Schwierigkeiten. Wo zum Beispiel wird die Instanz festgeschrieben und entsorgt?

Das Entsorgen einer Arbeitseinheit kann am Ende der Webanforderung erfolgen. Viele Menschen jedoch, falsch gehen Sie davon aus, dass dies auch der Ort ist, an dem Sie die Arbeitseinheit verpflichten. An diesem Punkt in der Anwendung können Sie jedoch nicht mit Sicherheit feststellen, ob die Arbeitseinheit tatsächlich übertragen werden sollte. Wenn z. B. der Code der Geschäftsschicht eine Ausnahme ausgelöst hat, die weiter oben im Aufrufstapel abgefangen wurde, können Sie definitiv nicht verpflichten wollen.

Die eigentliche Lösung besteht wieder darin, explizit eine Art von Bereich zu verwalten, aber diesmal innerhalb der Composition Root. Die Abstraktion der gesamten Geschäftslogik hinter dem Befehl/Handler-Muster können Sie einen Dekorator schreiben, der um jeden Befehlshandler gewickelt werden kann, um dies zu ermöglichen. Beispiel:

class TransactionalCommandHandlerDecorator<TCommand>
    : ICommandHandler<TCommand>
{
    readonly DbContext context;
    readonly ICommandHandler<TCommand> decorated;

    public TransactionCommandHandlerDecorator(
        DbContext context,
        ICommandHandler<TCommand> decorated)
    {
        this.context = context;
        this.decorated = decorated;
    }

    public void Handle(TCommand command)
    {
        this.decorated.Handle(command);

        context.SaveChanges();
    } 
}

Dadurch wird sichergestellt, dass Sie diesen Infrastrukturcode nur einmal schreiben müssen. Jeder solide DI-Container erlaubt es Ihnen, einen solchen Dekorator zu konfigurieren, der um alle ICommandHandler<T> Implementierungen auf konsistente Weise.

46voto

Es gibt zwei widersprüchlich Empfehlungen von Microsoft und viele Menschen verwenden DbContexts auf völlig unterschiedliche Weise.

  1. Eine Empfehlung lautet "DbContexts so schnell wie möglich entsorgen" denn ein lebendiger DbContext belegt wertvolle Ressourcen wie db Verbindungen etc....
  2. Die anderen Staaten, die Ein DbContext pro Anfrage ist hochgradig empfohlen

Diese widersprechen einander, denn wenn Ihre Anforderung eine Menge von nicht bezogen auf die Db Zeug tut, dann ist Ihre DbContext für keinen Grund gehalten. Daher ist es Verschwendung, Ihren DbContext am Leben zu halten, während Ihre Anfrage nur auf zufällige Dinge wartet, um erledigt zu werden...

So viele Menschen, die den Regel 1 haben ihre DbContexts innerhalb ihrer "Repository-Muster" und erstellen eine neue Instanz pro Datenbankabfrage also X*DbContext per Anfrage

Sie erhalten nur ihre Daten und entsorgen den Kontext so schnell wie möglich. Dies wird berücksichtigt durch VIELE Menschen eine akzeptable Praxis. Dies hat zwar den Vorteil, dass Ihre db-Ressourcen so wenig Zeit wie möglich beansprucht werden, aber es geht eindeutig zu Lasten aller UnitOfWork et Caching Süßigkeiten, die EF zu bieten hat.

Eine einzelne Person am Leben erhalten vielseitig einsetzbar Instanz von DbContext maximiert die Vorteile von Caching aber da DbContext ein nicht fadensicher und jede Web-Anfrage in einem eigenen Thread läuft, ist ein DbContext pro Anfrage der längste können Sie es behalten.

Die Empfehlung des EF-Teams, einen Db-Kontext pro Anfrage zu verwenden, basiert eindeutig auf der Tatsache, dass in einer Webanwendung eine UnitOfWork höchstwahrscheinlich innerhalb einer Anfrage stattfinden wird und diese Anfrage einen Thread hat. Ein DbContext pro Anfrage ist also der ideale Nutzen von UnitOfWork und Caching.

Aber In vielen Fällen ist dies nicht der Fall. Ich betrachte Protokollierung eine separate UnitOfWork und damit einen neuen DbContext für das Post-Request-Logging in asynchrone Threads ist völlig akzeptabel

Schließlich stellt sich heraus, dass die Lebensdauer eines DbContextes auf diese beiden Parameter beschränkt ist. UnitOfWork et Thema

34voto

user4893106 Punkte 476

Es gibt keine einzige Antwort, die die Frage wirklich beantwortet. Der OP hat nicht nach einem Singleton/Pro-Applikations-DbContext-Design gefragt, sondern nach einem Pro-(Web)-Anfrage-Design und welche potenziellen Vorteile es geben könnte.

Ich beziehe mich auf http://mehdi.me/ambient-dbcontext-in-ef6/ denn Mehdi ist eine fantastische Quelle:

Mögliche Leistungssteigerung.

Jede DbContext-Instanz unterhält einen First-Level-Cache für alle Entitäten, die sie aus der Datenbank lädt. Wenn Sie eine Entität anhand ihres Primärschlüssels abfragen, versucht der DbContext zunächst, sie aus seinem First-Level-Cache abzurufen, bevor er sie standardmäßig aus der Datenbank abfragt. Abhängig von Ihrem Datenabfragemuster kann die Wiederverwendung desselben DbContext über mehrere aufeinanderfolgende Geschäftstransaktionen hinweg dazu führen, dass dank des DbContext-Zwischenspeichers der ersten Ebene weniger Datenbankabfragen durchgeführt werden müssen.

Sie ermöglicht das "Lazy-Loading".

Wenn Ihre Dienste persistente Entitäten zurückgeben (im Gegensatz zur Rückgabe von View-Modellen oder anderen Arten von DTOs) und Sie die Vorteile des Lazy-Loading für diese Entitäten nutzen möchten, muss die Lebensdauer der DbContext-Instanz, aus der diese Entitäten abgerufen wurden, über den Umfang der Geschäftstransaktion hinausgehen. Wenn die Servicemethode die von ihr verwendete DbContext-Instanz vor der Rückkehr entsorgt, würde jeder Versuch, die Eigenschaften der zurückgegebenen Entitäten mit Lazy-Loading zu nutzen, fehlschlagen (ob die Verwendung von Lazy-Loading eine gute Idee ist oder nicht, ist eine ganz andere Diskussion, auf die wir hier nicht eingehen werden). In unserer Webanwendung würde Lazy-Loading typischerweise in Controller-Action-Methoden für Entitäten verwendet werden, die von einer separaten Service-Schicht zurückgegeben werden. In diesem Fall müsste die DbContext-Instanz, die von der Servicemethode zum Laden dieser Entitäten verwendet wurde, für die Dauer der Webanforderung (oder zumindest bis zum Abschluss der Aktionsmethode) aktiv bleiben.

Allerdings gibt es auch Nachteile. Dieser Link enthält viele weitere Ressourcen zu diesem Thema.

Ich poste das nur, falls jemand über diese Frage stolpert und sich nicht in Antworten verliert, die nicht wirklich auf die Frage eingehen.

21voto

Ian Punkte 4825

Ich bin mir ziemlich sicher, dass es daran liegt, dass der DbContext überhaupt nicht thread-sicher ist. Es ist also keine gute Idee, das Ding zu teilen.

17voto

Rick Strahl Punkte 16515

Eine Sache, die weder in der Frage noch in der Diskussion wirklich angesprochen wird, ist die Tatsache, dass DbContext Änderungen nicht rückgängig machen kann. Sie können Änderungen einreichen, aber Sie können den Änderungsbaum nicht löschen. Wenn Sie also einen Kontext pro Anfrage verwenden, haben Sie Pech, wenn Sie Änderungen aus irgendeinem Grund wegwerfen müssen.

Ich persönlich erstelle bei Bedarf Instanzen von DbContext - in der Regel in Verbindung mit Geschäftskomponenten, die den Kontext bei Bedarf neu erstellen können. Auf diese Weise habe ich die Kontrolle über den Prozess, anstatt dass mir eine einzelne Instanz aufgezwungen wird. Außerdem muss ich den DbContext nicht bei jedem Start des Controllers erstellen, unabhängig davon, ob er tatsächlich verwendet wird. Wenn ich dann immer noch Instanzen pro Anfrage haben möchte, kann ich sie im CTOR erstellen (über DI oder manuell) oder sie nach Bedarf in jeder Controller-Methode erstellen. Ich persönlich wähle in der Regel den letzteren Ansatz, um die Erstellung von DbContext-Instanzen zu vermeiden, wenn sie nicht tatsächlich benötigt werden.

Es kommt auch darauf an, aus welchem Blickwinkel man es betrachtet. Für mich hat die Instanz pro Anfrage nie einen Sinn ergeben. Gehört der DbContext wirklich in die Http-Anfrage? In Bezug auf das Verhalten ist das der falsche Ort. Ihre Geschäftskomponenten sollten Ihren Kontext erstellen, nicht die Http-Anfrage. Dann können Sie Ihre Geschäftskomponenten nach Bedarf erstellen oder wegwerfen und müssen sich keine Gedanken über die Lebensdauer des Kontexts machen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X