简体   繁体   English

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

[英]Unit of work with EF 6 and Dependency injection Design problems

I develop web application with entity framework 6, and have difficulties with designing the application structure. 我用实体框架6开发Web应用程序,并且在设计应用程序结构时遇到了困难。 My main issue is how to deal with the dependency injection in my specific case. 我的主要问题是如何在我的特定情况下处理依赖注入。

The code below is how I would like the application to look like. 下面的代码是我希望应用程序的样子。 I'm using Autofac but I guess it is basic enough for every DI user to understand: 我正在使用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..
    }
}

This is the implementation I find ideal, however I don't know how to accomplish this, Because my ProductsDataService is singleton, therefore it is not related to the Owned scope that is created by the unit of works factory. 这是我认为理想的实现,但是我不知道如何实现这一点,因为我的ProductsDataService是单例,因此它与工厂工厂单元创建的Owned范围无关。 Is there a way I can associate the Repositories to be created and take in their ctor the same DbContext that was created for the unit of work? 有没有办法可以将要创建的存储库关联起来,并将与工作单元创建的DbContext相同的DbContext接受? Change the code in the RepositoriesFactory somehow? 以某种方式更改RepositoriesFactory中的代码?

At the moment what I have is that the unit of work contains the repositories factory so that the context within the repositories will be the same as in the unit of work (I register the DbContext as per scope), The manager at the moment does the job of the DataService as well, which I dont like. 目前我所拥有的是工作单元包含存储库工厂,以便存储库中的上下文与工作单元中的上下文相同(我按照范围注册DbContext),此时管理器执行DataService的工作也是我不喜欢的。

I know I can pass around the UnitOfWork - method injection to the DataService methods, but I'd rather use Ctor injection, as it looks better in my opinion. 我知道我可以将UnitOfWork - 方法注入传递给DataService方法,但我宁愿使用Ctor注入,因为在我看来它看起来更好。

What I want is to seperate this - A manager that it's job is to instantiate unit of works and commit them if needed, and another class (DataService) that actually executes the logic. 我想要的是分离这个 - 一个管理器,它的工作是实例化工作单元并在需要时提交它们,以及另一个实际执行逻辑的类(DataService)。

Regardless, I would like to hear your opinion about this implementation if you have any comments / ideas for improvement. 无论如何,如果您有任何改进意见/想法,我想听听您对此实施的意见。

Thanks for your time! 谢谢你的时间!

EDIT: This is what I ended up with: 编辑:这是我最终得到的:

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..
    }
}

You don't want singleton DbContext in a singleton instance. 您不希望在单例实例中使用单例DbContext This is ok, it can be done with factory. 这没关系,可以在工厂完成。 Additionally you want to share this DbContext . 此外,您想要共享此DbContext This is ok also, you can resolve and return DbContext with related life time on the factory. 这也没关系,你可以解决并返回DbContext与工厂的相关生命周期。 The problem is ; 问题是 ; you want share non-singleton DbContext in a single instance without managing lifetime (Tcp/Ip request). 您希望在单个实例中共享非单例DbContext而不管理生命周期(Tcp / Ip请求)。

What's the reason of ProductService and ProductManager are singleton ? ProductServiceProductManager是单身的原因是什么? I suggest yo to use ProductService and ProductManager per lifetimescope. 我建议你在每个lifetimescope上使用ProductServiceProductManager When you have http request it's fine. 当你有http请求它没关系。 When you have tcp/ip request you can begin new lifetime scope(as top level as you can) then resolve ProductManager there. 当您有tcp / ip请求时,您可以开始新的生命周期范围(尽可能为顶层),然后在那里解析ProductManager

Update : Code for solution 1 which I mentioned on comments. 更新 :我在评论中提到的解决方案1的代码。

Managers have to be singleton (as you told). Managers必须是单身人士(正如你所说)。

Other than managers you should register dbcontext , services , repositories and Uow as per lifetime scope. 除了managers您应该per lifetime范围注册dbcontextservicesrepositoriesUow

We could initilaize like this: 我们可以像这样初始化:

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

But This is kind of service locator. 但这是一种服务定位器。 We hide Uow and ProductDataService dependencies. 我们隐藏了UowProductDataService依赖项。

So we should implement a provider: 所以我们应该实现一个提供者:

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

And we register it as per dependency (Because we will use it with factory). 我们per dependency注册它(因为我们将它与工厂一起使用)。

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

Your ProductsManager class should be like this. 您的ProductsManager类应该是这样的。 (Now we don't hide any dependecies). (现在我们不隐藏任何家属)。

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

I have tested with my own classes. 我已经用自己的课程进行了测试。

You have singleton manager instance which has a factory to create manager provider. 你有一个单独的经理实例,它有一个工厂来创建经理提供者。 Manager Providers are per dependency because every time we should get new instance in singleton. 管理器提供程序是依赖的,因为每次我们都应该在单例中获取新实例。 Everything in providers per lifetime so their lifetime are connected providers per dependency lifetime. 每个生命周期中提供程序中的所有内容,因此它们的生命周期是依赖关

When you addproduct in manager Container creates 1 Provider ,1 DbContext , 1 DataService and 1 Uow ( DbContext is shared). 当您在管理器中添加产品时, Container会创建1个Provider ,1个DbContext ,1个DataService和1个Uow (共享DbContext )。 Provider is disposed (per dependency) with all realeted instances (DbContex,Uow,DataService) after returns on method in Manager . Manager返回方法之后, Provider (根据依赖性)处理所有已重新发布的实例(DbContex,Uow,DataService)。

I agree with Bruno Garcia's advice about the problem with your code. 我同意Bruno Garcia关于代码问题的建议。 However, I see a few other problems with it. 但是,我看到了其他一些问题。

I will start by saying I haven't used the Unit Of Work pattern explicitly like you have, but I do understand what you're going for. 我将首先说明我没有像你一样明确地使用工作单元模式,但我确实理解你的目标。

The problem that Bruno didn't get into is the fact that you have poor separation of concerns. 布鲁诺没有涉及的问题是你的关注点分离不佳。 He hinted at that a bit, and I'll explain more: Your controller has two separate competing objects in it, both trying to utilize the same resource (DbContext). 他稍微暗示了一点,我将解释更多:你的控制器中有两个独立的竞争对象,两者都试图利用相同的资源(DbContext)。 As he said, what you're looking to do is to have just a single DbContext for each request. 正如他所说,你要做的是为每个请求只有一个DbContext。 There's a problem with that, though: There is nothing stopping a Controller from trying to continue to utilize the ProductsRepository after the UnitOfWork is disposed of. 但是有一个问题:在处理UnitOfWork之后,没有什么能阻止Controller尝试继续使用ProductsRepository。 If you do, the connection to the database has already been disposed of. 如果这样做,则已经处理了与数据库的连接。

Because you have two objects that need to use the same resource, you should rearchitect it where one object encapsulates the other. 因为您有两个需要使用相同资源的对象,所以应该将其重新架构在一个对象封装另一个对象的位置。 This also gives the added benefit of hiding from the Controller any concerns at all about Data Propagation. 这也带来了额外的好处,即从控制器中隐藏任何关于数据传播的问题。

All the Controller should know about is your Service object, which should contain all of the business logic as well as gateways to the Repository and its Unit of Work, while keeping it invisible from the Service's consumer. 所有Controller应该知道的是您的Service对象,它应该包含所有业务逻辑以及存储库及其工作单元的网关,同时保持对服务的消费者不可见。 This way, the Controller only has a single object to worry about handling and disposing of. 这样,Controller只有一个对象担心处理和处理。

One of the other ways you could get around this would be to have the ProductsRepository derive from the UnitOfWork so that you don't have to worry about any duplicated code. 您可以解决此问题的另一种方法是让ProductsRepository派生自UnitOfWork,这样您就不必担心任何重复的代码。

Then, inside your AddProduct method, you would call _context.SaveChanges() before returning that object back up along the pipeline to your Controller. 然后,在AddProduct方法中,您可以在将该对象沿管道返回到Controller之前调用_context.SaveChanges()

UPDATE (Braces are styled for compactness) 更新 (大括号的样式是紧凑的)

Here is the layout of what you'd want to do: 以下是您要做的事情的布局:

UnitOfWork is your bottom most layer that includes the connection to the database. UnitOfWork是您最底层的,包含与数据库的连接。 However, make this abstract as you don't want to allow a concrete implementation of it. 但是,请将此abstract视为abstract因为您不希望允许其具体实现。 You no longer need the interface, as what you do within your Commit method should never be exposed, and the saving of the object should be done within the methods. 您不再需要该接口,因为您在Commit方法中的操作永远不会暴露,并且应该在方法中保存对象。 I'll show how down the line. 我将展示如何下线。

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

Your repository is the next layer up. 您的存储库是下一层。 Derive that from UnitOfWork so that it inherits all of the behavior, and will be the same for each of the specific types. 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;
    }
}

With that in place, all you care about now is just having your ProductsRepository. 有了这个,你现在关心的只是拥有你的ProductsRepository。 In your DataService layer, utilize Dependency Injection and just pass the ProductsRepository itself. 在DataService层中,使用依赖注入并只传递ProductsRepository本身。 If you are really set on using the factory, then pass the factory, but have your member variable still be the IProductsRepository . 如果你真的开始使用工厂,那么通过工厂,但你的成员变量仍然是IProductsRepository Don't make each method have to figure that out. 不要让每个方法都弄明白。

Don't forget to have all of your interfaces derive from IDisposable 不要忘记让所有接口都来自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();
    }
}

If you'd dead set on using the ProductsManager , you can, but it is just another layer that is no longer providing much benefit. 如果你已经开始使用ProductsManager ,你可以,但它只是另一层不再提供很多好处。 Same deal would go with that class. 同一笔交易将与该课程相同。

I'll finish with your Controller as I would have it. 我将完成你的控制器,就像我想的那样。

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

It seems the issue is not really making sure the DbContext instance injected into UnitOfWork and ProductsRepository is the same. 似乎问题并不是确保注入UnitOfWorkProductsRepository的DbContext实例是相同的。

This could be achieved by registering DbContext as InstancePerLifetimeScope and creating a new LifetimeScope before resolving IUnitOfWork and ProductsRepository . 这可以通过将DbContext注册为InstancePerLifetimeScope并在解析IUnitOfWorkProductsRepository之前创建新的LifetimeScope来实现。 Any dependency not owned by you would be disposed at the time of disposal of the LifetimeScope . 任何非您拥有的依赖关系将在处置LifetimeScope时处理。

The problem seems to be that you don't have an explicit relationship between those two classes. 问题似乎是这两个类之间没有明确的关系。 Your UoW doesn't depend on 'any DbContext', it depends on whatever DbContext is involved in the current transaction. 您的UoW不依赖于“任何DbContext”,它取决于当前事务中涉及的DbContext。 That specific one. 那个具体的。

There's no direct relationship between your UoW and the Repositories either. 您的UoW和存储库之间也没有直接关系。 That doesn't look like a UoW pattern . 这看起来不像UoW模式

I couldn't figure out who's going to Dispose the IRepository created by your IRepositoryFactory . 我无法弄清楚谁将要处理由IRepository创建的IRepositoryFactory You are using the container to resolve it (via the ILifetimeScope you got injected into RepositoriesFactory ). 您正在使用容器来解决它(通过注入RepositoriesFactoryILifetimeScope )。 Unless whoever gets that instance from the Factory disposes it, it would only be disposed by disposing the LifetimeScope injected into IRepositoryFactory . 除非从Factory获得该实例的任何人处置它,否则只能通过处理注入IRepositoryFactoryLifetimeScope来处理它。

Another problem that would arise is ownership of DbContext. 可能出现的另一个问题是DbContext的所有权。 You'd be able to Dispose it on that using block via Dispose on your IUnitOfWork . 您可以通过IUnitOfWork上的Dispose using块来处理它。 But your UnitOfWork doesn't own that instance either. 但是您的UnitOfWork也不拥有该实例。 The container does. 容器呢。 Would the Repositories also try disposing the DbContext? 存储库是否也会尝试处理DbContext? They also received via constructor injection. 他们还通过构造函数注入接收。

I'd suggest rethinking this solution. 我建议重新考虑这个解决方案。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM