IdentifiantMot de passe
Loading...
Mot de passe oubli� ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les r�ponses en temps r�el, voter pour les messages, poser vos propres questions et recevoir la newsletter

Contribuez .NET Discussion :

Imbriquer des transactions en C#


Sujet :

Contribuez .NET

  1. #1
    Expert confirm�
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    F�vrier 2010
    Messages
    4 197
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 46
    Localisation : France, Rh�ne (Rh�ne Alpes)

    Informations professionnelles :
    Activit� : Chef de projets
    Secteur : High Tech - �diteur de logiciels

    Informations forums :
    Inscription : F�vrier 2010
    Messages : 4 197
    Billets dans le blog
    1
    Par d�faut Imbriquer des transactions en C#
    Bonjour,

    Cette librairie a �t� con�ue pour �tre utilis�e avec SQL Server.
    Elle utilise les interfaces IDbConnection et IDbCommand afin d'�tre compatible aussi avec OleDbConnection et OdbcConnection, mais aussi pour vous premettre de la rendre compatible avec le SGBD de votre choix s'il supporte les transactions imbriqu�es ou les points de sauvegarde de transaction.


    L'objet SqlTransaction ne supporte pas d'ouvrir deux transactions imbriqu�es.
    Idem avec OleDbTransaction.

    Pourtant, lorsqu'on a un long traitement, pouvoir imbriquer des transactions, c'est bien plus pratique que de g�rer manuellement des Save() et ne plus trop savoir � quel savepoint on doit remonter lorsqu'on souhaite annuler une op�ration.

    Limitation connues :
    - SQL Server ne supporte pas les transactions imbriqu�es. A la place, il supporte les "savepoints". Il s'agit de commit interm�diaires au sein d'une unique transaction, qui permettent, lors d'un rollback, de choisir si on annule toute la transaction, ou seulement ce qui a �t� modifi� depuis un savepoint donn�. Donc une fois "� l'int�rieur" d'une transaction imbriqu�es, vous devez la valider ou l'annuler avant de pouvoir modifier la transaction parente. Pour information, Oracle, qui est un des rares SGBD � supporter les transactions imbriqu�es ne recommande de toute fa�on pas de modifier une transaction parente lorsqu'une transaction imbriqu�e est active : en effet, on peut tr�s ais�ment se retrouver avec un deadlock si la transaction m�re tente de modifier des donn�es modifi�es par la transaction imbriqu�e. Pour que la notion d'imbrication soit lisible, utilisez des using() pour chaque objet MyTransaction.
    - Le niveau de transaction en cours est stock� au niveau d'objet IDbConnexion. Donc si vous croyez faire une requ�te en dehors de la transaction en utilisant directement un cnx.CreateCommand, votre requ�te sera malgr� tout ex�cut�e au sein de la transaction en cours.
    - MyTransaction.CreateNestedTransaction() retourne une nouvelle transaction [g]� la suite de la transaction en cours[/g], et n'a absolument rien � voir avec l'instance de MyTransaction qui a cr�� la nouvelle transaction. Il n'y a aucun moyen de faire autrement, ou alors demandez � Microsoft de supporter les transactions imbriqu�es
    - MyTransaction.CreateCommand() retourne un nouvel objet IDbCommand qui utilise [g]la transaction en cours[/g], et n'a absolument rien � voir avec l'instance de MyTransaction qui a cr�� la nouvelle transaction. Il n'y a aucun moyen de faire autrement, ou alors demandez de nouveau � Microsoft de supporter les transactions imbriqu�es
    - Le code devrait �tre threadsafe. Il n'a cependant pas �t� test� dans un tel contexte.
    - Il faut imp�rativement faire un Begin() et Commit()/Rollback() pour chaque transaction cr�e.
    Code qui ne marche pas : a la sortie du using(cnx), la transaction A n'est pas commit�e. Un rollback se produit alors sur l'ensemble de la transaction ! A droite, le code SQL ex�cut� :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    using (SqlConnexion cnx = new SqlConnexion(cnx_string))
    {
       MyTransaction tranA = new MyTransaction(cnx);
       tranA.Begin();                                               BEGIN TRANSACTION
       [...]
       MyTransaction tranB = tranA.CreateNestedTransaction();
       tranB.Begin();                                               SAVE TRANSACTION nested
       [...]
       tranA.Commit();
    }
    Code qui marche :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
    using (SqlConnexion cnx = new SqlConnexion(cnx_string))
    {
       MyTransaction tranA = new MyTransaction(cnx);
       tranA.Begin();                                               BEGIN TRANSACTION
       [...]
       MyTransaction tranB = tranA.CreateNestedTransaction();
       tranB.Begin();                                               SAVE TRANSACTION nested
       [...]
       tranB.Commit();                                              
       tranA.Commit();                                              COMMIT TRANSACTION
    }
    Voici le code, que j'ai essay� de documenter au maximum :
    Code c# : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    176
    177
    178
    179
    180
    181
    182
    183
    184
    185
    186
    187
    188
    189
    190
    191
    192
    193
    194
    195
    196
    197
    198
    199
     
    using System;
    using System.Data;
     
    namespace CustomTransaction
    {
        /// <summary>
        /// Objet qui permet d'effectuer des transactions imbriquées.
        /// On ne catch absolument aucune erreur volontairement.
        /// Cette librairie a été écrite pour SQL Server, même si elle utilise l'interface IDbConnexion.
        /// Pour créer une transaction imbriquée, on peut uiliser au choix l'un ou l'autre constructeur de MyTransaction, ou la méthode CreateNestedTransaction.
        /// </summary>
        public class MyTransaction : IDisposable
        {
            // Private attributes
            IDbConnection Cnx;
            bool IsActive = false;
            string Name = string.Empty;
            const int NAME_LENGTH = 4;
     
            /// <summary>
            /// Créée une transaction liée à la connexion.
            /// </summary>
            /// <param name="cnx">Connexion ouverte à la base de données.</param>
            public MyTransaction(IDbConnection cnx)
            {
                // On stocke la connexion.
                Cnx = cnx;
     
                // On crée un nom aléatoire pour la transaction.
                if (MyTransactionContext.Level > 0)
                {
                    string lettres = "abcdefghijklmnopqrstuvwxyz";
                    char[] name = new char[NAME_LENGTH + 2];
                    name[0] = '[';
                    name[NAME_LENGTH + 1] = ']';
                    for (int i = 1; i <= NAME_LENGTH; i++)
                    {
                        name[i] = lettres[MyRandom.Next(0, 26)];
                    }
     
                    Name = string.Concat(name);
                }
            }
     
            /// <summary>
            /// Créée une transaction imbriquée.
            /// </summary>
            /// <param name="parent">Transaction mère.</param>
            public MyTransaction(MyTransaction parent) : this(parent.Cnx)
            {
            }
     
            /// <summary>
            /// Débute une transaction
            /// </summary>
            public void Begin()
            {
                // Si un jour on souhaite que la classe soit threadsafe, c'est mieux de poser un lock
                lock (this)
                {
                    using (IDbCommand cmd = Cnx.CreateCommand())
                    {
                        if (MyTransactionContext.Level == 0)
                        {
                            // On est au premier niveau, on commence donc une transaction réelle
                            cmd.CommandText = "BEGIN TRANSACTION";
                        }
                        else
                        {
                            // On est dans une transaction imbriquée : en réalité, il s'agit d'un savepoint
                            cmd.CommandText = string.Format("SAVE TRANSACTION {0}", Name);
                        }
                        cmd.ExecuteNonQuery();
                    }
                    // La transaction est démarrée
                    IsActive = true;
                    MyTransactionContext.Level++;
                }
            }
     
            /// <summary>
            /// Valide la transaction
            /// </summary>
            public void Commit()
            {
                // Si un jour on souhaite que la classe soit threadsafe, c'est mieux de poser un lock
                lock (this)
                {
                    if (IsActive)
                    {
                        using (IDbCommand cmd = Cnx.CreateCommand())
                        {
                            if (--MyTransactionContext.Level == 0)
                            {
                                // On est au premier niveau, on commit donc la transaction
                                cmd.CommandText = "COMMIT";
                                cmd.ExecuteNonQuery();
                            }
                            // Si on n'est pas au niveau 0, on ne fait rien de plus, puisque nous sommes dans un savepoint
                        }
                        // La transaction est terminée
                        IsActive = false;
                    }
                }
            }
     
            /// <summary>
            /// Annule la transaction
            /// </summary>
            public void Rollback()
            {
                // Si un jour on souhaite que la classe soit threadsafe, c'est mieux de poser un lock
                lock (this)
                {
                    if (IsActive)
                    {
                        using (IDbCommand cmd = Cnx.CreateCommand())
                        {
                            if (--MyTransactionContext.Level == 0)
                            {
                                // On est au premier niveau, on annule dont toute la transaction
                                cmd.CommandText = "ROLLBACK";
                            }
                            else
                            {
                                // On est dans une transaction imbriquée, on annule jusqu'au savepoint qui a débuté la transaction
                                cmd.CommandText = string.Format("ROLLBACK TRANSACTION {0}", Name);
                            }
                            cmd.ExecuteNonQuery();
                        }
                        // La transaction est terminée
                        IsActive = false;
                    }
                }
            }
     
            /// <summary>
            /// Crée un IDbCommand à partir de la connexion. Cet objet Command sera utilisé dans la transaction.
            /// </summary>
            /// <returns>Nouvel objet Command</returns>
            public IDbCommand CreateCommand()
            {
                return Cnx.CreateCommand();
            }
     
            /// <summary>
            /// Crée une transaction imbriquée.
            /// </summary>
            /// <returns>Nouvelle transaction imbriquée.</returns>
            public MyTransaction CreateNestedTransaction()
            {
                return new MyTransaction(this);
            }
     
            /// <summary>
            /// Rollback implicite de la transaction si elle est encore active.
            /// </summary>
            public void Dispose()
            {
                // Le test pour savoir si la transaction est active est déjà pris en compte dans la méthode Rollback()
                Rollback();
            }
     
            /// <summary>
            /// Classe statique qui permet d'avoir un Random unique pour toutes les classes 
            /// et éviter ainsi de générer systématiquement les mêmes séries
            /// </summary>
            private static class MyRandom
            {
                private static Random rnd;
     
                static MyRandom()
                {
                    rnd = new Random();
                }
     
                public static int Next(int min, int max)
                {
                    return rnd.Next(min, max);
                }
            }
     
            /// <summary>
            /// Niveau actuel d'imbrication de transaction
            /// Attention, si vous avec des connexions dans des threads séparés, vous allez avoir des problèmes !
            /// </summary>
            private static class MyTransactionContext
            {
                [ThreadStatic]
                public static int Level;
     
                static MyTransactionContext()
                {
                    Level = 0;
                }
            }
        }
    }

    Comment l'utiliser :
    Cr�ation d'une transaction � partir d'une connexion (qui doit imp�rativement �tre ouverte) :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
     
    MyTransaction tranA = new MyTransaction(maSqlConnexion);
    Cr�ation d'une transaction imbriqu�e :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
     
    MyTransaction tranB = new MyTransaction(maSqlConnexion);
    // ou
    MyTransaction tranB = new MyTransaction(tranA);
    // ou
    MyTransaction tranB = tranA.CreateNestedTransaction();
    Ces trois syntaxes sont absolument �quivalentes.
    La transaction tranB n'est pas d�pendante de la transaction tranA. Elle est juste ajout�e � la suite des transactions en cours dans l'objet maSqlConnexion.

    Cr�ation d'un object IDbCommand utilisant la transaction en cours :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
     
    maSqlConnexion.CreateCommand();
    // ou
    tranA.CreateCommand();
    Les deux syntaxes sont absolument �quivalentes, l'objet IDbCommand cr�� n'�tant absolument pas li� � l'objet MyTransaction qui l'a cr��.

    D�but de la transaction :
    Validation de la transaction :
    Annullation de la transaction :
    Et un exemple complet d'utilisation :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
     
    using System;
    using System.Data;
    using System.Data.Sql;
    using System.Data.SqlClient;
    using CustomTransaction;
     
    namespace TestNestedTransaction
    {
        class Program
        {
            static void Main(string[] args)
            {
                Console.WriteLine("Utilisation de transactions imbriquée (B imbriquée dans A)");
                Console.WriteLine("On utilise une librairie custom qui simule une transaction.");
     
                using (SqlConnection cnx = new SqlConnection("Server=localhost\\SQLEXPRESS;Database=testlock;Trusted_Connection=True;"))
                {
                    cnx.Open();
     
                    using (SqlCommand cmd = cnx.CreateCommand())
                    {
                        cmd.CommandText = "insert into test (name) values (@name);";
                        SqlParameter pName = cmd.Parameters.Add("name", SqlDbType.VarChar, 50);
     
                        Console.WriteLine();
                        Console.WriteLine("Cas 1 :");
                        Console.WriteLine("- Une simple transaction committée");
                        Console.WriteLine("On s'attend à ce que la ligne reste dans la base");
     
                        using (MyTransaction tranA = new MyTransaction(cnx))
                        {
                            tranA.Begin();
                            pName.Value = "Cas 1, transaction seule";
                            cmd.ExecuteNonQuery();
                            tranA.Commit();
                        }
     
                        Console.WriteLine();
                        Console.WriteLine("Cas 2 :");
                        Console.WriteLine("- Une simple transaction rollbackée");
                        Console.WriteLine("On s'attend à ce qu'aucune ligne ne reste dans la base");
     
                        using (MyTransaction tranA = new MyTransaction(cnx))
                        {
                            tranA.Begin();
                            pName.Value = "Cas 2, transaction seule";
                            cmd.ExecuteNonQuery();
                            tranA.Rollback();
                        }
     
                        Console.WriteLine();
                        Console.WriteLine("Cas 3 :");
                        Console.WriteLine("- La transaction parente est rollbackée");
                        Console.WriteLine("- La transaction fille est committée");
                        Console.WriteLine("On s'attend à ce qu'aucune ligne ne reste dans la base");
     
                        using (MyTransaction tranA = new MyTransaction(cnx))
                        {
                            tranA.Begin();
                            pName.Value = "Cas 3, transaction parente";
                            cmd.ExecuteNonQuery();
                            using (MyTransaction tranB = new MyTransaction(cnx))
                            {
                                tranB.Begin();
                                pName.Value = "Cas 3, transaction fille";
                                cmd.ExecuteNonQuery();
                                tranB.Commit();
                            }
                            tranA.Rollback();
                        }
     
                        Console.WriteLine();
                        Console.WriteLine("Cas 4 :");
                        Console.WriteLine("- La transaction parente est committée");
                        Console.WriteLine("- La transaction fille est rollbackée");
                        Console.WriteLine("On s'attend à ce que seule la ligne de la transaction parente soit dans la base");
     
                        using (MyTransaction tranA = new MyTransaction(cnx))
                        {
                            tranA.Begin();
                            pName.Value = "Cas 4, transaction parente";
                            cmd.ExecuteNonQuery();
                            using (MyTransaction tranB = new MyTransaction(cnx))
                            {
                                tranB.Begin();
                                pName.Value = "Cas 4, transaction fille";
                                cmd.ExecuteNonQuery();
                                tranB.Rollback();
                            }
                            tranA.Commit();
                        }
     
                        Console.WriteLine();
                        Console.WriteLine("Cas 5 :");
                        Console.WriteLine("- La transaction parente est committée");
                        Console.WriteLine("- La transaction fille est committée");
                        Console.WriteLine("On s'attend à ce que les deux lignes soient dans la base");
     
                        using (MyTransaction tranA = new MyTransaction(cnx))
                        {
                            tranA.Begin();
                            pName.Value = "Cas 5, transaction parente";
                            cmd.ExecuteNonQuery();
                            using (MyTransaction tranB = new MyTransaction(cnx))
                            {
                                tranB.Begin();
                                pName.Value = "Cas 5, transaction fille";
                                cmd.ExecuteNonQuery();
                                tranB.Commit();
                            }
                            tranA.Commit();
                        }
     
                        Console.WriteLine();
                        Console.WriteLine("Cas 6 :");
                        Console.WriteLine("- La transaction parente est rollbackée");
                        Console.WriteLine("- La transaction fille est rollbackée");
                        Console.WriteLine("On s'attend à ce qu'aucune ligne ne soit dans la base");
     
                        using (MyTransaction tranA = new MyTransaction(cnx))
                        {
                            tranA.Begin();
                            pName.Value = "Cas 6, transaction parente";
                            cmd.ExecuteNonQuery();
                            using (MyTransaction tranB = new MyTransaction(cnx))
                            {
                                tranB.Begin();
                                pName.Value = "Cas 6, transaction fille";
                                cmd.ExecuteNonQuery();
                                tranB.Rollback();
                            }
                            tranA.Rollback();
                        }
     
                        Console.WriteLine();
                        Console.WriteLine("Résultat :");
     
                        cmd.CommandText = "select id, name from test order by id;";
                        cmd.Parameters.Clear();
                        Console.WriteLine("ID\tNAME");
                        Console.WriteLine("------- --------------------------------------------------");
                        SqlDataReader da = cmd.ExecuteReader();
                        while (da.Read())
                        {
                            Console.WriteLine("{0}\t{1}", da.GetInt32(0), da.GetString(1));
                        }
                    }
                    cnx.Close();
                }
     
                Console.WriteLine();
                Console.WriteLine("Fin");
     
                Console.ReadKey(true);
            }
        }
    }
    Ce qui donne :
    Code : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
     
    Utilisation de transactions imbriquée (B imbriquée dans A)
    On utilise une librairie custom qui simule une transaction imbriquée.
     
    Cas 1 :
    - Une simple transaction committée
    On s'attend à ce que la ligne reste dans la base
     
    Cas 2 :
    - Une simple transaction rollbackée
    On s'attend à ce qu'aucune ligne ne reste dans la base
     
    Cas 3 :
    - La transaction parente est rollbackée
    - La transaction fille est committée
    On s'attend à ce qu'aucune ligne ne reste dans la base
     
    Cas 4 :
    - La transaction parente est committée
    - La transaction fille est rollbackée
    On s'attend à ce que seule la ligne de la transaction parente soit dans la base
     
    Cas 5 :
    - La transaction parente est committée
    - La transaction fille est committée
    On s'attend à ce que les deux lignes soient dans la base
     
    Cas 6 :
    - La transaction parente est rollbackée
    - La transaction fille est rollbackée
    On s'attend à ce qu'aucune ligne ne soit dans la base
     
    Résultat :
    ID      NAME
    ------- --------------------------------------------------
    56      Cas 1, transaction seule
    60      Cas 4, transaction parente
    62      Cas 5, transaction parente
    63      Cas 5, transaction fille
     
    Fin

  2. #2
    R�dacteur/Mod�rateur


    Homme Profil pro
    D�veloppeur .NET
    Inscrit en
    F�vrier 2004
    Messages
    19 875
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 43
    Localisation : France, Paris (�le de France)

    Informations professionnelles :
    Activit� : D�veloppeur .NET
    Secteur : High Tech - �diteur de logiciels

    Informations forums :
    Inscription : F�vrier 2004
    Messages : 19 875
    Par d�faut
    Bien vu

    Mais est-ce qu'on ne peut pas d�j� faire des transactions imbriqu�es en utilisant TransactionScope ? (la question n'a rien de rh�torique, je ne connais pas la r�ponse...)

    Sinon, pour le probl�me de ta classe statique : effectivement ce n'est pas tr�s propre... Autres approches possibles :

    - Tu peux marquer le champ Level avec l'attribut ThreadStatic. Chaque thread aura sa propre copie de la valeur, et donc plus de probl�me. Ca reste pas tr�s propre � mon avis, mais �a ne demande quasiment aucune modif par rapport � ton code actuel

    - Quand tu cr�es une transaction imbriqu�e, tu peux lui passer en param�tre la transaction parente. Comme �a, plut�t que de v�rifier le niveau, tu peux simplement v�rifier s'il y a une transaction parente. Si ce n'est pas le cas, c'est que tu es au niveau 0. A mon avis c'est plus propre que de se reposer sur un �tat global.

  3. #3
    Membre Expert Avatar de Er3van
    Homme Profil pro
    Architecte Logiciel
    Inscrit en
    Avril 2008
    Messages
    1 430
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rh�ne (Rh�ne Alpes)

    Informations professionnelles :
    Activit� : Architecte Logiciel
    Secteur : Industrie

    Informations forums :
    Inscription : Avril 2008
    Messages : 1 430
    Par d�faut
    A priori c'est possible d'imbriquer des TransactionScope, il faudrait que je teste pour �tre s�r. Il faut peut-�tre jouer sur les TransactionScopeOption pour avoir ce que l'on souhaite.

    Si j'ai le temps je teste dans la journ�e !

  4. #4
    Expert confirm�
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    F�vrier 2010
    Messages
    4 197
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 46
    Localisation : France, Rh�ne (Rh�ne Alpes)

    Informations professionnelles :
    Activit� : Chef de projets
    Secteur : High Tech - �diteur de logiciels

    Informations forums :
    Inscription : F�vrier 2010
    Messages : 4 197
    Billets dans le blog
    1
    Par d�faut
    Merci pour ta r�ponse.

    Apparement, TransactionScope (que je ne connais pas) permettrait, aux dire de certains autres forumeurs, de faire des transactions imbriqu�es.

    Au d�tail pr�s que SQL Server par exemple, ne supporte pas, m�me en interne, les transactions imbriqu�es : il doit se baser en interne sur un artifice du m�me genre que le mien.

    Aussi, comme j'ai d�j� r�pondu dans un autre topic, ce qui me d�range fortement avec TransactionScope, c'est qu'il ne soit pas dans le namespace "System.Data".
    Pour moi, cela implique plusieurs limitations qui ne m'encouragent pas � l'utiliser :
    - La transaction n'est pas forc�ment g�r�e par le SGBD (en effet, c'est Microsoft Transaction Server qui g�re la transaction, et la d�l�gue au SGBD)
    - Le TransactionScope n'est pas li� � la connexion � la base de donn�es, et vice-versa. En cas d'utilisation de plusieurs connexions � diff�rentes bases, on ne peut pas choisir quelle connexion utilisera des transaction et telle autre pas.
    - De la m�me mani�re, on ne peux pas rendre une partie du code "non transactionnelle".
    - Et surtout : le fait que TransactionScope ne soit pas dans le namespace Data montre qu'il a �t� mis en place dans l'optique �ventuellement de g�rer aussi d'autres �l�ments en transaction (syst�me de fichier, m�moire, etc. ?) m�me si ces fonctionnalit� ne sont pas prises en charge actuellement.

    Pour en revenir � ThreadStatic, merci pour l'astuce

    Quant au fait de passer la transaction en param�tre, oui et non.

    Dans l'absolu, pour g�rer de vraies transactions imbriqu�es, il faudrait en effet pouvoir passer la transaction m�re en param�tre � la transaction fille : plus propre, et s�mantiquement plus logique.
    Le seul souci, c'est que derri�re, SQL Server ne supporte pas les transactions imbriqu�es, et par cons�quent, je suis oblig� de g�rer un niveau d'imbrication "s�quentiel" global au thread. En effet, il ne faut pas que lorsque je suis au niveau 3, je puisse cr�er une transaction de niveau 2 : car en r�alit�, elle sera au niveau 4, et il n'y a aucun artifice � ma connaissance qui permette d'�viter �a (mise � part �ventuellement passer par une connexion s�par�e, mais on va se retrouver avec des locks et autres joyeuset�s).

    Plus g�n�ralement, m�me Oracle qui semble �tre un des rares SGBD a supporter les transactions imbriqu�es ne semble pas accepter qu'on modifie une transaction parente lorsqu'une transaction fille est active... quoi que... la phrase, en anglais issue de la documentation, porte � confusion et voici comme je l'ai comprise :
    "Quand une transaction m�re ouvre une transaction imbriqu�e, elle ne [g]devrait[/g] (may) plus effectuer de modifications autre que commit, rollback ou begin nested transaction."
    Il n'y a pas d'explication sur le "may" :
    - Est-ce un abus de langage et "must" est plus appropri� ? J'en doute
    - A mon avis c'est simplement que la transaction m�re risque de se prendre un lock � cause de la transaction fille, et ainsi se retrouver dans �tat de deadlock. C'est � v�rifier.

    Enfin bref, de toute fa�on, j'ai pour le moment �crit cette librairie plut�t pour SQL Server (puisqu'avec Oracle on peut faire de vraies transactions imbriqu�es, on n'a pas besoin de faire de SAVE). Et donc dans la r�alit�, on ne peux faire que �a (en T-SQL) :

    Code sql : S�lectionner tout - Visualiser dans une fen�tre � part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
     
    BEGIN TRANSACTION A
    [...]
    SAVE TRANSACTION B
    [...]
    SAVE TRANSACTION C
    [...]
    SAVE TRANSACTION D
    [...]
    SAVE TRANSACTION E
    [...]
    ROLLBACK TRANSACTION D
    [...]
    SAVE TRANSACTION F
    [...]
    COMMIT TRANSACTION A
    => C'est parfaitement s�quentiel, et il n'y a aucun moyen apr�s le "SAVE TRANSACTION D" d'aller modifier des donn�es au niveau du scope de B (c'est � dire que si on veut rollbacker D, on est oblig� de rollbacker tout ce qui a �t� fait apr�s D, sans aucun moyen de faire autrement).

  5. #5
    Membre Expert Avatar de Er3van
    Homme Profil pro
    Architecte Logiciel
    Inscrit en
    Avril 2008
    Messages
    1 430
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Rh�ne (Rh�ne Alpes)

    Informations professionnelles :
    Activit� : Architecte Logiciel
    Secteur : Industrie

    Informations forums :
    Inscription : Avril 2008
    Messages : 1 430
    Par d�faut
    Citation Envoy� par StringBuilder Voir le message
    - Et surtout : le fait que TransactionScope ne soit pas dans le namespace Data montre qu'il a �t� mis en place dans l'optique �ventuellement de g�rer aussi d'autres �l�ments en transaction (syst�me de fichier, m�moire, etc. ?) m�me si ces fonctionnalit� ne sont pas prises en charge actuellement.
    Je te confirme que tu g�res d'autres transactions, et c'est bien le but.

    Je m'en sers actuellement dans un programme qui consomme des messages en provenance d'une file MQ (IBM MQSeries), et ins�re les donn�es en base apr�s traitement (la version courte).

    Le principe avec MQ, c'est qu'un message consomm� est sorti de la file, et a priori perdu.
    Avec le TransactionScope, je peux g�rer �a tr�s facilement : je consomme le message, et si pour une raison ou une autre ma requ�te SQL se vautre, par exemple � cause d'un probl�me r�seau, alors mon message est remis dans la file � la m�me position qu'il �tait (car l'ordre peut jouer au point de vue applicatif), et donc aucune perte.
    Le programme s'execute ensuite de nouveau et si tout fonctionne, tout est commit� (c�t� base, et �galement la consommation du message MQ).

    Alors, oui, c'est peut-�tre un bien pour un mal et �a ne s'adapte pas � tous les cas, mais de mon point de vue c'est exactement ce que je cherche.

    EDIT : J'ai mis un +1 quand m�me pour l'id�e !

  6. #6
    R�dacteur/Mod�rateur


    Homme Profil pro
    D�veloppeur .NET
    Inscrit en
    F�vrier 2004
    Messages
    19 875
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 43
    Localisation : France, Paris (�le de France)

    Informations professionnelles :
    Activit� : D�veloppeur .NET
    Secteur : High Tech - �diteur de logiciels

    Informations forums :
    Inscription : F�vrier 2004
    Messages : 19 875
    Par d�faut
    Citation Envoy� par StringBuilder Voir le message
    - Le TransactionScope n'est pas li� � la connexion � la base de donn�es, et vice-versa. En cas d'utilisation de plusieurs connexions � diff�rentes bases, on ne peut pas choisir quelle connexion utilisera des transaction et telle autre pas.
    - De la m�me mani�re, on ne peux pas rendre une partie du code "non transactionnelle".
    Bah d�j� �a permet de faire des transactions distribu�es sur plusieurs bases par exemple, ce qui est plus compliqu� avec des transactions "classiques". Et le fait de ne pas pouvoir faire de traitement hors transaction sur une DB pendant que tu fais un traitement dans une transaction sur une autre me semble une bonne chose, puisque �a permet de garantir la coh�rence des donn�es... mais effectivement il y a sans doute des sc�narios o� c'est g�nant

  7. #7
    Expert confirm�
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    F�vrier 2010
    Messages
    4 197
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 46
    Localisation : France, Rh�ne (Rh�ne Alpes)

    Informations professionnelles :
    Activit� : Chef de projets
    Secteur : High Tech - �diteur de logiciels

    Informations forums :
    Inscription : F�vrier 2010
    Messages : 4 197
    Billets dans le blog
    1
    Par d�faut
    Citation Envoy� par tomlev Voir le message
    Et le fait de ne pas pouvoir faire de traitement hors transaction sur une DB pendant que tu fais un traitement dans une transaction sur une autre me semble une bonne chose, puisque �a permet de garantir la coh�rence des donn�es... mais effectivement il y a sans doute des sc�narios o� c'est g�nant
    Un exemple classique o� c'est g�nant :
    J'ai un trigger qui effectue une v�rification fonctionnelle durant son traitement.
    Je souhaite conserver la trace dans une table de LOG des actions effectu�es par le trigger.
    => En cas d'erreur fonctionnelle, le trigger d�clenche une erreur, qui sera traduite sour forme d'un ROLLBACK dans la transaction appelante.
    => Il faut que l'insertion dans la table de LOG ne soit pas prise en compte dans la transaction, sinon elle perd tout son int�r�t.

    SQL Server, en interne, g�re lui aussi des choses hors transaction � l'int�rieur d'une transaction : si tu fais un INSERT dans une table avec un champ IDENTITY, alors m�me en cas le ROLLBACK, l'identifiant attribu� restera r�serv�, et le compteur incr�ment�. Si on souhaite par exemple g�rer son propre compteur � l'aide d'une PS par exemple, on peut souhaiter aussi laisser le trou, afin de mettre en �vidence le fait qu'on a bel et bien essay� d'ins�rer une ligne, mais qu'elle a �chou�e.

    Bon, en m�me temps, la librairie que j'ai �crit ne permet pas non plus, au sein de la transaction, de d�cider si une ligne est prise en compte ou non dans la transaction. La seule solution sera d'ouvrir une seconde connexion (et serrer les fesses pour en pas avoir de LOCK), ou utiliser, comme �a existe sous Oracle, une instruction qui permet de rendre non transactionnelle une requ�te � l'int�rieur d'une transaction.

  8. #8
    Expert confirm�
    Avatar de StringBuilder
    Homme Profil pro
    Chef de projets
    Inscrit en
    F�vrier 2010
    Messages
    4 197
    D�tails du profil
    Informations personnelles :
    Sexe : Homme
    �ge : 46
    Localisation : France, Rh�ne (Rh�ne Alpes)

    Informations professionnelles :
    Activit� : Chef de projets
    Secteur : High Tech - �diteur de logiciels

    Informations forums :
    Inscription : F�vrier 2010
    Messages : 4 197
    Billets dans le blog
    1
    Par d�faut
    Je viens de :
    - Enrichir la librairie avec un second constructeur pour MyTransaction ainsi que les m�thodes CreateNestedTransaction() et CreateCommand()
    Ces deux m�thodes ne sont l� que pour commodit� (et lisibilit� du code). Elles n'apportent rien d'un point de vue fonctionnel, contrairement � ce qu'on pourrait croire !
    - Am�liorer la documentation
    - Donner des exemples unitaires d'utilisation des m�thodes

Discussions similaires

  1. R�ponses: 5
    Dernier message: 24/08/2005, 11h21
  2. Apropos des Transactions au sein d'un Stored Procedure
    Par Sarbacane dans le forum Connexion aux bases de donn�es
    R�ponses: 6
    Dernier message: 16/11/2004, 08h21
  3. Annuler des transactions
    Par sgire dans le forum ASP
    R�ponses: 2
    Dernier message: 04/05/2004, 09h31
  4. gestion des transactions
    Par viny dans le forum Requ�tes
    R�ponses: 2
    Dernier message: 26/03/2004, 21h53
  5. R�ponses: 12
    Dernier message: 18/03/2004, 15h09

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo