使用多个dbcontext实例和依赖项注入

时间:2015-01-19 19:03:35

标签: c# entity-framework ninject autofac dbcontext

这是几周前我问here这个问题的一个类似问题。需求发生了重大变化。

我有一个新的和独特的(我在stackoverflow搜索中找不到这样的东西)业务要求:

我创建了两个独立的实体框架6 DbContexts,指向两个结构不同的数据库,让我们称它们为PcMaster和PcSubs。虽然PcMaster是一个直接的数据库,而PcMasterContext将有一个静态连接字符串,但PcSubs数据库用作模板来创建新的数据库。显然,由于这些复制的数据库将具有相同的确切结构,因此想法只是在实例化dbcontext时将连接字符串中的数据库(目录)名称更改为指向不同的数据库。我还使用了存储库模式和依赖注入(目前是Ninject,但考虑转向Autofac)。

我没有看到DbContext的IDbContext接口,除非你想自己创建一个。但是,我看到很多人说这不是一个好主意或不是最好的做法。

基本上,我想要做的是,在某些条件下,不仅应用程序需要在PCMasterContext和PCSubsContext之间切换,而且如果PCSubsContext是当前上下文,还要将连接字符串修改为PCSubsContext。我在存储库中使用的dbContext需要指向不同的数据库。我不知道如何使用像Ninject或Autofac这样的IoC容器来做到这一点。这是我到目前为止创建的一些代码片段。我们非常感谢您对一些实际工作解决方案的帮助。

这是我的基础存储库接口

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; }
}

有没有办法,我可以为两个数据库使用和/或注入一个通用dbcontext以及不同数据库的连接字符串。如何在没有相应接口的情况下在Ioc容器中注册“DbContext”,并且仍然能够在运行时注入连接字符串?一些代码示例将真正有所帮助。

1 个答案:

答案 0 :(得分:9)

解决方案实际上非常简单。您需要确保PCSubsDBContext具有接受连接字符串,连接字符串名称,数据库名称或类似内容的构造函数。这样,您可以根据它所处的上下文创建正确的PCSubsDBContext。注入其ctor的值可能取决于登录用户或某个请求。这是你已经知道该怎么做的事情。

如何注册取决于您的容器,但您通常必须为此注册一个委托。这可能如下所示:

// 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; }
}

现在,您可以在执行期间注入PCSubsDBContext并使用其IPcSubsContextProvider属性,而不是在构建对象图时,而不是直接向消费​​者注入Context。这允许仅在需要时才创建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; } } } 。实施将是微不足道的:

// 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())));

此实现可以使用作用域/请求生活方式进行注册:

container.Verify();

在此之后,Simple Injector将使验证配置变得非常容易:

{{1}}

它也允许你做diagnose your configuration

使用其他容器,这将更难做到。

相关问题