Frédéric Colin
WCF : mise en place d'une transaction faisant intervenir Transactional NTFS
Windows Communication Foundation permet de mettre en place des transactions de manière simple et déclarative. Dans cet exemple, je vais vous montrer comment travailler en mode TransactionFlow et comment enrôler un autre type de Resource Manager : TxF
Par Frédéric Colin publié le 20/01/2008 à 22:07
 
Dans le monde .NET, les transactions respectent les caractéristiques de base classiques à savoir : Atomicité, Cohérence, Isolation et Durabilité. Au sein d'une application .NET donnée, les transactions peuvent être gérées à différent niveaux :
  • Au niveau du « Resource Manager » directement (un SGBD par exemple)
  • Au niveau ADO.NET
  • Au niveau COM+
  • En utilisant « System.Transaction »
Etant entendu que c'est en définitive à la charge de chaque « Resource Manager » de fournir son propre mécanisme de compensation en cas d'échec d'une étape de la transaction pour assurer l'annulation des actions le concernant.
Vous imaginez facilement, que plus la transaction sera pilotée proche du SGBD, plus elle sera performante et par conséquent ... propriétaire. D'un autre côté, plus l'on s'éloigne du « Resource Manager », plus il sera facile pour la transaction de « s'enrôler » dans une transaction ambiante gérée à un autre niveau : via Microsoft Distributed Transaction Coordinator, le service MSDTC bien connu des systèmes d'exploitation Windows.
Bref, le choix de la méthode est fonction de critères discriminants tels que :
  • Besoin d'interagir avec des « Resources Managers » hétérogènes distribués ou non
  • Le besoin précédent est-il ponctuel ou systématique
  • Les transactions sont-elles gérées dans le corps d'une seule méthode ou réparties sur différentes méthodes de classes.
Pour ma part, je choisis souvent la voie du milieu bien connue des bouddhistes : ni trop, ni trop peu. Et il est vrai qu'avec l'arrivée de « System.Transaction », le choix devient plus simple à faire. Pour mettre en oeuvre lesdites transactions de cet exemple, nous utiliserons l'espace de nommage « System.Transaction » disponible depuis le Framework 2.0.
Comme je le disais précédemment, cet espace de nom au travers de sa classe « TransactionScope » représente un bon compromis. En effet, il s'agit d'un système intelligent hybride qui utilise par défaut une transaction locale et qui peut utiliser une transaction distribuée supportée par MSDTC. Ce système de gestion de transaction est donc capable de promouvoir une transaction dite « simple » vers une transaction distribuée si une des conditions suivantes est remplie :
  • Utilisation d'objets différents faisant intervenir de nouveaux contextes transactionnels
  • Transport du flot d'exécution vers un autre « AppDomain »
  • Interaction avec une un autre « Resource Manager »
Il va de soit que le temps de promotion d'une transaction simple vers une transaction distribuée a un coût sur les performances générales de la transaction (le temps de la promotion justement), mais c'est le prix de la généricité dans ce cas.

Quelques particularités importantes :
  • L'instance de la classe « TransactionScope » est souvent utilisée via un bloc « Using » chargé de la libération des ressources (TransactionScope est « IDisposable »)
  • Dans ce cadre là, il n'est pas utile d'annuler (et impossible car la méthode n'existe pas) la transaction dans le bloc. Le non-appel à la méthode « Complete » et la sortie du bloc « using » suffisent
  • Le TimeOut d'exécution est de 60 secondes
  • Possibilité d'imbriquer les Scope transactionnels les uns avec les autres
  • Possibilité d'abonnement à deux événements intéressants
    • System.Transactions.Transaction.Current.TransactionCompleted : transaction validée
    • System.Transactions.TransactionManager.DistributedTransactionStarted : promotion d'une transaction simple vers une transaction distribuée. Dans ce cas, il faut bien faire attention au fait que la transaction est promue après le traitement de l'événement.
Voici un exemple d'utilisation :

using (TransactionScope scope = new TransactionScope())

{

    try

    {

        using (SqlConnection myCn1 = new SqlConnection("..."))

        {

            SqlCommand cmd = new SqlCommand("Insert …", myCn1);

            cmd.CommandType = CommandType.Text;

            myCn1.Open();

            cmd.ExecuteNonQuery();

        }

 

        using (SqlConnection myCn2 = new SqlConnection("..."))

        {

            SqlCommand cmd = new SqlCommand("Delete …", myCn2);

            cmd.CommandType = CommandType.Text;

            myCn2.Open();

            cmd.ExecuteNonQuery();

        }

 

        scope.Complete();

    }

    catch (Exception ex) { … }

    finally { … }

}

On se rend bien compte de l'apport de transparence à l'utilisation d'un « TransactionScope ». Par la suite, il est possible d'accéder aux propriétés de la transaction ambiante via « System.Transactions.Transaction.Current ».
Le fonctionnement d'un gestionnaire de ressources distribuées tel que Microsoft Distributed Transaction Coordinator (MSDTC) est le suivant :
  • L'application appelle le Transaction Manager pour créer la transaction (gestion du contexte transactionnel, de l'atomicité et de la durabilité)
  • Tous les composants intervenants sont enrôlés dans la transaction ambiante
  • Le Transaction Manager garde de manière séquentielle un log afin que la décision finale de validation ou d'invalidation soit durable et garantie
  • Au final, si tous les RM impliqués sont prêts alors la transaction est validée et le log est effacé
  • Sinon le Transaction Manager diffuse un ordre d'annulation à tous les « Resources Managers » impliqués
Il faut savoir que WCF supporte les transactions « natives » et « WS-Atomic » (standard). Ce dernier protocole définit comment implémenter des transactions sur la base d'un commit à deux phases :
  • Phase 1
    • Le coordinateur demande à chaque « Resource Manager » (RM) de se préparer au commit
    • Chaque RM vote pour valider ou invalider la transaction
    • Le coordinateur collecte les votes et prend une décision (commit ou rollback)
  • Phase 2
    • Le coordinateur demande à chaque RM de valider ou d'invalider
    • Si un RM doit valider, il accuse réception de l'achèvement ou bien réalise le rollback
    • Le coordinateur attend les AR des RM pour indiquer que la transaction s'est terminée correctement
Ce mécanisme permet donc d'implémenter des transactions de type « tout ou rien » avec des systèmes faiblement couplés dispersés sur différentes plateformes. WCF fournit une implémentation de WS-AT construite sur MSDTC. Il est possible de configurer WS-AT via les propriétés du DTC. Pour cela, il faut registrer la dll suivante pour faire apparaitre l'onglet qui va bien !
  • Regasm.exe /codebase wsatui.dll (« C:\Program Files\Microsoft SDKs\Windows\v6.0A\bin »)
 
Onglet WS-AT MSDTC

Onglet WS-AT MSDTC

 
Finalement, au beau milieu de la réalisation de l'article, je me suis dit qu'il serait dommage de réaliser la couche l'accès aux données avec le Fx 3.5 sans utiliser Linq To SQL. Je m'y suis donc attelé afin de mettre en place 3 choses : la sélection éventuellement filtrée, l'ajout et la suppression.

Linq To Sql est une implémentation d'un petit mapper objet-relationnel basé sur ADO.NET, intégré à Visual Studio 2008 et ciblant pour l'instant exclusivement les bases SQL Server. Il permet donc de faire la liaison entre des classes et des tables en base de données de manière quasi transparente pour le développeur. Je dis quasi transparente, car il faudra quand même décrire le mapping existant soit à la main sous la forme d'un fichier de mapping ou bien avec des attributs, soit en utilisant le Designer intégré à Visual Studio 2008. Voici un exemple de requêtage que j'ai pu utiliser au travers d'une classe générique :

public List<T> GetTable(Func<T, Boolean> predicate)

{

    DataContext dc =

        new DataContext(

            ConfigurationManager.ConnectionStrings["NorthwindConnectionString"].ConnectionString,

            new System.Data.Linq.Mapping.AttributeMappingSource()

        );

 

    return (

        (from c in dc.GetTable<T>()

        select c).Where(predicate).ToList()

    );

}

Je reviendrai techniquement sur cette partie un peu plus loin dans cet article. Ce que j'apprécie particulièrement dans l'utilisation de Linq To Sql et de la syntaxe Linq en général c'est que l'on décrit précisément ce que l'on veut obtenir, mais pas comment faire pour l'obtenir. C'est donc l'implémentation sous-jacente de Linq qui se charge du comment (Linq To Object, Linq To Xml, Linq To Sql, etc.) contrairement à ce que l'on faisait avant en ouvrant précisément une connexion et en exécutant tel ordre SQL par exemple. L'idée d'apporter intrinsèquement et nativement au langage le requêtage sans passer par un Framework compliqué est brillante.
Vous trouverez sur le Web de nombreuses explications sur l'utilisation de Linq To Sql et je ne m'étendrai pas davantage là-dessus. Je vous recommande donc le plan suivant pour une mise en jambe rapide :
  • Le Training Kit Visual Studio 2008
  • Les vidéos réalisées par Mitsuru Furuta (Microsoft) réalisé lors du Tour de France Accès aux données
  • Les quelques articles rédigés par Scott Guthrie sur Linq To SQL et son intégration avec ASP.NET
Une chose était importante pour moi dans cet exemple : le fait de pouvoir utiliser mes propres définitions de contrat de données et non pas celles générées par le Designer Graphique de Linq To Sql. Or, le Designer Linq To SQL ne permet pas cette séparation stricte. J'ai donc du réaliser cela à la main et j'ai choisi la décoration par attribut à la définition d'un fichier de mapping car plus simple à faire à la main.
Non non, il ne s'agit pas d'une marque de motos bien connue des spécialistes, mais d'un service nouvellement offert par les plateformes Windows Vista et bientôt Windows Server 2008 seules. Il permet de rendre les transactions disponibles au niveau des composants systèmes tels que NTFS (Transactional NTFS ou TxF) et la base de registres (Transactional Registry ou TxR).

Le KTM est aussi dépendant d'une autre API Système, le Common Log File System (CLFS) assurant la gestion des logs, rendant ainsi possible une gestion transactionnelle. Dans le cadre de cet article, nous travaillerons seulement sur TxF. TxF permet ainsi aux opérations de gestion de fichiers sur une partition NTFS de participer à une transaction ambiante, qu'elle soit distribuée ou non. Par exemple :
  • Mise à jour d'un fichier (contenu et attributs)
  • La création d'un fichier et l'insertion en base de données
  • La création de fichiers sur plusieurs machines
  • Participation à une transaction avec plusieurs autres Resource Managers transactionnels (SGBD, MSMQ)
  • Scénario supporté : un écrivain – plusieurs lecteurs concurrents et consistants. Les lecteurs transactionnels voient toujours une version consistante du fichier accédé. Attention, il n'y a pas de support pour les mises à jour concurrentes sur différentes transactions. La conséquence est que TxF n'est pas la solution adéquate pour les scénarios avec des utilisateurs multiples.
Comme il s'agit d'une API que je qualifierai de « bas niveau », cela nécessite de manipuler des structures et des fonctions C. Nous ferons alors beaucoup de P/Invoke. Voici la logique de l'utilisation :
  • Création de la transaction ambiante via un TransactionScope
  • Récupération d'un « Transacted File Handle » en écriture en lieu et place d'un simple handle
  • TxF verrouille l'accès
    • Toute opération de modification sur le fichier en dehors de cette transaction recevra une erreur
    • Tout lecteur transactionnel sur le fichier recevra une erreur à l'ouverture dès lors que le fichier est ouvert dans un cadre non transactionnel. Ce tableau issu de la documentation du MSDN résume bien les verrouillages :
       
      Verrouillages TxF

      Verrouillages TxF

       
    • En d'autres termes, TxF fournit un niveau d'isolation de type « Read-Committed ». Cela signifie qu'en mode transactionnel :
      • Un écrivain unique verra la version la plus récente ainsi que tous les changements réalisés dans le cadre de cette transaction.
      • Un lecteur verra seulement la dernière version validée qui existait à l'ouverture du fichier. Le lecteur est donc complètement isolé des modifications des écrivains transactionnels.
  • Travail sur le handle transactionnel
  • Fermeture du handle transactionnel (avant de faire le commit)
  • Validation / invalidation de la transaction ambiante
WCF utilise des « Policy Assertions » afin de contrôler et paramétrer le flot transactionnel. Ces dernières sont spécifiées par des attributs à la fois sur les contrats et les services, mais aussi par configuration.

Les transactions peuvent être initiées par le client ou par le service accédé. Dans le cas d'une transaction initiée par le client, nous utiliserons l'attribut « TransactionFlow » sur le contrat de service afin de préciser la capacité d'un service à accepter une transaction provenant d'un client et à se paramétrer en conséquence. Bien entendu, cette notion n'est pas applicable aux méthodes « OneWay » du fait de leurs natures asynchrones.

[OperationContract()]

[TransactionFlow(TransactionFlowOption.Mandatory)]

void Debiter(Int32 NumCompte, Decimal montant);

D'autres paramètres viennent influencer le comportement transaction d'un service. L'attribut « ServiceBehavior » applicable sur la classe qui implémente les méthodes d'un contrat de service possède des propriétés intéressantes :
  • TransactionAutoCompleteOnSessionClose » (false)
    • Toute transaction incomplète sera complétée quand la session fermera. Si true, le channel entrant devra gérer les sessions
  • « ReleaseServiceInstanceOnTransactionComplete » (true)
    • Précise si l'instance du service doit être libérée lorsque la transaction est complétée. Si true, tout nouveau message entraine la création d'une nouvelle instance
  • « TransactionIsolationLevel » (Unspecified)
    • Spécifie le niveau d'isolation de la transaction au sein du service
  • « TransactionTimeout » (60 secondes)
    • Durée maximale pendant laquelle la nouvelle transaction doit être complétée
Enfin, l'attribut « OperationBehavior », applicable sur l'implémentation d'une méthode en particulier, permet aussi de paramétrer une transaction via les propriétés :
  • Propriété « TransactionScopeRequired » (false)
    • Indique si l'opération doit s'exécuter au sein d'une transaction
  • Propriété « TransactionAutoComplete » (true)
    • Auto-commit de la transaction si l'opération s'exécute avec succès

[OperationBehavior(TransactionScopeRequired = true, TransactionAutoComplete = true)]

public int AddNewEmployee(Sample.DataContracts.Employee employee)

Maintenant que nous avons succinctement passé en revue l'ensemble des technologies utilisées dans cet article, analysons un peu le code de la solution.

 Commentaire - WCF : mise en place d'une transaction faisant intervenir Transactional NTFS 

 Dernières Publications      

Utilisation de jQuery avec ASP.NET MVC
  Développer une IHM à page unique avec ASP.NET MVC et jQuery
par Nicolas Moyère posté le 30/06/2008 à 10:28, lu 824 fois, #0
Tags: ASP.NET MVC, Ajax
Windows Media Center et WCF : développez votre maison intelligente
  Le développement d'applications pour Windows Media Center est facilité avec l'arrivée du SDK 5.3. Même si l'on sent un modèle objet bien lourd derrière, il devient plus facile d'exposer les fonctionnalités de WMC sous la forme de services WCF.
par Frédéric Colin posté le 23/06/2008 à 08:04, lu 891 fois, #0
Notions avancées avec Biztalk Server 2006 R2
  Utilisation des notions d'interchange, corrélation et convoi avec BizTalk Server 2006 R2
par Kader Yildirim posté le 09/06/2008 à 08:04, lu 705 fois, #0
Lucene Persistence Engine pour Evaluant Universal Storage Services
  Suite à l'article de Laurent Kempé, voici un moteur de stockage pour EUSS permettant l'indexation d'entités métier avec Lucene.
par Nicolas Penin posté le 01/06/2008 à 23:38, lu 1091 fois, #1
Tags: C#, Linq
XMLA Trivia : Découverte du XMLA
  Le XMLA (XML for Analysis) est un langage normalisé par plusieurs éditeurs BI pour simplifier l'accès aux données aux cubes et aux métadonnées des bases multidimensionnelles.
par Renaud Harduin posté le 25/05/2008 à 11:57, lu 1008 fois, #1
Exploiter les données CSV via Linq en toute simplicité
  A partir du requêteur dynamique fourni en exemple avec Visual Studio 2008, nous allons essayer de remplir les propriétés d'un ensemble d'objets à partir des données d'un fichier CSV. Nous enrichirons aussi le parseur de nos propres fonctions.
par Frédéric Mélantois posté le 17/05/2008 à 11:41, lu 2785 fois, #0
Comment manipuler simplement le contenu d'un fichier WordML ?
  Manipulations autour du format WordML
par Fabien Reinle posté le 14/05/2008 à 23:55, lu 1405 fois, #0
Polymorphisme et contrats de données WCF
  WCF aborde les types polymorphes du point de vue de la sérialisation. En effet, la connaissance du type réel potentiel est rendue nécessaire dès la description du contrat de données. Une fois n'est pas coutume, j'ai réalisé l'exemple en VB.NET.
par Frédéric Colin posté le 14/05/2008 à 08:40, lu 2931 fois, #2

 Dernières Actualités      

Reprise du projet Reflector par RedGate
  La nouvelle était connue depuis quelques jours par les développeurs de plugins, mais c’est désormais officiel : Lutz Roeder, le responsable de Reflector confie à la société RedGate le futur du projet....
Microsoft publie Visual Studio 2008 Service Pack 1
  Il est recommandé d’utiliser l’outil Visual Studio 2008 Service Pack preparation Tool avant de faire l’installation du Service Pack si vous avez installé des versions béta sur votre machine. Une fois que...
Tags: Framework .NET, Visual Studio 2008
Evaluant dévoile ses sources
  L'ensemble des projets R&D réalisés par les consultants de la SSII Evaluant sont en cours de publication sur CodePlex . L'objectif est de les centraliser et surtout d'augmenter leur visibilité. L'avantage...
Le Silverlight Tour en français!
  Le Silverlight Tour passe maintenant dans les pays francophones! En effet RunAtServer Consulting est partenaire du Silverlight Tour pour la gestion de cette formation Silverlight en français à commencer...
Microsoft publie ASP.NET AJAX 4.0 CodePlex Preview 1
  Cette pré-version contient les améliorations suivantes: Client-side template rendering Declarative instantiation of behaviors and controls DataView control Markup extensions Bindings Vous pouvez en lire...
Tags: Ajax
Deep Earth – Une belle utilisation de Virtual Earth et de Silverlight Deep Zoom
  Ce projet très intéressant est disponible sur Codeplex et vous pouvez voir une démo sur la page suivante . Bien entendu comme touts les projets sur Codeplex vous avez accès aux sources....
Tags: Silverlight