2013-07-31 24 views
11

Possiedo un server MySQL a cui accedo utilizzando Entity Framework 4.0. Nel database ho un tavolo chiamato Works in cui alcuni conti. Sviluppo sito web con Asp.net. Questa tabella accelera un altro utente nello stesso momento. E questa situazione causa problemi di incesto errati.Incremento atomico con Entity Framework

Il mio codice del genere:

dbEntities myEntity = new dbEntities(); 

var currentWork = myEntity.works.Where(xXx => xXx.RID == 208).FirstOrDefault(); 
Console.WriteLine("Access work"); 

if (currentWork != null) 
{ 
    Console.WriteLine("Access is not null"); 
    currentWork.WordCount += 5;//Default WordCount is 0 
    Console.WriteLine("Count changed"); 
    myEntity.SaveChanges(); 
    Console.WriteLine("Save changes"); 
} 
Console.WriteLine("Current Count:" + currentWork.WordCount); 

Se uno in più l'accesso al database filo stesso tempo, solo le ultime modifiche rimangono.

Corrente di uscita:

t1 Discussione: One - t2: Discussione Due

t1: il lavoro di accesso

t2: lavoro Accesso

t2: L'accesso non è null

t1: L'accesso non è nullo

t1: Conte ha cambiato

t2: COUNT cambiato

T1: Salva le modifiche

t2: Salva modifiche

t1: Count attuale: 5

t2: conteggio attuale : 5

Uscita prevista:

t1: il lavoro di accesso

t2: lavoro Accesso

t2: L'accesso non è nullo

t1: L'accesso non è nullo

t1: COUNT cambiato

t2: conteggio modificato

t1: Salva modifiche

t2: Salva modifiche

t1: Count attuale: 5

t2: Count attuale: 10

so perché apeear questo problema, perché questo codice non è atomico. Come posso attivare l'operazione atomica?

+0

So che in MsSql puoi avere transazioni, puoi fare lo stesso in MySql? So anche che 'SaveChanges()' consente un valore booleano, ma non credo che ciò possa aiutare in questo caso. – gunr2171

+0

puoi provare questo, 'lock (currentWork) { Console.WriteLine (" Access is not null "); currentWork.WordCount + = 5; // Default WordCount è 0 Console.WriteLine ("Conteggio modificato"); myEntity.SaveChanges(); Console.WriteLine ("Salva modifiche"); } 'è da un po 'che ho fatto una discreta filettatura – Armand

+0

Oh, e comunque: un buon trucco. Puoi mettere la tua condizione 'where' all'interno del tuo' primo o predefinito' e rimuovere 'where' completamente. – gunr2171

risposta

0

modo seguente funziona se si ospita il sito a singolo processo (non funzionerà con il web farm o web gardsen):

private static readonly Locker = new object(); 

    void Foo() 
    { 
      lock(Locker) 
      { 
       dbEntities myEntity = new dbEntities(); 

       var currentWork = myEntity.works.Where(xXx => xXx.RID == 208).FirstOrDefault(); 
       Console.WriteLine("Access work"); 

       if (currentWork != null) 
       { 
        Console.WriteLine("Access is not null"); 
        currentWork.WordCount += 5;//Default WordCount is 0 
        Console.WriteLine("Count changed"); 
        myEntity.SaveChanges(); 
        Console.WriteLine("Save changes"); 
       } 
       Console.WriteLine("Current Count:" + currentWork.WordCount); 
      } 
    } 

Che altro si può fare, è usare query SQL grezzo via ObjectContext :

if (currentWork != null) 
      { 
       Console.WriteLine("Access is not null"); 
       myEntity.ExecuteStoredCommand("UPDATE works SET WordCount = WordCount + 5 WHERE RID = @rid", new MySqlParameter("@rid", MySqlDbType.Int32){Value = 208) 
       Console.WriteLine("Count changed"); 

      } 
    var record = myEntity.works.FirstOrDefault(xXx => xXx.RID == 208); 
    if(record != null) 
     Console.WriteLine("Current Count:" + record .WordCount); 
+0

Userò webfarm in futuro e il metodo di blocco potrebbe essere un problema in futuro. Ho pensato a ExecuteStoreCommand ma vorrei scrivere un codice completo con EF. – Dreamcatcher

12

Con Entity Framework non è possibile rendere un'operazione "atomica". Avete le fasi: entità

  1. carico dal database
  2. Variazione contatore in memoria
  3. Salva cambiato entità nel database

Tra questi passi un altro client può caricare l'entità dal database che ha ancora il vecchio valore.

Il modo migliore per gestire questa situazione è utilizzare la concorrenza ottimistica . Significa fondamentalmente che la modifica al passaggio 3 non verrà salvata se il contatore non è più lo stesso di quando è stata caricata l'entità nel passaggio 1. Invece, si otterrà un'eccezione che è possibile gestire ricaricando l'entità e riapplicare il cambiamento.

Il flusso di lavoro sarebbe simile a questa:

  • Nel entità Work proprietà WordCount deve essere contrassegnato come un segno della concorrenza (annotazioni o API Fluente in caso di Codice-First) entità
  • carico dal database
  • Variazione contatore in memoria
  • chiamata SaveChanges in un blocco e di cattura try-catch eccezioni di tipo DbUpdateConcurrencyException
  • Se si verifica un'eccezione ricaricare l'entità nel catch blocco dal database, applicare di nuovo la modifica e chiamare SaveChanges nuovo
  • Ripetere l'ultimo passaggio fino a quando non ha fatto eccezione si verifica più

In this answer è possibile trovare un codice esempio per questa procedura (utilizzando DbContext).

+0

Posso usare questa soluzione Database Primo approccio? – Dreamcatcher

+0

@Dreamcatcher: Sì. C'è un'opzione nella finestra di progettazione per le impostazioni di proprietà di 'WordCount' per contrassegnarla come token di concorrenza. – Slauma

+0

@Slauma - E se il cambiamento deve avvenire senza feedback dell'utente? Ad esempio, un cliente ordina un prodotto e l'inventario deve essere decrementato? – Sam