2009-11-18 4 views
1

Pour une application que nous sommes en train de développer, nous devons lire n lignes d'une table, puis mettre à jour ces lignes de manière sélective en fonction de critères spécifiques au domaine. Pendant cette opération, tous les autres utilisateurs de la base de données doivent être verrouillés pour éviter les mauvaises lectures.SQLite Transaction non validée

Je commence une transaction, lisez les lignes, et tout en réitérant sur le recordset construire une chaîne de déclarations de mise à jour. Une fois que j'ai fini de lire le jeu d'enregistrements, je ferme le jeu d'enregistrements et exécute les mises à jour. À ce stade, je valide la transaction, mais aucune des mises à jour n'est effectuée sur la base de données.

private static SQLiteConnection OpenNewConnection() 
     { 

     try 
     { 
      SQLiteConnection conn = new SQLiteConnection(); 
      conn.ConnectionString = ConnectionString;//System.Configuration.ConfigurationManager.AppSettings["ConnectionString"]; 
      conn.Open(); 
      return conn; 
     }    
     catch (SQLiteException e) 
     { 
      LogEvent("Exception raised when opening connection to [" + ConnectionString + "]. Exception Message " + e.Message); 
      throw e; 
     } 
    } 

    SQLiteConnection conn = OpenNewConnection(); 
      SQLiteCommand command = new SQLiteCommand(conn); 
      SQLiteTransaction transaction = conn.BeginTransaction(); 
// Also fails   transaction = conn.BeginTransaction(); 
      transaction = conn.BeginTransaction(IsolationLevel.ReadCommitted); 
      command.CommandType = CommandType.Text; 
      command.Transaction = transaction; 
      command.Connection = conn; 
      try 
      { 
       string sql = "select * From X Where Y;"; 
       command.CommandText = sql; 
       SQLiteDataReader ranges; 

       ranges = command.ExecuteReader(); 
       sql = string.Empty; 
       ArrayList ret = new ArrayList(); 
       while (MemberVariable > 0 && ranges.Read()) 
       { 
        // Domain stuff 

        sql += "Update X Set Z = 'foo' Where Y;"; 
       } 
       ranges.Close(); 
       command.CommandText = sql; 
       command.ExecuteNonQuery(); 
           // UPDATES NOT BEING APPLIED 
       transaction.Commit(); 
       return ret; 

      } 
      catch (Exception ex) 
      { 
       transaction.Rollback(); 
       throw; 
      } 
      finally 
      { 
       transaction.Dispose(); 
       command.Dispose(); 
       conn.Close(); 
      } 

      return null; 

Si je supprime la transaction, tout fonctionne comme prévu. Le "Domaine stuff" est domaine specfic et autre que la lecture des valeurs du jeu d'enregistrements n'accède pas à la base de données. Ai-je oublié un pas?

+2

Quelqu'un at-il remarqué à quel point il est étrange que ce site utilise des barres de défilement dans la page malgré la fréquence à laquelle Jeff Atwood se plaint de ce type de conception? – marr75

Répondre

4

Lorsque vous mettez un point d'arrêt sur votre transaction.Commit() ligne vous voyez se faire frapper?

réponse finale:

de verrouillage de SQLite ne fonctionne pas comme vous présumez voir http://www.sqlite.org/lockingv3.html. Compte tenu de cela, je pense que vous avez un problème de détermination de la portée de la transaction qui peut être facilement résolu en réorganisant votre code en tant que tel:

string selectSql = "select * From X Where Y;";  
using(var conn = OpenNewConnection()){ 
    StringBuilder updateBuilder = new StringBuilder(); 

    using(var cmd = new SQLiteCommand(selectSql, conn)) 
    using(var ranges = cmd.ExecuteReader()) { 
     while(MemberVariable > 0 && ranges.Read()) { 
      updateBuilder.Append("Update X Set Z = 'foo' Where Y;"); 
     } 
    } 

    using(var trans = conn.BeginTransaction()) 
    using(var updateCmd = new SQLiteCommand(updateBuilder.ToString(), conn, trans) { 
     cmd.ExecuteNonQuery(); 
     trans.Commit(); 
    } 
} 
+0

Oui et l'appel ExecuteNonQuery renvoie le nombre correct de lignes modifiées. – MikeP

+0

Prenons un peu de recul: Pourquoi ouvrez-vous la transaction si tôt? – JeffreyABecker

+0

La table lue et écrite est la même table et toutes les mises à jour doivent être séquentielles entre différents utilisateurs. L'application doit bloquer les lectures supplémentaires jusqu'à ce que la demande actuelle a été écrite. – MikeP

2

Notes complémentaires au sujet des commentaires dans ce post/réponse sur les transactions dans SQLite. Ceux-ci s'appliquent à SQLite 3.x en utilisant la journalisation et peuvent ou non s'appliquer à différentes configurations - WAL est légèrement différente mais je ne suis pas familier avec elle. Voir locking in SQLite pour l'information définitive.

Toutes les transactions dans SQLite sont SERIALIZABLE (voir le pragma read_uncommitted pour une petite exception). A nouveau lecture ne bloque pas/échoue sauf si le processus d'écriture a commencé (il y a un verrou EXCLUSIVE/PENDING maintenu) et une écriture ne commencera pas tant que toutes les lectures en cours sont terminées et il peut obtenir un verrou EXCLUSIVE (ce n'est pas vrai pour WAL mais l'isolation de la transaction est toujours la même). La séquence entière ci-dessus ne sera pas atomique dans le code et la séquence peut être lue (A) -> lire (B) -> écrire (A) -> lire (B), où A et B représente différentes connexions (imaginez sur différents threads). A la fois en lecture (B), les données sont toujours cohérentes même s'il y avait une écriture entre les deux.

Pour rendre la séquence de code lui-même atomique un mécanisme de synchronisation lock ou similaire est requise. Alternativement, le verrouillage/synchronisation peut être créé avec SQLite lui-même en utilisant un pragma locking_mode de "exclusif". Cependant, même si le code ci-dessus n'est pas atomique les données adhèrent au contrat SQL sérialisable (à l'exception d'un bug grave ;-)

Bonne codage


Voir Locking in SQLite, SQLite pragmas et Atomic Commit in SQLite

Questions connexes