Using multiple instances of dbcontext and dependency injection

This is a kind of similar question that I asked here a few weeks ago with one major change in requirement.

I have a new and unique (I did not find anything like this in my search on the stack) business requirement:

I created two separate entities framework 6 DbContexts that point to two structurally different databases, let's call them PcMaster and PcSubs. Although PcMaster is a direct database, and PcMasterContext will have a static connection string, the PcSubs database is used as a template for creating new databases. Obviously, since these copied databases will have the same exact structure, the idea is to simply change the name of the database (directory) in the connection string to point to another db when the dbcontext instance is created. I also used the repository pattern and dependency injection (currently Ninject, but thinking about switching to Autofac).

I have not seen the IDbContext interface for DbContext unless you want to create it yourself. But then I saw how many said it was not a good idea or a good practice.

Basically, I want that under certain conditions, not only the application should switch between PCMasterContext and PCSubsContext, but also change the connection string to PCSubsContext if PCSubsContext is the current context. The dbContext that I used in the repository should point to a different database. I don't know how to do this with an IoC container like Ninject or Autofac. Here are some code snippets that I have created so far. Help with some real working solutions is much appreciated.

Here is my interface for basic storage

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

Here is my base repository base

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

And now, here is the interface for one of the resulting repositories:

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

Now my PCSubs dbContext created by the designer:

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

Is there any way I can just use and / or enter one common dbcontext for both databases along with a connection string for different databases. How can I register a β€œDbContext” in an Ioc container without an appropriate interface and still be able to enter a connection string at runtime? Some code examples will really help.

+7
c # entity-framework ninject autofac dbcontext
source share
1 answer

The solution is actually quite simple. You must make sure that your PCSubsDBContext has a constructor that accepts a connection string, connection string name, database name, or something similar. This way you can create the correct PCSubsDBContext based on the context in which it lives. What value for input in its ctor probably depends on the user in the system or a specific request. This is what you already know how to do.

How to register in your container is a bit, but you will usually have to register a delegate for this. It might look like this:

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

Since the context creation depends on the availability of the request, it may even slightly change your design so that PCSubsDBContext can be loaded lazily, while you can still build the rest of the object graph without having a web request. This is very valuable because it allows you to check the configuration of your container .

The solution (as always) is to introduce a new abstraction, for example:

 public interface IPcSubsContextProvider { PCSubsDBContext Context { get; } } 

Now instead of injecting PCSubsDBContext directly into consumers, you can now enter IPcSubsContextProvider and use its Context property at run time (but not when building an object graph). This allows you to create PCSubsDBContext only if necessary, and only after the rest of the graph of the object has been built. The implementation will be trivial:

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

This implementation can be registered using a binding / query style:

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

After that, Simple Injector will simplify the configuration check:

 container.Verify(); 

And it allows you to diagnose your configuration .

In other containers this will be harder to do.

+8
source share

All Articles