2012-05-27 15 views
18

Mam kilka pytań dotyczących pożądanego czasu życia kontekstu Entity Framework w aplikacji ASP.NET MVC. Czy nie najlepiej jest utrzymać kontekst przy życiu przez jak najkrótszy czas?Pytania dotyczące Entity Framework Context Lifetime

Rozważmy następujące działania kontrolera:

public ActionResult Index() 
{ 
    IEnumerable<MyTable> model; 

    using (var context = new MyEntities()) 
    { 
     model = context.MyTable; 
    } 

    return View(model); 
} 

Powyższy kod nie zadziała, ponieważ kontekst Entity Framework upadł poza zakresem natomiast widok renderuje stronę. W jaki sposób inni utworzą powyższy kod?

+0

'model = context.MyTable.ToList()' - 'ToList()' wykona zapytanie. W twoim przypadku IQueryable nie działałby poza zakresem kontekstu. – Andrei

Odpowiedz

41

Chodźmy kontrowersyjny!

Nie zgadzam się z ogólnym MVC + EF konsensusu, że utrzymanie kontekst żyje przez cały wniosek jest dobrą rzeczą dla wielu powodów:

wzrost Niska wydajność Czy wiesz jak drogie stworzenie nowy kontekst bazy danych to? Dobrze...„DataContext jest lekki i nie jest drogie, aby utworzyć” to z MSDN

Get MKOl źle i będzie się wydawać w porządku .. aż pójdziesz na żywo Jeśli skonfigurowaniu kontenera IoC do dysponowania Twój kontekst dla ciebie, a ty źle, naprawdę źle. Mam teraz dwa razy widziałem ogromne przecieki pamięci utworzone z kontenera IoC nie zawsze poprawnie pozbywając się kontekstu. Nie zauważysz, że źle ustawiłeś, dopóki twoje serwery nie zaczną się rozpadać podczas normalnego poziomu jednoczesnych użytkowników. Nie stanie się to w fazie rozwoju, więc wykonaj kilka testów obciążenia!

Przypadkowe lazy loading Państwo zwrócić IQueryable swoich ostatnich artykułów, dzięki czemu można je wymienić na swojej stronie głównej. Pewnego dnia ktoś inny zostanie poproszony o wyświetlenie liczby komentarzy obok odpowiedniego artykułu. Więc dodać prosty kawałek kodu do widoku pokazać liczbę Komentarz Lubie tak ...

@foreach(var article in Model.Articles) { 
    <div> 
     <b>@article.Title</b> <span>@article.Comments.Count() comments</span> 
    </div> 
} 

Wygląda w porządku, działa bez zarzutu. Ale w rzeczywistości nie uwzględniłeś komentarzy w swoich zwróconymi danymi, więc teraz zrobi to nowe wywołanie bazy danych dla każdego artykułu w pętli. WYBIERZ N + 1 problem. 10 artykułów = 11 wywołań bazy danych. Okay, więc kod jest błędny, ale łatwo popełnić błąd, aby tak się stało.

Możesz temu zapobiec, zamykając kontekst w swojej warstwie danych. Ale czy kod nie będzie łamał się z wyjątkiem NullReferenceException na article.Comments.Count()? Tak, spowoduje to zmianę warstwy, aby uzyskać dane potrzebne dla warstwy widoku. Tak powinno być.

Zapach kodu Jest coś nie tak z trafieniem w bazę danych z widoku. Wiesz, że IQueryable tak naprawdę nie trafiło w bazę danych, więc zapomnij o tym obiekcie. Upewnij się, że twoja baza danych została trafiona, zanim opuści twoją warstwę danych.

Więc odpowiedź

Kod powinien być (moim zdaniem) jak to

dataLayer:

public List<Article> GetArticles() 
{ 
    List<Article> model; 

    using (var context = new MyEntities()) 
    { 
     //for an example I've assumed your "MyTable" is a table of news articles 
     model = (from mt in context.Articles 
       select mt).ToList(); 
     //data in a List<T> so the database has been hit now and data is final 
    } 

    return model; 
} 

Kontroler:

public ActionResult Index() 
{ 
    var model = new HomeViewModel(); //class with the bits needed for you view 
    model.Articles = _dataservice.GetArticles(); //irrelevant how _dataService was intialised 
    return View(model); 
} 

Po dokonaniu to i rozumiem to może wtedy yo możesz zacząć eksperymentować z posiadaniem kontekstu uchwytu kontenera IoC, ale zdecydowanie nie wcześniej. Kieruj moim ostrzeżeniem - widziałem dwie duże awarie :)

Ale szczerze, rób to, co lubisz, programowanie jest zabawne i powinno być kwestią preferencji. Po prostu ci mówię. Ale cokolwiek robisz, nie zaczynaj używać kontekstu IoC na kontroler lub żądanie, tylko dlatego, że "robią to wszystkie fajne dzieciaki". Zrób to, ponieważ naprawdę dbasz o jego zalety i rozumiesz, jak to działa poprawnie.

+3

Myślę, że warto zacytować cały akapit z MSDN: "Ogólnie rzecz biorąc, wystąpienie DataContext jest zaprojektowane tak, aby przetrwać dla jednej" jednostki pracy ", jednak twoja aplikacja definiuje ten termin.DaneContext jest lekkie i nie jest drogie w tworzeniu. LINQ do aplikacji SQL tworzy wystąpienia DataContext w zakresie metody lub jako element krótkotrwałych klas reprezentujących logiczny zestaw powiązanych operacji na bazie danych. " –

+1

@BritishDeveloper: Pytanie dotyczyło DbContext z przestrzeni nazw System.Data.Entity (struktura encji encji dbcontext). Nie znalazłem nic "lekkości" w opisie tego kontekstu w [MSDN] (http://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext%28v=vs.113%29. aspx). Czy masz inne dowody? – vk5880

+0

Ach, to było 2 lata temu, cba, aby znaleźć więcej dowodów dla ciebie. Jednak mam doświadczenie zawodowe w stosowaniu tej metody od kilku lat, w dużych witrynach, bez problemów związanych z hakowaniem i wydajnością. – BritishDeveloper

2

Po pierwsze, należy rozważyć umieszczenie dostępu do bazy danych w oddzielnych klasach.

Po drugie, moim ulubionym rozwiązaniem jest użycie "jednego kontekstu na żądanie" (jeśli używasz MVC, uważam, że jest to jeden kontekst na kontroler).

Zamówiony edit:

Zapraszamy do obejrzenia tej odpowiedzi, może to pomoże też. Pamiętaj, że używam formularzy internetowych, więc nie mogę go teraz zweryfikować w MVC, ale może ci to pomóc lub przynajmniej podać kilka wskazówek. https://stackoverflow.com/a/10153406/1289283

Kilka przykładów wykorzystania tego dbcontext:

public class SomeDataAccessClass 
{ 
    public static IQueryable<Product> GetAllProducts() 
    { 
     var products = from o in ContextPerRequest.Current.Products 
         select o; 
     return products; 
    } 
} 

Następnie można zrobić coś takiego:

public ActionResult Index() 
{ 
    var products = SomeDataAccessClass.GetProducts(); 
    return View(products); 
} 

Proste, prawda? Nie musisz już martwić się, że pozbędziesz się kontekstu, piszesz tylko ten kod, który naprawdę potrzebujesz.

Niektórzy ludzie lubią jeszcze bardziej urozmaicać rzeczy, dodając wzór UnitOfWork lub pojemniki IoC ... Ale lubię to podejście bardziej ze względu na jego prostotę.

+0

Dzięki, ale staram się dowiedzieć, jak ludzie będą budować kod, więc jest tylko jeden kontekst na żądanie?Gdzie powinienem go utworzyć i jak zapewnić, że zostanie on dostarczony w odpowiednim czasie, gdy żądanie zostanie zrealizowane? –

5

zgadzam się z jednego kontekstu na życzenie normalnie zrobić to poprzez wiązanie kontekst .InRequestScope korzystając Ninject, który działa bardzo dobrze, jest:

Bind<MyContext>().ToSelf().InRequestScope(); 

Również jego bardzo dobra praktyka, aby wymienić zestaw tak blisko do zapytania, jak to możliwe, tj .:

public ActionResult Index() 
{ 
    IEnumerable<MyTable> model; 

    using (var context = new MyEntities()) 
    { 
     model = (from mt in context.MyTable 
       select mt).ToArray(); 
    } 
    return View(model); 
} 

pomoże to uniknąć niechcianego zapytania z widoku.

1

można wykorzystać metodę .ToList() rozszerzenie LINQ jako takie:

public ActionResult Index() 
{ 
    IEnumerable<MyTable> model; 

    using (var context = new MyEntities()) 
    { 
     model = (from mt in context.MyTable 
       select mt).ToList(); 
    } 
    return View(model); 
} 
+0

Tak, mogę, ale to nie rozwiązuje potencjalnych problemów z wydajnością poprzez wielokrotne odtworzenie kontekstu podczas pojedynczego żądania HTTP. Właśnie dlatego szukam przykładów tego, jak robią to inni. –

+1

Czy zmierzyłeś wpływ na wydajność? Czy pasuje do twoich oczekiwań/wymagań? –

+0

Nie mierzyłem wydajności. Próbuję tylko dowiedzieć się, jak inni mają do czynienia z tym szczególnym aspektem EF. Mam problem ze znalezieniem wielu przykładów. –

Powiązane problemy