使用genericrepository绑定命名对象
本文关键字:对象 绑定 genericrepository 使用 | 更新日期: 2023-09-27 18:14:53
我正在开发一个ASP。.NET MVC 4 Web Api,使用c#, .NET Framework 4.0, Entity Framework Code First 6.0和Ninject。
我有两个不同的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。
任何建议吗?
让我们从基础开始。
据我所知,命名绑定只能与代码中属性的常量值一起工作,比如[Named("foo")]
属性,或者通过使用"服务位置",比如IResolutionRoot.Get<T>(string name)
。这两种方法都不适用于您的场景,因此不可能使用命名绑定。这就剩下了条件绑定(.When(...)
方法)。
你有2个数据库,每个有n个实体。2数据库两种配置即2种不同的IUnitOfWork
配置。但是,"用户"请求的不是特定的数据库,而是特定的实体。因此,您需要一个地图entity-->database
(字典)。我认为没有办法绕过这一点,但你可以设计出某种惯例。按照约定实现它,这样你就不必输入和维护大量的代码。
方案一:.WhenInjectedInto<>
具有开箱即用的对象功能,以及大量的体力劳动:
Bind<IUnitOfWork>().To<UnitOfWorkOfDatabaseA>()
.WhenInjectedInto<IRepository<SomeEntityOfDatabaseA>>();
Bind<IUnitOfWork>().To<UnitOfWorkOfDatabaseA>()
.WhenInjectedInto<IRepository<SomeOtherEntityOfDatabaseA>>();
Bind<IUnitOfWork>().To<UnitOfWorkOfDatabaseB>()
.WhenInjectedInto<IRepository<SomeEntityOfDatabaseB>>();
你懂的,…对吧?
解决方案2.1:自定义
When(..)
实现
不再有那么多的体力劳动和维护了。让我把代码转储给你,如下所示:
公共接口IRepository{UnitOfWork {get;}}
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;
}
}
解决方案2.2基于
When(...)
的自定义约定
让我们介绍一个小约定。数据库TRZIC的实体名称以TRZIC
开头,如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
, (EntityD, EntityE, EntityF) => DatabaseB)
。
如果你说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>>();
}
另一个利用代码可读性的解决方案:
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>
也实现它
另一个解决方案:
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
。
我不知道这是否正确。