EF 6 的工作单元和依赖注入设计问题

Unit of work with EF 6 and Dependency injection Design problems

我用entity framework6开发web应用,在设计应用结构上有困难。我的主要问题是在我的具体情况下如何处理依赖注入。

下面的代码是我希望应用程序的样子。我正在使用 Autofac,但我想它对每个 DI 用户来说都足够基本了:

public interface IUnitOfWork
{
    bool Commit();
}

public class UnitOfWork : IUnitOfWork, IDisposable
{
    private DbContext _context;

    public UnitOfWork(DbContext context)
    {
        _context = context;
    }

    public bool Commit()
    {
        // ..
    }

    public bool Dispose()
    { 
          _context.Dispose();
    }
}

public class ProductsController : ApiController 
{
     public ProductsController(IProductsManager managet)
}   


public class ProductsManager : IProductsManager
{
    private Func<Owned<IUnitOfWork>> _uowFactory;
    private IProductsDataService _dataService;

    public Manager(Func<Owned<IUnitOfWork>> uowFactory, IProductsDataService dataService)
    {
        _dataService = dataService;
        _uowFactory = uowFactory;
    }

    public bool AddProduct(ProductEntity product)
    {
        using (ownedUow = _uowFactory())
        {
            var uow = ownedUow.Value;

            var addedProduct = _dataService.AddProduct(product);

            if (addedProduct != null)
                uow.Commit();
        }
    }
}

public interface IProductsDataService
{
    ProductEntity AddProduct (Product product)
}

public class ProductsDataService : IProductsDataService 
{
    private IRepositoriesFactory _reposFactory;

    public DataService(IRepositoriesFactory reposFactory)
    {
        _reposFactory = reposFactory;
    }

    public ProductEntity AddProduct(ProductEntity product)
    {
        var repo = reposFactory.Get<IProductsRepository>();

        return repo.AddProduct(product);
    }
}


public interface IRepositoriesFactory
{
    T Get<T>() where T : IRepository
}

public class RepositoriesFactory : IRepositoriesFactory
{
    private ILifetimeScope _scope;

    public RepositoriesFactory(ILifetimeScope scope)
    {
        _scope = scope;
    }

    public T Get<T>() where T : IRepository
    {
        return _scope.Resolve<T>();
    }

}

public interface IProductsRepository
{
    ProductEntity AddProduct(ProductEntity);
}


public ProductsRepository : IProductsRepository
{
    private DbContext _context;

    public ProductsRepository(DbContext context)
    {
        _context = context;
    }

    public ProductEntity AddProduct(ProductEntity)
    {
        // Implementation..
    }
}

这是我认为理想的实现,但我不知道如何实现,因为我的 ProductsDataService 是单例的,因此它与由工程单元工厂创建的 Owned 范围无关。 有没有一种方法可以关联要创建的存储库,并在它们的构造函数中采用为工作单元创建的相同 DbContext?以某种方式更改 RepositoriesFactory 中的代码?

目前我所拥有的是工作单元包含存储库工厂,因此存储库中的上下文将与工作单元中的上下文相同(我根据范围注册了 DbContext), 经理现在也负责 DataService 的工作,这是我不喜欢的。

我知道我可以绕过 UnitOfWork - 方法注入到 DataService 方法,但我宁愿使用 Ctor 注入,因为我认为它看起来更好。

我想要的是将其分开 - 一个管理器,它的工作是实例化工作单元并在需要时提交它们,另一个 class (DataService) 实际执行逻辑。

无论如何,如果您有任何改进意见/想法,我想听听您对这个实现的看法。

感谢您的宝贵时间!

编辑:这就是我最终得到的:

public interface IUnitOfWork
{
    bool Commit();
}

public class DatabaseUnitOfWork : IUnitOfWork
{
    private DbContext _context;

    public DatabaseUnitOfWork(DbContext context)
    {
        _context = context;
    }

    public bool Commit()
    {
        // ..
    }
}

// Singleton
public class ProductsManager : IProductsManager
{
    private Func<Owned<IProductsDataService>> _uowFactory;

    public ProductsManager(Func<Owned<IProductsDataService>> uowFactory)
    {
        _uowFactory = uowFactory;
    }

    public bool AddProduct(ProductEntity product)
    {
        using (ownedUow = _uowFactory())
        {
            var dataService = ownedUow.Value;

            var addedProduct = _dataService.AddProduct(product);

            if (addedProduct != null)
                uow.Commit();
        }
    }
}

public interface IProductsDataService : IUnitOfWork
{
    ProductEntity AddProduct (Product product)
}

public class ProductsDataService : DatabaseUnitOfWork, IDataService 
{
    private IRepositoriesFactory _reposFactory;

    public DataService(IRepositoriesFactory reposFactory)
    {
        _reposFactory = reposFactory;
    }

    public ProductEntity AddProduct(ProductEntity product)
    {
        var repo = _reposFactory .Get<IProductsRepository>();

        return repo.AddProduct(product);
    }
}


public interface IRepositoriesFactory
{
    Get<T>() where T : IRepository
}

public class RepositoriesFactory : IRepositoriesFactory
{
    private ILifetimeScope _scope;

    public RepositoriesFactory(ILifetimeScope scope)
    {
        _scope = scope;
    }

    public Get<T>() where T : IRepository
    {
        return _scope.Resolve<T>();
    }

}

public interface IProductsRepository
{
    ProductEntity AddProduct(ProductEntity);
}


public ProductsRepository : IProductsRepository
{
    private DbContext _context;

    public ProductsRepository(DbContext context)
    {
        _context = context;
    }

    public ProductEntity AddProduct(ProductEntity)
    {
        // Implementation..
    }
}

看来问题并不是真正确保注入 UnitOfWorkProductsRepository 的 DbContext 实例相同。

这可以通过将 DbContext 注册为 InstancePerLifetimeScope 并在解析 IUnitOfWorkProductsRepository 之前创建一个新的 LifetimeScope 来实现。 任何不属于您的依赖项将在处置 LifetimeScope.

时被处置

问题似乎是您在这两者之间没有明确的关系 类。您的 UoW 不依赖于 'any DbContext',它取决于当前事务中涉及的任何 DbContext。具体那个。

你的 UoW 和知识库之间也没有直接关系。这看起来不像 UoW pattern

我无法确定谁将处置您的 IRepositoryFactory 创建的 IRepository。您正在使用容器来解决它(通过您注入 RepositoriesFactoryILifetimeScope)。除非从 Factory 中获取该实例的人处置它,否则只能通过处置注入 IRepositoryFactory.

LifetimeScope 来处置它

另一个可能出现的问题是 DbContext 的所有权。您可以通过 IUnitOfWork 上的 Dispose 在那个 using 块上处理它。但是您的 UnitOfWork 也不拥有该实例。容器可以。存储库还会尝试处理 DbContext 吗?他们还通过构造函数注入接收。

我建议重新考虑这个解决方案。

我同意 Bruno Garcia 关于您的代码问题的建议。但是,我发现它还有其他一些问题。

首先我会说我没有像您那样明确地使用工作单元模式,但我确实理解您的目的。

Bruno 没有解决的问题是你的关注点分离很差。他稍微暗示了一下,我会解释更多:您的控制器中有两个独立的竞争对象,它们都试图利用相同的资源 (DbContext)。正如他所说,您要做的是为每个请求设置一个 DbContext。但是,这有一个问题:在处理 UnitOfWork 后,没有什么可以阻止 Controller 尝试继续使用 ProductsRepository。如果这样做,则与数据库的连接已被释放。

因为您有两个对象需要使用相同的资源,所以您应该在一个对象封装另一个对象的地方重新构建它。这也提供了一个额外的好处,即向控制器隐藏任何关于数据传播的问题。

控制器应该知道的只是您的服务对象,它应该包含所有业务逻辑以及存储库及其工作单元的网关,同时保持它对服务的消费者不可见。这样,Controller 只需担心处理和处置一个对象。

解决此问题的其他方法之一是让 ProductsRepository 从 UnitOfWork 派生,这样您就不必担心任何重复的代码。

然后,在您的 AddProduct 方法中,您将调用 _context.SaveChanges(),然后将该对象沿着管道返回到您的控制器。

UPDATE(大括号的样式是为了紧凑)

这是您想要执行的布局:

UnitOfWork 是最底层,包括与数据库的连接。但是,将此设置为 abstract,因为您不想允许它的具体实现。您不再需要接口,因为您在 Commit 方法中所做的事情永远不会暴露,并且对象的保存应该在方法中完成。我会展示如何下线。

public abstract class UnitOfWork : IDisposable {
    private DbContext _context;

    public UnitOfWork(DbContext context) {
        _context = context;
    }

    protected bool Commit() {
        // ... (Assuming this is calling _context.SaveChanges())
    }

    public bool Dispose() {
        _context.Dispose();
    }
}

您的存储库在下一层。从 UnitOfWork 派生,以便它继承所有行为,并且对于每个特定类型都是相同的。

public interface IProductsRepository {
    ProductEntity AddProduct(ProductEntity product);
}

public ProductsRepository: UnitOfWork, IProductsRepository {
    public ProductsRepository(DbContext context) : base(context) { }

    public ProductEntity AddProduct(ProductEntity product) {
        // Don't forget to check here. Only do that where you're using it.
        if (product == null) {
            throw new ArgumentNullException(nameof(product));
        }

        var newProduct = // Implementation...

        if (newProduct != null) {
            Commit();
        }

        return newProduct;
    }
}

有了它,您现在关心的只是拥有您的 ProductsRepository。在您的 DataService 层中,利用依赖注入并仅传递 ProductsRepository 本身。如果你真的打算使用工厂,那么通过工厂,但你的成员变量仍然是 IProductsRepository。不要让每个方法都必须弄清楚。

不要忘记让 所有 接口派生自 IDisposable

public interface IProductsDataService : IDisposable {
    ProductEntity AddProduct(ProductEntity product);
}

public class ProductsDataService : IProductsDataService {
    private IProductsRepository _repository;

    public ProductsDataService(IProductsRepository repository) {
        _repository = repository;
    }

    public ProductEntity AddProduct(ProductEntity product) {
        return _repository.AddProduct(product);
    }

    public bool Dispose() {
        _repository.Dispose();
    }
}

如果您坚决要使用 ProductsManager,您可以,但它只是另一层,不再提供太多好处。 class.

同样的交易

我会按照我的意愿完成你的控制器。

public class ProductsController : Controller {
    private IProductsDataService _service;

    public ProductsController(IProductsDataService service) {
        _service = service;
    }

    protected override void Dispose(bool disposing) {
        _service.Dispose();

        base.Dispose(disposing);
    }

    // Or whatever you're using it as.
    [HttpPost]
    public ActionResult AddProduct(ProductEntity product) {
        var newProduct = _service.AddProduct(product);

        return View(newProduct);
    }
}

您不希望在单例实例中使用单例 DbContext。没关系,可以用工厂来完成。另外你想分享这个 DbContext。这也可以,您可以解决 return DbContext 与工厂相关的生命周期。 问题是;您希望在单个实例中共享非单例 DbContext 而无需管理生命周期(Tcp/Ip 请求)。

ProductServiceProductManager是单例的原因是什么? 我建议你在每个生命周期中使用 ProductServiceProductManager。当你有 http 请求时,它很好。当您有 tcp/ip 请求时,您可以开始新的生命周期范围(尽可能最高级别)然后在那里解析 ProductManager

更新:我在评论中提到的解决方案 1 的代码。

Managers 必须是单身人士(如您所说)。

managers 之外,您应该将 dbcontextservicesrepositoriesUow 注册为 per lifetime 范围。

我们可以这样初始化:

public class ProductsManager : IProductsManager
    {
        //This is kind of service locator. We hide Uow and ProductDataService dependencies.
        private readonly ILifetimeScope _lifetimeScope;

        public ProductsManager(ILifetimeScope lifetimeScope)
        {
            _lifetimeScope = lifetimeScope;
        }
    }

但这是一种服务定位器。我们隐藏 UowProductDataService 依赖项。

所以我们应该实现一个提供者:

public IProductsManagerProvider : IProductsManager
{

}
public class ProductsManagerProvider : IProductsManagerProvider
{
    private readonly IUnitOfWork _uow;
    private readonly IProductsDataService _dataService;

    public ProductsManagerProvider (IUnitOfWork uow, IProductsDataService dataService)
    {
        _dataService = dataService;
        _uow = uow;
    }

    public bool AddProduct(ProductEntity product)
    {
        var result=false;
        var addedProduct = _dataService.AddProduct(product);
        if (addedProduct != null)
            result=_uow.Commit()>0;
        return result;
    }
}

然后我们将其注册为per dependency(因为我们将在工厂中使用它)。

container.RegisterType<ProductsManagerProvider>().As<IProductsManagerProvider>().InstancePerDependency();

你的ProductsManagerclass应该是这样的。 (现在我们不隐藏任何依赖项)。

public class ProductsManager : IProductsManager
{
    private readonly Func<Owned<IProductsManagerProvider>> _managerProvider;
    //Now we don't hide any dependencies.
    public ProductsManager(Func<Owned<IProductsManagerProvider>> managerProvider)
    {
        _managerProvider = managerProvider;
    }

    public bool AddProduct(ProductEntity product)
    {
        using (var provider = _managerProvider())
        {
            return provider.Value.AddProduct(product);
        }
    }
}

我用自己的 classes 进行了测试。

您有单例管理器实例,它有一个工厂来创建管理器提供程序。 Manager Providers 是每个依赖项,因为每次我们都应该在单例中获取新实例。每个生命周期提供者中的所有内容,因此它们的生命周期是每个依赖生命周期的连接提供者。

当您在管理器中添加产品时 Container 创建 1 个 Provider、1 个 DbContext、1 个 DataService 和 1 个 UowDbContext 是共享)。 ProviderManager.

中的方法 return 之后与所有已实现的实例 (DbContex、Uow、DataService) 一起处理(每个依赖项)