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


我用实体框架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..
    }
}

这是我觉得理想的实现,但是我不知道如何实现这一点,因为我的ProductsDataService是单例,因此它与工厂工厂单元创建的Owned范围无关。 有没有一种方法可以将要创建的存储库关联起来并将它们与为工作单元创建的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..
    }
}

14
2018-04-09 15:19


起源


你可以做的最简单的事情就是不做 ProductsDataService 单身人士将其注入为终身拥有。您是否有理由想要将此类型设为单身? - Igor
如何从单身人士改变它帮助我? - S. Peter
我不想依赖于工作单元中的数据服务,那么谁会依赖它呢? - S. Peter
编辑后,你的 DbContext 仍未分享。 - Erkan Demirel
“具有实体框架的工作单元”,实体框架本身实现了一个工作单元....(鼓)....它的DbContext。我建议你重新考虑一下你的实现,并问问自己为什么你真的需要做你想做的事情?阅读这篇精彩文章: stackoverflow.com/questions/26055497/... - Marcus Höglund


答案:


我同意Bruno Garcia关于代码问题的建议。但是,我看到了其他一些问题。

我将首先说明我没有像你一样明确地使用工作单元模式,但我确实理解你的目标。

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

因为您有两个需要使用相同资源的对象,所以应该将其重新架构在一个对象封装另一个对象的位置。这也带来了额外的好处,即从控制器中隐藏任何关于数据传播的问题。

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

您可以解决此问题的另一种方法是让ProductsRepository派生自UnitOfWork,这样您就不必担心任何重复的代码。

然后,在你的内心 AddProduct 方法,你会打电话 _context.SaveChanges() 在将该对象沿管道返回到Controller之前。

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,你可以,但它只是另一层不再提供很多好处。同一笔交易将与该课程相同。

我将完成你的控制器,因为我会拥有它。

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

4
2018-04-13 18:18



我的控制器只依赖于Managers,例如:ProductsController使用ProductsManager。每个请求的上下文将是理想的,但我无法管理,因为我有来自Tcp / IP的请求,而不仅是HTTP。我很想听听其他实现方式的想法。关于源自工作单元的存储库听起来很奇怪。将更改保存在执行逻辑的方法中 - 这不是一个坏习惯吗?而且,每个DataService可以使用多个存储库。您的意思是在DataService方法中调用保存更改吗?并使DataService继承工作单元? - S. Peter
此外,代码总是很好! - S. Peter
将telegramsManager更改为单例将导致大量更改。但是DataService真的没有理由成为单例。多亏了你,我有了一个想法,让每个DataService都来自我的工作单元。 Repositotries工厂被注入每个数据服务构造函数。经理有Func <Owned <IProductsDataService >>。它很棒,这是你的解决方案吗? - S. Peter
为您添加了该代码。抱歉耽搁了!如果您想要更深入的建议,我建议 Adam Freeman的MVC系列。 - krillgar
@ S.Peter如果你想奖励赏金,你需要手动完成。截至目前,它只会奖励一半。但如果这对你有所帮助,我们将不胜感激。无论如何,你仍然失去了你的Rep的全部金额。 - krillgar


你不想要单身人士 DbContext 在单例实例中。这没关系,可以在工厂完成。此外,您想要分享这个 DbContext。这也没关系,你可以解决并返回 DbContext 与相关的工厂寿命。 问题是;你想要分享非单身人士 DbContext 在没有管理生命周期的单个实例中(Tcp / Ip请求)。

是什么原因 ProductService 和 ProductManager 单身? 我建议你使用 ProductService 和 ProductManager 每个生命时间镜。当你有http请求它没关系。当你有tcp / ip请求时,你可以开始新的生命周期范围(尽可能达到最高级别),然后解决 ProductManager 那里。

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

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

以外 managers 你应该注册 dbcontextservicesrepositories 和 Uow 如 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;
        }
    }

但这是一种服务定位器。我们藏起来 Uow 和 ProductDataService 依赖。

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

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

你的 ProductsManager 上课应该是这样的。 (现在我们不隐藏任何家属)。

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

我已经用我自己的课程进行了测试。

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

当您在经理中添加产品时 Container 创造1 Provider,1 DbContext,1 DataService 和1 Uow (DbContext 是共享的)。 Provider 在返回方法后,将所有已重新发布的实例(DbContex,Uow,DataService)置于(依赖于) Manager


4
2018-04-14 10:53



获取Tcp / Ip请求时如何开始新的生命周期范围? - S. Peter
当您收到Tcp / Ip请求时,如何调用产品经理? - Erkan Demirel
它很复杂,我们有一个包含所有经理的课程,我们开发了一个“广播”机制。每个经理都可以广播消息,经验管理者将收到该消息,而管理人员无需了解其他经理 - S. Peter
所以当你在ProductsController或任何其他控制器上有http请求时。你想只是保存或从数据库读取,或者你想要广播?简而言之:您使用控制器进行广播吗? - Erkan Demirel
每个控制器直接使用拟合管理器,因此不进行广播 - S. Peter


似乎问题并没有真正确保注入DbContext实例 UnitOfWork 和 ProductsRepository 是一样的。

这可以通过将DbContext注册为 InstancePerLifetimeScope 并创造一个新的 LifetimeScope 在解决之前 IUnitOfWork 和 ProductsRepository。 任何非您拥有的抚养权将在处置时被处置 LifetimeScope

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

您的UoW和存储库之间也没有直接关系。这看起来不像 UoW模式

我无法弄清楚谁会去处理 IRepository 由你创建 IRepositoryFactory。您正在使用容器来解决它(通过 ILifetimeScope 你被注入了 RepositoriesFactory)。除非有人从中得到那个实例 Factory 处理它,它只会通过处置它来处理 LifetimeScope 注入 IRepositoryFactory

可能出现的另一个问题是DbContext的所有权。你可以把它处理掉 using 阻止你的处理 IUnitOfWork。但是你的 UnitOfWork 也不拥有该实例。容器呢。存储库是否也会尝试处理DbContext?他们还通过构造函数注入接收。

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


2
2018-04-13 15:50



首先非常感谢你的回复。我忘记复制的一件事是处理工作单元。当处理创建的范围时,工作单元处理上下文。存储库不需要特殊处理(如果我没错),所以我让垃圾收集器接受它们。您如何建议我应该处理DataService和工作单元之间的关系?目前,DataService中的每个方法都采用了适合的工作单元,我讨厌并且肯定有一个比我还没有找到的更好的解决方案。 - S. Peter