Unity容器 - 具有通用统一工作的多个数据库

时间:2015-11-29 10:24:37

标签: c# dependency-injection solid-principles

我在这里使用EF6的通用统一工作: https://genericunitofworkandrepositories.codeplex.com/

我有一个使用两个数据库的应用程序。我已经创建了额外的UnitOfWork接口和实现原始工作单元接口的类:

namespace Repository.Pattern.UnitOfWork
{
    public interface ILotteryBackOfficeUnitOfWorkAsync : IUnitOfWorkAsync
    {
    }
}

第二个数据库初始化的第二工作单元类型:

namespace Repository.Pattern.Ef6
{
    public class LotteryBackOfficeUnitOfWork : UnitOfWork, ILotteryBackOfficeUnitOfWorkAsync
    {
        public LotteryBackOfficeUnitOfWork(IDataContextAsync dataContext)
            : base(dataContext)
        { }
    }
}

在统一中,我为不同的数据上下文注册了两个工作单元:

public static void RegisterTypes(IUnityContainer container)
{
    // NOTE: To load from web.config uncomment the line below. Make sure to add a Microsoft.Practices.Unity.Configuration to the using statements.
    // container.LoadConfiguration();

    // TODO: Register your types here
    // container.RegisterType<IProductRepository, ProductRepository>();

    var purusLotteryConnectionString = WebConfigurationManager.ConnectionStrings["PurusLotteryContext"].ConnectionString;
    var purusLotteryBackOfficeConnectionString = WebConfigurationManager.ConnectionStrings["PurusLotteryBackOfficeContext"].ConnectionString;

    container.RegisterType<IDataContextAsync, PurusLotteryContext>(new InjectionConstructor(purusLotteryConnectionString));
    container.RegisterType<IUnitOfWorkAsync, UnitOfWork>(new HierarchicalLifetimeManager());

    container.RegisterType<IDataContextAsync, PurusLotteryBackOfficeContext>("LotteryBackOfficeContext", new InjectionConstructor(purusLotteryBackOfficeConnectionString));
    container.RegisterType<ILotteryBackOfficeUnitOfWorkAsync, LotteryBackOfficeUnitOfWork>(new HierarchicalLifetimeManager(),
        new InjectionConstructor(container.Resolve<IDataContextAsync>("LotteryBackOfficeContext")));

    container.RegisterType<IHomeService, HomeService>();
}

它有效,但这是正确的程序吗?

2 个答案:

答案 0 :(得分:3)

我看到的一个错误是您在注册阶段解决了。这不仅是危险的(在不同的DI库的文档中也是explained),在你的情况下它导致PurusLotteryBackOfficeContext被用作常量,因此被注入为 Singleton 进入LotteryBackOfficeUnitOfWork。换句话说,虽然这似乎在开发过程中起作用,但这不起作用,因为DbContext can't be used as singleton

相反,您应该尽可能多地使用Unity的自动布线功能,否则DI库在构建对象图by hand方面没有优势(而且只是缺点)。

除此之外,您违反了设计中的Liskov Substitution Principle(LSP),这会导致DI配置出现问题。您违反了LSP,因为您有两个相同抽象的不兼容实现。 PurusLotteryContextPurusLotteryBackOfficeContext都实现IDataContextAsync,但它们不兼容,因为它们不能互换,因为它们都在完全不同的数据库模式上工作。虽然它们似乎共享相同的界面,但它们不共享相同的合同。只需看看当您将PurusLotteryContext注入需要与后台一起工作的某个类时会发生什么。应用程序将中断,这意味着您违反了LSP。

解决方案是给他们自己独立的抽象。起初这可能看起来很奇怪,因为它们都有相同的方法。但请记住,界面不仅仅是一组方法签名;接口描述了契约和行为,并且由于两个实现都在完全不同的数据库模式上工作,因此它们具有完全不同的契约。当您将其分开时,您的代码将如下所示:

public class PurusLotteryContext : IPurusLotteryDataContextAsync { 
    public PurusLotteryContext(string conString) : base(conString) { }
}

public class LotteryUnitOfWork : ILotteryUnitOfWorkAsync {
    public LotteryUnitOfWork(IPurusLotteryDataContextAsync dc) { }
}

public class PurusLotteryBackOfficeContext : IPurusLotteryBackOfficeDataContextAsync { 
    public PurusLotteryBackOfficeContext(string conString) : base(conString) { }
}

public class LotteryBackOfficeUnitOfWork : ILotteryBackOfficeUnitOfWorkAsync {
    public LotteryBackOfficeUnitOfWork(IPurusLotteryBackOfficeDataContextAsync dc) { }
}

这允许您进行以下注册:

container.Register<IPurusLotteryDataContextAsync>(new HierarchicalLifetimeManager(),
    new InjectionFactory(c => new PurusLotteryContext(purusLotteryConnectionString)));

container.Register<IPurusLotteryBackOfficeDataContextAsync>(
    new HierarchicalLifetimeManager(),
    new InjectionFactory(c => new PurusLotteryBackOfficeContext(
        purusLotteryBackOfficeConnectionString)));

container.RegisterType<ILotteryUnitOfWorkAsync, LotteryUnitOfWork>(
    new HierarchicalLifetimeManager());
container.RegisterType<ILotteryBackOfficeUnitOfWorkAsync, LotteryBackOfficeUnitOfWork>(
    new HierarchicalLifetimeManager());

请注意有关此注册的一些事项:

  1. 数据上下文实现注册为分层​​结构,因为您通常需要实体框架DbContext to have a 'per request' lifestyle
  2. 与使用容器的自动布线功能相比,使用工厂注册数据上下文实现。这是因为自动布线对这些类没有帮助,工厂委托不仅会使注册更简单,因为它也更加类型安全(如果我们发生错误,编译器会帮助我们)。

答案 1 :(得分:1)

我知道我的答案来得很晚,但我想指出一个不同的方向,而不是@Steve建议。

命名注册怎么样?使用名称注册具有不同实现的相同接口。理解你有合同和不同的实施,没有任何错误。

如果您想在注册两个实现时保持相同的界面,可以使用其他名称进行操作,请查看下面的答案

https://stackoverflow.com/a/18665983

现在,LSP的意图是派生类型必须完全可替代其基类型,并且在您的情况下不会创建具有相同功能和不同签名的新合同。我不同意@Steve的建议。 Bellow我附上一个很好的LSP例子

https://stackoverflow.com/a/584732/819153

另一个兴趣点是UnitOfWork。

  

当您使用EntityFramework并实例化DbContext时 -   你正在创建一个新的UnitOfWork.With EntityFramework你可以通过使用SaveChanges()“刷新并重置”UnitofWork,你不需要SaveChanges只返回新的ID - EF已经在事务范围内完成了!

这是一篇很好的文章,你可以阅读它。

http://rob.conery.io/2014/03/04/repositories-and-unitofwork-are-not-a-good-idea/

我建议直接注入DataContext而不是IUnitOfWork

希望这个帮助