2013-07-31 24 views
11

Mam serwer MySQL, do którego uzyskuję dostęp za pomocą Entity Framework 4.0. W bazie danych mam tabelę o nazwie Działa, w której niektóre się liczą. Tworzę stronę internetową za pomocą Asp.net. Ta tabela jest dostępna dla jeszcze jednego użytkownika w tym samym czasie. I ta sytuacja powoduje problem z nieprawidłowym wykryciem.Atomowy przyrost z Entity Framework

Mój kod tak:

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

Jeśli ktoś więcej niż dostęp wątku w tym samym czasie w bazie, tylko ostatnie zmiany pozostają.

Prąd wyjściowy:

t1 Temat: One - t2: Temat Dwa

t1: praca Dostęp

t2: praca Dostęp

t2: Access nie jest null

t1: Dostęp nie jest pusty

t1: Ilość zmienił

t2: Ilość zmienił

T1: Zapisz zmiany

T2: Save changes

T1: Aktualna ilość: 5

t2: bieżącego liczenia : 5

Oczekuje wyjściowa:

t1: praca Dostęp

t2: praca Dostęp

t2: Access nie jest null

t1: Dostęp jest not null

t1: Ilość zmienił

t2: Liczba zmienna

t1: Zapisz zmiany

T2: Zapisz zmiany

t1: Aktualna ilość: 5

T2: Aktualna ilość: 10

wiem dlaczego apeear tego problemu, ponieważ ten kod nie jest atomowy. Jak mogę zmienić działanie atomowe?

+0

Wiem, że w MsSql możesz mieć transakcje, czy możesz zrobić to samo w MySql? Również wiem, że 'SaveChanges()' pozwala na wartość logiczną, ale nie sądzę, że to pomoże w tym przypadku. – gunr2171

+0

możesz spróbować tego, 'lock (currentWork) { Console.WriteLine (" Dostęp nie jest pusty "); currentWork.WordCount + = 5; // Domyślny WordCount to 0 Console.WriteLine ("Count changed"); myEntity.SaveChanges(); Console.WriteLine ("Zapisz zmiany"); } 'minęło trochę czasu, od kiedy zrobiłem przyzwoity wątek – Armand

+0

Aha, i przy okazji: dobra sztuczka. Możesz umieścić warunek 'where' w swoim' pierwszym lub domyślnym 'i całkowicie usunąć' where'. – gunr2171

Odpowiedz

0

Następny sposób będzie działać, jeśli hosting witryny w jednym procesie (nie będzie działać z farmy internetowej lub strony internetowej 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); 
      } 
    } 

Co jeszcze można zrobić, to użyć kwerendy SQL surowego poprzez 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

Będę korzystać z webfarmu w przyszłości, a metoda zamka może być problematyczna w przyszłości. Myślałem o ExecuteStoreCommand, ale chciałbym napisać pełny kod z EF. – Dreamcatcher

12

Z Entity Framework nie można tego uczynić operacją "atomową". Trzeba etapy: podmiot

  1. obciążenia z bazy
  2. Zmiana licznika w pamięci
  3. Zapisz zmienione podmiot do bazy

Pomiędzy tymi etapami inny klient może załadować podmiot z bazy danych, która nadal ma starą wartość.

Najlepszym sposobem radzenia sobie z tą sytuacją jest użycie optymalnej współbieżności . Zasadniczo oznacza to, że zmiana w kroku 3 nie zostanie zapisana, jeśli licznik nie będzie już taki sam, jak podczas ładowania obiektu w kroku 1. Zamiast tego otrzymasz wyjątek, który możesz obsłużyć, ponownie wczytując jednostkę i ponowne zastosowanie zmiany.

Workflow będzie wyglądać następująco:

  • w jednostce Work własnością WordCount muszą być oznaczone jako wyraz współbieżności (adnotacje lub Fluent API w przypadku Code-pierwszy) podmiotu
  • Load z bazy
  • Zmiana licznika w pamięci
  • połączeń SaveChanges w ciągu try-catch blokowych i złapać wyjątkami typu DbUpdateConcurrencyException
  • Jeśli wyjątek występuje przeładowanie podmiot w bloku catch z bazy danych, stosuje się znowu zmiany i wywołać SaveChanges ponownie
  • Powtórz ostatni krok, aż bez wyjątku występuje już

W this answer można znaleźć kod przykład dla tej procedury (używając DbContext).

+0

Czy mogę użyć tego rozwiązania Baza danych Pierwsze podejście? – Dreamcatcher

+0

@Dreamcatcher: Tak. W projektancie jest opcja dla ustawień właściwości 'WordCount', aby oznaczyć go jako token współbieżnościowy. – Slauma

+0

@Slauma - A jeśli zmiana musi nastąpić bez opinii użytkowników? Na przykład klient zamawia produkt, a zapas musi zostać zmniejszony? – Sam