2010-04-30 6 views
26

Je tente d'imbriquer TransactionScopes (.net 4.0) comme vous imbriquez des transactions dans SQL Server, mais il semble qu'elles fonctionnent différemment. Je souhaite que mes transactions enfants puissent être annulées en cas d'échec, mais permettent à la transaction parente de décider si l'opération entière doit être validée/restaurée. Le problème est lorsque la première complète se produit, la transaction est annulée. Je réalise que complet est différent à commettre.Nested/Child TransactionScope Rollback

Un exemple très simplifié de ce que je suis en train de faire:

static void Main(string[] args) 
{ 
    using(var scope = new TransactionScope()) // Trn A 
    { 
     // Insert Data A 

     DoWork(true); 
     DoWork(false); 

     // Rollback or Commit 
    } 
} 

// This class is a few layers down 
static void DoWork(bool fail) 
{ 
    using(var scope = new TransactionScope()) // Trn B 
    { 
     // Update Data A 

     if(!fail) 
     { 
      scope.Complete(); 
     } 
    } 
} 

Je ne peux pas utiliser les options ou RequiresNew comme Suppress Trn B repose sur des données insérées par Trn A. Si j'utiliser ces options , Trn B est bloqué par Trn A.

Des idées comment je voudrais le faire fonctionner, ou si c'est même possible en utilisant l'espace de noms System.Transactions?

Merci

Répondre

46

Vous n'êtes probablement pas aimer cette réponse, mais ...

vote dans un champ imbriqué

Bien qu'un champ imbriqué peut se joindre à la transaction ambiante de l'étendue racine, l'appel complet dans la portée imbriquée n'a aucun effet sur la portée racine. Seulement si toutes les étendues de la portée de la racine jusqu'à la dernière portée imbriquée votent pour valider la transaction, la transaction sera validée.

(De Implementing an Implicit Transaction using Transaction Scope)

La classe TransactionScope ne fournit malheureusement pas un mécanisme (que je sache) pour séparer des unités de travail. C'est tout ou rien. Vous pouvez empêcher toute transaction de se produire sur une unité de travail spécifique en utilisant TransactionScopeOption.Suppress, mais ce n'est probablement pas ce que vous voulez, car vous perdriez alors l'atomicité pour tout ce qui se trouve dans cette portée.

Il n'y a qu'une seule transaction "ambiante" lorsque vous utilisez un TransactionScope. Une fois qu'un TransactionScope est disposé ou est collecté sans que Complete soit exécuté, la transaction ambiante entière est annulée; c'est tout, jeu terminé.

En fait, SQL Server ne prend pas en charge les transactions imbriquées vraies du tout, bien qu'il est possible (quoique un peu unintuitive) pour obtenir le même résultat final avec l'utilisation appropriée des SAVE TRAN déclarations. Ré-implémenter cette logique comme une procédure stockée (ou plusieurs d'entre eux) pourrait être votre meilleure option si vous avez besoin de ce comportement particulier.

+0

Merci pour cela, j'avais peur d'une réponse comme celle-ci. –

+0

Je n'ai pas compris: pourquoi la réponse est décevante? Robert veut que toute portée interne soit capable de revenir en arrière, alors que seul le plus éloigné devrait pouvoir s'engager. Une solution simple sera: dans chaque portée, appelez complete() si vous ne voulez pas restaurer. Ensuite, la portée la plus éloignée a la possibilité de valider, uniquement si tous les internes choisissent de ne pas restaurer. – Alireza

+3

@Alireza: L'OP veut commettre sélectivement certaines transactions internes tout en annulant tout * else * qui n'était pas explicitement validé.Votre "solution simple" est exactement comme fonctionne le 'TransactionScope' et est documentée pour fonctionner et clairement expliquée par cette réponse même, mais elle ne commettra pas réellement les changements faits dans les portées intérieures si la portée externe ne se termine pas. Ce n'est tout simplement pas possible en utilisant 'System.Transactions'. – Aaronaught