将不同的 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>>();
(只是概念验证,代码未经测试)
Autofac 不够智能,无法知道"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);
}
}
在我的应用程序中,我需要与两个数据库交互。我有两个域 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>>();
(只是概念验证,代码未经测试)
Autofac 不够智能,无法知道"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);
}
}