2015-07-17 32 views
5

nello strato Business Logic di un'applicazione Entity Framework-based, tutti i metodi che agiscono su DB dovrebbe (come ho sentito) da inserire all'interno di:Entity Framework - uso concomitante di contenitori

using(FunkyContainer fc = new FunkyContainer()) 
{ 
    // do the thing 

    fc.SaveChanges(); 
} 

Naturalmente, per mia comodità, spesso questi metodi si usano l'un l'altro, per non ripetermi. Il rischio che vedo qui è il seguente:

public void MainMethod() 
{ 
    using(FunkyContainer fc = new FunkyContainer()) 
    { 
     // perform some operations on fc 
     // modify a few objects downloaded from DB 

     int x = HelperMethod(); 

     // act on fc again 

     fc.SaveChanges(); 
    } 
} 
public int HelperMethod() 
{ 
    using(FunkyContainer fc2 = new FunkyContainer()) 
    { 
     // act on fc2 an then: 

     fc2.SaveChanges(); 

     return 42; 
    } 
} 

io non guardare bene a me, quando si crea il contenitore fc2, mentre fc è ancora aperto e non è stato ancora salvato. Quindi questo porta alla mia domanda numero uno:

  1. È possibile aprire più contenitori contemporaneamente e agire su di essi con noncuranza una pratica accettabile?

sono arrivato a una conclusione, che avrei potuto scrivere un semplice oggetto di guardia in stile come questo:

public sealed class FunkyContainerAccessGuard : IDisposable 
{ 
    private static FunkyContainer GlobalContainer { get; private set; } 
    public FunkyContainer Container // simply a non-static adapter for syntactic convenience 
    { 
     get 
     { 
      return GlobalContainer; 
     } 
    } 

    private bool IsRootOfHierarchy { get; set; } 

    public FunkyContainerAccessGuard() 
    { 
     IsRootOfHierarchy = (GlobalContainer == null); 

     if (IsRootOfHierarchy) 
      GlobalContainer = new FunkyContainer(); 
    } 

    public void Dispose() 
    { 
     if (IsRootOfHierarchy) 
     { 
      GlobalContainer.Dispose(); 

      GlobalContainer = null; 
     } 
    } 
} 

Ora l'utilizzo sarebbe come segue:

public void MainMethod() 
{ 
    using(FunkyContainerAccessGuard guard = new FunkyContainerAccessGuard()) 
    { 
     FunkyContainer fc = guard.Container; 

     // do anything with fc 

     int x = HelperMethod(); 

     fc.SaveChanges(); 
    } 
} 
public int HelperMethod() 
{ 
    using(FunkyContainerAccessGuard guard = new FunkyContainerAccessGuard()) 
    { 
     FunkyContainer fc2 = guard.Container; 

     // do anything with fc2 

     fc2.SaveChanges(); 
    } 
} 

Quando il HelperMethod è chiamato da MainMethod, il GlobalContainer è già stata creata, e la sua utilizzato da entrambi i metodi, quindi non c'è alcun conflitto. Inoltre, HelperMethod può anche essere utilizzato separatamente, quindi crea il proprio contenitore.

Tuttavia, questo mi sembra un enorme eccessivo per me; quindi:

  1. Questo problema è già stato risolto in forma di classe (IoC?) o almeno di un bel motivo di progettazione?

Grazie.

+1

Non penso che FunkyContainerAccessGuard funzioni. Con MainMethod e HelperMethod condivide lo stesso DbContext e viene eliminato da HelperMethod, ma in questo modo si richiama il metodo SaveChanges dell'oggetto in MainMethod, che genera un'eccezione ObjectDisposed. – mr100

+1

No, HelperMethod non eliminerà il contesto condiviso. –

+0

@ Holen Holterman - hai ragione, mio ​​errore. La guardia è stata eliminata lì, e non il contenitore stesso. – mr100

risposta

3
  1. L'apertura simultanea di più contenitori e la loro incuria è una pratica accettabile?

Generalmente questo è perfettamente accettabile, a volte anche necessario, ma bisogna essere cauti con quello. Avere più contenitori allo stesso tempo è particolarmente utile quando si eseguono operazioni di multithreading. A causa di come funziona db in genere, ogni thread dovrebbe avere il proprio DbContext che non dovrebbe essere condiviso con altri thread. Svantaggi nell'uso DbContext multiplo allo stesso tempo è che ciascuno di essi utilizzerà connessione db separato, e talvolta sono limitate, ciò può comportare l'applicazione occasionalmente essere in grado di connettersi al database. Altro svantaggio è il fatto che l'entità generata da un DbContext non può essere utilizzata con l'entità generata da altri DbContext. Nel tuo esempio HelperMethod restituisce tipo primitivo, quindi questo è perfettamente sicuro, ma se sarebbe tornato qualche oggetto entità che in MainMethod si desidera assegnare ad esempio per alcune proprietà di navigazione di entità creata da MainMethod DbContext quindi si riceverà un'eccezione. Per superare questo problema in MainMethod dovresti utilizzare Id di entità restituita da HelperMethod per recuperare ancora una volta quell'entità, questa volta con contesto fc. D'altra parte v'è un vantaggio di utilizzare molteplici contesti - se uno contesto ha alcuni problemi, ad esempio, si è cercato di salvare qualcosa che ha violato constaint indice, quindi tutti i successivi studi di salvare le modifiche comporterà la stessa eccezione come il cambiamento difettoso sarà essere ancora in sospeso.Se si utilizzano più DbContexts, se uno fallisce, il secondo funzionerà in modo indipendente - questo è il motivo per cui DbContexts non dovrebbe vivere a lungo. Quindi in generale direi che la regola migliore utilizzo potrebbe essere:

  • Ogni thread dovrebbe usare una DbContext separata
  • Tutti i metodi che esegue sullo stesso thread dovrebbe condividere lo stesso DbContext

Naturalmente il sopra si applica se il lavoro da fare è breve. DbContext non dovrebbe vivere a lungo. L'esempio migliore sarebbe quello delle applicazioni web: ogni richiesta del server viene gestita da thread separati e le operazioni per generare una risposta generalmente non richiedono molto tempo. In tal caso, tutti i metodi eseguiti per generare una risposta dovrebbero condividere per comodità lo stesso DbContext. Ma ogni richiesta dovrebbe essere servita da DbContext separato.

  1. Questo problema è già stato risolto in forma di classe (IoC?) O almeno qualche schema di progettazione piacevole?

Ciò che è necessario assicurare è che la classe DbContext è singleton per thread, ma ogni thread ha la propria istanza di tale classe. Secondo me, il modo migliore per assicurare questo è con IoC. Per esempio in Autofac nelle applicazioni web posso registrare la mia DbContext con la seguente regola:

builder 
    .RegisterType<MyDbContext>() 
    .InstancePerHttpRequest(); 

In questo modo autofac CIO genera una DbContext per ogni richiesta e condivido istanza esistente all'interno della richiesta servire thread. Non è necessario preoccuparsi qui per smaltire il tuo DbContext. Il tuo IoC lo farà quando il tuo thread è finito.

2

Lavorare in più connessioni allo stesso tempo non è l'approccio giusto la maggior parte del tempo perché:

  1. È possibile ottenere distribuito deadlock che SQL Server non può risolvere.
  2. Potrebbe non essere possibile visualizzare i dati precedentemente scritti ma non ancora impegnati.
  3. Non è possibile condividere entità oltre i limiti del contesto (qui: metodi).
  4. Altre risorse di utilizzo.
  5. Nessuna capacità di eseguire il trasferimento oltre i limiti di contesto (qui: metodi).

Questi sono svantaggi molto gravi. Di solito, il modello migliore è quello di avere un contesto, una connessione e una transazione per la richiesta che l'app sta elaborando (richiesta HTTP o WCF). È molto semplice da configurare ed evita un sacco di problemi.

EF deve essere utilizzato come modello di oggetti dal vivo. Non paralizzarlo riducendolo a CRUD.

static FunkyContainer GlobalContainer 

Che non funziona. Non devi condividere un contesto tra le richieste. Super pericoloso. Prendi in considerazione la possibilità di archiviare un contesto in HttpContext.Items o qualsiasi altro negozio per richiesta nella tua app.