我是否正确使用和处理Entity Framework的对象上下文(每个请求)?

时间:2015-03-11 10:43:55

标签: c# asp.net entity-framework objectcontext

我有一个Web应用程序,我刚刚开始使用Entity Framework。我阅读了初学者教程,以及有关Web应用程序每个请​​求的对象上下文的好处的主题。 但是,我不确定我的背景是否在正确的位置......

我找到了这个非常有用的帖子(Entity Framework Object Context per request in ASP.NET?)并使用了建议的代码:

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

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

然后,我在我的网页中使用它:

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

我读了很多,但这对我来说仍然有点困惑。 Page_Load中的上下文getter是否正常?我仍然需要使用“使用”或使用Global.asax方法处理吗?

如果我感到困惑,我很抱歉,如果有人能帮我理解它应该在哪里,我真的非常感激。

非常感谢!

按照母语回答和评论进行编辑:

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

页面:

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

和ItemService类:

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

2 个答案:

答案 0 :(得分:1)

我认为您应该阅读有关EntityFramework和UnitofWork模式的存储库模式的更多信息。

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

我知道这是mvc,你可能正在使用网络表单,但你可以了解如何实现它。

在每个请求上处理上下文有点奇怪,因为可能会有请求您不会触及数据库,因此您将执行不必要的代码。

您应该做的是获取一个数据访问层,并实现一个存储库模式,您将访问页面背后代码所需的任何方法。

答案 1 :(得分:1)

我不会依赖Thread.CurrentContext财产。

首先,Microsoft说,Context类不能直接在您的代码中使用: https://msdn.microsoft.com/en-us/library/system.runtime.remoting.contexts.context%28v=vs.110%29.aspx

其次,假设你想对数据库进行异步调用。

在这种情况下,我们会构建一个额外的MyEntities个实例,并且 将被置于Application_EndRequest

此外,ASP.NET本身并不保证在执行请求时不切换线程。 我有一个类似的问题,看看这个:

is thread switching possible during request processing?

我会改用"MyDb_" + typeof(MyEntities).ToString()

Application_EndRequest中处理数据库上下文是可以的,但它会产生一点性能损失,因为您的上下文将不会停留超过需要的时间,最好尽快关闭它(您实际上,不需要一个开放的上下文来呈现页面,对吗?)

如果必须在代码的不同部分之间共享,那么上下文预请求实现就会有意义,每次都要创建一个新实例。

例如,如果您使用存储库模式,并且多个存储库在执行请求时共享相同的数据库上下文。 最后,您调用SaveChanges,并且不同存储库所做的所有更改都将在单个事务中提交。

但是在您的示例中,您直接从您网页的代码中调用数据库,在这种情况下,我没有看到任何直接使用using创建上下文的理由。

希望这有帮助。

更新:每个请求具有上下文的示例:

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