Kiedy powinienem utworzyć nowy DbContext ()

83

Obecnie używam DbContextpodobnego do tego:

namespace Models
{
    public class ContextDB: DbContext
    {

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

        public ContextDB()
        {

        }
    }
}

Następnie używam następującego wiersza u góry WSZYSTKICH moich kontrolerów, które potrzebują dostępu do bazy danych. Używam go również w mojej klasie UserRepository, która zawiera wszystkie metody odnoszące się do użytkownika (takie jak pobranie aktywnego użytkownika, sprawdzenie, jakie ma role itp.):

ContextDB _db = new ContextDB();

Myśląc o tym… zdarzają się sytuacje, w których jeden użytkownik może mieć aktywnych wiele DbContexts… tj. jeśli odwiedza kontroler, który korzysta z UserRepository ... to może nie być najlepszy pomysł i mam kilka pytań na ten temat

  1. Kiedy powinienem utworzyć nowy kontekst DbContext / czy powinienem mieć jeden kontekst globalny, który mam przekazywać?
  2. Czy mogę mieć jeden globalny kontekst, którego mogę używać ponownie we wszystkich miejscach?
  3. Czy to powoduje spadek wydajności?
  4. Jak wszyscy to robią?
JensB
źródło
Musiałem
oznaczyć
2
W tym przypadku użyłbym iniekcji zależności (np. Ninject), więc utworzyłby jeden DbContextna żądanie. Stworzyłbym również warstwę usług. Sprawdź to pytanie i odpowiedź SO
Zbigniew

Odpowiedzi:

82

Używam kontrolera podstawowego, który ujawnia DataBasewłaściwość, do której mają dostęp kontrolery pochodne.

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

Wszystkie kontrolery w mojej aplikacji pochodzą BaseControlleri są używane w następujący sposób:

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

A teraz odpowiedz na twoje pytania:

Kiedy powinienem utworzyć nowy kontekst DbContext / czy powinienem mieć jeden kontekst globalny, który mam przekazywać?

Kontekst powinien być tworzony na żądanie. Utwórz kontekst, zrób z nim to, co musisz, a następnie pozbądź się go. W przypadku rozwiązania klasy bazowej, z którego korzystam, musisz się tylko martwić o użycie kontekstu.

Nie próbuj mieć globalnego kontekstu (nie tak działają aplikacje internetowe).

Czy mogę mieć jeden globalny kontekst, którego mogę używać ponownie we wszystkich miejscach?

Nie, jeśli zachowasz kontekst wokół, będzie on śledzić wszystkie aktualizacje, dodatki, usunięcia itp., A to spowolni Twoją aplikację, a nawet może spowodować pojawienie się w niej dość subtelnych błędów.

Prawdopodobnie powinieneś zdecydować się na ujawnienie swojego repozytorium lub kontekstu kontrolerowi, ale nie obu. Posiadanie dwóch kontekstów umożliwiających dostęp za pomocą tej samej metody doprowadzi do błędów, jeśli obaj mają różne wyobrażenia o bieżącym stanie aplikacji.

Osobiście wolę ujawniać DbContextbezpośrednio, ponieważ większość przykładów repozytoriów, które widziałem, i tak kończy się jako cienkie opakowania DbContext.

Czy to powoduje spadek wydajności?

Pierwsze utworzenie pliku DbContextjest dość kosztowne, ale po jego wykonaniu wiele informacji jest zapisywanych w pamięci podręcznej, dzięki czemu kolejne instancje są znacznie szybsze. istnieje większe prawdopodobieństwo, że problemy z wydajnością wynikają z utrzymywania kontekstu, niż w przypadku tworzenia ich za każdym razem, gdy potrzebujesz dostępu do bazy danych.

Jak wszyscy to robią?

To zależy.

Niektórzy ludzie wolą używać struktury iniekcji zależności, aby przekazać konkretne wystąpienie ich kontekstu do kontrolera podczas jego tworzenia. Obie opcje są w porządku. Mój jest bardziej odpowiedni dla aplikacji na małą skalę, w których wiesz, że używana baza danych nie ulegnie zmianie.

niektórzy mogą twierdzić, że nie możesz tego wiedzieć i dlatego metoda wstrzykiwania zależności jest lepsza, ponieważ sprawia, że ​​aplikacja jest bardziej odporna na zmiany. Moja opinia na ten temat jest taka, że ​​prawdopodobnie się nie zmieni (SQL server i Entity Framework nie są niejasne) i że najlepiej spędzam czas na pisaniu kodu specyficznego dla mojej aplikacji.

Benjamin Gale
źródło
4
Nie mam nic do dodania poza tym, że miło jest widzieć niektóre z moich własnych preferencji odzwierciedlone w postach innych osób. Nigdy tak naprawdę nie rozumiałem przykładów repozytoriów, które skutkują kolejną warstwą dodaną do DbContext, bez dodawania czegokolwiek, i jestem fanem tworzenia klasy bazowej, która ujawnia chroniony DbContext.
dark_perfect
4
Chciałem dodać, że ta odpowiedź jest świetna, ale teraz jest nieaktualna wraz z wydaniem EF6, który automatycznie usuwa kontekst po każdym użyciu. W związku z tym istnieją scenariusze, w których tworzenie kontekstu na sesję (globalnego) jest w porządku. Jeśli używasz EF6 do łączenia się z procedurami składowanymi i blokowanie danych nie stanowi problemu, prawdopodobnie idealnym rozwiązaniem jest utworzenie kontekstu raz w kontrolerze podstawowym i pozostawienie wszystkich kontrolerów, które wymagały dostępu do bazy danych, dziedziczenia z bazy. Najbardziej poprawną odpowiedzią jest to, że musisz być na bieżąco z technologią i stosować właściwy wzór dla swojej architektury.
Atters
Co zrobić, jeśli chcę wywołać metodę niektórych modeli z akcji kontrolera, które używają DbContext? Jak mogę to przekazać do metody modelek?
RMazitov
Następnie wykonujesz zapytania do bazy danych i logikę biznesową na kontrolerze, do którego nie należą. Należy utworzyć usługę wywoływaną z kontrolera. To znaczy, że UserController wywołuje metodę w usłudze UserService.
Fred
@Fred, tak, i jak mogę przekazać DbContext do UserService, używając prostego parametru do działania, czy nie?
RMazitov
10

Próbuję odpowiedzieć na podstawie własnego doświadczenia.

1. Kiedy powinienem utworzyć nowy kontekst DbContext / powinienem mieć jeden kontekst globalny, który będę przekazywać?

Kontekst powinien być wstrzykiwany przez iniekcję zależności i nie powinien być tworzony samodzielnie. Najlepszym rozwiązaniem jest utworzenie go jako usługi o określonym zakresie przez iniekcję zależności. (Zobacz moją odpowiedź na pytanie 4)

Należy również rozważyć użycie odpowiedniej warstwowej struktury aplikacji, takiej jak Kontroler> BusinessLogic> Repozytorium. W tym przypadku nie byłoby tak, że kontroler otrzymuje kontekst db, ale zamiast tego repozytorium. Wstrzyknięcie / utworzenie instancji kontekstu db w kontrolerze mówi mi, że architektura Twojej aplikacji łączy wiele obowiązków w jednym miejscu, czego - w żadnych okolicznościach - nie mogę polecić.

2. Czy mogę mieć jeden globalny kontekst, którego mogę używać ponownie we wszystkich miejscach?

Tak, możesz , ale pytanie powinno brzmieć: „ Czy powinienem ...” -> NIE. Kontekst ma być używany na żądanie, aby zmienić repozytorium, a następnie ponownie go usunąć.

3. Czy to powoduje spadek wydajności?

Tak, ponieważ DBContext po prostu nie jest stworzony do tego, aby był globalny. Przechowuje wszystkie dane, które zostały wprowadzone lub przeszukane, dopóki nie zostaną zniszczone. Oznacza to, że kontekst globalny będzie coraz większy i większy, operacje na nim będą coraz wolniejsze, aż pojawią się wyjątki braku pamięci lub umrzesz ze starości, ponieważ wszystko spowolniło do pełzania.

Otrzymasz również wyjątki i wiele błędów, gdy wiele wątków jednocześnie uzyskuje dostęp do tego samego kontekstu.

4. Jak wszyscy to robią?

DBContext wstrzyknięty przez iniekcję zależności przez fabrykę; zakres:

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

Mam nadzieję, że moje odpowiedzi będą pomocne.

Ravior
źródło
Co zrobić, jeśli chcę użyć DBContext w Startup.cs w samej metodzie ConfigureServices? Mam oprogramowanie pośredniczące OICD, w którym muszę uzyskać dostęp do bazy danych, ale nie mogę uzyskać dostępu do DBContext lub nie wiem jak?
bbrinck
W metodzie configureServices Twój DBContext jest prawdopodobnie niedostępny, ponieważ został tam skonfigurowany. ServiceProvider, z którego faktycznie pobierasz DBContext w czasie wykonywania, będzie najpierw dostępny w metodzie Configure () (nie „ConfigureServices”!). Tam możesz zażądać kontekstu za pomocą ApplicationBuilder, wpisując "app.ApplicationServices.GetRequiredService <MyDbContext> ();" (Zastąp MyDbContext nazwą klasy własnego kontekstu).
Ravior,
Jeśli kontrolerom wstrzykuje się repozytorium, w jaki sposób (kontrolerowie) zapisują zmiany? Powiedzmy, że wysyłam żądanie POST, aby wstawić coś do bazy danych, kontroler obsługuje żądanie, używa repozytorium do dodania nowo utworzonego obiektu zobiektem… co potem? Kto utrzymuje zmiany?
MMalke,
@MMalke Repozytorium to robi. Zwykle ma funkcję „SaveData (Data myData)”, a następnie Repozytorium tworzy instancję swojego rodzeństwa Entity-Framework dla klasy danych, dodaje ją do odpowiedniego zestawu dbset w dbcontext, a następnie wywołuje SaveChanges (). Jedyne, co robi kontroler, to wywołanie funkcji Repository.SaveData (myData).
Ravior
1

W tej chwili próbuję tego podejścia, które pozwala uniknąć tworzenia wystąpienia kontekstu, gdy wywołujesz akcje, które go nie używają.

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);
    }
}
Andrzej
źródło
2
Chociaż nie ma nic złego w Twoim podejściu, uważam, że kontekst Entity Framework zrobi wszystko w leniwy sposób i żadna prawdziwa praca nie zostanie wykonana, dopóki nie uzyskasz dostępu do bazy danych. Zatem narzut tworzenia kontekstu EF powinien być bardzo mały.
Martin Liversage
Zrobiłem kilka badań i wydaje się, że to prawda. Zrobiłem prosty test, porównując to, co GC.GetTotalMemory()zwróciło (nie jest idealne, ale tak właśnie znalazłem) i różnica nigdy nie była większa niż 8 KB.
Andrew,
0

Jest to oczywiście starsze pytanie, ale jeśli używasz DI, możesz zrobić coś takiego i określić zakres wszystkich obiektów na czas trwania żądania

 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
źródło
0

Kontekst należy usunąć natychmiast po każdej operacji Save (). W przeciwnym razie każdy kolejny zapis będzie trwał dłużej. Miałem projekt, który tworzył i zapisywał złożone jednostki bazy danych w cyklu. Ku mojemu zdziwieniu, operacja stała się trzykrotnie szybsza po przeniesieniu "using (var ctx = new MyContext ()) {...}" wewnątrz cyklu.

Gregory Khrapunovich
źródło