【问题标题】:Am I using and disposing Entity Framework's Object Context (per request) correctly?我是否正确使用和处理实体框架的对象上下文(每个请求)?
【发布时间】:2015-03-11 10:43:55
【问题描述】:

我有一个 Web 应用程序,我刚刚开始使用实体框架。我阅读了初学者教程,以及有关 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 中的上下文获取器可以吗?我还需要使用“使用”还是使用 Global.asax 方法可以处理?

如果我对某些事情感到困惑,我很抱歉,如果有人能帮助我理解它应该在哪里,我会非常非常感激。

非常感谢!

根据 nativehr 回答和 cmets 进行编辑:

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

【问题讨论】:

  • 我不太喜欢为所有请求保留上下文(特别是如果您的网站流量非常大),但是是的,它可以工作,您不需要使用 using 和/或 dispose上下文在 Application_EndRequest 以外的任何其他地方(正如你正在做的那样),那么你的代码就很好了。
  • 如果通过上下文公开的数据以只读方式使用,则考虑全局上下文实例是合适的。任何其他必须将用户数据写入数据库的场景都需要隔离每个请求的上下文实例,否则同一实例跟踪的其他用户数据也将被持久化。

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


【解决方案1】:

我不会依赖 Thread.CurrentContext 属性。

首先,微软表示,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);
    }
}

【讨论】:

  • 简化 - 假设您在请求处理期间使用了 2 个类 - ClassA 和 ClassB,两者都需要调用数据库。相反,他们都创建了自己的 Context 实例,而是在 HttpContext 中查找它。首先,这将导致 1 个打开的数据库连接设置为 2,这意味着它将提高性能,其次,两个类所做的更改将保存为 one 事务,因此您可以避免更改从ClassA 被写入数据库,然后出现问题,ClassB 的更改丢失。
  • 比如你的页面调用UserService获取用户列表,调用WaresService获取商品列表,这些服务依次在当前HttpContext中查找MyEntities。但是,如果您直接从页面调用 db,我认为根本不需要存储上下文 - 我只会写 using var db = new MyEntities() 我需要获取数据并立即关闭它。
  • 这已经是另一个问题了——你的应用的整个设计。如果您使用服务,我建议您进行所有与服务中的数据库相关的工作——我的意思是获取和保存数据。当您通过服务获取用户,但直接从Btn_Click 保存他们时,这看起来像是一种设计味道。如果您在Page_Load 中调用service.GetUsers,为什么不从Btn_click 中调用service.SaveUser?在这种情况下,Page 根本不知道存储用户,它只做它的工作 - 呈现标记。
  • 是的,但我个人更喜欢为每个请求存储一个 UnitOfWork,而不是 DbContext。 UnitOfWork 就像 db 的包装器。请查看更新后的答案,我与 cmets 分享了一些示例代码。
  • 不,您通常拥有与实体一样多的存储库。 Service 可以封装多个 repositpries(就像 CoolService 封装了 UserRepository 和 ItemRepository)。但这当然是一个要讨论的话题,如果你的应用程序真的很简单,你可能可以省略服务层......我分享的代码只是我使用的概念,但你可以找到许多其他的 Repository 实现, UnitOfWork 和 ContextPerRequest。
【解决方案2】:

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

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

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

在每个请求上处理上下文有点奇怪,因为可能有些请求你不会接触数据库,所以你会做一些不必要的代码。

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

【讨论】:

  • 我实际上不使用 MVC,但如果它更好,可能会改变它!非常感谢您的回答对我有帮助,而且您是对的,有很多请求我没有修改数据库,所以似乎没有必要。我不知道存储库和工作单元,但我会阅读它。再次感谢!
猜你喜欢
  • 2014-03-19
  • 2013-05-08
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多