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,因为您对同一抽象有两个不兼容的实现。 PurusLotteryContext
和 PurusLotteryBackOfficeContext
都实现了 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());
请注意有关此注册的一些事项:
- 数据上下文实现被注册为分层的,因为您通常需要 Entity Framework DbContext to have a 'per request' lifestyle.
- 与使用容器的自动装配功能相比,数据上下文实现是使用工厂注册的。这是因为自动装配对这些 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
希望对您有所帮助
我在这里使用 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,因为您对同一抽象有两个不兼容的实现。 PurusLotteryContext
和 PurusLotteryBackOfficeContext
都实现了 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());
请注意有关此注册的一些事项:
- 数据上下文实现被注册为分层的,因为您通常需要 Entity Framework DbContext to have a 'per request' lifestyle.
- 与使用容器的自动装配功能相比,数据上下文实现是使用工厂注册的。这是因为自动装配对这些 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
希望对您有所帮助