Czytałem wiele artykułów wyjaśniających, jak skonfigurować Entity Framework, DbContext
aby tylko jeden był tworzony i używany na żądanie HTTP za pomocą różnych frameworków DI.
Dlaczego to dobry pomysł? Jakie korzyści zyskujesz stosując to podejście? Czy istnieją sytuacje, w których byłby to dobry pomysł? Czy są rzeczy, które można zrobić za pomocą tej techniki, których nie można zrobić, gdy tworzy się instancję DbContext
s dla wywołania metody repozytorium?
Odpowiedzi:
Zacznijmy od echa Iana: posiadanie singla
DbContext
dla całej aplikacji jest złym pomysłem. Jedyną sytuacją, w której ma to sens, jest posiadanie aplikacji jednowątkowej i bazy danych używanej wyłącznie przez tę instancję pojedynczej aplikacji. NieDbContext
jest bezpieczny dla wątków, a ponieważDbContext
dane w pamięci podręcznej szybko się zestarzeją. Doprowadzi to do różnego rodzaju problemów, gdy wielu użytkowników / aplikacji pracuje jednocześnie nad tą bazą danych (co jest oczywiście bardzo częste). Ale oczekuję, że już to wiesz i po prostu chcesz wiedzieć, dlaczego nie po prostu wstrzyknąć nowej instancji (tj. Przejściowy styl życia)DbContext
każdemu, kto jej potrzebuje. (aby uzyskać więcej informacji o tym, dlaczego pojedynczyDbContext
- lub nawet w kontekście na wątek - jest zły, przeczytaj tę odpowiedź ).Zacznę od stwierdzenia, że rejestracja
DbContext
jako przejściowa może działać, ale zazwyczaj chcesz mieć jedno wystąpienie takiej jednostki pracy w określonym zakresie. W aplikacji internetowej może być praktyczne zdefiniowanie takiego zakresu na granicach żądania sieciowego; w ten sposób styl życia na żądanie internetowe. Umożliwia to działanie całego zestawu obiektów w tym samym kontekście. Innymi słowy, działają w ramach tej samej transakcji biznesowej.Jeśli nie masz celu, aby zestaw operacji działał w tym samym kontekście, w takim przypadku przejściowy styl życia jest w porządku, ale jest kilka rzeczy do obejrzenia:
_context.SaveChanges()
(w przeciwnym razie zmiany zostaną utracone). Może to skomplikować Twój kod i nałożyć na niego drugą odpowiedzialność (odpowiedzialność za kontrolowanie kontekstu), co stanowi naruszenie zasady pojedynczej odpowiedzialności .DbContext
] nigdy nie opuszczają zakresu takiej klasy, ponieważ nie można ich użyć w kontekście innej klasy. Może to ogromnie skomplikować Twój kod, ponieważ gdy potrzebujesz tych jednostek, musisz załadować je ponownie według identyfikatora, co może również powodować problemy z wydajnością.DbContext
implementujeIDisposable
, prawdopodobnie nadal chcesz usunąć wszystkie utworzone instancje. Jeśli chcesz to zrobić, zasadniczo masz dwie opcje. Musisz je zutylizować w ten sam sposób zaraz po wywołaniucontext.SaveChanges()
, ale w takim przypadku logika biznesowa przejmuje własność obiektu, który jest przekazywany z zewnątrz. Drugą opcją jest usunięcie 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 wcale nie wstrzykiwanie
DbContext
. Zamiast tego wstrzykujesz plik,DbContextFactory
który jest w stanie utworzyć nową instancję (kiedyś używałem tego podejścia). W ten sposób logika biznesowa wyraźnie kontroluje kontekst. Jeśli mogłoby to wyglądać tak:Zaletą tego jest to, że zarządzasz życiem
DbContext
jawnie i łatwo to skonfigurować. Pozwala także na użycie jednego kontekstu w pewnym zakresie, który ma wyraźne zalety, takie jak uruchamianie kodu w pojedynczej transakcji biznesowej i możliwość przekazywania jednostek, ponieważ pochodzą one z tego samegoDbContext
.Minusem jest to, że będziesz musiał przechodzić
DbContext
od metody do metody (która nazywa się Method Injection). Zauważ, że w pewnym sensie to rozwiązanie jest takie samo jak podejście „zakresowe”, ale teraz zakres jest kontrolowany w samym kodzie aplikacji (i być może jest wielokrotnie powtarzany). Jest to aplikacja odpowiedzialna za tworzenie i usuwanie jednostki pracy. PonieważDbContext
jest tworzony po skonstruowaniu wykresu zależności, konstruktor Wtrysk jest poza obrazem i musisz przejść do metody Wtrysk, gdy musisz przekazać kontekst z jednej klasy do drugiej.Metoda wstrzykiwania nie jest taka zła, ale kiedy logika biznesowa staje się bardziej złożona i angażuje się więcej klas, będziesz musiał przekazać ją od metody do metody i klasy do klasy, co może bardzo skomplikować kod (widziałem to w przeszłości). W przypadku prostej aplikacji to podejście wystarczy.
Ze względu na wady to podejście fabryczne ma zastosowanie w przypadku większych systemów, inne podejście może być przydatne i to jest to, w którym pozwalasz kontenerowi lub kodowi infrastruktury / rootowaniu kompozycji zarządzać jednostką pracy. To jest styl, którego dotyczy twoje pytanie.
Pozwalając kontenerowi i / lub infrastrukturze obsłużyć to, kod aplikacji nie jest zanieczyszczony przez konieczność utworzenia (opcjonalnie) zatwierdzenia i usunięcia instancji UoW, co zapewnia logikę biznesową prostą i czystą (tylko jedna odpowiedzialność). Z tym podejściem wiążą się pewne trudności. Na przykład, czy popełniłeś i zlikwidowałeś instancję?
Pozbywanie się jednostki pracy można wykonać na końcu żądania internetowego. Jednak wielu ludzi błędnie zakłada, że jest to również miejsce, w którym można zatwierdzić jednostkę pracy. Jednak w tym momencie aplikacji po prostu nie można ustalić, czy jednostka pracy powinna zostać faktycznie zatwierdzona. np. jeśli kod warstwy biznesowej zgłosił wyjątek, który został złapany wyżej na stosie wywołań, zdecydowanie nie chcesz zatwierdzać.
Prawdziwym rozwiązaniem jest ponownie jawne zarządzanie jakimś zakresem, ale tym razem zrób to w katalogu głównym. Wyodrębnienie całej logiki biznesowej stojącej za wzorcem polecenia / procedury obsługi , będziesz mógł napisać dekorator, który można owinąć wokół każdego modułu obsługi poleceń, który pozwala to zrobić. Przykład:
Zapewnia to, że kod infrastruktury trzeba zapisać tylko raz. Każdy solidny pojemnik DI umożliwia skonfigurowanie takiego dekoratora, aby był owijany wokół wszystkich
ICommandHandler<T>
implementacji w spójny sposób.źródło
CreateCommand<TEnity>
i ogólnyCreateCommandHandler<TEntity> : ICommandHandler<CreateCommand<TEntity>>
(i zrobić to samo dla Aktualizuj i Usuń, i miał jednoGetByIdQuery<TEntity>
zapytanie). Jednak powinieneś zadać sobie pytanie, czy ten model jest użyteczną abstrakcją dla operacji CRUD, czy tylko dodaje złożoności. Mimo to możesz skorzystać z możliwości łatwego dodawania problemów przekrojowych (przez dekoratorów) za pomocą tego modelu. Będziesz musiał rozważyć zalety i wady.TransactionCommandHandlerDecorator
? na przykład, jeśli zdobiona klasa jestInsertCommandHandler
klasą, jak mogłaby zarejestrować operację wstawiania w kontekście (DbContext w EF)?Żadna odpowiedź tutaj nie odpowiada na pytanie. OP nie zapytał o projekt DbContext dla pojedynczego / na aplikację, zapytał o projekt żądania dla (sieci) i jakie potencjalne korzyści mogłyby istnieć.
Odniosę się do http://mehdi.me/ambient-dbcontext-in-ef6/, ponieważ Mehdi jest fantastycznym źródłem:
Pamiętaj, że są też wady. Ten link zawiera wiele innych zasobów do przeczytania na ten temat.
Wystarczy opublikować to na wypadek, gdyby ktoś natknął się na to pytanie i nie wchłonął odpowiedzi, które w rzeczywistości nie dotyczą tego pytania.
źródło
Istnieją dwie sprzeczne zalecenia firmy Microsoft i wiele osób korzysta z DbContexts w całkowicie rozbieżny sposób.
Te są ze sobą sprzeczne, ponieważ jeśli twoje żądanie robi wiele niezwiązanych z Db rzeczy, to twój DbContext jest przechowywany bez powodu. Dlatego marnowanie DbContext przy życiu jest niepotrzebne, podczas gdy twoja prośba czeka tylko na zrobienie losowych rzeczy ...
Tak wiele osób, które przestrzegają reguły 1, ma DbContexts w swoim „ Wzorcu repozytorium” i tworzy nowe wystąpienie na zapytanie do bazy danych, więc X * DbContext na żądanie
Po prostu pobierają swoje dane i usuwają kontekst jak najszybciej. WIELU ludzi uważa to za dopuszczalną praktykę. Zaletą tego jest zajmowanie zasobów bazy danych przez minimalny czas, ale wyraźnie poświęca całą pracę UnitOfWork i buforowanie EF cukierki .
Utrzymanie przy życiu pojedynczej, wielozadaniowej instancji DbContext maksymalizuje korzyści buforowania, ale ponieważ DbContext nie jest bezpieczny dla wątków, a każde żądanie WWW działa w swoim własnym wątku, DbContext na żądanie jest najdłuższy możliwych.
Tak więc zalecenie zespołu EF dotyczące używania kontekstu 1 Db na żądanie jest wyraźnie oparte na fakcie, że w aplikacji sieciowej UnitOfWork najprawdopodobniej znajdzie się w jednym żądaniu i to żą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ą. I rozważyć Logging oddzielny UnitOfWork zatem o nowy DbContext dla post-Request zalogowaniu asynchronicznych wątków jest całkowicie akceptowalne
Wreszcie okazuje się, że czas życia DbContext jest ograniczony do tych dwóch parametrów. UnitOfWork and Thread
źródło
Jestem pewien, że dzieje się tak, ponieważ DbContext wcale nie jest bezpieczny dla wątków. Dlatego dzielenie się tym nigdy nie jest dobrym pomysłem.
źródło
Jedną z rzeczy, które tak naprawdę nie zostały poruszone w pytaniu lub dyskusji, jest 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 chcesz odrzucić zmiany z jakiegokolwiek powodu.
Osobiście tworzę instancje DbContext w razie potrzeby - zazwyczaj dołączone do komponentów biznesowych, które mają możliwość odtworzenia kontekstu, jeśli jest to wymagane. W ten sposób mam kontrolę nad procesem, zamiast narzucania mi jednej instancji. Nie muszę też tworzyć DbContext przy każdym uruchomieniu kontrolera, niezależnie od tego, czy faktycznie zostanie wykorzystany. Następnie, jeśli nadal chcę mieć instancje na żądanie, mogę je utworzyć w CTOR (przez 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 wystąpień DbContext, gdy nie są one faktycznie potrzebne.
To zależy, pod jakim kątem też na to spojrzysz. Dla mnie instancja na żądanie nigdy nie miała sensu. Czy DbContext naprawdę należy do żądania HTTP? Pod względem zachowania jest to złe miejsce. Komponenty biznesowe powinny tworzyć kontekst, a nie żądanie HTTP. Następnie możesz utworzyć lub wyrzucić komponenty biznesowe w razie potrzeby i nigdy nie martwić się o czas życia kontekstu.
źródło
Zgadzam się z wcześniejszymi opiniami. Dobrze jest powiedzieć, że jeśli zamierzasz udostępniać DbContext w aplikacji jednowątkowej, potrzebujesz więcej pamięci. Na przykład moja aplikacja internetowa na platformie Azure (jedna bardzo mała instancja) potrzebuje kolejnych 150 MB pamięci i mam około 30 użytkowników na godzinę.
Oto prawdziwy przykładowy obraz: aplikacja została wdrożona o godzinie 12:00
źródło
Podoba mi się to, że wyrównuje jednostkę pracy (tak, jak widzi ją użytkownik - tj. Przesłanie strony) z jednostką pracy w sensie ORM.
Dlatego możesz sprawić, że przesyłanie całej strony będzie transakcyjne, czego nie możesz zrobić, jeśli ujawniasz metody CRUD przy każdym tworzeniu nowego kontekstu.
źródło
Innym niedocenianym powodem nie używania pojedynczego DbContext, nawet w aplikacji z pojedynczym wątkiem dla jednego użytkownika, jest wzorzec mapy tożsamości, którego używa. Oznacza to, że za każdym razem, gdy pobierasz dane za pomocą zapytania lub identyfikatora, zachowa on pobrane instancje w pamięci podręcznej. Następnym razem, gdy odzyskasz ten sam byt, otrzymasz buforowane wystąpienie bytu, jeśli jest dostępne, wraz ze wszelkimi modyfikacjami, które wprowadziłeś w tej samej sesji. Jest to konieczne, aby metoda SaveChanges nie kończyła się na wielu różnych instancjach tego samego rekordu (ów) bazy danych; w przeciwnym razie kontekst musiałby w jakiś sposób scalić dane ze wszystkich tych instancji.
Przyczyną tego problemu jest singleton DbContext może stać się bombą zegarową, która ostatecznie może buforować całą bazę danych + obciążenie obiektów .NET w pamięci.
Istnieje wiele sposobów na obejście tego zachowania przy użyciu tylko zapytań Linq z
.NoTracking()
metodą rozszerzenia. Również w dzisiejszych czasach komputery mają dużo pamięci RAM. Ale zwykle nie jest to pożądane zachowanie.źródło
Inną kwestią, na którą należy zwrócić uwagę w Entity Framework, jest szczególnie użycie kombinacji tworzenia nowych encji, leniwego ładowania, a następnie korzystania z tych nowych encji (z tego samego kontekstu). Jeśli nie używasz IDbSet.Create (w porównaniu z nowymi), Leniwe ładowanie tego obiektu nie działa, gdy zostanie pobrane z kontekstu, w którym został utworzony. Przykład:
źródło