2013-07-31 4 views
11

J'ai un serveur MySQL auquel j'accède en utilisant Entity Framework 4.0. Dans la base de données j'ai une table appelée Works dans laquelle certains compte. Je développe un site web avec Asp.net. Cette table accésable un utilisateur de plus en même temps. Et cette situation provoque un mauvais problème d'incertions.Incrément atomique avec Entity Framework

Mon code comme ça:

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

Si un de plus que l'accès à la base de données fil en même temps, seules les dernières modifications restent.

Courant de sortie:

t1: Discussion Un - t2: Discussion Deux

t1: Travaux d'accès

t2: Travaux d'accès

t2: l'accès est non nul

t1: L'accès n'est pas nul

t1: Nombre changé

t2: Nombre changé

t1: Enregistrer les modifications

t2: Enregistrer les modifications

t1: nombre actuel: 5

t2: nombre actuel : 5

Résultats escomptés:

t1: Travaux d'accès

t2: Travaux d'accès

t2: L'accès est non nul

t1: L'accès est non nul

t1: Nombre changé

t2: Nombre modifié

t1: Enregistrer les modifications

t2: Enregistrer les modifications

t1: nombre actuel: 5

t2: nombre actuel: 10

Je sais pourquoi apeear ce problème, car ce code n'est pas atomique. Comment puis-je tourner opération atomique?

+0

Je sais dans MsSql vous pouvez avoir des transactions, pouvez-vous faire la même chose dans MySQL? Aussi je sais que 'SaveChanges()' permet un booléen, mais je ne pense pas que cela aidera dans ce cas. – gunr2171

+0

pouvez-vous essayer ceci, 'lock (currentWork) { Console.WriteLine (" Accès n'est pas nul "); currentWork.WordCount + = 5; // WordCount par défaut est 0 Console.WriteLine ("Count changed"); myEntity.SaveChanges(); Console.WriteLine ("Enregistrer les modifications"); } 'ça fait un moment que j'ai fait du filetage décent – Armand

+0

Oh, et en passant: une bonne astuce. Vous pouvez placer votre condition 'where' dans votre' first ou default' et supprimer complètement 'where'. – gunr2171

Répondre

0

façon suivante fonctionnera si vous hébergement de votre site dans le processus unique (il ne fonctionnera pas avec la ferme Web ou gardsen web):

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

Que vous pouvez faire, est d'utiliser des requêtes SQL 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

Je vais utiliser webfarm dans le futur et la méthode de verrouillage pourrait être un problème dans le futur. J'ai pensé ExecuteStoreCommand mais j'aimerais écrire du code complet avec EF. – Dreamcatcher

12

Avec Entity Framework, vous ne pouvez pas rendre cette opération "atomique". Vous avez les étapes:

  1. entité de charge de base de données
  2. Changement compteur dans la mémoire
  3. Enregistrer changé entité à la base de données

Entre ces étapes un autre client peut charger l'entité de la base de données a toujours l'ancienne valeur.

La meilleure façon de gérer cette situation est d'utiliser simultané optimiste. Cela signifie que la modification à l'étape 3 ne sera pas enregistrée si le compteur n'est plus le même que lorsque vous avez chargé l'entité à l'étape 1. Au lieu de cela, vous obtiendrez une exception que vous pouvez gérer en rechargeant l'entité et réappliquer le changement.

Le workflow ressemblerait à ceci:

  • Dans l'entité Work la propriété WordCount doit être marqué comme un jeton d'accès simultané (annotations ou API Parlant couramment le cas du code-Première)
  • entité de charge de base de données
  • Changement compteur dans la mémoire
  • Appel SaveChanges dans un bloc try-catch et capture des exceptions de type DbUpdateConcurrencyException
  • Si une exception se produit recharger l'entité dans le bloc catch de la base de données, appliquer le changement à nouveau et appeler SaveChanges à nouveau
  • Répétez la dernière étape jusqu'à ce que aucune exception ne survient plus

En this answer vous pouvez trouver un code exemple pour cette procédure (en utilisant DbContext).

+0

Puis-je utiliser cette solution Database First approach? – Dreamcatcher

+0

@Dreamcatcher: Oui. Il existe une option dans le concepteur pour les paramètres de propriété de WordCount pour le marquer en tant que jeton de concurrence. – Slauma

+0

@Slauma - Et si le changement doit se produire sans les commentaires des utilisateurs? Par exemple, un client commande un produit et l'inventaire doit être décrémenté? – Sam