【问题标题】:EF - Best way to manage DbContext?EF - 管理 DbContext 的最佳方式?
【发布时间】:2016-05-17 12:09:00
【问题描述】:

我知道有很多关于这个主题的文章,但是无论我在哪里看,对我来说要么太复杂要么不清楚。

我的团队首先开发使用实体框架代码的 Web 应用程序。我们还使用 Autofac 进行依赖注入。

目前,数据访问如下:

提供给不包含 EF 的项目的 API:

public class DataService
{
   private IDbContextFactory<MyContext> _factory;
   private IDataServiceExecutor _dataServiceExecutor;

   public DataService(IDbContextFactory<MyContext> factory, IDataServiceExecutor executor)
   {
       _factory = factory;
       _dataServiceExecutor = executor;
   }

   public void AddItem(Product prod)
   {
       using (var context = _factory.Create())
       {
           if (_dataServiceExecutor.AddItem(context, prod))
               context.SaveChanges();
       }
   }
}

我的 DataServiceExecutor:

public class DataServiceExecutor
{
    private IRepository<Product> _products;

    public DataService(IRepository<Product> products...more repositories)
    {
       _products = products;
    }

    public bool AddItem(DbContext context, Prouduct prod)
    {
        try
        {
            _products.AddItem(context, prod);

            return true;
        }
        catch(Exception ex)
        {
             Log(..)
             return false;
        }
    }
}

我所有的存储库都继承自这个抽象存储库:

public abstract class EFRepository<T> : IRepository<T>
{
    public void AddItem<T>(DbContext context, T item)
    {
        context.Set<T>().Add(item);
    }

    .
    .
    .
}

好处是每个事务都以这种方式使用上下文。

不好的是我的服务方法和我的存储库方法都直接获取上下文。应用程序不是很大,所以现在上下文的方法注入很好,但它可能会变得更大,所以在我看来,当前状态下的上下文注入是有问题的。而且看起来很糟糕。

也许还有更多我不知道的利弊..

有什么我不熟悉的方法可以更好地访问数据?

【问题讨论】:

标签: c# entity-framework ef-code-first dbcontext


【解决方案1】:

DataServiceExecutor(本质上是一个动词)这样的类总是会造成设计缺陷。这是一个伪装成类的方法(Execute...)。这些类的职责并不明确,因为它们的功能不可避免地属于其他类。

像控制反转这样的伟大模式本身的问题在于,可以使用 IoC 容器来注入 任何 依赖项。因此,它们允许您创建错综复杂的依赖关系和分散的职责,并且仍然可以在管理生命周期方面做得不错。它们可能会掩盖您原本会遇到的生命周期问题。

所以让我们暂时忽略 IoC,看看如果您调用 DataServiceExecutor.AddItem,您的代码在简单的对象创建中会是什么样子。

var product = new Product();
var factory = new DbContextFactory<MyContext>(); // Dependencies unknown
var productRepository = new Repository<Product>(context);
var executor = new DataServiceExecutor(productRepository);
var dataService = new DataService(factory, executor);

dataServiceAddItem 方法中,您基本上拥有:

using (var context = _factory.Create())
{
    executor.AddItem(context, product);
    context.SaveChanges();
}

如果DataService 会收到productRepository 而不是executor,这将归结为:

productRepository.AddItem(context, product);
context.SaveChanges();

executor 可以轻松取出。它的唯一作用似乎是错误记录。但这也可以由DataService 完成。

但我们还没有完成。正如您自己指出的那样,这些将上下文作为参数的方法有点尴尬。但是现在DataServiceExecutor 已经不在了,这样做更自然:

var product = new Product();
var factory = new DbContextFactory<MyContext>();
using (var context = _factory.Create())
{
    var productRepository = new Repository<Product>(context);
    productRepository.AddItem(product);

    // And for example
    var orderRepository = new Repository<Order>(context);
    orderRepository.AddItem(order);

    // One SaveChanges call!
    context.SaveChanges();
}

DataService 也不见了。

现在EFRepository 将其context 存储为成员变量,AddItem 看起来像这样:

public void AddItem<T>(T item)
{
    _context.Set<T>().Add(item);
}

现在回到 IoC。

鉴于 IoC,您的代码的主要问题是 DataService.AddItem 内部的这个:

using (var context = _factory.Create())

您创建了一个不受 IoC 容器管理的上下文。它的生命周期范围为AddItem 方法。因此,您必须到处传递它,以确保业务事务中的所有内容都使用这个实例。通过将存储库的依赖项(对上下文)带回构造函数注入,让 IoC 容器管理上下文的生命周期要容易得多。

顺便说一句,您说DataService 是“提供给不包含 EF 的项目的 API”的一部分。但它确实在其通用参数中引用了MyContext。也许MyContext 也是一个抽象,我不知道。无论如何,您也可以通过 IoC 提供这种抽象的实例。

【讨论】:

  • 感谢您的详细解答!首先,该服务确实提供了 API,Autofac 在其 ctor 中注入了上下文工厂。其次,执行者的目的是实际执行操作,服务职责是在操作成功时保存更改和/或处置上下文。我确实知道传递上下文很丑陋,但不知道如何使用依赖注入来防止它并且为每个事务保持相同的上下文,我从你的回答中也无法理解如何注入它
  • 您可以像注入工厂等一样注入它。即通过构造函数注入。我确信有很多关于如何使用 Autofac 注入上下文的示例。但它可能会在您的程序流程中发生很大变化。我认为这是最大的问题。但我向您展示的是一种更常见(和通用)的方式来处理存储库和上下文。
  • 我再次阅读了您的第一条评论,一切都非常正确,摆脱执行器是明智的,以及获取存储库的依赖关系并在其 ctor 中获取上下文。现在我面临另一个大问题,即如何在我的 startApp 中实际注册 dbContext 对象。它迫使我引用我真的不喜欢的 Ef
  • 我不知道 AutoFac 实际操作,但在 Castle Windsor 中,可以在另一个程序集中创建“安装程序”,将内部类型注册到容器中。 IoC 容器会自动发现安装程序类。也许使用 AutoFac 可以实现类似的方法。
  • 但这会迫使我从我的数据项目中引用 Autofac,这是同样的问题,不是吗?
【解决方案2】:

我的意见是解决方案没有正确分层。我猜DataService是从外部访问的顶层?

在这种情况下,我将更改为以下内容:

  • 服务获取通过构造函数注入的存储库(或数据层)(然后正确处置)。服务方法则只需要相关参数。
  • 数据层有什么用途?可以去掉吗?我通常在包含所有存储库的服务下方有一个数据层。在这种情况下,数据层可以在服务处理数据层时负责处理所有存储库。
  • 存储库可以保持原样,但应该通过构造函数注入上下文。

【讨论】:

  • 更新了我的问题以使其更清晰。如果你能用一段代码来描述你的答案,也许我会更好地理解
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2010-11-28
  • 2020-05-23
  • 1970-01-01
  • 1970-01-01
  • 2012-12-14
  • 2018-04-14
  • 2010-09-13
相关资源
最近更新 更多