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

15 c# entity-framework dependency-injection unit-of-work autofac

我用实体框架6开发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..
    }
}
Run Code Online (Sandbox Code Playgroud)

这是我认为理想的实现,但是我不知道如何实现这一点,因为我的ProductsDataService是单例,因此它与工厂工厂单元创建的Owned范围无关.有没有办法可以将要创建的存储库关联起来,并将与工作单元创建的DbContext相同的DbContext接受?以某种方式更改RepositoriesFactory中的代码?

目前我所拥有的是工作单元包含存储库工厂,以便存储库中的上下文与工作单元中的上下文相同(我按照范围注册DbContext),此时管理器执行DataService的工作也是我不喜欢的.

我知道我可以将UnitOfWork - 方法注入传递给DataService方法,但我宁愿使用Ctor注入,因为在我看来它看起来更好.

我想要的是分离它 - 一个管理器,它的工作是实例化工作单元并在需要时提交它们,以及另一个实际执行逻辑的类(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..
    }
}
Run Code Online (Sandbox Code Playgroud)

kri*_*gar 3

我同意布鲁诺·加西亚关于您的代码问题的建议。但是,我发现它还存在一些其他问题。

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

布鲁诺没有遇到的问题是关注点分离不佳。他暗示了一点,我将解释更多:您的控制器中有两个独立的竞争对象,两者都试图利用相同的资源(DbContext)。正如他所说,您想要做的是为每个请求只有一个 DbContext。但这有一个问题:在处理 UnitOfWork 后,没有什么可以阻止 Controller 尝试继续使用 ProductsRepository。如果这样做,则与数据库的连接已被释放。

因为您有两个对象需要使用相同的资源,所以您应该重新构建它,其中一个对象封装另一个对象。这还带来了额外的好处,即向控制器隐藏任何有关数据传播的担忧。

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

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

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

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

这是您想要执行的操作的布局:

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

您的存储库是下一层。派生它,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;
    }
}
Run Code Online (Sandbox Code Playgroud)

完成此操作后,您现在所关心的只是拥有您的 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();
    }
}
Run Code Online (Sandbox Code Playgroud)

如果您执意要使用ProductsManager,也可以,但它只是另一层,不再提供太多好处。那个班级也有同样的待遇。

我将按照我的意愿完成您的控制器。

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