c#entity framework:正确使用库类中的DBContext类

我用来实现我的存储库类,你可以看到下面

public Class MyRepository { private MyDbContext _context; public MyRepository(MyDbContext context) { _context = context; } public Entity GetEntity(Guid id) { return _context.Entities.Find(id); } } 

不过,我最近读了这篇文章,说这是一个不好的做法,有数据上下文作为您的存储库中的私有成员: http : //devproconnections.com/development/solving-net-scalability-problem

现在,理论上这篇文章是正确的:因为DbContext实现了IDisposable,所以最正确的实现是以下内容。

 public Class MyRepository { public Entity GetEntity(Guid id) { using (MyDbContext context = new MyDBContext()) { return context.Entities.Find(id); } } } 

然而,根据这个其他文章configurationDbContext将不是必需的: http ://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html

哪两条是正确的? 我很困惑。 把DbContext作为存储库类中的私有成员,真的可以导致“可伸缩性问题”,正如第一篇文章所build议的那样?

我想你不应该按照第一篇文章,我会告诉你为什么。

遵循第一种方法,您几乎可以DbContext Entity Framework通过DbContext提供的所有function,包括其第一级caching,其身份映射,工作单元以及更改跟踪和延迟加载function。 这是因为在上面的场景中,为每个数据库查询创build了一个新的DbContext实例,并在之后立即进行处理,因此阻止DbContext实例能够在整个业务事务中跟踪数据对象的状态。

DbContext作为存储库类中的私有属性也有其问题。 我相信更好的方法是有一个CustomDbContextScope。 这个方法很好的解释了这个人:Mehdi El Gueddari

这篇文章http://mehdi.me/ambient-dbcontext-in-ef6/关于EntityFramework我见过的最好的文章之一。; 你应该完全阅读,我相信它会回答你所有的问题。

假设您拥有多个存储库,并且需要更新来自不同存储库的2条logging。 而且你需要做交易(如果一个失败 – 两个更新回滚):

 var repositoryA = GetRepository<ClassA>(); var repositoryB = GetRepository<ClassB>(); repository.Update(entityA); repository.Update(entityB); 

所以,如果你有自己的DbContext为每个存储库(情况2),你需要使用TransactionScope来实现这一点。

更好的方法 – 一个操作共享一个DbContext(一个调用,一个工作单元 )。 所以,DbContext可以pipe理事务。 EF是相当的。 您可以只创build一个DbContext,在许多存储库中进行所有更改,调用一次SaveChanges,在完成所有操作和工作后处理它。

这里是UnitOfWork模式实现的例子。

你的第二种方式可以适用于只读操作。

根规则是: 您的DbContext生存期应限制在您正在运行的事务中

在这里,“事务”可能指的是只读查询或写查询。 正如你可能已经知道的,交易应该尽可能短。

这就是说,我认为你应该在大多数情况下赞成“使用”的方式,而不是使用私人成员。

我可以看到使用私人成员的唯一情况是CQRS模式(CQRS:对其工作原理的交叉检查) 。

顺便说一下,迭戈维加在乔恩·加兰特的post中的回应也给出了一些明智的build议:

有两个主要的原因,我们的示例代码总是使用“使用”或以其他方式处理上下文:

  1. 默认的自动打开/closures行为相对比较容易覆盖:您可以通过手动打开连接来控制打开和closures连接的时间。 一旦你在你的代码的某个部分开始这样做,那么忘记将上下文变成有害的,因为你可能会泄漏开放的连接。

  2. DbContext按照推荐的模式实现了IDiposable,其中包括公开一个派生types可以覆盖的虚拟保护Dispose方法,例如需要将其他非托pipe资源聚合到上下文的生命周期中。

HTH

你链接的第一篇文章忘了一个重要的事情:所谓的NonScalableUserRepostory实例的生命周期是什么(它也忘记使NonScalableUserRepostory实现IDisposable也是为了恰当的处理DbContext实例)。

想象下面的情况:

 public string SomeMethod() { using (var myRepository = new NonScalableUserRepostory(someConfigInstance)) { return myRepository.GetMyString(); } } 

那么… NonScalableUserRepostory类中仍然会有一些私人的DbContext字段,但是上下文只能使用一次 。 所以这与文章中描述的最佳实践完全一样。

所以问题不是“ 我应该使用私人成员还是使用声明? ”,而是“ 我的上下文应该是什么样的一生? ”。

答案就是:尽可能地缩短它。 工作单元的概念代表了一个商业运作。 基本上,你应该为每个工作单位都有一个新的DbContext

如何定义一个工作单元以及如何实施将取决于您的应用程序的性质; 例如,对于ASP.Net MVC应用程序, DbContext的生命周期通常是HttpRequest的生命周期,即每次用户生成新的Web请求时都会创build一个新的上下文。


编辑:

回答你的评论:

一个解决办法是通过构造函数注入工厂方法。 这里有一个基本的例子:

 public class MyService : IService { private readonly IRepositoryFactory repositoryFactory; public MyService(IRepositoryFactory repositoryFactory) { this.repositoryFactory = repositoryFactory; } public void BusinessMethod() { using (var repo = this.repositoryFactory.Create()) { // ... } } } public interface IRepositoryFactory { IRepository Create(); } public interface IRepository : IDisposable { //methods } 

使用哪种方法取决于存储库的责任。

版本库的职责是运行完整的事务吗? 即进行更改,然后通过调用SaveChanges保存对数据库的更改。 还是只是一个更大的交易的一部分,因此只会做出更改而不保存它们?

案例#1)存储库将运行完整的交易(它将进行更改并保存):

在这种情况下,第二种方法更好(第二个代码示例的方法)。

我只会通过引入这样一个工厂来稍微修改这个方法:

 public interface IFactory<T> { T Create(); } public class Repository : IRepository { private IFactory<MyContext> m_Factory; public Repository(IFactory<MyContext> factory) { m_Factory = factory; } public void AddCustomer(Customer customer) { using (var context = m_Factory.Create()) { context.Customers.Add(customer); context.SaveChanges(); } } } 

我正在做这个轻微的变化,以启用dependency injection 。 这使我们能够在以后改变我们创造语境的方式。

我不希望存储库有自己创build上下文的责任。 实现IFactory<MyContext>的工厂将负责创build上下文。

注意存储库如何pipe理上下文的生命周期,创build上下文,做一些更改,保存更改,然后处理上下文。 在这种情况下,存储库具有比上下文更长的生命周期。

案例#2)存储库是一个更大的事务的一部分(它会做一些改变,其他存储库会做其他更改,然后别人将通过调用SaveChanges提交事务):

在这种情况下,第一种方法(你首先在你的问题中描述)比较好。

想象一下,这将继续了解存储库如何成为更大交易的一部分:

 using(MyContext context = new MyContext ()) { repository1 = new Repository1(context); repository1.DoSomething(); //Modify something without saving changes repository2 = new Repository2(context); repository2.DoSomething(); //Modify something without saving changes context.SaveChanges(); } 

请注意,每个事务都使用一个新的资源库实例。 这意味着版本库的生命周期非常短。

请注意,我正在新build代码库(这是违反dependency injection)。 我只是以此为例。 在真实的代码中,我们可以使用工厂来解决这个问题。

现在,我们可以对这种方法做一个改进,即隐藏接口背后的上下文,以便存储库不再能够访问SaveChanges (查看Interface Interregation Principle )。

你可以有这样的东西:

 public interface IDatabaseContext { IDbSet<Customer> Customers { get; } } public class MyContext : DbContext, IDatabaseContext { public IDbSet<Customer> Customers { get; set; } } public class Repository : IRepository { private IDatabaseContext m_Context; public Repository(IDatabaseContext context) { m_Context = context; } public void AddCustomer(Customer customer) { m_Context.Customers.Add(customer); } } 

如果需要,您可以将其他需要的方法添加到界面中。

请注意,这个接口不会从IDisposableinheritance。 这意味着Repository类不负责pipe理上下文的生命周期。 这种情况下的上下文比版本库有更长的生命周期。 其他人将pipe理上下文的生命周期。

第一篇文章的注释:

第一篇文章build议您不要使用您在问题中描述的第一种方法(将上下文注入到存储库中)。

该文章不清楚如何使用资料库。 它被用作单个事务的一部分吗? 还是它跨越多个交易?

我猜测(我不确定),在文章描述的方式(消极),存储库被用作长期运行的服务,将跨越很多交易。 在这种情况下,我同意这篇文章。

但是我在这里提出的build议是不同的,我build议这种方法只在每次需要事务时创build一个存储库的新实例的情况下使用。

第二篇文章的注释:

我认为,第二篇文章谈论什么与您应该采用哪种方法无关。

第二篇文章讨论是否有必要在任何情况下处理上下文(与存储库的devise无关)。

请注意,在两种devise方法中,我们正在处理上下文。 唯一的区别是谁负责这种处理。

文章说, DbContext似乎清理资源,而不需要显式地处理上下文。

第一个代码跟scable问题没有关系,不好的原因是他为每个糟糕的存储库创build了新的上下文,评论者中有哪个评论,但他甚至没有回复。 在web中,它是1请求1 dbContext,如果您打算使用存储库模式,那么它将转换为1请求>多个存储库> 1 dbContext。 这很容易用IoC来实现,但不是必要的。 这是没有IoC你怎么做的:

 var dbContext = new DBContext(); var repository = new UserRepository(dbContext); var repository2 = new ProductRepository(dbContext); // do something with repo 

至于处置与否,我通常会处理,但是如果领导本身说了这话可能没有理由去做。 我只是想处置,如果它有IDisposable。

基本上DbContext类只不过是一个包装,它处理所有的数据库相关的东西,如:1.创build连接2.执行查询。 现在,如果我们使用普通的ado.net来完成上面提到的东西,那么我们需要通过在using语句中编写代码或者在连接类对象上调用close()方法来正确closures连接。

现在,由于上下文类在内部实现了IDisposable接口,因此在使用语句中编写dbcontext是一个好习惯,所以我们不必关心closures连接。

谢谢。

我使用第一种方法(注入dbContext)当然,它应该是一个IMyDbContext,并且您的dependency injection引擎正在pipe理上下文的生命周期,所以它只在需要的时候才会生效。

这可以让你嘲笑testing的上下文,第二种方法使得无法检查没有上下文的数据库实体。

第二种方法(使用)更好,因为它只能确保最less的连接时间,并且更容易使线程安全。

我认为,第一种方法更好,即使将其丢弃,也不必为每个存储库创build一个dbcontext。 但是,在第一种情况下,您可以使用databaseFactory来仅实例化一个dbcontext:

  public class DatabaseFactory : Disposable, IDatabaseFactory { private XXDbContext dataContext; public ISefeViewerDbContext Get() { return dataContext ?? (dataContext = new XXDbContext()); } protected override void DisposeCore() { if (dataContext != null) { dataContext.Dispose(); } } } 

在Repository中使用这个实例:

 public class Repository<TEntity> : IRepository<TEntity> where TEntity : class { private IXXDbContext dataContext; private readonly DbSet<TEntity> dbset; public Repository(IDatabaseFactory databaseFactory) { if (databaseFactory == null) { throw new ArgumentNullException("databaseFactory", "argument is null"); } DatabaseFactory = databaseFactory; dbset = DataContext.Set<TEntity>(); } public ISefeViewerDbContext DataContext { get { return (dataContext ?? (dataContext = DatabaseFactory.Get())); } public virtual TEntity GetById(Guid id){ return dbset.Find(id); } ....