将不同的 DbContexts 注入基于域 class 的通用存储库 - Autofac

Inject different DbContexts into generic repository based on Domain class - Autofac

在我的应用程序中,我需要与两个数据库交互。我有两个域 classes,它们位于两个不同的数据库中。我还有一个通用存储库模式,它在其构造函数中接受 UoW。我正在寻找一种基于域 class 注入适当 UoW 的方法。 我不想为第二个数据库编写第二个通用存储库。。有什么好的解决办法吗?

public interface IEntity
{
    int Id { get; set; }
}

位于数据库 A

public class Team: IEntity
{
    public int Id { get; set; }
    public string Name{ get; set; }

}

位于数据库 B

public class Player: IEntity
{
    public int Id { get; set; }
    public string FullName { get; set; }
}

我也有 UoW 的通用存储库模式

public interface IUnitOfWork
{
    IList<IEntity> Set<T>();
    void SaveChanges();
}

public class DbADbContext : IUnitOfWork
{
    public IList<IEntity> Set<T>()
    {
        return new IEntity[] { new User() { Id = 10, FullName = "Eric Cantona" } };
    }

    public void SaveChanges()
    {

    }
}

public class DbBDataContext: IUnitOfWork
{
    public IList<IEntity> Set<T>()
    {
        return new IEntity[] { new Tender() { Id = 1, Title = "Manchester United" } };
    }

    public void SaveChanges()
    {

    }

public interface IRepository<TEntity> where TEntity: class, IEntity
{
    IList<IEntity> Table();
}

public class BaseRepository<TEntity> : IRepository<TEntity> where TEntity : class, IEntity
{

    protected readonly IUnitOfWork Context;
    public BaseRepository(IUnitOfWork context)
    {
        Context = context;
    }

    IList<IEntity> IRepository<TEntity>.Table()
    {
        return Context.Set<TEntity>();
    }
}

我已经找到文章说 Autofac 会用最后一个值覆盖注册。我知道我的问题是如何注册 DbContexts。

 var builder = new ContainerBuilder();
 // problem is here
        builder.RegisterType<DbADbContext >().As<IUnitOfWork>()
        builder.RegisterType<DbBDbContext >().As<IUnitOfWork>()

 builder.RegisterGeneric(typeof(BaseRepository<>)).As(typeof(IRepository<>));
        var container = builder.Build();

这个怎么样:

builder.RegisterType<DbContextBase>().As<IUnitOfWork>()

    DbADataContext: DbContextBase,IUnitOfWork
    DbBDataContext: DbContextBase,IUnitOfWork

或者在您的注册中,您可以做类似的事情:

containerBuilder.RegisterGeneric(typeof(DbADataContext<>)).Named("DbADataContext", typeof(IUnitOfWork<>));
containerBuilder.RegisterGeneric(typeof(DbBDataContext<>)).Named("DbBDataContext", typeof(IUnitOfWork<>));

如果你想保持单一 BaseRepository 及其接口,你必须以某种方式配置,实体将由哪个 DbContext 处理。它可以在应用程序的注册部分完成,但在那种情况下你不能将你的 BaseRepostory<T> 注册为开放通用,但在你的注册中要明确,像这样:

containerBuilder.RegisterType<DbADataContext>().Named<IUnitOfWork>("A");
containerBuilder.RegisterType<DbBDataContext>().Named<IUnitOfWork>("B");

containerBuilder.Register(c => new BaseRepository<Team>(c.ResolveNamed<IUnitOfWork>("A")).As<IRepostory<Team>>();
containerBuilder.Register(c => new BaseRepository<Player>(c.ResolveNamed<IUnitOfWork>("B")).As<IRepository<Player>>();

(只是概念验证,代码未经测试)

A​​utofac 不够智能,无法知道"automatically"您想在每个存储库中使用哪个工作单元。

我的灵感来自于@tdragon 的回答。

第一步是注册Named DbContext

        builder.RegisterType<Database1>()
            .Keyed<IUnitOfWork>(DbName.Db1)
            .Keyed<DbContext>(DbName.Db1).AsSelf().InstancePerRequest();

        builder.RegisterType<Database2>()
            .Keyed<IUnitOfWork>(DbName.Db2)
            .Keyed<DbContext>(DbName.Db2).AsSelf().InstancePerRequest();

请注意 DbName 只是一个枚举。

以下代码扫描数据访问层程序集以查找域 类。然后,它注册 ReadOnlyRepository 和 BaseRepository。此代码的位置在 DIConfig

Type entityType = typeof(IEntity);
var entityTypes =   Assembly.GetAssembly(typeof(IEntity))
                    .DefinedTypes.Where(t => t.ImplementedInterfaces.Contains(entityType));


var baseRepoType = typeof(BaseRepository<>);
var readOnlyRepoType = typeof(ReadOnlyRepository<>);
var baseRepoInterfaceType = typeof(IRepository<>);
var readOnlyRepoInterfaceType = typeof(IReadOnlyRepository<>);
var dbContextResolver = typeof(DbContextResolverHelper).GetMethod("ResolveDbContext");

foreach (var domainType in entityTypes)
{
  var baseRepositoryMaker = baseRepoType.MakeGenericType(domainType);
  var readonlyRepositoryMarker = readOnlyRepoType.MakeGenericType(domainType);

 var registerAsForBaseRepositoryTypes = baseRepoInterfaceType.MakeGenericType(domainType);
 var registerAsForReadOnlyRepositoryTypes = readOnlyRepoInterfaceType.MakeGenericType(domainType);

 var dbResolver = dbContextResolver.MakeGenericMethod(domainType);
            // register BaseRepository
 builder.Register(c => Activator.CreateInstance(baseRepositoryMaker, dbResolver.Invoke(null, new object[] { c }))
            ).As(registerAsForBaseRepositoryTypes).InstancePerRequest(jobTag);
            //register readonly repositories
 builder.Register(c => Activator.CreateInstance(readonlyRepositoryMarker, dbResolver.Invoke(null, new object[] { c })))
           .As(registerAsForReadOnlyRepositoryTypes).InstancePerRequest(jobTag);

}

以下方法尝试在每个 DbContext 中查找 DbSet,以找出 DataContext/Database 所属的域 类。

public class DbContextResolverHelper
{
    private static readonly ConcurrentDictionary<Type, DbName> TypeDictionary = new ConcurrentDictionary<Type, DbName>();


    public static DbContext ResolveDbContext<TEntity>(IComponentContext c) where TEntity : class, IEntity
    {
        var type = typeof(DbSet<TEntity>);


        var dbName = TypeDictionary.GetOrAdd(type, t =>
        {

            var typeOfDatabase1 = typeof(Database1);
            var entityInDatabase1 = typeOfDatabase1 .GetProperties().FirstOrDefault(p => p.PropertyType == type);
            return entityInDatabase1 != null ? DbName.Db1: DbName.Db2;


        });

        return c.ResolveKeyed<DbContext>(dbName);
    }
}