2015-01-19 22 views
7

Questa è una specie di domanda simile che ho chiesto a here alcune settimane fa con un cambiamento significativo nel requisito.Utilizzo di più istanze di dbcontext e dipendenza dipendenza

Ho un nuovo e unico (non ho trovato nulla di simile nella mia ricerca StackOverflow) requisito aziendale:

ho creato due distinte Entity Framework 6 DbContexts che puntano a due database strutturalmente diverse, cerchiamo di chiamata loro PcMaster e PcSubs. Mentre PcMaster è un database semplice e PcMasterContext avrà una stringa di connessione statica, il database PcSubs viene utilizzato come modello per creare nuovi database. Ovviamente, dal momento che questi database copiati avranno la stessa struttura esatta, l'idea è di cambiare semplicemente il nome del database (catalogo) nella stringa di connessione per puntare a un db differente quando viene creato il dbcontext. Ho anche utilizzato il pattern di repository e l'iniezione delle dipendenze (attualmente Ninject, ma pensando di passare ad Autofac).

Non ho visto un'interfaccia IDbContext per DbContext, a meno che non si desideri crearne uno da soli. Ma poi, ho visto molti dire che non è una buona idea o non la migliore pratica.

In sostanza, ciò che voglio fare è, in determinate condizioni, non solo l'applicazione deve passare tra PCMasterContext e PCSubsContext, ma anche modificare la stringa di connessione in PCSubsContext se PCSubsContext è il contesto corrente. il dbContext che ho usato nel repository deve puntare a un database differente. Non so come posso farlo con un contenitore IoC come Ninject o Autofac. Ecco alcuni frammenti di codice che ho creato finora. L'aiuto con alcune soluzioni di lavoro reali è molto apprezzato.

Qui è la mia interfaccia per il repository di base

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

Qui è la mia base astratta repository

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

Ed ora, ecco l'interfaccia per uno dei repository derivati:

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

Ora, il mio PCSub dbContext generato dal 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; } 
} 

C'è un modo, posso solo utilizzare e/o iniettare un dbcontext generico per entrambi i database con la stringa di connessione per diversi database. Come dovrei registrare "DbContext" nel contenitore Ioc senza un'interfaccia corrispondente ed essere ancora in grado di iniettare la stringa di connessione in fase di runtime? Alcuni esempi di codice saranno davvero utili.

risposta

8

La soluzione è in realtà abbastanza semplice. Devi assicurarti che il tuo PCSubsDBContext abbia un costruttore che accetta una stringa di connessione, un nome di stringa di connessione, un nome di database o qualcosa di simile. In questo modo è possibile creare il PCSubsDBContext corretto in base al contesto in cui vive. Il valore da iniettare nel suo sensore dipende probabilmente dall'utente loggato o da una determinata richiesta. Questo è qualcosa che sai già come fare.

Come registrare dipende un po 'dal contenitore, ma in genere è necessario registrare un delegato per questo.Questo potrebbe assomigliare a questo:

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

Dal creare il contesto è in funzione della disponibilità di una richiesta, potrebbe anche essere un buon cambiamento il vostro disegno un po 'in modo che il PCSubsDBContext può essere caricato pigramente, mentre si può ancora costruire il resto del grafico dell'oggetto senza l'esistenza di una richiesta web. Questo è molto prezioso, perché questo ti permette di verify your container's configuration.

La soluzione (come sempre) è quello di introdurre una nuova astrazione, come ad esempio:

public interface IPcSubsContextProvider 
{ 
    PCSubsDBContext Context { get; } 
} 

Ora, invece di iniettare un PCSubsDBContext direttamente in consumatori, ora si può iniettare un IPcSubsContextProvider e utilizzare la sua proprietà Context durante esecuzione (ma non durante la costruzione del grafico dell'oggetto). Ciò consente di creare PCSubsDBContext solo se è necessario e solo dopo che il resto del grafico dell'oggetto è stato creato. Un'implementazione sarebbe banale:

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

Questa implementazione può essere registrato con uno stile di vita di ambito/richiesta:

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

Dopo questo, semplice iniettore rendere molto facile per verificare la configurazione:

container.Verify(); 

E ti permette anche di fare diagnose your configuration.

Con gli altri contenitori questo sarà più difficile da fare.

+0

Grazie Steven. Questo ha funzionato perfettamente. –