我正在使用C#,.NET Framework 4.0,Entity Framework Code First 6.0和Ninject开发ASP.NET MVC 4 Web Api。
我有两个不同的DbContext
自定义实现来连接两个不同的数据库。
这是我的NinjectConfigurator
课程(部分):
private void AddBindings(IKernel container)
{
container.Bind<IUnitOfWork>().
To<TRZICDbContext>().InRequestScope().Named("TRZIC");
container.Bind<IUnitOfWork>().
To<INICDbContext>().InRequestScope().Named("INIC");
container.Bind<IGenericRepository<CONFIGURATIONS>>().
To<GenericRepository<CONFIGURATIONS>>();
container.Bind<IGenericRepository<INCREMENTAL_TABLE>>().
To<GenericRepository<INCREMENTAL_TABLE>>();
// More implementation...
}
CONFIGURATIONS
是TRZIC
表,INCREMENTAL_TABLE
是INIC
表。
我正在使用IGenericRepository
,在这里我遇到了问题:
public class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
protected DbSet<TEntity> DbSet;
private readonly DbContext dbContext;
public GenericRepository(IUnitOfWork unitOfWork)
{
dbContext = (DbContext)unitOfWork;
DbSet = dbContext.Set<TEntity>();
}
// Hidden implementation..
}
我不知道如何使用[Named("TRZIC")]
public GenericRepository(IUnitOfWork unitOfWork)
或者我需要在其他地方使用它。
此处IUnitOfWork
实施取决于TEntity。
有什么建议吗?
答案 0 :(得分:2)
让我们从基础开始。
据我所知,命名绑定仅适用于代码中的常量值,例如[Named("foo")]
属性,或者使用“{1}}之类的”服务位置“。这两种情况都不适用于您的场景,因此命名绑定是不可能的。
这使您得到条件绑定(IResolutionRoot.Get<T>(string name)
方法)。
你有2个数据库,每个数据库都有n个实体。
2数据库意味着两个配置意味着2个不同的.When(...)
配置。
但是,“用户”不是请求特定数据库,而是请求特定实体。
因此,您需要一张地图IUnitOfWork
(字典)。我认为没有办法解决这个问题,但你可能会设计出某种惯例。按惯例实现它,因此您不必键入和维护大量代码。
解决方案1:entity-->database
具有开箱即用的ninject功能,以及大量的手工劳动:
.WhenInjectedInto<>
你得到漂移,对吧?
解决方案2.1:自定义Bind<IUnitOfWork>().To<UnitOfWorkOfDatabaseA>()
.WhenInjectedInto<IRepository<SomeEntityOfDatabaseA>>();
Bind<IUnitOfWork>().To<UnitOfWorkOfDatabaseA>()
.WhenInjectedInto<IRepository<SomeOtherEntityOfDatabaseA>>();
Bind<IUnitOfWork>().To<UnitOfWorkOfDatabaseB>()
.WhenInjectedInto<IRepository<SomeEntityOfDatabaseB>>();
实施
不再需要手工劳动和维护了。 让我把代码转储给你,见下文:
公共接口IRepository { IUnitOfWork UnitOfWork {get; } }
When(..)
解决方案2.2基于public class Repository<TEntity> : IRepository<TEntity>
{
public IUnitOfWork UnitOfWork { get; set; }
public Repository(IUnitOfWork unitOfWork)
{
UnitOfWork = unitOfWork;
}
}
public interface IUnitOfWork { }
class UnitOfWorkA : IUnitOfWork { }
class UnitOfWorkB : IUnitOfWork { }
public class Test
{
[Fact]
public void asdf()
{
var kernel = new StandardKernel();
kernel.Bind(typeof (IRepository<>)).To(typeof (Repository<>));
kernel.Bind<IUnitOfWork>().To<UnitOfWorkA>()
.When(request => IsRepositoryFor(request, new[] { typeof(string), typeof(bool) })); // these are strange entity types, i know ;-)
kernel.Bind<IUnitOfWork>().To<UnitOfWorkB>()
.When(request => IsRepositoryFor(request, new[] { typeof(int), typeof(double) }));
// assert
kernel.Get<IRepository<string>>()
.UnitOfWork.Should().BeOfType<UnitOfWorkA>();
kernel.Get<IRepository<double>>()
.UnitOfWork.Should().BeOfType<UnitOfWorkB>();
}
private bool IsRepositoryFor(IRequest request, IEnumerable<Type> entities)
{
if (request.ParentRequest != null)
{
Type injectInto = request.ParentRequest.Service;
if (injectInto.IsGenericType && injectInto.GetGenericTypeDefinition() == typeof (IRepository<>))
{
Type entityType = injectInto.GetGenericArguments().Single();
return entities.Contains(entityType);
}
}
return false;
}
}
的自定义约定
让我们介绍一个小会议。数据库TRZIC的实体名称以When(...)
开头,例如TRZIC
。数据库INIC的实体名称以TRZIC_Foo
开头,与INIC
类似。我们现在可以调整以前的解决方案:
INIC_Bar
这样我们就不需要显式映射public class Test
{
[Fact]
public void asdf()
{
var kernel = new StandardKernel();
kernel.Bind(typeof (IRepository<>)).To(typeof (Repository<>));
kernel.Bind<IUnitOfWork>().To<UnitOfWorkA>()
.When(request => IsRepositoryFor(request, "TRZIC")); // these are strange entity types, i know ;-)
kernel.Bind<IUnitOfWork>().To<UnitOfWorkB>()
.When(request => IsRepositoryFor(request, "INIC"));
// assert
kernel.Get<IRepository<TRZIC_Foo>>()
.UnitOfWork.Should().BeOfType<UnitOfWorkA>();
kernel.Get<IRepository<INIC_Bar>>()
.UnitOfWork.Should().BeOfType<UnitOfWorkB>();
}
private bool IsRepositoryFor(IRequest request, string entityNameStartsWith)
{
if (request.ParentRequest != null)
{
Type injectInto = request.ParentRequest.Service;
if (injectInto.IsGenericType && injectInto.GetGenericTypeDefinition() == typeof (IRepository<>))
{
Type entityType = injectInto.GetGenericArguments().Single();
return entityType.Name.StartsWith(entityNameStartsWith, StringComparison.OrdinalIgnoreCase);
}
}
return false;
}
}
,(EntityA, EntityB, EntityC) => DatabaseA
。
答案 1 :(得分:0)
如果您说IUnitOfWork
取决于TEntity
,为什么不让IUnitOfWork
通用?
public class TRZIC {}
public class INIC {}
public interface IUnitOfWork<TEntity> {}
public class TRZICDbContext : DbContext, IUnitOfWork<TRZIC> {}
public class INICDbContext : DbContext, IUnitOfWork<INIC> {}
public interface IGenericRepository<TEntity> {}
public class GenericRepository<TEntity> : IGenericRepository<TEntity>
where TEntity : class
{
public GenericRepository(IUnitOfWork<TEntity> unitOfWork)
{
var dbContext = (DbContext) unitOfWork;
}
}
private static void AddBindings(IKernel container)
{
container
.Bind<IUnitOfWork<TRZIC>>()
.To<TRZICDbContext>();
container
.Bind<IUnitOfWork<INIC>>()
.To<INICDbContext>();
container
.Bind<IGenericRepository<TRZIC>>()
.To<GenericRepository<TRZIC>>();
container
.Bind<IGenericRepository<INIC>>()
.To<GenericRepository<INIC>>();
}
答案 2 :(得分:0)
另一种利用代码可读性的解决方案:
public interface IUnitOfWork {}
// both named A and B
public class UnitOfWorkA : IUnitOfWork {}
public class UnitOfWorkB : IUnitOfWork {}
public abstract class GenericRepository<TEntity> : IGenericRepository<TEntity> where TEntity : class
{
protected DbSet<TEntity> DbSet;
private readonly DbContext dbContext;
public GenericRepository(IUnitOfWork unitOfWork)
{
dbContext = (DbContext)unitOfWork;
DbSet = dbContext.Set<TEntity>();
}
// other IGenericRepository methods
}
public class GenericRepositoryForA<TEntity> : GenericRepository<TEntity>
{
public GenericRepositoryForA([Named("A")]IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
}
public class GenericRepositoryForB<TEntity> : GenericRepository<TEntity>
{
public GenericRepositoryForB([Named("B")]IUnitOfWork unitOfWork)
: base(unitOfWork)
{
}
}
这允许您将特定数据库上下文作为依赖项请求,或者在需要时获取它们。而且您只需要实施GenericRepository
一次。
它极大地即兴创建了代码可见性,因为您实际上通过查看变量类型/名称来了解您正在使用的数据库上下文,而不是在没有任何关于其实际类型的视觉细节的情况下注入IUnitOfWork
。
我建议添加一些额外的接口,如果你想对它进行单元测试(你应该!)。
简单地添加
public interface IGenericRepositoryForA<TEntity> : IGenericRepository<TEntity>
并让GenericRepositoryForA<TEntity>
实现它。
答案 3 :(得分:0)
另一种解决方案:
private void AddBindings(IKernel container)
{
container.Bind<IUnitOfWork>().To<TRZICDbContext>().InRequestScope();
container.Bind<IGenericRepository<CONFIGURATIONS>>().
To<GenericRepository<CONFIGURATIONS>>();
container.Bind<IGenericRepository<INCREMENTAL_TABLE>>().
To<GenericRepository<INCREMENTAL_TABLE>>().WithConstructorArgument("unitOfWork", new INICDbContext());
// More code..
}
我使用WithConstructorArgument
表示我想使用INICDbContext
。
我不知道这是否正确。