22

I have some questions about the desired lifetime of an Entity Framework context in an ASP.NET MVC application. Isn't it best to keep the context alive for the shortest time possible?

Consider the following controller action:

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

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

    return View(model);
}

The code above won't work because the Entity Framework context has gone out of scope while the view renders the page. How would others structure the code above?

Andrei
  • 42,814
  • 35
  • 154
  • 218
Jonathan Wood
  • 65,341
  • 71
  • 269
  • 466
  • `model = context.MyTable.ToList()` - `ToList()` will execute your query. In your case IQueryable indeed wouldn't work outside of context scope. – Andrei Apr 20 '17 at 16:20
  • update: there is dedicated article from MS on exactly the same context: https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/ "In many web applications, each HTTP request corresponds to a single unit-of-work. This makes tying the context lifetime to that of the request a good default for web applications. ASP.NET Core applications are configured using dependency injection. EF Core can be added to this configuration using AddDbContext in the ConfigureServices method of Startup.cs. For example: ..." – Avi Feb 10 '23 at 13:26

4 Answers4

53

Let's get controversial!

I disagree with the general MVC + EF consensus that keeping a context alive throughout the entire request is a good thing for a number of reasons:

Low performance increase Do you know how expensive creating a new database context is? Well... "A DataContext is lightweight and is not expensive to create" that's from MSDN

Get the IoC wrong and it'll seem fine.. until you go live If you set up your IoC container to dispose of your context for you and you get it wrong, you really really get it wrong. I've twice now seen massive memory leaks created from an IoC container not always disposing of a context correctly. You won't realise you've set it up wrong until your servers start crumbling during normal levels of concurrent users. It won't happen in development so do some load tests!

Accidental lazy loading You return an IQueryable of your most recent articles so that you can list them on your homepage. One day someone else is asked to show the number of comments next to the respective article. So they add a simple bit of code to the View to show the comment count like so...

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

Looks fine, works fine. But actually you didn't include the comments in your returned data so now this will make a new database call for each article in the loop. SELECT N+1 issue. 10 article = 11 database calls. Okay so the code is wrong but it is an easy mistake to make so it will happen.

You can prevent this by shutting your context down in you data layer. But won't the code break with a NullReferenceException on the article.Comments.Count() ? Yes it will so it will force you to edit the Data layer to get the data needed for the View layer. This is how is should be.

Code smell There is just something wrong about hitting the database from your View. You know that an IQueryable hasn't actually hit the database yet right so forget that object. Make sure your database is hit before it leaves your data layer.

So the answer

Your code should be (in my opinion) like this

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

Controller:

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

Once you have done this and understand this then perhaps you can begin experimenting with having an IoC container handle context but definitely not before. Head my warning - I've seen two large scale failures :)

But honestly do what you like, programming is fun and should be a matter of preference. I'm just telling you mine. But whatever you do, don't start using IoC context per controller or per request just because "all the cool kids are doing it." Do it because you really truly care about it's benefits and understand how it's done correctly.

BritishDeveloper
  • 13,219
  • 9
  • 52
  • 62
  • 4
    I think it's worth quoting the whole paragraph from MSDN: "In general, a DataContext instance is designed to last for one 'unit of work' however your application defines that term. A DataContext is lightweight and is not expensive to create. A typical LINQ to SQL application creates DataContext instances at method scope or as a member of short-lived classes that represent a logical set of related database operations." – David Hammond Nov 20 '13 at 14:50
  • 1
    @BritishDeveloper: The question was about DbContext from System.Data.Entity namespace (entity framework dbcontext class). I have found nothing "lightweightness" in the description of this context at [MSDN](http://msdn.microsoft.com/en-us/library/system.data.entity.dbcontext%28v=vs.113%29.aspx). Do you have other evidences? – Vladislav Kostenko Feb 17 '14 at 09:14
  • 1
    Ah, this was 2 years ago, cba to find more evidence for you. However, I do have professional experience of using this method for the past several years, on big sites, without a hitch or performance concern. – BritishDeveloper Feb 26 '14 at 11:33
  • 1
    In reviewing this (years later), I would just add one more thought: The issue isn't limited to just *creating* a database context. In large projects, I often see the same data being referenced from multiple places. If a new database context is being created each time, there the data must be retrieved each time, rather than it being cached in a context that is open for the length of the request. – Jonathan Wood Jun 12 '15 at 15:49
  • After wrestling with our own data layers for a little over a year now, we are starting to build them JUST like you described here... for these very reasons. You are spot-on. As for caching, we have an entire object that caches the DATA (not the context or objects), which is handled at a business-logic (aka controller) level, not a data level. – Jerry May 02 '16 at 22:43
  • **Linq2Sql** context is lightweight. **EF** context - not really. You are referring to a wrong DataContext class right from the start, hence the whole answer is questionable. – Andrei Apr 20 '17 at 16:17
  • 5 year old answer mate ;) For what it is worth I'm still happy with this pattern, particularly if you're starting out and could do with the simplest approach. How many times are you realistically going to hit the database during the same HTTP request in an ASP.NET app anyway? If your concern is scale the answer should be very few, if at all – BritishDeveloper Jul 05 '17 at 20:41
  • this article describes this exact use case, to manage short dbcontext lifecycle in controllers: https://learn.microsoft.com/en-us/ef/core/dbcontext-configuration/ – Avi Feb 10 '23 at 13:30
6

I agree with one context per request, we normally do this by binding the context .InRequestScope using Ninject, which works really well, is:

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

Also its really good practice to enumerate the set as close to the query as possible ie:

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

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

this will help you avoid augmenting the query unintentionally from your view.

undefined
  • 33,537
  • 22
  • 129
  • 198
2

Firstly, you should consider putting your database access to separate classes.

Secondly, my favorite solution to this is to use "one context per request" (if you're using MVC, I believe it's one context per controller).

Requested edit:

Have a look at this answer, maybe it will help you too. Please note I'm using webforms, so can't verify it in MVC at the moment, but it may be helpful for you or at least give you some pointers. https://stackoverflow.com/a/10153406/1289283

Some example usage of this dbcontext:

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

Then you can do something like this:

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

Simple, right? You don't have to worry about disposing your context anymore, you write only the code you really need.

Some people like to further spice things up a little bit by adding UnitOfWork pattern, or maybe IoC containers... But I like this approach more because of its simplicity.

Community
  • 1
  • 1
walther
  • 13,466
  • 5
  • 41
  • 67
  • Thanks, but I'm trying to figure out how people would structure the code so there is only one context per request? Where would I create it, and how would I ensure it is disposed in a timely manner when the request is done? – Jonathan Wood May 27 '12 at 22:24
1

Can you utilize LINQ's .ToList() extension method as such:

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

    using (var context = new MyEntities())
    {
        model = (from mt in context.MyTable
                select mt).ToList();
    }
    return View(model);
}
Jesse C. Slicer
  • 19,901
  • 3
  • 68
  • 87
  • Yes I can, but that doesn't address potential performance issues from recreating the context several times during a single HTTP request. That's why I'm looking for examples of how others are doing this. – Jonathan Wood May 27 '12 at 22:29
  • 1
    Have you measured the performance impact? Does it fit within your expectations/requirements? – Jesse C. Slicer May 27 '12 at 22:32
  • I have not measured performance. I'm just trying to figure out how others are dealing with this particular aspect of EF. I'm having trouble finding many examples. – Jonathan Wood May 27 '12 at 22:37