Transactions en .net

144

Quelles sont les meilleures pratiques pour effectuer des transactions en C # .Net 2.0. Quelles sont les classes à utiliser? Quels sont les pièges à surveiller, etc. Tous ces éléments de validation et d'annulation. Je commence juste un projet dans lequel je pourrais avoir besoin de faire des transactions tout en insérant des données dans la base de données. Toutes les réponses ou liens pour des informations, même de base sur les transactions, sont les bienvenus.

Malik Daud Ahmad Khokhar
la source
Voici un bon exemple de transactions en .NET sur codeproject à utiliser comme point de départ.
Mitchel Sellers

Réponses:

271

Il existe 2 principaux types de transactions; transactions de connexion et transactions ambiantes. Une transaction de connexion (telle que SqlTransaction) est directement liée à la connexion db (telle que SqlConnection), ce qui signifie que vous devez continuer à transmettre la connexion - OK dans certains cas, mais n'autorise pas «créer / utiliser / libérer» utilisation, et ne permet pas le travail cross-db. Un exemple (formaté pour l'espace):

using (IDbTransaction tran = conn.BeginTransaction()) {
    try {
        // your code
        tran.Commit();
    }  catch {
        tran.Rollback();
        throw;
    }
}

Pas trop compliqué, mais limité à notre connexion "conn". Si nous voulons faire appel à différentes méthodes, nous devons maintenant passer "conn".

L'alternative est une transaction ambiante; nouveau dans .NET 2.0, l' objet TransactionScope (System.Transactions.dll) permet une utilisation sur une plage d'opérations (les fournisseurs appropriés s'enrôleront automatiquement dans la transaction ambiante). Cela facilite la rétro-intégration dans le code existant (non transactionnel) et la conversation avec plusieurs fournisseurs (bien que DTC soit impliqué si vous en parlez à plus d'un).

Par exemple:

using(TransactionScope tran = new TransactionScope()) {
    CallAMethodThatDoesSomeWork();
    CallAMethodThatDoesSomeMoreWork();
    tran.Complete();
}

Notez ici que les deux méthodes peuvent gérer leurs propres connexions (ouvrir / utiliser / fermer / éliminer), mais elles feront silencieusement partie de la transaction ambiante sans que nous ayons à passer quoi que ce soit.

Si vos erreurs de code, Dispose () sera appelé sans Complete (), il sera donc annulé. L'imbrication attendue, etc. est prise en charge, bien que vous ne puissiez pas annuler une transaction interne mais terminer la transaction externe: si quelqu'un n'est pas satisfait, la transaction est annulée.

L'autre avantage de TransactionScope est qu'il n'est pas uniquement lié aux bases de données; tout fournisseur prenant en charge les transactions peut l'utiliser. WCF, par exemple. Ou il existe même des modèles d'objets compatibles avec TransactionScope (c'est-à-dire des classes .NET avec capacité de restauration - peut-être plus facile qu'un souvenir, même si je n'ai jamais utilisé cette approche moi-même).

Dans l'ensemble, un objet très, très utile.

Quelques mises en garde:

  • Sur SQL Server 2000, un TransactionScope ira immédiatement au DTC; ceci est corrigé dans SQL Server 2005 et au-dessus, il peut utiliser le LTM (beaucoup moins de surcharge) jusqu'à ce que vous parliez à 2 sources, etc., quand il est élevé au DTC.
  • Il y a un problème qui signifie que vous devrez peut-être modifier votre chaîne de connexion
Marc Gravell
la source
CSLA .NET 2.0 prend en charge l'objet TransactionScope!
Binoj Antony
Le problème ici est lorsque vous avez une transaction dans la première méthode et que cette méthode (encapsulation) ne sait pas si elle sera appelée à partir d'une transaction parente ou non.
Eduardo Molteni
1
@Eduardo - ce n'est pas un problème lors de l'utilisation de TransactionScope, ce qui le rend très attrayant. De telles transactions s'emboîtent, et seuls les plus externes sont validés.
Marc Gravell
J'espère que vous écoutez toujours. Vous avez dit qu'il existe "des modèles d'objets compatibles avec TransactionScope". Pouvez-vous m'en indiquer quelques-uns? THX.
majkinetor
1
Encore une fois Marc, une autre excellente explication. Lorsque vous dites «l'imbrication attendue est prise en charge», est-ce que cela concerne les blocs de transaction définis dans les méthodes (CallAMethodThatDoesSomeWork () par exemple) elles-mêmes? Ou avec le périmètre de transaction défini à l'extérieur, ce n'est pas obligatoire?
Phil Cooper
11
protected void Button1_Click(object sender, EventArgs e)
   {


       using (SqlConnection connection1 = new SqlConnection("Data Source=.\\SQLEXPRESS;AttachDbFilename=|DataDirectory|\\Database.mdf;Integrated Security=True;User Instance=True"))
       {
           connection1.Open();

           // Start a local transaction.
           SqlTransaction sqlTran = connection1.BeginTransaction();

           // Enlist a command in the current transaction.
           SqlCommand command = connection1.CreateCommand();
           command.Transaction = sqlTran;

           try
           {
               // Execute two separate commands.
               command.CommandText =
                "insert into [doctor](drname,drspecialization,drday) values ('a','b','c')";
               command.ExecuteNonQuery();
               command.CommandText =
                "insert into [doctor](drname,drspecialization,drday) values ('x','y','z')";
               command.ExecuteNonQuery();

               // Commit the transaction.
               sqlTran.Commit();
               Label3.Text = "Both records were written to database.";
           }
           catch (Exception ex)
           {
               // Handle the exception if the transaction fails to commit.
               Label4.Text = ex.Message;


               try
               {
                   // Attempt to roll back the transaction.
                   sqlTran.Rollback();
               }
               catch (Exception exRollback)
               {
                   // Throws an InvalidOperationException if the connection 
                   // is closed or the transaction has already been rolled 
                   // back on the server.
                   Label5.Text = exRollback.Message;

               }
           }
       }


   }
Ali Gholizadeh
la source
4

Vous pouvez également encapsuler la transaction dans sa propre procédure stockée et la gérer de cette façon au lieu de faire des transactions en C # lui-même.

Charles Graham
la source
1

si vous en avez juste besoin pour des trucs liés à la base de données, certains OR Mappers (par exemple NHibernate) prennent en charge les transactions par défaut.

Joachim Kerschbaumer
la source
0

Cela dépend également de ce dont vous avez besoin. Pour les transactions SQL de base, vous pouvez essayer d'effectuer des transactions TSQL en utilisant BEGIN TRANS et COMMIT TRANS dans votre code. C'est le moyen le plus simple mais il est complexe et vous devez faire attention à vous engager correctement (et à revenir en arrière).

J'utiliserais quelque chose comme

SQLTransaction trans = null;
using(trans = new SqlTransaction)
{
    ...
    Do SQL stuff here passing my trans into my various SQL executers
    ...
    trans.Commit  // May not be quite right
}

Tout échec vous fera sortir directement de usinget la transaction sera toujours validée ou annulée (selon ce que vous lui demandez de faire). Le plus gros problème auquel nous avons été confrontés était de nous assurer qu'il était toujours engagé. L'utilisation garantit que la portée de la transaction est limitée.

Brody
la source