Wann sollte ich einen neuen DbContext erstellen ()

83

Ich verwende derzeit eine DbContextähnliche:

namespace Models
{
    public class ContextDB: DbContext
    {

        public DbSet<User> Users { get; set; }
        public DbSet<UserRole> UserRoles { get; set; }

        public ContextDB()
        {

        }
    }
}

Ich verwende dann die folgende Zeile oben auf ALLEN meinen Controllern, die Zugriff auf die Datenbank benötigen. Ich benutze es auch in meiner UserRepository-Klasse, die alle Methoden enthält, die sich auf den Benutzer beziehen (z. B. den aktiven Benutzer abrufen, überprüfen, welche Rollen er hat usw.):

ContextDB _db = new ContextDB();

Denken Sie darüber nach. Es gibt Fälle, in denen ein Besucher mehrere DbContexts aktiv haben kann. Wenn er einen Controller besucht, der das UserRepository verwendet, ist dies möglicherweise nicht die beste Idee, und ich habe ein paar Fragen dazu

  1. Wann sollte ich einen neuen DbContext erstellen / sollte ich einen globalen Kontext haben, den ich weitergebe?
  2. Kann ich einen globalen Kontext haben, den ich an allen Orten wiederverwenden kann?
  3. Verursacht dies einen Leistungseinbruch?
  4. Wie machen das alle anderen?
JensB
quelle
Ich musste als Duplikat markieren - siehe stackoverflow.com/questions/12871666/linq-and-datacontext für eine ziemlich gute Diskussion
SAJ14SAJ
2
In diesem Fall würde ich die Abhängigkeitsinjektion (z. B. Ninject) verwenden, sodass DbContextpro Anforderung eine erstellt wird. Ich würde auch eine Serviceschicht erstellen. Überprüfen Sie diese SO Frage & Antwort
Zbigniew

Antworten:

82

Ich verwende einen Basis-Controller, der eine DataBaseEigenschaft verfügbar macht , auf die abgeleitete Controller zugreifen können.

public abstract class BaseController : Controller
{
    public BaseController()
    {
        Database = new DatabaseContext();
    }

    protected DatabaseContext Database { get; set; }

    protected override void Dispose(bool disposing)
    {
        Database.Dispose();
        base.Dispose(disposing);
    }
}

Alle Controller in meiner Anwendung stammen von folgenden Anwendungen BaseControllerund werden wie folgt verwendet:

public class UserController : BaseController
{
    [HttpGet]
    public ActionResult Index()
    {
        return View(Database.Users.OrderBy(p => p.Name).ToList());
    }
}

Um Ihre Fragen zu beantworten:

Wann sollte ich einen neuen DbContext erstellen / sollte ich einen globalen Kontext haben, den ich weitergebe?

Der Kontext sollte pro Anfrage erstellt werden. Erstellen Sie den Kontext, tun Sie, was Sie damit tun müssen, und entfernen Sie ihn dann. Mit der von mir verwendeten Basisklassenlösung müssen Sie sich nur um die Verwendung des Kontexts kümmern.

Versuchen Sie nicht, einen globalen Kontext zu haben (so funktionieren Webanwendungen nicht).

Kann ich einen globalen Kontext haben, den ich an allen Orten wiederverwenden kann?

Nein, wenn Sie einen Kontext beibehalten, werden alle Aktualisierungen, Ergänzungen, Löschungen usw. nachverfolgt. Dies verlangsamt Ihre Anwendung und kann sogar dazu führen, dass in Ihrer Anwendung einige ziemlich subtile Fehler auftreten.

Sie sollten sich wahrscheinlich dafür entscheiden, entweder Ihr Repository oder Ihren Kontext für Ihren Controller verfügbar zu machen, aber nicht für beide. Wenn zwei Kontexte über dieselbe Methode aufgerufen werden, führt dies zu Fehlern, wenn beide unterschiedliche Vorstellungen über den aktuellen Status der Anwendung haben.

Persönlich ziehe ich es vor, DbContextdirekt zu belichten, da die meisten Repository-Beispiele, die ich gesehen habe, DbContextsowieso nur als dünne Wrapper enden .

Verursacht dies einen Leistungseinbruch?

Das erste Mal, wenn ein DbContexterstellt wird, ist ziemlich teuer, aber sobald dies geschehen ist, werden viele Informationen zwischengespeichert, so dass nachfolgende Instanziierungen viel schneller sind. Es ist wahrscheinlicher, dass Leistungsprobleme auftreten, wenn ein Kontext beibehalten wird, als wenn Sie jedes Mal, wenn Sie Zugriff auf Ihre Datenbank benötigen, eines instanziieren.

Wie machen das alle anderen?

Es hängt davon ab, ob.

Einige Benutzer bevorzugen die Verwendung eines Abhängigkeitsinjektionsframeworks, um eine konkrete Instanz ihres Kontexts beim Erstellen an ihren Controller zu übergeben. Beide Optionen sind in Ordnung. Meins ist besser für eine kleine Anwendung geeignet, bei der Sie wissen, dass sich die verwendete Datenbank nicht ändern wird.

Einige argumentieren möglicherweise, dass Sie dies nicht wissen können, und deshalb ist die Methode der Abhängigkeitsinjektion besser, da sie Ihre Anwendung widerstandsfähiger gegenüber Änderungen macht. Ich bin der Meinung, dass sich dies wahrscheinlich nicht ändern wird (SQL Server und Entity Framework sind kaum unklar) und dass meine Zeit am besten damit verbracht wird, den für meine Anwendung spezifischen Code zu schreiben.

Benjamin Gale
quelle
4
Ich habe dem nichts hinzuzufügen, außer es ist schön zu sehen, dass sich einige meiner eigenen Vorlieben in den Posts anderer Leute widerspiegeln. Ich habe Repository-Beispiele, die dazu führen, dass einem DbContext eine weitere Ebene hinzugefügt wird, nie wirklich verstanden, ohne wirklich etwas hinzuzufügen, und ich bin ein Fan davon, eine Basisklasse zu erstellen, die einen geschützten DbContext verfügbar macht.
dark_perfect
4
Wollte hinzufügen, dass diese Antwort großartig ist, aber jetzt mit der Veröffentlichung von EF6 veraltet ist, die den Kontext nach jeder Verwendung automatisch beseitigt. Daher gibt es Szenarien, in denen das Erstellen eines (globalen) Kontexts pro Sitzung in Ordnung ist. Wenn Sie EF6 für die Konnektivität zu gespeicherten Prozeduren verwenden und das Sperren von Daten kein Problem darstellt, ist es möglicherweise ideal, den Kontext einmal in einem Basiscontroller zu erstellen und alle Controller, für die Datenbankzugriff erforderlich ist, von der Basis zu erben. Die richtigste Antwort ist, dass Sie sich über die Technologie auf dem Laufenden halten und das richtige Muster für Ihre Architektur anwenden müssen.
Atters
Was ist, wenn ich eine Modellmethode aus einer Controller-Aktion aufrufen möchte, die DbContext verwendet? Wie kann ich es an die Modellmethode übergeben?
RMazitov
Dann führen Sie Datenbankabfragen und Geschäftslogik in der Steuerung durch, wo sie nicht hingehören. Sie sollten einen Dienst erstellen, der von Ihrem Controller aufgerufen wird. Dh Ihr UserController ruft eine Methode in Ihrem UserService auf.
Fred
@Fred, ja, und wie übergebe ich DbContext an UserService, indem ein einfacher Parameter funktioniert oder nein?
RMazitov
10

Ich versuche aus eigener Erfahrung zu antworten.

1. Wann sollte ich einen neuen DbContext erstellen / sollte ich einen globalen Kontext haben, den ich weitergebe?

Der Kontext sollte durch die Abhängigkeitsinjektion injiziert und nicht von Ihnen selbst instanziiert werden. Best-Practice besteht darin, es durch die Abhängigkeitsinjektion als Scoped-Service erstellen zu lassen. (Siehe meine Antwort auf Frage 4)

Bitte erwägen Sie auch die Verwendung einer geeigneten mehrschichtigen Anwendungsstruktur wie Controller> BusinessLogic> Repository. In diesem Fall empfängt Ihr Controller nicht den Datenbankkontext, sondern das Repository. Das Einfügen / Instanziieren eines Datenbankkontexts in einen Controller zeigt mir, dass Ihre Anwendungsarchitektur viele Verantwortlichkeiten an einem Ort vereint, was ich unter keinen Umständen empfehlen kann.

2. Kann ich einen globalen Kontext haben, den ich an allen Orten wiederverwenden kann?

Ja, Sie können haben, aber die Frage sollte sein " Sollte ich haben ..." -> NEIN. Der Kontext soll pro Anfrage verwendet werden, um Ihr Repository zu ändern, und dann ist es wieder weg.

3. Verursacht dies einen Leistungseinbruch?

Ja, weil der DBContext einfach nicht dafür gemacht ist, global zu sein. Es speichert alle Daten, die eingegeben oder abgefragt wurden, bis sie zerstört werden. Das bedeutet, dass ein globaler Kontext immer größer wird, die Vorgänge darauf immer langsamer werden, bis Sie Ausnahmen aufgrund von Speichermangel bekommen oder an Altersschwäche sterben, weil sich alles zu einem Crawl verlangsamt hat.

Sie erhalten auch Ausnahmen und viele Fehler, wenn mehrere Threads gleichzeitig auf denselben Kontext zugreifen.

4. Wie machen das alle anderen?

DBContext, der durch Abhängigkeitsinjektion von einer Fabrik injiziert wird; Umfang:

services.AddDbContext<UserDbContext>(o => o.UseSqlServer(this.settings.DatabaseOptions.UserDBConnectionString));

Ich hoffe meine Antworten waren hilfreich.

Ravior
quelle
Was ist, wenn ich den DBContext in der Datei Startup.cs in der ConfigureServices-Methode selbst verwenden möchte? Ich habe eine OICD-Middleware, auf die ich zugreifen muss, aber ich kann nicht auf den DBContext zugreifen oder weiß nicht wie?
Bbrinck
In der configureServices-Methode ist Ihr DBContext wahrscheinlich nicht verfügbar, da Sie ihn dort konfiguriert haben. Der ServiceProvider, von dem Sie Ihren DBContext zur Laufzeit tatsächlich erhalten, ist zuerst in der Configure () -Methode (nicht "ConfigureServices"!) Verfügbar. Dort können Sie den Kontext mit dem ApplicationBuilder anfordern, indem Sie "app.ApplicationServices.GetRequiredService <MyDbContext> ()" eingeben. (Ersetzen Sie MyDbContext durch den Klassennamen Ihres eigenen Kontexts).
Ravior
Wenn Controller ein Repository erhalten, wie speichern sie (Controller) Änderungen? Angenommen, ich sende eine POST-Anfrage, um etwas in die Datenbank einzufügen. Der Controller verarbeitet die Anfrage und verwendet das Repository, um die neu erstellten Objekte hinzuzufügen. Was dann? Wer beharrt auf den Veränderungen?
MMalke
@MMalke Das Repository macht das. Es hat normalerweise die Funktion "SaveData (Data myData)" und dann erstellt das Repository eine Instanz seines Entity-Framework-Geschwisters für die Datenklasse, fügt sie dem entsprechenden dbset im dbcontext hinzu und ruft dann SaveChanges () auf. Der Controller ruft lediglich die Funktion Repository.SaveData (myData) auf.
Ravior
1

Im Moment versuche ich diesen Ansatz, der es vermeidet, den Kontext zu instanziieren, wenn Sie Aktionen aufrufen, die ihn nicht verwenden.

public abstract class BaseController : Controller
{
    public BaseController() { }

    private DatabaseContext _database;
    protected DatabaseContext Database
    {
        get
        {
            if (_database == null)
                _database = new DatabaseContext();
            return _database;
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (_database != null)
            _database.Dispose();
        base.Dispose(disposing);
    }
}
Andrew
quelle
2
Obwohl an Ihrem Ansatz nichts auszusetzen ist, glaube ich, dass ein Entity Framework-Kontext alles auf träge Weise erledigt und keine echte Arbeit ausgeführt wird, bis Sie tatsächlich auf die Datenbank zugreifen. Der Aufwand für die Erstellung eines EF-Kontexts sollte daher sehr gering sein.
Martin Liversage
Ich habe einige Nachforschungen angestellt und es scheint, dass das richtig ist. Ich habe einen einfachen Test durchgeführt, indem ich verglichen habe, was GC.GetTotalMemory()zurückgegeben wurde (nicht perfekt, aber es ist das, was ich gefunden habe), und der Unterschied war nie größer als 8 KB.
Andrew
0

Dies ist offensichtlich eine ältere Frage, aber wenn Sie DI verwenden, können Sie so etwas tun und alle Ihre Objekte für die Lebensdauer der Anforderung erfassen

 public class UnitOfWorkAttribute : ActionFilterAttribute
    {
        public override void OnActionExecuting(HttpActionContext actionContext)
        {
            var context = IoC.CurrentNestedContainer.GetInstance<DatabaseContext>();
            context.BeginTransaction();
        }

        public override void OnActionExecuted(HttpActionExecutedContext actionContext)
        {
            var context = IoC.CurrentNestedContainer.GetInstance<DatabaseContext>();
            context.CloseTransaction(actionContext.Exception);
        }
    }
CSharper
quelle
0

Sie sollten den Kontext sofort nach jedem Save () - Vorgang entsorgen. Andernfalls dauert jedes weitere Speichern länger. Ich hatte ein Projekt, das komplexe Datenbankentitäten in einem Zyklus erstellt und gespeichert hat. Zu meiner Überraschung wurde der Vorgang dreimal schneller, nachdem ich "using (var ctx = new MyContext ()) {...}" innerhalb des Zyklus verschoben hatte.

Gregory Khrapunovich
quelle