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

Err*_*ale 68 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);
      }
}
Run Code Online (Sandbox Code Playgroud)

However I recently read this article which says that's a bad practice to have data context as a private member in your repository: http://devproconnections.com/development/solving-net-scalability-problem

Now, theoretically the article is right: since DbContext implements IDisposable the most correct implementation would be the following.

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

However, according to this other article disposing DbContext would be not essential: http://blog.jongallant.com/2012/10/do-i-have-to-call-dispose-on-dbcontext.html

Which of the two articles is right? I'm quite confused. Having DbContext as private member in your repository class can really cause "scalability problems" as the first article suggests?

Fab*_*Luz 38

我想你不应该听完第一篇文章,我会告诉你原因.

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

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

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


Bac*_*cks 18

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

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

repository.Update(entityA);
repository.Update(entityB);
Run Code Online (Sandbox Code Playgroud)

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

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

以下是UnitOfWork模式实现的示例.

您的第二种方式可能适用于只读操作.

  • 那个怎么样?UnitOfWork仍然使用每个实体概念的存储库.如果有任何这些例子显示控制器做了很多工作.应该有一个服务层完成所有工作,UoW应该在服务层内.控制器应该是薄而笨的. (8认同)
  • 这是为什么存储库是一个失败的概念的典型代表.对每个实体的回购说不. (4认同)

Dud*_*lou 13

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

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

也就是说,我会说在大多数情况下你应该赞成"使用"方式,而不是使用私人会员.

我可以看到使用私人会员的唯一一个案例是CQRS模式(CQRS:如何运作的交叉检查).

顺便说一句,在Jon Gallant的帖子中,Diego Vega的反应也给出了一些明智的建议:

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

  1. 默认的自动打开/关闭行为相对容易覆盖:您可以通过手动打开连接来控制何时打开和关闭连接.一旦你开始在代码的某些部分中执行此操作,那么忘记使用上下文变得有害,因为您可能正在泄漏打开的连接.

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

HTH


Yac*_*sad 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();
        }            
    }
}
Run Code Online (Sandbox Code Playgroud)

我正在做这个小改动以启用依赖注入.这使我们以后能够改变创建上下文的方式.

我不希望存储库负责自己创建上下文.实施的工厂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();
}
Run Code Online (Sandbox Code Playgroud)

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

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

现在,我们可以对此方法进行的一项增强是隐藏接口背后的上下文,以便存储库不再具有访问权限SaveChanges(请参阅接口隔离原则).

你可以这样:

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);      
    }
}
Run Code Online (Sandbox Code Playgroud)

如果需要,可以向界面添加其他方法.

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

关于第一篇文章的说明:

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

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

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

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

关于第二条的说明:

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

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

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

文章说,DbContext似乎清理资源而不需要明确地处理上下文.