2013-02-17 10 views
12

ho un controllore:DbContext è stato eliminato e autofac

private readonly ILogger _logger;  
private readonly IRepository _repository; 

public HomeController(ILogger logger, IRepository repository) 
{ 
    _logger = logger; 
    _repository = repository; 
} 

Questo è il repository:

public class EfRepository : IRepository 
{ 
    // ...methods for add, delete, update entities 
    // .... 

    public void Dispose() 
    { 
     if (this._context != null) 
     { 
      this._context.SaveChanges(); 
      (this._context as IDisposable).Dispose(); 
      this._context = null; 
     } 
    } 
} 

Infine, tipi di registrazione a CIO:

_builder.RegisterType<Logger>().As<ILogger>(); 
_builder.RegisterType<EfRepository>().As<IRepository>().WithParameter("context", new PcpContext()); 

Quando corro l'applicazione ottengo questo errore:

The operation cannot be completed because the DbContext has been disposed.

Ho provato a cambiare EfRepository registrazione in questo modo:

_builder.RegisterType<EfRepository>() 
    .As<IRepository>() 
    .WithParameter("context", new PcpContext()).InstancePerLifetimeScope(); 

In questo caso il primo traguardo richiesta, ma quando si tenta di aprire altre pagine, ottengo di nuovo l'errore. Dov'è il problema?

+3

MAI impegnare il nostro DbContext a disposizione. Dispose verrà chiamato in caso di eccezione, ma non si desidera salvare alcuna modifica quando ciò accade. – Steven

+0

@Steven: ho cancellato questa riga, comunque questo non risolve il problema. – user1260827

+1

Ogni volta che ho visto SaveChanges all'interno di un dispose, il programmatore stava avendo problemi. Im ancora per capire quale modello di progettazione è che suggerisce o raccomanda "commit" o "salva" in Dispose. Sto per suggerire di rivalutare il piano per salvare all'interno di smaltire. Che dire della gestione degli errori. perché si associano le modifiche di "commit" a un database con la garbage collection? Vale la pena leggere http://msdn.microsoft.com/en-us/library/fs2xkftw%28VS.80%29.aspx Quando si avvia lo smaltimento? forse inizia qui: http://stackoverflow.com/questions/898828/c-sharp-finalize-dispose-pattern –

risposta

19

Quando si utilizza il metodo WithParameter, l'istanza del parametro sarà la stessa per ogni oggetto risolto. Quindi con .WithParameter("context", new PcpContext()) si sta effettivamente utilizzando la stessa istanza della classe PcpContext per qualsiasi istanza risolta di IRepository.

Con il codice corrente, quando un'istanza di IRepository viene eliminata, disporrà anche quell'istanza di PcpContext. Quindi qualsiasi tentativo successivo di risolvere un IRepository riceverà l'istanza PcpContext che è stata eliminata. È necessario un modo per ricevere una nuova nuova istanza di EF DbContext su ciascuna richiesta HTTP che viene eliminata alla fine della richiesta.

Una possibilità potrebbe essere quella di registrare un blocco di codice per l'IRepository in modo che il blocco di codice viene eseguito ogni volta che un IRepository deve essere risolto:

_builder.Register<IRepository>(c => new EfRepository(new PcpContext())) 

Una soluzione migliore sarebbe quella di creare un nuovo IDatabaseContext astrazione, l'aggiornamento di EfRepository dipende quindi dalla nuova astrazione di IDatabaseContext anziché dalla classe PcpContext (che potrebbe già essere il caso :)).

La classe di implementazione per IDatabaseContext sarà la classe PcpContext, che deve ereditare da EF DbContext e probabilmente ricevere la stringa di connessione come parametro.

public class EfRepository : IRepository 
{ 
    private readonly IDatabaseContext _context; 

    public EfRepository(IDatabaseContext context) 
    { 
     _context = context; 
    } 

    ...methods for add, delete, update entities 

    //There is no longer need for this to be disposable. 
    //The disaposable object is the database context, and Autofac will take care of it 
    //public void Dispose() 
} 

public interface IDatabaseContext : IDisposable 
{ 
    ... declare methods for add, delete, update entities 
} 

public class PcpContext: DbContext, IDatabaseContext 
{ 
    public EntityFrameworkContext(string connectionString) 
     : base(connectionString) 
    { 
    } 

    ...methods exposing EF for add, delete, update entities 

    //No need to implement IDisposable as we inherit from DbContext 
    //that already implements it and we don´t introduce new resources that should be disposed of 
} 

Questo migliora con l'idea di utilizzare un contenitore CIO e lasciando l'onere della gestione vita per loro. Ora la classe di repository non ha bisogno di essere usa e getta né deve gestire e smaltire la sua dipendenza da IDatabaseContext. È Autofac che terrà traccia dell'istanza del contesto e la eliminerà quando appropriato.

Per lo stesso motivo, probabilmente si desidera utilizzare InstancePerLifetimeScope con la dipendenza del contesto del database. Ciò significherebbe che lo stesso contesto EF è condiviso per ogni istanza di repository sulla stessa richiesta Http e viene eliminato alla fine della richiesta.

_builder.RegisterType<EfRepository>() 
    .As<IRepository>(); 

_builder.RegisterType<PcpContext>() 
    .As<IDatabaseContext>() 
    .WithParameter("connectionString", "NameOfConnStringInWebConfig") 
    .InstancePerLifetimeScope(); 
+0

Non uso Autofac, quindi è stato interessante leggere ... +1 buona analisi e spiegazione –

+0

Grazie @soadyp ! Io uso Unity invece di Autofac. Sebbene i principi siano gli stessi, i dettagli su come ciascun contenitore specifico implementa la gestione a vita sarà diverso. (Ad esempio con Unity vorrei usare una vita Hierarchichal per 'IDatabaseContext' combinata con un nuovo contenitore figlio creato per richiesta Http) –

+0

E se avessi più DbContexts limitati? Registrare il DbContext come chiave? – Chazt3n

0

sono andato con la semplice soluzione di un 'blocco di codice' come @ Daniel J.G suggerito (un lambda).

Di seguito un esempio di codice di questo in Autofac. L'esempio di Daniels è per Unity, come anche lui stesso menziona se stesso.Perché l'OP ha aggiunto Autofac come un tag di questo sembrava importante per me:

_builder.Register(c => new AppDbContext()).As(typeof(AppDbContext)); 

Quel codice risolto il problema DbContext has been disposed stavo avendo con Entity Framework. Si noti che, rispetto alla maggior parte degli altri contenitori DI, incluso Unity, Autofac cambia la cosa registrata e la cosa a cui si risolve.

Per il codice di esempio dato dal PO la correzione sarebbe qualcosa di simile:

_builder.Register(c => new EfRepository(new PcpContext())).As(IRepository); 

Si noti che questo ultimo bit è codice non testato. Ma dovresti comunque fare riferimento alla risposta di Daniels per maggiori informazioni, perché penso che abbia ragione con la "migliore opzione". Ma potresti usare la mia opzione di soluzione se non hai tempo adesso (come me). Basta aggiungere un TODO in modo da poter compensare il debito tecnico che stai subendo :).

Quando lo vedrò, vedrò se posso aggiornare questa risposta con codice di lavoro per Autofac che segue la sua "opzione migliore". Per prima cosa voglio leggere attentamente lo this article. Durante la lettura rapida, mi sembra che le persone di Autofac stiano promuovendo l'uso del 'Service Locator' per la gestione dell'ambito della vita. Ma secondo Mark Seemann è an anti-pattern, quindi ho alcune cose da capire ... Qualunque esperto di DI con un'opinione?

Problemi correlati