我是否正确使用和处理 Entity Framework 的对象上下文(根据请求)?
Am I using and disposing Entity Framework's Object Context (per request) correctly?
我有一个刚开始使用的 Web 应用程序 Entity Framework。我阅读了初学者教程,以及有关每个 Web 应用程序请求的对象上下文优势的主题。
但是,我不确定我的上下文是否在正确的位置...
我发现这非常有用 post (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 是否正常?我还需要使用 "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 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();
}
}
}
我认为您应该阅读更多关于 EntityFramework 和 UnitofWork 模式的存储库模式。
我知道这是 mvc,您可能正在使用 Web 表单,但您可以了解如何实现它。
在每个请求上配置上下文有点奇怪,因为可能有些请求不会触及数据库,因此您将执行不必要的代码。
您应该做的是获得一个数据访问层并实现一个存储库模式,您将在页面的代码后面以您需要的任何方法访问该存储库模式。
我不会依赖Thread.CurrentContext
属性.
首先,Microsoft 表示,Context
class 不打算直接从您的代码中使用:
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);
}
}
我有一个刚开始使用的 Web 应用程序 Entity Framework。我阅读了初学者教程,以及有关每个 Web 应用程序请求的对象上下文优势的主题。 但是,我不确定我的上下文是否在正确的位置...
我发现这非常有用 post (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 是否正常?我还需要使用 "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 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();
}
}
}
我认为您应该阅读更多关于 EntityFramework 和 UnitofWork 模式的存储库模式。
我知道这是 mvc,您可能正在使用 Web 表单,但您可以了解如何实现它。
在每个请求上配置上下文有点奇怪,因为可能有些请求不会触及数据库,因此您将执行不必要的代码。
您应该做的是获得一个数据访问层并实现一个存储库模式,您将在页面的代码后面以您需要的任何方法访问该存储库模式。
我不会依赖Thread.CurrentContext
属性.
首先,Microsoft 表示,Context
class 不打算直接从您的代码中使用:
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);
}
}