使用多个 dbcontext 实例和依赖注入
Using multiple dbcontext instances and dependency injection
这与我几周前问过的一个类似问题 here 有一个显着的要求变化。
我有一个新的和独特的(我在我的 Whosebug 搜索中没有找到类似的东西)业务需求:
我创建了两个单独的 entity framework 6 DbContexts,它们指向两个结构不同的数据库,我们称它们为 PcMaster 和 PcSubs。虽然 PcMaster 是一个直接的数据库并且 PcMasterContext 将有一个静态连接字符串,但 PcSubs 数据库用作创建新数据库的模板。显然,由于这些复制的数据库将具有完全相同的结构,因此我们的想法是在实例化 dbcontext 时仅更改连接字符串中的数据库(目录)名称以指向不同的数据库。我还使用了存储库模式和依赖项注入(目前 Ninject,但正在考虑迁移到 Autofac)。
我还没有看到 DbContext 的 IDbContext 接口,除非你想自己创建一个。但是,我看到很多人说这不是一个好主意或不是最佳实践。
基本上,我想做的是,在某些情况下,应用程序不仅需要在PCMasterContext和PCSubsContext之间切换,而且如果PCSubsContext是当前上下文,还需要将连接字符串修改为PCSubsContext。我在存储库中使用的 dbContext 需要指向不同的数据库。我不知道如何使用 IoC 容器(例如 Ninject 或 Autofac)执行此操作。这是我到目前为止创建的一些代码片段。非常感谢您提供一些实际可行的解决方案。
这是我的基本存储库界面
public interface IPCRepositoryBase<T> where T : class
{
void Add(T entity);
void Delete(T entity);
T FindOne(Expression<Func<T, bool>> predicate);
IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
IQueryable<T> GetAll();
void SetConnection(string connString);
//...
//...
}
这是我的抽象存储库基础
public abstract class PCRepositoryBase<T> : IPCRepositoryBase<T>, IDisposable where T : class
{
protected readonly IDbSet<T> dbSet;
protected DbContext dbCtx;
public PCRepositoryBase(DbContext dbCtx)
{
this.dbCtx = dbCtx;
dbSet = dbCtx.Set<T>();
}
public void SetConnection(string connString)
{
dbCtx.Database.Connection.ConnectionString = connString;
}
public IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return dbSet.Where(predicate); // DataContext.Set<T>().Where( predicate );
}
public virtual IQueryable<T> GetAll()
{
return dbSet;
}
public T FindOne(Expression<Func<T, bool>> predicate)
{
return dbSet.SingleOrDefault(predicate);
}
//... Not all implementations listed
//...
}
现在,这是派生存储库之一的界面:
public interface ISubscriberRepository : IPCRepositoryBase<Subscriber>
{
IQueryable<Subscriber> GetByStatusName( PCEnums.enumSubsStatusTypes status );
IQueryable<Subscriber> GetByBusinessName( string businessName );
//...
//...
}
public class SubscriberRepository : PCRepositoryBase<Subscriber>, ISubscriberRepository
{
public SubscriberRepository( DbContext context ) : base( context ) { }
public IQueryable<Subscriber> GetByStatusName( PCEnums.enumSubsStatusTypes status )
{
return FindBy(x => x.SubsStatusType.Name == status.ToString());
}
public IQueryable<Subscriber> GetByBusinessName( string businessName )
{
return FindBy( s => s.BusinessName.ToUpper() == businessName.ToUpper() );
}
//... other operations omitted for brevity!
}
现在,我的 PCSubs dbContext 由设计师生成:
public partial class PCSubsDBContext : DbContext
{
public PCSubsDBContext() : base("name=PCSubsDBContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Currency> Currencies { get; set; }
public virtual DbSet<DurationName> DurationNames { get; set; }
public virtual DbSet<Subscriber> Subscribers { get; set; }
}
有没有办法,我可以只使用 and/or 为两个数据库注入一个通用的 dbcontext 以及不同数据库的连接字符串。我如何在没有相应接口的情况下在 Ioc 容器中注册 "DbContext" 并且仍然能够在运行时注入连接字符串?一些代码示例真的很有帮助。
解决方法其实很简单。您需要确保您的 PCSubsDBContext
有一个接受连接字符串、连接字符串名称、数据库名称或类似内容的构造函数。通过这种方式,您可以根据它所在的上下文创建适当的 PCSubsDBContext
。注入其构造函数的值可能取决于登录用户或特定请求。这是您已经知道该怎么做的事情。
如何注册在一定程度上取决于您的容器,但您通常必须为此注册一个委托。这可能看起来像这样:
// Autofac
builder.Register<PCSubsDBContext>(c =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest());
// Ninject
kernel.Bind<PCSubsDBContext>().ToMethod(m =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest());
// Simple Injector
container.Register<PCSubsDBContext>(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest());
由于创建上下文取决于请求的可用性,因此甚至可以稍微更改您的设计,以便 PCSubsDBContext
可以延迟加载,同时您仍然可以构建其余部分不存在 Web 请求的对象图。这是非常有价值的,因为这可以让你 verify your container's configuration.
解决方案(一如既往)是引入新的抽象,例如:
public interface IPcSubsContextProvider
{
PCSubsDBContext Context { get; }
}
现在,您现在可以注入 IPcSubsContextProvider
并在执行期间(但不在构建期间)使用其 Context
属性,而不是直接将 PCSubsDBContext
注入消费者对象图)。这允许 PCSubsDBContext
仅在需要时创建,并且仅在构建对象图的其余部分之后创建。实施将是微不足道的:
class LazyPcSubsContextProvider : IPcSubsContextProvider
{
private readonly Lazy<PCSubsDBContext> context;
public LazyPcSubsContextProvider(Func<PCSubsDBContext> factory) {
this.context = new Lazy<PCSubsDBContext>(factory);
}
public PCSubsDBContext Context { get { return this.context.Value; } }
}
此实现可以注册为 scoped/request 生活方式:
// Autofac
builder.Register<IPcSubsContextProvider>(c =>
new LazyPcSubsContextProvider(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest())))
.InstancePerHttpRequest();
// Ninject
kernel.Bind<IPcSubsContextProvider>().ToMethod(m =>
new LazyPcSubsContextProvider(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest())))
.InRequestScope();
// Simple Injector
container.RegisterPerWebRequest<IPcSubsContextProvider>(() =>
new LazyPcSubsContextProvider(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest())));
在此之后,Simple Injector 将使验证配置变得非常容易:
container.Verify();
它还允许您diagnose your configuration。
对于其他容器,这将更难做到。
这与我几周前问过的一个类似问题 here 有一个显着的要求变化。
我有一个新的和独特的(我在我的 Whosebug 搜索中没有找到类似的东西)业务需求:
我创建了两个单独的 entity framework 6 DbContexts,它们指向两个结构不同的数据库,我们称它们为 PcMaster 和 PcSubs。虽然 PcMaster 是一个直接的数据库并且 PcMasterContext 将有一个静态连接字符串,但 PcSubs 数据库用作创建新数据库的模板。显然,由于这些复制的数据库将具有完全相同的结构,因此我们的想法是在实例化 dbcontext 时仅更改连接字符串中的数据库(目录)名称以指向不同的数据库。我还使用了存储库模式和依赖项注入(目前 Ninject,但正在考虑迁移到 Autofac)。
我还没有看到 DbContext 的 IDbContext 接口,除非你想自己创建一个。但是,我看到很多人说这不是一个好主意或不是最佳实践。
基本上,我想做的是,在某些情况下,应用程序不仅需要在PCMasterContext和PCSubsContext之间切换,而且如果PCSubsContext是当前上下文,还需要将连接字符串修改为PCSubsContext。我在存储库中使用的 dbContext 需要指向不同的数据库。我不知道如何使用 IoC 容器(例如 Ninject 或 Autofac)执行此操作。这是我到目前为止创建的一些代码片段。非常感谢您提供一些实际可行的解决方案。
这是我的基本存储库界面
public interface IPCRepositoryBase<T> where T : class
{
void Add(T entity);
void Delete(T entity);
T FindOne(Expression<Func<T, bool>> predicate);
IQueryable<T> FindBy(Expression<Func<T, bool>> predicate);
IQueryable<T> GetAll();
void SetConnection(string connString);
//...
//...
}
这是我的抽象存储库基础
public abstract class PCRepositoryBase<T> : IPCRepositoryBase<T>, IDisposable where T : class
{
protected readonly IDbSet<T> dbSet;
protected DbContext dbCtx;
public PCRepositoryBase(DbContext dbCtx)
{
this.dbCtx = dbCtx;
dbSet = dbCtx.Set<T>();
}
public void SetConnection(string connString)
{
dbCtx.Database.Connection.ConnectionString = connString;
}
public IQueryable<T> FindBy(Expression<Func<T, bool>> predicate)
{
return dbSet.Where(predicate); // DataContext.Set<T>().Where( predicate );
}
public virtual IQueryable<T> GetAll()
{
return dbSet;
}
public T FindOne(Expression<Func<T, bool>> predicate)
{
return dbSet.SingleOrDefault(predicate);
}
//... Not all implementations listed
//...
}
现在,这是派生存储库之一的界面:
public interface ISubscriberRepository : IPCRepositoryBase<Subscriber>
{
IQueryable<Subscriber> GetByStatusName( PCEnums.enumSubsStatusTypes status );
IQueryable<Subscriber> GetByBusinessName( string businessName );
//...
//...
}
public class SubscriberRepository : PCRepositoryBase<Subscriber>, ISubscriberRepository
{
public SubscriberRepository( DbContext context ) : base( context ) { }
public IQueryable<Subscriber> GetByStatusName( PCEnums.enumSubsStatusTypes status )
{
return FindBy(x => x.SubsStatusType.Name == status.ToString());
}
public IQueryable<Subscriber> GetByBusinessName( string businessName )
{
return FindBy( s => s.BusinessName.ToUpper() == businessName.ToUpper() );
}
//... other operations omitted for brevity!
}
现在,我的 PCSubs dbContext 由设计师生成:
public partial class PCSubsDBContext : DbContext
{
public PCSubsDBContext() : base("name=PCSubsDBContext")
{
}
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
throw new UnintentionalCodeFirstException();
}
public virtual DbSet<Currency> Currencies { get; set; }
public virtual DbSet<DurationName> DurationNames { get; set; }
public virtual DbSet<Subscriber> Subscribers { get; set; }
}
有没有办法,我可以只使用 and/or 为两个数据库注入一个通用的 dbcontext 以及不同数据库的连接字符串。我如何在没有相应接口的情况下在 Ioc 容器中注册 "DbContext" 并且仍然能够在运行时注入连接字符串?一些代码示例真的很有帮助。
解决方法其实很简单。您需要确保您的 PCSubsDBContext
有一个接受连接字符串、连接字符串名称、数据库名称或类似内容的构造函数。通过这种方式,您可以根据它所在的上下文创建适当的 PCSubsDBContext
。注入其构造函数的值可能取决于登录用户或特定请求。这是您已经知道该怎么做的事情。
如何注册在一定程度上取决于您的容器,但您通常必须为此注册一个委托。这可能看起来像这样:
// Autofac
builder.Register<PCSubsDBContext>(c =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest());
// Ninject
kernel.Bind<PCSubsDBContext>().ToMethod(m =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest());
// Simple Injector
container.Register<PCSubsDBContext>(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest());
由于创建上下文取决于请求的可用性,因此甚至可以稍微更改您的设计,以便 PCSubsDBContext
可以延迟加载,同时您仍然可以构建其余部分不存在 Web 请求的对象图。这是非常有价值的,因为这可以让你 verify your container's configuration.
解决方案(一如既往)是引入新的抽象,例如:
public interface IPcSubsContextProvider
{
PCSubsDBContext Context { get; }
}
现在,您现在可以注入 IPcSubsContextProvider
并在执行期间(但不在构建期间)使用其 Context
属性,而不是直接将 PCSubsDBContext
注入消费者对象图)。这允许 PCSubsDBContext
仅在需要时创建,并且仅在构建对象图的其余部分之后创建。实施将是微不足道的:
class LazyPcSubsContextProvider : IPcSubsContextProvider
{
private readonly Lazy<PCSubsDBContext> context;
public LazyPcSubsContextProvider(Func<PCSubsDBContext> factory) {
this.context = new Lazy<PCSubsDBContext>(factory);
}
public PCSubsDBContext Context { get { return this.context.Value; } }
}
此实现可以注册为 scoped/request 生活方式:
// Autofac
builder.Register<IPcSubsContextProvider>(c =>
new LazyPcSubsContextProvider(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest())))
.InstancePerHttpRequest();
// Ninject
kernel.Bind<IPcSubsContextProvider>().ToMethod(m =>
new LazyPcSubsContextProvider(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest())))
.InRequestScope();
// Simple Injector
container.RegisterPerWebRequest<IPcSubsContextProvider>(() =>
new LazyPcSubsContextProvider(() =>
new PCSubsDBContext(GetConnectionStringForCurrentRequest())));
在此之后,Simple Injector 将使验证配置变得非常容易:
container.Verify();
它还允许您diagnose your configuration。
对于其他容器,这将更难做到。