c#entity framework:在您的repository类中正确使用DBContext类

时间:2015-10-09 14:42:24

标签: 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);
          }
      }
}

但是,根据其他文章,处理DbContext并不重要:http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html

这两篇文章中的哪一篇是对的?我很困惑。 将DbContext作为您的存储库类中的私有成员可能会导致"可伸缩性问题"正如第一篇文章所暗示的那样?

10 个答案:

答案 0 :(得分:36)

我认为你不应该关注第一篇文章,我会告诉你原因。

遵循第一种方法,您几乎失去了Entity Framework通过DbContext提供的所有功能,包括其第一级缓存,身份映射,工作单元及其更改跟踪和懒惰的能力。这是因为在上面的场景中,为每个数据库查询创建了一个新的DbContext实例,并在之后立即处理,从而阻止DbContext实例在整个业务中跟踪数据对象的状态事务。

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

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

答案 1 :(得分:18)

假设您拥有多个存储库,并且需要更新来自不同存储库的2条记录。你需要做事务性的(如果一个失败 - 都更新回滚):

var repositoryA = GetRepository<ClassA>();
var repositoryB = GetRepository<ClassB>();

repository.Update(entityA);
repository.Update(entityB);

因此,如果每个存储库都有自己的DbContext(案例2),则需要使用TransactionScope来实现此目的。

更好的方法 - 为一个操作设置一个共享DbContext(一个调用,一个unit of work)。所以,DbContext可以管理事务。 EF非常适合这种情况。 您只能创建一个DbContext,在许多存储库中进行所有更改,调用SaveChanges一次,在完成所有操作和工作后进行处理。

Here是UnitOfWork模式实现的示例。

您的第二种方式对于只读操作非常有用。

答案 2 :(得分:12)

根规则是:您的DbContext生命周期应限于您正在运行的交易

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

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

我可以看到使用私人会员的唯一一个案例是CQRS pattern (CQRS : A Cross Examination Of How It Works)

顺便说一句,Jon Gallant's post中的迭戈维加回应也给出了一些明智的建议:

  

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

     
      
  1. 默认的自动打开/关闭行为相对容易覆盖:您可以控制何时打开连接并且   通过手动打开连接关闭。一旦你开始这样做   在你的代码的某些部分,然后忘记了上下文   变得有害,因为你可能会泄漏开放的连接。

  2.   
  3. DbContext按照推荐的模式实现IDiposable,其中包括公开虚拟保护的Dispose方法   派生类型可以覆盖,例如,如果需要聚合其他   非托管资源进入上下文的生命周期。

  4.   

HTH

答案 3 :(得分:5)

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

存储库是否有责任运行完整的事务?即通过调用`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。这使我们以后能够改变创建上下文的方式。

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

注意存储库如何管理上下文的生命周期,它创建上下文,进行一些更改,保存更改,然后处理上下文。在这种情况下,存储库的生命周期比上下文更长。

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

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

请注意我在我的代码中新建了存储库(这违反了依赖注入)。我只是把它作为一个例子。在实际代码中,我们可以使用工厂来解决这个问题。

现在,我们可以对此方法进行的一项增强是隐藏接口背后的上下文,以便存储库不再具有SaveChanges的访问权限(请查看Interface Segregation Principle)。< / p>

你可以这样:

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

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

请注意,此界面不会从IDisposable继承。这意味着Repository类不负责管理上下文的生命周期。在这种情况下,上下文的生命周期比存储库大。其他人将管理上下文的生命周期。

关于第一篇文章的说明:

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

文章不清楚如何使用存储库。它是否用作单个交易的一部分?或者它跨越多个交易?

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

但我在这里建议的是不同的,我建议这种方法仅用于每次需要事务时创建存储库的新实例的情况。

关于第二篇文章的说明:

我认为第二篇文章所讨论的内容与您应该使用哪种方法无关。

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

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

文章说DbContext似乎在不需要明确处理上下文的情况下清理资源。

答案 4 :(得分:4)

您链接的第一篇文章忘记了一件重要的事情:所谓的NonScalableUserRepostory实例的生命周期是什么(它也忘了让NonScalableUserRepostory实现IDisposable,为了正确处理DbContext实例)。

想象一下以下情况:

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

嗯...... DbContext类中仍然会有一些私有NonScalableUserRepostory字段,但上下文只会用一次。所以它与文章描述的最佳实践完全相同。

所以问题不是&#34; 我应该使用私有成员还是使用使用声明?&#34;,它更多&#34; 应该是什么是我上下文的生命周期?&#34;。

答案是:尽量缩短它。 Unit Of Work的概念代表了一项商业运作。基本上,每个工作单元都应该有一个新的DbContext

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

编辑:

回答你的评论:

一种解决方案是通过构造函数注入工厂方法。这是一些基本的例子:

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
}

答案 5 :(得分:2)

第一个代码与scability问题没有关系,它之所以糟糕是因为他为每个存储库创建了新的上下文,这是一个不好的评论者,但是他甚至没有回复。在web中,它是1个请求1 dbContext,如果您打算使用存储库模式,那么它将转换为1个请求&gt;许多存储库&gt; 1个dbContext。这很容易用IoC实现,但不是必需的。如果没有IoC,你就是这样做的:

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

至于处理与否,我通常处理它,但如果领导本身说这个,那么可能没有理由这样做。如果它有IDisposable,我只想处理。

答案 6 :(得分:1)

基本上DbContext类只不过是一个处理所有数据库相关内容的包装器,如: 1.创建连接 2.执行查询。 现在,如果我们使用普通的ado.net进行上述操作,那么我们需要通过在using语句中编写代码或在连接类对象上调用close()方法来正确地关闭连接。

现在,由于上下文类在内部实现了IDisposable接口,因此最好在using语句中编写dbcontext,这样我们就不必关心关闭连接了。

感谢。

答案 7 :(得分:0)

我使用第一种方式(注入dbContext)当然它应该是一个IMyDbContext,并且您的依赖注入引擎正在管理上下文的生命周期,因此它只在需要时才生效。

这使您可以模拟测试的上下文,第二种方法使得无法在没有数据库的情况下检查实体以供上下文使用。

答案 8 :(得分:0)

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

答案 9 :(得分:0)

我认为第一种方法更好,你永远不必为每个存储库创建一个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);
    }
....