C# 我是否正在使用和处理实体框架';s对象上下文(每个请求)是否正确?

C# 我是否正在使用和处理实体框架';s对象上下文(每个请求)是否正确?,c#,asp.net,entity-framework,objectcontext,C#,Asp.net,Entity Framework,Objectcontext,我有一个web应用程序,我刚刚开始使用实体框架。我阅读了初学者教程,以及关于每个web应用请求的对象上下文好处的主题。 然而,我不确定我的上下文是否在正确的位置 我发现这篇非常有用的帖子()并使用了建议的代码: public static class DbContextManager { public static MyEntities Current { get { var key = "MyDb_" + HttpCont

我有一个web应用程序,我刚刚开始使用实体框架。我阅读了初学者教程,以及关于每个web应用请求的对象上下文好处的主题。 然而,我不确定我的上下文是否在正确的位置

我发现这篇非常有用的帖子()并使用了建议的代码:

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();
    }
}
我确实读了很多书,但这对我来说还是有点困惑。 页面加载中的上下文获取程序正常吗?我还需要使用“using”吗,还是可以使用Global.asax方法进行处理

如果我弄糊涂了,我很抱歉,如果有人能帮我理解它应该在哪里,我将非常非常感激

非常感谢

编辑以下nativehr答案和评论:

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

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

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

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


您应该做的是获得一个数据访问层,并实现一个存储库模式,您可以通过页面后面的代码中需要的任何方法来访问该模式。

我不会依赖于
线程.CurrentContext
属性

首先,Microsoft说,
上下文
类不打算直接从您的代码中使用:

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

在这种情况下,将构造一个额外的
MyEntities
实例,它将
Application\u EndRequest
中处理

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

我将使用
“MyDb_u3;”+typeof(myenties).ToString()

Application\u EndRequest
中处理db上下文是可以的,但它会对性能产生一些影响,因为您的上下文不会被处理的时间超过需要的时间,最好尽快关闭它(您实际上不需要打开上下文来呈现页面,对吗?)

如果必须在代码的不同部分之间共享,而不是每次创建一个新实例,则上下文预请求实现是有意义的

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

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

希望这有帮助

更新:每个请求包含上下文的示例:

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

我不太喜欢为所有请求保留上下文(特别是当您的网站流量非常大时),但是是的,它可以工作,并且您不需要使用和/或在应用程序请求之外的任何其他位置处理上下文(就像您正在做的那样)然后,您的代码是完全好的。如果上下文中的数据以只读方式使用,那么考虑全局上下文实例是合适的。任何其他必须将用户数据写入数据库的场景都需要独立的每个请求上下文实例,否则由同一实例跟踪的其他用户数据也将被持久化。我实际上不使用MVC,但如果更好的话,可能会对其进行更改!非常感谢你的回答,帮助了我,而且你是对的,有很多请求我没有修改数据库,所以看起来很不必要。我不知道存储库和工作单元,但我会阅读它。再次感谢!简化-假设您在请求处理过程中使用了两个类——ClassA和ClassB,它们都需要调用db。它们不是创建自己的上下文实例,而是在
HttpContext
中查找它。首先,这将导致1个开放数据库连接而不是2个,这意味着它将提高性能,其次,两个类所做的更改将保存为一个事务,因此您可以避免将ClassA中的更改写入db,然后出现错误并丢失ClassB中的更改。例如,您的页面调用UserService获取用户列表,调用WareService获取商品列表,这些服务依次在当前的
HttpContext
中查找
MyEntities
。但是,如果你直接从页面调用db,我认为根本不需要存储上下文-我只需要使用var db=new myenties()编写
,在那里我需要获取数据并立即关闭它。这已经是另一个问题了-你的应用程序的整个设计。如果您使用服务,我建议您在服务中完成与db相关的所有工作—我指的是获取和保存数据。当您通过服务获取用户,但直接从
Btn\u保存用户时,单击
这看起来像是一种设计气味。如果您在
页面中调用
服务.GetUsers
加载
为什么不在
Btn中调用
服务.SaveUser
?在本例中,
Page
根本不知道如何存储用户,它只做它的工作—呈现标记。是的,但我个人更喜欢为每个请求存储一个工作单元,而不是DbContext。UnitOfWork的作用类似于数据库的包装器。请看更新的答案,我分享了一些带有注释的示例代码。不,你通常有和实体一样多的存储库。服务可以封装多个repositpries(如CoolSe)