Subscribe to this Blog

Subscribe to this Blog

ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t) - Stephen Walther on ASP.NET MVC

ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

In this tip, I demonstrate how you can dispose of a DataContext within an ASP.NET MVC controller. Next, I argue that there is no compelling reason to do this.

Several people have emailed me recently with the same question about whether or not it is important to call Dispose() on the DataContext object. In all of the code samples that I write, I create an instance of a DataContext object, but I never properly dispose of it. Is this wrong?

The DataContext class implements the IDisposable interface. In general, if a class implements the IDisposable interface, then that is good evidence that you should call Dispose(). But keep reading.

Classes that implement the IDisposable interface typically use resources that cannot be cleaned up by the .NET framework garbage collector. Calling the IDisposable.Dispose() method executes code that explicitly releases a precious resource back into the world.

A prime example of a class that implements the IDisposable interface is the SqlConnection class. A SqlConnection class uses a Microsoft SQL Server database connection. Because SQL Server supports a limited number of connections, it is important to release a connection as quickly as possible.

Typically, you do not call the Dispose() method directly. Typically, you take advantage of a Using statement in your code like this:

C# Code

using (var con = new SqlConnection(conString))
{
    var cmd = new SqlCommand("SELECT * FROM Products");
    var reader = cmd.ExecuteReader();
}

VB.NET Code

Using con = New SqlConnection(conString)
    Dim cmd = New SqlCommand("SELECT * FROM Products")
    Dim reader = cmd.ExecuteReader()
End Using

A Using statement calls the Dispose() method at the end of the Using block automatically. The Using statement calls the Dispose() method even if there was an error in the code.

Dispose of the DataContext

Because the DataContext implements IDisposable, it would seem like the right way to use the DataContext in a controller is like Listing 1. In Listing 1, a DataContext is used within a Using statement.

Listing 1 – BadController.cs

using System.Linq;
using System.Web.Mvc;
using MvcFakes;
using Tip34.Models;

namespace Tip34.Controllers
{
    public class BadController : Controller
    {
        private IDataContext _dataContext;

        public BadController()
            : this(new DataContextWrapper("dbcon", "~/Models/Movie.xml")){ }

        public BadController(IDataContext dataContext)
        {
            _dataContext = dataContext;
        }

        public ActionResult Index()
        {
            using (_dataContext)
            {
                var table = _dataContext.GetTable<Movie>();
                var movies = from m in table select m;
                return View("Index", movies);
            }
        }
    
    }
}

The controller in Listing 1 is taking advantage of the IDataContext, DataContextWrapper, and FakeDataContext objects discussed in the previous tip:

http://weblogs.asp.net/stephenwalther/archive/2008/08/16/asp-net-mvc-tip-33-unit-test-linq-to-sql.aspx

By taking advantage of these objects, you can easily test your MVC application.

Within the Index() method, the DataContext is used within a Using statement. The Using statement causes the Dispose() method to be called on the DataContext object (The DataContextWrapper.Dispose() method delegates to the DataContext.Dispose() method).

You can use the controller in Listing 1 with a typed view. The typed view casts the ViewData.Model property to an instance of IQueryable. The code-behind for the typed view is contained in Listing 2.

Listing 2 -- \Views\Bad\Index.aspx.cs

using System.Linq;
using System.Web.Mvc;
using Tip34.Models;

namespace Tip34.Views.Bad
{
    public partial class Index : ViewPage<IQueryable<Movie>>
    {
    }
}

Unfortunately, however, the code in Listing 1 does not work. If you try to execute this code, you get the error displayed in Figure 1. You receive the error: “Cannot access a disposed object”. The problem is that the DataContext has already been disposed before the DataContext can be used in the view.

Figure 1 – Error from early disposal

clip_image002

One of the big benefits of using LINQ to SQL is that it supports deferred execution. A LINQ to SQL statement is not actually executed against the database until you start iterating through the results. Because the movie records are not accessed until they are displayed in the Index view, the DataContext is disposed before the records are retrieved from the database and you get the error.

One easy way to fix the problem with the controller in Listing 1 is to pass the movies as a list instead of as an IQueryable. The modified Index() action method in Listing 3 does not cause an error:

Listing 3 – HomeController.cs

using System.Linq;
using System.Web.Mvc;
using MvcFakes;
using Tip34.Models;

namespace Tip34.Controllers
{
    public class HomeController : Controller
    {
        private IDataContext _dataContext;

        public HomeController()
            : this(new DataContextWrapper("dbcon", "~/Models/Movie.xml")){ }

        public HomeController(IDataContext dataContext)
        {
            _dataContext = dataContext;
        }
        
        public ActionResult Index()
        {
            using (_dataContext)
            {
                var table = _dataContext.GetTable<Movie>();
                var movies = from m in table select m;
                return View("Index", movies.ToList());
            }
        }

        public ActionResult Details(int id)
        {
            using (_dataContext)
            {
                var table = _dataContext.GetTable<Movie>();
                var movie = table.SingleOrDefault(m=>m.Id == id);
                return View("Details", movie);
            }
        }
    }
}

The only difference between the Index() method in Listing 3 and the Index() method in Listing 4 is that the ToList() method is called on the movies before the movies are passed to the view.

The controller in Listing 3 also contains a Details() action that displays details for a particular movie. You do not need to do anything special when retrieving a single database record. LINQ to SQL does not used deferred execution when you retrieve a single record.

If you change the typed view so that it expects a list instead of an IQueryable, then everything works fine:

using System.Collections.Generic;
using System.Web.Mvc;
using Tip34.Models;

namespace Tip34.Views.Home
{
    public partial class Index : ViewPage<List<Movie>>
    {
    }
}

The modified controller now works because the ToList() method forces the LINQ to SQL statement to be executed against the database within the controller and not within the view.

You can unit test the Home controller in Listing 3 just fine. The class in Listing 4 contains a unit test that verifies that the Index() method returns a set of database records.

Listing 4 -- \Controllers\HomeControllerTest.cs

using System.Collections.Generic;
using System.Web.Mvc;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MvcFakes;
using Tip34.Controllers;
using Tip34.Models;

namespace Tip34Tests.Controllers
{
    [TestClass]
    public class HomeControllerTest
    {
        [TestMethod]
        public void Index()
        {
            // Create Fake DataContext
            var context = new FakeDataContext();
            var table = context.GetTable<Movie>();
            table.InsertOnSubmit(new Movie(1, "Star Wars"));
            table.InsertOnSubmit(new Movie(2, "King Kong"));
            context.SubmitChanges();
            
            // Create Controller
            var controller = new HomeController(context);

            // Act
            ViewResult result = controller.Index() as ViewResult;

            // Assert
            var movies = (List<Movie>)result.ViewData.Model;
            Assert.AreEqual("Star Wars", movies[0].Title);
        }

    }
}

This unit test is taking advantage of the FakeDataContext class described in the previous tip to create an in-memory version of a DataContext. First, the unit test adds some fake database records to the FakeDataContext. Next, the Index() action is invoked and a result is returned. Finally, the ViewData contained in the result is compared against the fake database records. If the ViewData contains the first fake database record then the Index() action works correctly and success is achieved.

Don’t Dispose of the DataContext

In the previous section, I demonstrated how you can Dispose() of a DataContext within an MVC controller action. In this section, I question the necessity of doing this.

When you call the DataContext Dispose() method, the DataContext delegates the call to the SqlProvider class. The SqlProvider class does two things. First, the SqlProvider calls the ConnectionManager.DisposeConnection() method which closes any open database connection associated with the DataContext. Second, the SqlProvider sets several objects to null (including the ConnectionManager).

So, the most important consequence of calling the DataContext.Dispose() method is that any open connections associated with the DataContext get closed. This might seem really important, but it’s not. The reason that it is not important is that the DataContext class already manages its connections. By default, the DataContext class opens and closes a connection automatically.

The DataContext object translates a LINQ to SQL query into a standard SQL database query and executes it. The DataContext object opens a database connection right before executing the SQL database query and closes the connection right after the query is executed (the connection is closed in the Finally clause of a Try..Catch block).

If you prefer, you can manage the opening and closing of the database connection used by a DataContext yourself. You can use the DataContext.Connection property to explicitly open a connection and explicitly close the connection. Normally, however, you don’t do this. You let the DataContext take care of itself.

Most often, when you call the Dispose() method on the DataContext object, any database connection associated with the DataContext object is already closed. The DataContext object has closed the database connection right after executing the database query. So, the Dispose() method really doesn't have anything to do.

Therefore, you really don’t get any huge benefits from calling Dispose() on the DataContext object. The only slight benefit is that the Dispose() method sets several objects to null so that they can be collected earlier by the garbage collector. Unless you are worried about every byte used by your application, or you are tracking a huge number of objects with your DataContext, I wouldn’t worry too much about the memory overhead of the DataContext object.

Summary

In this tip, I demonstrated how you can call the Dispose() method when working with a DataContext object within an MVC controller. Next, I argued that there really isn’t a very compelling reason to want to call the Dispose() method. I plan to continue to be lazy about disposing any DataContext objects that I used within my MVC controllers.

Published Tuesday, August 19, 2008 7:28 PM by swalther
Filed under: , ,

Comments

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 1:35 AM by brad

i sure hope your data context isnt that "close" to your controller... 3 tier; your doing it wrong.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 2:16 AM by Alex

If exception occurred, do I need to dispose the datacontext manually?

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 8:08 AM by Paul Wilson

I agree with Fredrik here.  If the designer of the class has taken the time to make it implement IDisposable, then you should Dispose it, preferably with a using block.  If you use Reflector to look at the internal implementation to make your Dispose decisions then you deserve to get burned when things change in the future.  And trying to save a line of code (that's all it is with using) is more silly than assuming those of us that do call Dispose are worried about saving a few bytes.

As a highly respected author and trainer I hope that you correct this and encourage best practices at all times.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 8:49 AM by AndrewSeven

From the lang reference: The using statement "Provides a convenient syntax that ensures the correct use of IDisposable objects. "

It seems to me that in this example, the HomeController class is only sometimes responsible for the dataContext resource: If the parameterless ctor is called, then HomeController should dispose the dataContext, if the datacontext is passed in, then it should not dispose it.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 9:04 AM by Fabrice Marguerie

Andrew has it, in my opinion. Disposing an object is the responsibility of the object that created it. If you don't follow this kind of rule, you'll end up trying to use disposed objects at some point or another.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 9:49 AM by Paul Day

Thank you for this post, I had experienced the error you showed and I did get around it by using .ToList(), but I didn't all of the whys.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 9:51 AM by Craig Stuntz

Failing to Dispose an object which implements IDisposable typically results in the object going into the finalization queue (read Chapter 19 of Jeffrey Richter's Applied Microsoft .NET Framework Programming for details). The results of this is that an object's memory that might have otherwise been freed in generation 01 be freed until a later generation collection.  If you're creating a lot of these objects, well, do the math.

So you should always Dispose any object which implements IDisposable.

In the case of Controllers and DataContexts, this turns out to be really easy, because Controller also implements IDisposable, and hence has a virtual Dispose method you can override.  So you don't have to wrap your use of DataContext in a using.  You can create it in the constructor (or wherever) and then dispose in the overridden Controller.Dispose.  In this case, using IQueryable in the view works just fine, because the framework does not Dispose the Controller until the View has been rendered.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 10:43 AM by Paul Day

Great response and idea Craig.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 9:09 PM by Craig Stuntz

Having a finalizer or not is an implementation detail which can change. Will there be a ~DataContext in the next version of the framework? Who knows? It's certainly possible. The MSDN article you cite recommends it, for example. Also, if you implement IDisposable and don't have a finalizer (along with following the rest of the pattern explained in the article), FxCop will complain.

I would never want to count on a disposable class not having a finalizer for all time.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, August 20, 2008 11:35 PM by AndrewPeters

The unit of work-per-request (or data context/session) pattern is a really good way to go.

I blogged about an ASP.NET MVC implementation here:

www.mindscape.co.nz/.../using-the-unit-of-work-per-request-pattern-in-aspnet-mvc

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Friday, September 5, 2008 9:41 AM by Simon

Great article thanks!

It's just a shame some people seem a bit uptight about not disposing of an object.  I agree that in most cases if an class implements IDisposable then it should be disposed of.  But in this case Stephen has explained what happens to the DataContext object on disposing.  

I agree with him that in most cases there's no need (unless you want to send objects for GC as early as possible - again as stated by Stephen).

Thanks again!!

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Friday, September 5, 2008 5:28 PM by Claus H

With all due respect this goes against anything anyone's learned about separation of concerns and layering. Why would you pass a datacontext to a view and effectively let the view execute a sql call (which is what you're doing by letting it be deferred)? This should be a library, and contexts should be properly disposed. The models passed to view should disconnected representations of the model. This might work for a scratch intranet app, but not for anything you'd get paid for to write. Sorry Stephen, this is sloppy.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Thursday, September 18, 2008 4:42 AM by Mike

@Claus H

Deferred execution and passing the queryable 'upwards' is also done in the MVC storefront project by Rob Conery. Lot's of comments on it, but he persisted and he's doing that 'officially' for Microsoft (as en employee, and it's posted on www.asp.net). So I think that pattern is here to stay.

# re: ASP.NET MVC Tip #34 – Dispose of Your DataContext (or Don’t)

Wednesday, December 10, 2008 4:33 PM by SC

This flies in the face of just about all established .NET guidelines.

It's as though the MVC team is doing the exact *opposite* of all established .NET practice:

* IDisposable means it could contain expensive resources and should be disposed when done using it. How is a developer supposed to know that DataContext is okay to not dispose in some cases, but that leaving a SqlConnection open is a "very bad thing"?

* The use IDisposable of non-disposable objects in the MVC framework is terrible as well - Dispose() means "clean up expensive resources", not "render a </form> tag"

No offense, but it's as though existing, established patterns and practices are being thrown out in a blind effort to implement TDD *at all costs*.

> This might work for a scratch intranet app,

> but not for anything you'd get paid for to

> write. Sorry Stephen, this is sloppy.

I couldn't agree more.