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

Unity Container - multiple databases with generic unity of work

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

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

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

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

我看到的一个错误是您在注册阶段解决了。这不仅危险(explained in the documentation of a different DI libary), in your case it causes the PurusLotteryBackOfficeContext to be used as constant and therefore injected as Singleton into the LotteryBackOfficeUnitOfWork. In other words, while this might seem to work during development, this will not work, because a DbContext can't be used as singleton.

相反,您应该尽可能多地使用 Unity 的自动装配功能,否则 DI 库与构建对象图相比没有优势(只有劣势)by hand

除此之外,您在设计中违反了 Liskov Substitution Principle (LSP),这会给您的 DI 配置带来麻烦。您违反了 LSP,因为您对同一抽象有两个不兼容的实现。 PurusLotteryContextPurusLotteryBackOfficeContext 都实现了 IDataContextAsync,但它们是不兼容的,因为它们不能互换,因为它们都在完全不同的数据库模式上工作。尽管它们可能看起来共享相同的接口,但它们并不共享相同的契约。看看当您将 PurusLotteryContext 注入需要与后台一起工作的 class 时会发生什么。应用程序将中断,这意味着您违反了 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. 数据上下文实现被注册为分层​​的,因为您通常需要 Entity Framework DbContext to have a 'per request' lifestyle.
  2. 与使用容器的自动装配功能相比,数据上下文实现是使用工厂注册的。这是因为自动装配对这些 classes 没有帮助,工厂委托不仅会使注册更简单,而且类型更安全(如果我们出错,编译器会帮助我们)。

我知道我的回答来晚了,但我想在这里指出一个不同的方向而不是@Steve 的建议。

命名注册呢?使用名称为不同的实现注册相同的接口。理解你有一个合同和不同的实现,这没有错。

如果您想在注册这两个实现时保持相同的接口,您可以使用不同的名称来实现,请查看下面的答案

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

另一个兴趣点是 UnitOfWork。

When you're using EntityFramework and you instantiate your DbContext - you're creating a new UnitOfWork.With EntityFramework you can "flush and reset" the UnitofWork by using SaveChanges(), you don't need to SaveChanges just to return the new ID - EF does it in the scope of the transaction already!

这是一篇您可以阅读的好文章。

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

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

希望对您有所帮助