Jeden DbContext na żądanie www ... dlaczego?

Czytałem wiele artykułów wyjaśniających, jak skonfigurować Entity Framework DbContext tak, że tylko jeden jest tworzony i używany na żądanie HTTP przy użyciu różnych frameworków DI.

Dlaczego w ogóle jest to dobry pomysł? Jakie korzyści zyskujesz stosując takie podejście? Czy są pewne sytuacje, w których byłby to dobry pomysł? Czy są rzeczy, które możesz zrobić za pomocą tej techniki, których nie możesz zrobić podczas tworzenia instancji DbContext s dla metody repozytorium zadzwonić?

Author: ArunPratap, 2012-05-14

9 answers

UWAGA: Ta odpowiedź mówi o Entity Framework DbContext, ale ma ona zastosowanie do wszelkiego rodzaju jednostek realizacji prac, takich jak LINQ do SQL 's DataContext i NHibernate' s ISession.

Zacznijmy od ECHA Iana: posiadanie pojedynczego DbContext dla całej aplikacji to zły pomysł. Jedyną sytuacją, w której ma to sens, jest sytuacja, w której masz jednowątkową aplikację i bazę danych, która jest używana wyłącznie przez tę pojedynczą instancję aplikacji. Na DbContext nie jest bezpieczny dla wątków, a ponieważ DbContext buforuje Dane, wkrótce staje się nieświeży. Sprawi to, że będziesz miał różnego rodzaju kłopoty, gdy wielu użytkowników / aplikacji pracuje jednocześnie nad tą bazą danych (co jest oczywiście bardzo powszechne). Ale spodziewam się, że już to wiesz i po prostu chcesz wiedzieć, dlaczego po prostu nie wstrzyknąć nowej instancji (tj. z przemijającym stylem życia) DbContext każdemu, kto tego potrzebuje. (aby uzyskać więcej informacji o tym, dlaczego pojedynczy DbContext -a nawet na temat kontekstu na wątek - jest zły, przeczytaj ta odpowiedź ).

Pozwolę sobie zacząć od stwierdzenia, że rejestracja DbContext jako transient może zadziałać, ale zazwyczaj chcesz mieć pojedynczą instancję takiej jednostki pracy w określonym zakresie. W aplikacji internetowej praktyczne może być zdefiniowanie takiego zakresu na granicach żądania sieciowego, a więc na żądanie sieciowe. Pozwala to na działanie całego zestawu obiektów w tym samym kontekście. Innymi słowy, działają w ramach tej samej firmy transakcja.

Jeśli nie masz celu, aby zestaw operacji działał w tym samym kontekście, w takim przypadku przemijający styl życia jest w porządku, ale jest kilka rzeczy do obejrzenia:]}
  • ponieważ każdy obiekt otrzymuje własną instancję, każda klasa, która zmienia stan systemu, musi wywołać _context.SaveChanges() (w przeciwnym razie zmiany zostałyby utracone). Może to skomplikować Twój kod i dodać do kodu drugą odpowiedzialność (odpowiedzialność za kontrolowanie kontekstu) i jest naruszenie Zasady jednolitej odpowiedzialności .
  • musisz się upewnić, że encje [załadowane i zapisane przez DbContext] nigdy nie opuszczają zakresu takiej klasy, ponieważ nie mogą być użyte w instancji kontekstowej innej klasy. Może to ogromnie skomplikować Twój kod, ponieważ gdy potrzebujesz tych elementów, musisz załadować je ponownie przez id, co może również powodować problemy z wydajnością.
  • Ponieważ DbContext implementuje IDisposable, prawdopodobnie nadal chcesz pozbyć się wszystkich stworzonych przypadki. Jeśli chcesz to zrobić, masz dwie opcje. Należy je usunąć tą samą metodą zaraz po wywołaniu context.SaveChanges(), ale w takim przypadku logika biznesowa przejmuje własność obiektu, który jest przekazywany z zewnątrz. Drugą opcją jest pozbycie się wszystkich utworzonych instancji na granicy żądania Http, ale w takim przypadku nadal potrzebujesz pewnego zakresu, aby poinformować kontener, kiedy te instancje muszą zostać usunięte.

Inną opcją jest Nie w ogóle wstrzyknąć DbContext. Zamiast tego, wstrzykujesz DbContextFactory, który jest w stanie utworzyć nową instancję (kiedyś używałem tego podejścia w przeszłości). W ten sposób logika biznesowa jawnie kontroluje kontekst. Jeśli może wyglądać tak:

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

        context.Entities.InsertOnSubmit(entities);

        context.SaveChanges();
    }
}
Plusem tego jest to, że zarządzasz życiem DbContext wyraźnie i łatwo jest to skonfigurować. Pozwala również na użycie pojedynczego kontekstu w określonym zakresie, co ma wyraźne zalety, takie jak uruchamianie kodu w jednej firmie transakcji, oraz możliwość przekazywania podmiotów, ponieważ pochodzą one z tego samego DbContext.

Minusem jest to, że będziesz musiał przekazać DbContext z metody do metody(która jest określana jako metoda wtrysku). Zauważ, że w pewnym sensie to rozwiązanie jest takie samo jak podejście "scoped" , ale teraz zakres jest kontrolowany w samym kodzie aplikacji (i może być powtarzany wiele razy). Jest to aplikacja, która jest odpowiedzialna za tworzenie i usuwanie jednostki pracy. Ponieważ DbContext jest tworzony po zbudowaniu grafu zależności, Konstruktor Injection jest poza obrazem i musisz przejść do metody Injection, gdy musisz przekazać kontekst z jednej klasy do drugiej.

Method Injection nie jest takie złe, ale kiedy logika biznesowa staje się bardziej złożona i angażuje się więcej klas, będziesz musiał przekazać ją z metody do metody i klasy do klasy, co może bardzo skomplikować kod (widziałem to w przeszłości). Do prostej aplikacji, takie podejście poradzi sobie jednak dobrze.

Ze względu na wady, to podejście fabryczne ma dla większych systemów, inne podejście może być przydatne i to jest takie, w którym pozwalasz kontenerowi lub kodzie infrastruktury / korzeń kompozycji zarządzać jednostką pracy. To jest styl, który dotyczy twojego pytania.

Pozwalając kontenerowi i / lub infrastrukturze zająć się tym, Twój kod aplikacji nie jest zanieczyszczany przez konieczność tworzenia, (opcjonalnie) commit i Pozbądź się instancji UoW, która utrzymuje logikę biznesową prostą i czystą (tylko jedna odpowiedzialność). Istnieją pewne trudności z tym podejściem. Na przykład, czy zobowiązujesz i pozbywasz się instancji?

Usunięcie jednostki pracy może być wykonane na końcu żądania internetowego. Jednak wiele osób błędnie zakłada, że jest to również miejsce, w którym można zaangażować jednostkę pracy. Jednak w tym momencie w aplikacji po prostu nie można określić na pewno, że jednostka praca powinna być rzeczywiście zaangażowana. na przykład, jeśli kod warstwy biznesowej wyrzucił wyjątek, który został złapany wyżej w callstack, zdecydowanie nie chcesz zatwierdzać.

Prawdziwym rozwiązaniem jest ponownie jawne zarządzanie jakimś zakresem, ale tym razem zrób to wewnątrz katalogu głównego kompozycji. Abstrahując całą logikę biznesową za wzorcem polecenia / obsługi , będziesz w stanie napisać dekorator, który może być owinięty wokół każdego programu obsługi poleceń, który pozwala to zrobić. Przykład:

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();
    } 
}

Gwarantuje to, że kod infrastruktury trzeba napisać tylko raz. Każdy kontener solid DI pozwala skonfigurować taki dekorator, aby był owinięty wokół wszystkich implementacji {22]} w spójny sposób.

 582
Author: Steven,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-10-27 21:25:34

Istnieją dwa sprzeczne zalecenia Microsoftu i wiele osób korzysta z DbContext w zupełnie odmienny sposób.

  1. jedną z rekomendacji jest "pozbądź się DbContext jak najszybciej" ponieważ posiadanie DbContext Alive zajmuje cenne zasoby, takie jak db połączenia itp....
  2. drugi stwierdza, że Jeden DbContext na żądanie jest wysoce reccomended

Te są ze sobą sprzeczne, ponieważ jeśli Twoja prośba robi wiele z niezwiązanych z DB rzeczy, a następnie DbContext jest przechowywane bez powodu. W ten sposób marnuje się DbContext, podczas gdy twoje żądanie czeka tylko na losowe rzeczy do zrobienia...

Tak wiele osób, które przestrzegają Zasady 1 ma swoje DbContext wewnątrz "wzorca repozytorium" i tworzy nową instancję na zapytanie bazy danych Tak x * DbContext na żądanie

Po prostu dostają swoje dane i pozbywają się kontekstu jak najszybciej. Jest to rozpatrywane przez Wielu ludzi jest akceptowalną praktyką. Chociaż ma to zalety zajmowania zasobów db przez minimalny czas, wyraźnie poświęca wszystkie UnitOfWork i buforowanie candy EF ma do zaoferowania.

Utrzymanie przy życiu pojedynczej wielozadaniowej instancji DbContext maksymalizuje korzyści z buforowania ale ponieważ DbContext jest nie bezpieczny dla wątku i każde żądanie sieci Web działa na własnym wątku, DbContext na żądanie jest najdłuższy można zatrzymaj to.

Więc rekomendacja zespołu EF o używaniu kontekstu 1 Db na żądanie jest wyraźnie oparta na fakcie, że w aplikacji internetowej UnitOfWork najprawdopodobniej będzie w jednym żądaniu i że żądanie ma jeden wątek. Tak więc jeden DbContext na żądanie jest idealną zaletą UnitOfWork i buforowania.

Ale w wielu przypadkach nie jest to prawdą. Uznaję Logging za oddzielną jednostkę pracy, dzięki czemu mamy nowy DbContext do logowania po żądaniu asynchronicznego wątki {[2] } jest całkowicie dopuszczalne

W końcu okazuje się, że żywotność DbContext jest ograniczona do tych dwóch parametrów. UnitOfWork i Thread

 40
Author: Anestis Kivranoglou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-12-13 15:10:00

Ani jedna odpowiedź tutaj rzeczywiście odpowiada na pytanie. OP nie zapytał o projekt DbContext singleton / per-application, zapytał o projekt per - (web)request i jakie potencjalne korzyści mogą istnieć.

I ' ll reference http://mehdi.me/ambient-dbcontext-in-ef6 / jako że Mehdi jest fantastycznym zasobem:

Możliwy wzrost wydajności.

Każda instancja DbContext utrzymuje bufor pierwszego poziomu wszystkich elementów ładowanych z bazy danych. Za każdym razem, gdy odpytywasz obiekt za pomocą klucza podstawowego, DbContext najpierw spróbuje pobrać go z bufora pierwszego poziomu, a następnie domyślnie odpytywa go z bazy danych. W zależności od wzorca zapytań dotyczących danych, ponowne użycie tego samego DbContext w wielu sekwencyjnych transakcjach biznesowych może skutkować mniejszą liczbą zapytań do bazy danych dzięki buforowi pierwszego poziomu DbContext.

Umożliwia leniwe Ładowanie.

Jeśli Twoje usługi zwracają trwałe podmioty (w przeciwieństwie do zwracanie modeli widoku lub innych rodzajów Dto) i chcesz skorzystać z leniwego ładowania tych podmiotów, czas życia instancji DbContext, z której te podmioty zostały pobrane, musi wykraczać poza zakres transakcji biznesowej. Jeśli metoda service usunie instancję DbContext, której użyła przed zwróceniem, każda próba Lazy-load właściwości zwracanych elementów zakończy się niepowodzeniem (to, czy używanie Lazy-Load jest dobrym pomysłem, jest zupełnie inną debatą, którą nie dostanie się tutaj). W naszym przykładzie aplikacji webowej, leniwe ładowanie będzie zwykle używane w metodach akcji kontrolera na obiektach zwracanych przez oddzielną warstwę usług. W takim przypadku instancja DbContext, która została użyta przez metodę service do załadowania tych encji, musi pozostać żywa przez czas trwania żądania sieci web (lub przynajmniej do czasu zakończenia metody akcji).

Należy pamiętać, że są również minusy. Ten link zawiera wiele innych zasobów do przeczytania temat.

Po prostu zamieszczam to na wypadek, gdyby ktoś inny natknął się na to pytanie i nie został pochłonięty odpowiedziami, które tak naprawdę nie odnoszą się do pytania.

 34
Author: user4893106,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2020-06-20 09:12:55

Jestem całkiem pewien, że to dlatego, że DbContext nie jest w ogóle Bezpieczny wątek. Więc dzielenie się tym nigdy nie jest dobrym pomysłem.

 22
Author: Ian,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-14 15:03:57

Jedna rzecz, która nie jest tak naprawdę poruszona w pytaniu lub dyskusji, to fakt, że DbContext nie może anulować zmian. Możesz przesyłać zmiany, ale nie możesz wyczyścić drzewa zmian, więc jeśli używasz kontekstu na żądanie, masz pecha, jeśli musisz wyrzucić zmiany z dowolnego powodu.

Osobiście tworzę instancje DbContext w razie potrzeby-zwykle dołączane do komponentów biznesowych, które mają możliwość odtworzenia kontekstu w razie potrzeby. W ten sposób mam kontrolę nad proces, a nie zmuszanie mnie do jednego wystąpienia. Nie muszę też tworzyć DbContext przy każdym uruchomieniu kontrolera, niezależnie od tego, czy rzeczywiście zostanie użyty. Następnie, jeśli nadal chcę mieć instancje na żądanie, mogę je utworzyć w CTOR (poprzez DI lub ręcznie) lub utworzyć je w razie potrzeby w każdej metodzie kontrolera. Osobiście zazwyczaj stosuję to drugie podejście, aby uniknąć tworzenia instancji DbContext, gdy nie są one rzeczywiście potrzebne.

To zależy z jakiego kąta Zobacz też Dla mnie instancja per request nigdy nie miała sensu. Czy DbContext naprawdę należy do żądania Http? Jeśli chodzi o zachowanie, to nie to miejsce. Komponenty biznesowe powinny tworzyć kontekst, a nie żądanie Http. Następnie możesz tworzyć lub wyrzucać komponenty Biznesowe w razie potrzeby i nigdy nie martwić się o żywotność kontekstu.

 18
Author: Rick Strahl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-06-16 18:20:02

Zgadzam się z wcześniejszymi opiniami. Warto powiedzieć, że jeśli zamierzasz udostępniać DbContext w aplikacji single thread, będziesz potrzebował więcej pamięci. Na przykład moja aplikacja internetowa na platformie Azure (jedna dodatkowa mała instancja) potrzebuje jeszcze 150 MB pamięci i mam około 30 użytkowników na godzinę. Udostępnianie aplikacji DBContext w żądaniu HTTP

Oto przykładowy obrazek: aplikacja została wdrożona w 12PM

 11
Author: Miroslav Holec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-04-08 13:36:22

Podoba mi się w tym, że wyrównuje jednostkę pracy (jak widzi to użytkownik-tj. stronę submit) z jednostką pracy w sensie ORM.

W związku z tym możesz dokonać transakcji całego przesłania strony, czego nie możesz zrobić, jeśli ujawniasz metody CRUD z każdym tworzeniem nowego kontekstu.

 3
Author: RB.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-05-14 14:58:59

Innym zaniżonym powodem nieużywania singleton DbContext, nawet w pojedynczej aplikacji z wątkiem pojedynczego użytkownika, jest wzór mapy tożsamości, którego używa. Oznacza to, że za każdym razem, gdy pobierasz dane za pomocą zapytania lub id, będzie ono przechowywać pobrane instancje encji w pamięci podręcznej. Następnym razem, gdy odzyskasz ten sam encja, otrzymasz buforowaną instancję encji, jeśli jest dostępna, z dowolnymi modyfikacjami wykonanymi w tej samej sesji. Jest to konieczne, aby oszczędzać metoda nie kończy się z wieloma różnymi instancjami encji tego samego rekordu bazy danych; w przeciwnym razie kontekst musiałby w jakiś sposób scalić dane ze wszystkich tych instancji encji.

Powodem, dla którego jest to problem, jest to, że singleton DbContext może stać się bombą zegarową, która może ostatecznie buforować całą bazę danych + narzut obiektów.NET w pamięci.

Istnieją sposoby obejścia tego zachowania, używając tylko zapytań Linq z metodą rozszerzenia .NoTracking(). Również w dzisiejszych czasach komputery mają dużo pamięci RAM. Ale zazwyczaj nie jest to pożądane zachowanie.

 3
Author: Dmitry S.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-08-06 18:52:23

Inną kwestią, na którą należy zwrócić uwagę w przypadku struktury encji, jest użycie kombinacji tworzenia nowych encji, leniwego ładowania, a następnie używanie tych nowych encji (z tego samego kontekstu). Jeśli nie używasz IDbSet.Create (vs just new), leniwe Ładowanie tego elementu nie działa, gdy jego Pobrano z kontekstu, w którym został utworzony. Przykład:

 public class Foo {
     public string Id {get; set; }
     public string BarId {get; set; }
     // lazy loaded relationship to bar
     public virtual Bar Bar { get; set;}
 }
 var foo = new Foo {
     Id = "foo id"
     BarId = "some existing bar id"
 };
 dbContext.Set<Foo>().Add(foo);
 dbContext.SaveChanges();

 // some other code, using the same context
 var foo = dbContext.Set<Foo>().Find("foo id");
 var barProp = foo.Bar.SomeBarProp; // fails with null reference even though we have BarId set.
 1
Author: Ted Elliott,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-03-23 17:04:16