使用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...
}

CONFIGURATIONSTRZIC表,INCREMENTAL_TABLEINIC表。

我使用的是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。

任何建议吗?

使用genericrepository绑定命名对象

让我们从基础开始。

据我所知,命名绑定只能与代码中属性的常量值一起工作,比如[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

我不知道这是否正确。