简体   繁体   中英

Am I using and disposing Entity Framework's Object Context (per request) correctly?

I have a web application where I have just began to use Entity Framework. I read the beginners tutorials, and topics about benefits of object context per request for web apps. However, I am not sure my context is at the right place...

I found this very useful post ( Entity Framework Object Context per request in ASP.NET? ) and used the suggested code :

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

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

And in Global.asax :

protected virtual void Application_EndRequest()
{
    var key = "MyDb_" + HttpContext.Current.GetHashCode().ToString("x")
                      + Thread.CurrentContext.ContextID.ToString();
    var context = HttpContext.Current.Items[key] as MyEntities;

    if (context != null)
    {
        context.Dispose();
    }
}

Then, I am using it in my pages :

public partial class Login : System.Web.UI.Page
{
    private MyEntities context;
    private User user;

    protected void Page_Load(object sender, EventArgs e)
    {
        context = DbContextManager.Current;

        if (Membership.GetUser() != null)
        {
            Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
            user = context.Users.Single(u => (u.Id == guid));
        }
    }

    protected void _Button_Click(object sender, EventArgs e)
    {
        Item item = context.Items.Single(i => i.UserId == user.Id);
        item.SomeFunctionThatUpdatesProperties();
        context.SaveChanges();
    }
}

I did read a lot but this is still a little bit confused for me. Is the context getter okay in Page_Load ? Do I still need to use "using" or will disposal be okay with the Global.asax method ?

If I am confusing something I am sorry and I would be really, really grateful if someone could help me understand where it should be.

Thanks a lot !

Edits following nativehr answer and comments :

Here is the DbContextManager:

public static class DbContextManager
{
    public static MyEntities Current
    {
        get
        {
            var key = "MyDb_" + typeof(MyEntities).ToString();
            var context = HttpContext.Current.Items[key] as MyEntities;

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

The page :

public partial class Login : System.Web.UI.Page
{
    private User user;

    protected void Page_Load(object sender, EventArgs e)
    {
        if (Membership.GetUser() != null)
        {
            Guid guid = (Guid)Membership.GetUser().ProviderUserKey;
            user = UserService.Get(guid);
        }
    }

    protected void _Button_Click(object sender, EventArgs e)
    {
        if (user != null)
        {
            Item item = ItemService.GetByUser(user.Id)
            item.SomeFunctionThatUpdatesProperties();
            ItemService.Save(item);
        }
    }
}

And the ItemService class :

public static class ItemService
{
    public static Item GetByUser(Guid userId)
    {
        using (MyEntities context = DbContextManager.Current)
        {
            return context.Items.Single(i => (i.UserId == userId));
        }
    }

    public static void Save(Item item)
    {
        using (MyEntities context = DbContextManager.Current)
        {
            context.SaveChanges();
        }
    }
}

I think you should read a bit more about repository pattern for EntityFramework and UnitofWork pattern.

Implementing the Repository and Unit of Work Patterns in an ASP.NET MVC

I know this is mvc and you are problably using web forms but you can get an idea of how to implement it.

Disposing the context on each request is a bit strange, because there might be requests where you will not touch the database, so you will be doing unnecessary code.

What you should do is get a layer for data access and implement a repository pattern that you will access on whatever method you will need on the code behind of your page.

I would not rely on Thread.CurrentContext property.

Firstly, Microsoft says, Context class is not intended to be used directly from your code: https://msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.context%28v=vs.110%29.aspx

Secondly, imagine you want to make an async call to the database.

In this case an additional MyEntities instance will be constructed, and it will not be disposed in Application_EndRequest .

Furthermore, ASP.NET itself does not guarantee not to switch threads while executing a request. I had a similar question, have a look at this:

is thread switching possible during request processing?

I would use "MyDb_" + typeof(MyEntities).ToString() instead.

Disposing db context in Application_EndRequest is OK, but it produces a bit performance hit, 'cause your context will stay not disposed longer than needed, it is better to close it as soon as possible (you actually don't need an open context to render the page, right?)

Context pre request implementation would make sense if it has to be shared between different parts of your code, insted of creating a new instance each time.

For example, if you utilize the Repository pattern, and several repositories share the same db context while executing a request. Finally you call SaveChanges and all the changes made by different repositories are committed in a single transaction.

But in your example you are calling the database directly from your page's code, in this case I don't see any reason to not create a context directly with using .

Hope this helps.

Update : a sample with Context per request:

//Unit of works acts like a wrapper around DbContext
//Current unit of work is stored in the HttpContext
//HttpContext.Current calls are kept in one place, insted of calling it many times
public class UnitOfWork : IDisposable
{
    private const string _httpContextKey = "_unitOfWork";
    private MyContext _dbContext;

    public static UnitOfWork Current
    {
        get { return (UnitOfWork) HttpContext.Current.Items[_httpContextKey]; }
    }

    public UnitOfWork()
    {
        HttpContext.Current.Items[_httpContextKey] = this;
    }

    public MyEntities GetContext()
    {
        if(_dbContext == null)
            _dbContext = new MyEntities();

        return _dbContext;
    }

    public int Commit()
    {
        return _dbContext != null ? _dbContext.SaveChanges() : null;
    }

    public void Dispose()
    {
        if(_dbContext != null)
            _dbContext.Dispose();
    }
}

//ContextManager allows repositories to get an instance of DbContext
//This implementation grabs the instance from the current UnitOfWork
//If you want to look for it anywhere else you could write another implementation of IContextManager
public class ContextManager : IContextManager
{
    public MyEntities GetContext()
    {
        return UnitOfWork.Current.GetContext();
    }
}

//Repository provides CRUD operations with different entities
public class RepositoryBase
{
    //Repository asks the ContextManager for the context, does not create it itself
    protected readonly IContextManager _contextManager;

    public RepositoryBase()
    {
        _contextManager = new ContextManager(); //You could also use DI/ServiceLocator here
    }
}

//UsersRepository incapsulates Db operations related to User
public class UsersRepository : RepositoryBase
{
    public User Get(Guid id)
    {
        return _contextManager.GetContext().Users.Find(id);
    }

    //Repository just adds/updates/deletes entities, saving changes is not it's business
    public void Update(User user)
    {
        var ctx = _contextManager.GetContext();
        ctx.Users.Attach(user);
        ctx.Entry(user).State = EntityState.Modified;
    }
}

public class ItemsRepository : RepositoryBase
{
    public void UpdateSomeProperties(Item item)
    {
        var ctx = _contextManager.GetContext();
        ctx.Items.Attach(item);

        var entry = ctx.Entry(item);
        item.ModifiedDate = DateTime.Now;

        //Updating property1 and property2
        entry.Property(i => i.Property1).Modified = true;
        entry.Property(i => i.Property2).Modified = true;
        entry.Property(i => i.ModifiedDate).Modified = true;
    }
}

//Service encapsultes repositories that are necessary for request handling
//Its responsibility is to create and commit the entire UnitOfWork
public class AVeryCoolService
{
    private UsersRepository _usersRepository = new UsersRepository();
    private ItemsRepository _itemsRepository = new ItemsRepository();

    public int UpdateUserAndItem(User user, Item item)
    {
        using(var unitOfWork = new UnitOfWork()) //Here UnitOfWork.Current will be assigned
        {
            _usersRepository.Update(user);
            _itemsRepository.Update(user); //Item object will be updated with the same DbContext instance!

             return unitOfWork.Commit();
            //Disposing UnitOfWork: DbContext gets disposed immediately after it is not longer used.
            //Both User and Item updates will be saved in ome transaction
        }
    }
}

//And finally, the Page
public class AVeryCoolPage : System.Web.UI.Page
{
    private AVeryCoolService _coolService;

    protected void Btn_Click(object sender, EventArgs e)
    {
        var user = .... //somehow get User and Item objects, for example from control's values
        var item = ....

        _coolService.UpdateUserAndItem(user, item);
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM