Edi Wang
Edi Wang

Reputation: 3637

How to manage ObjectContext lifetime correctly in multi-tier application using Entity Framework?

I have seen many examples using Entity Framework in MVC3 applications, they are very simple demos which only have one mvc3 web project with edmx inside it.

So, they can use the best practice for open and close connection by "using" statement:

using(var context = new SchoolEntities())
{
    // do some query and return View with result.
}

And, It can use lazy load (navigation properties) inside the "using" statment correctly, because the context is not yet disposed:

foreach(var item in student.Course)
{
    // do something with the navigation property Course
}

All things seems to be perfect until it becomes an n-tier application.

I created DAL, BLL, and a MVC3 UI.

The DAL have edmx inside it, and operator classes like SchoolDA.cs:

public class StudentDA()
{
    public Student FindStudent(int studentId)
    {
        using(var context = new SchoolContext())
        {
            // do query, return a student object.
        }
    }
}

Then, in the BLL, if I use:

var student = studentDa.FindStudent(103);

then invoke it's navigation property:

student.Course

I will get an error (of course):

The ObjectContext instance has been disposed and can no longer be used for operations that require a connection.

So, I have to change StudentDA.cs like this:

public class StudentDA() : IDisposable
{
    private SchoolEntites context;

    public StudentDA()
    {
        context = new SchoolEntities();
    }

    public void Dispose()
    {
        context.Dispose();
    }

    public Student FindStudent(int studentId)
    {
        // do query, return a student object.
    }
}

Then, the BLL will change like this:

public Student FindStudent(int id)
{
    using(var studentDa = new StudentDA())
    {
        // this can access navigation properties without error, and close the connection correctly.
        return studentDa.FindStudent(id);
    }
}

All things seem to be perfect again until it meet Update() method.

Now, if I want to update a student object which is taken from BLL.FindStudent(), the context.SaveChanges() will return 0, because the context is already disposed in the BLL.FindStudent(), and nothing will be updated to database.

var optStudent = new StudentBO();
var student = optStudent.FindStudent(103);
student.Name = "NewValue";
optStudent.Update(student);

Does anyone have idea on how to use EntityFramework in 3 tire application? or how can I manage the context correctly. I will use navigation propertites very often in the web layer, but I can't always remain connection open to consume the server memory.

Upvotes: 3

Views: 2300

Answers (2)

Akeel
Akeel

Reputation: 121

Thanks for your answer Kamyar. I came across this whilst looking for a simple strategy to manage the ObjectContext lifetime without having to use an IoC framework, which seems a bit overkill for my needs.

I also came across your other post here, for disposing of the context at the end of the request.

Thought this might be useful for others coming across this, so just posting my implementation of your code here:

Context manager class -

internal static class MyDBContextManager
    {
        //Unique context key per request and thread
        private static string Key
        {
            get
            { 
                return string.Format("MyDb_{0}{1}", arg0: HttpContext.Current.GetHashCode().ToString("x"),
                    arg1: Thread.CurrentContext.ContextID);
            }
        }

        //Get and set request context
        private static MyDBContext Context
        {
            get { return HttpContext.Current.Items[Key] as MyDBContext; }
            set { HttpContext.Current.Items[Key] = value; }
        }

        //Context per request
        public static MyDBContext Current
        {
            get
            {
                //if null, create new context 
                if (Context == null)
                {
                    Context = new MyDBContext();
                    HttpContext.Current.Items[Key] = Context;
                }
                return Context;
            }
        }

        //Dispose any created context at the end of a request - called from Global.asax
        public static void Dispose()
        {
            if (Context != null)
            {
                Context.Dispose();
            }
    }
}

Global.asax (MVC) -

    public override void Init()
    {
        base.Init();
        EndRequest +=MvcApplication_EndRequest; 
    }

    private void MvcApplication_EndRequest(object sender, EventArgs e)
    {
        MyDBContextManager.Dispose();
    }

Upvotes: 0

Kamyar
Kamyar

Reputation: 18797

There are multiple ways to handle EF context's lifetime. In web apps, usually context is unique for an HttpRequest. For example, if you want to handle this manually in a web application and have a per Thread/HttpRequest EF context, you can do so with the following (Code copied from http://www.west-wind.com/weblog/posts/2008/Feb/05/Linq-to-SQL-DataContext-Lifetime-Management):

internal static class DbContextManager
{
    public static DbContext Current
    {
        get
        {
            var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
                      + Thread.CurrentContext.ContextID.ToString();
            var context = HttpContext.Current.Items[key] as MyDbContext;

            if (context == null)
            {
                context = new MyDbContext();
                HttpContext.Current.Items[key] = context;
            }
            return context;
        }
    }
}  

And then you can easily use:

var ctx = DbContextManager.Current

But I suggest you leave the lifetime management to an IoC framework like Autofac, Castle Windsor, or Ninject which automatically handle the creation/disposal of your registered obejcts along with many other features.

Upvotes: 7

Related Questions