2010-03-21 8 views
3

Le code suivant jette un TransactionAbortedException avec le message "La transaction a avorté" et un TransactionPromotionException intérieur avec le message "Échec lors d'une tentative de promouvoir transaction":Membership.GetUser() dans TransactionScope lance TransactionPromotionException

using (TransactionScope transactionScope = new TransactionScope()) 
    { 
     try 
     { 
      using (MyDataContext context = new MyDataContext()) 
      { 
       Guid accountID = new Guid(Request.QueryString[ "aid" ]); 
       Account account = (from a in context.Accounts where a.UniqueID.Equals(accountID) select a).SingleOrDefault(); 
       IQueryable <My_Data_Access_Layer.Login> loginList = from l in context.Logins where l.AccountID == account.AccountID select l; 

       foreach (My_Data_Access_Layer.Login login in loginList) 
       { 
        MembershipUser membershipUser = Membership.GetUser(login.UniqueID); 
       } 

       [... lots of DeleteAllOnSubmit() calls] 

       context.SubmitChanges(); 
       transactionScope.Complete(); 
      } 
     } 

     catch (Exception E) 
     { 
     [... reports the exception ...] 
     } 
    } 

L'erreur se produit à l'appel au Membership.GetUser().

Ma chaîne de connexion est:

 <add name="MyConnectionString" connectionString="Data Source=localhost\SQLEXPRESS;Initial Catalog=MyDatabase;Integrated Security=True" 
    providerName="System.Data.SqlClient" /> 

Tout ce que j'ai read me dit que TransactionScope devrait juste obtenir par magie appliquée aux appels d'adhésion. L'utilisateur existe (je m'attendrais à un retour null autrement.)

+0

Que fait 'GetUser'? Si vous recherchez simplement des choses, quel est le besoin de 'TransactionScope'? – shahkalpesh

+0

GetUser() fait partie de l'API Microsoft Membership. J'ai supprimé beaucoup de code qui supprime toutes les connexions pour un compte, puis le compte lui-même, pour des raisons de lisibilité. –

Répondre

6

Les exceptions de masques de classe TransactionScope. Le plus probable est que quelque chose à l'intérieur de cette portée échoue (en lançant une exception), et le TransactionAbortedException est simplement un effet secondaire qui se produit lorsque le contrôle quitte le bloc using. Essayez de tout emballer à l'intérieur du TransactionScope dans un bloc try-catch, avec un rethrow à l'intérieur du catch, et de définir un point d'arrêt à cet endroit; vous devriez être capable de voir quelle est la véritable erreur.

Une autre chose, TransactionScope.Complete doit être la dernière instruction exécutée avant la fin du bloc using contenant le TransactionScope. Dans ce cas, vous devriez probablement vous débrouiller, puisque vous ne faites aucun travail par la suite, mais mettre l'appel à Complete à l'intérieur d'une portée interne a tendance à créer davantage de code sujet aux bogues.


Mise à jour:

Maintenant que nous savons ce que l'exception interne est (transaction promotion de l'échec), il est plus clair ce qui se passe.

Le problème est que dans le TransactionScope, vous ouvrez réellement une autre connexion de base de données avec GetUser. Le fournisseur d'adhésion ne sait pas comment réutiliser le DataContext que vous avez déjà ouvert; il doit ouvrir sa propre connexion, et quand le TransactionScope voit ceci, il essaye de favoriser à une transaction distribuée.

Échec car MSDTC est probablement désactivé sur le serveur Web, le serveur de base de données ou les deux.

Il n'y a pas moyen d'éviter la transaction distribuée si vous allez ouvrir deux connexions séparées, donc il y a vraiment quelques façons de contourner ce problème:

  1. Déplacer les GetUser appels en dehors la TransactionScope . C'est-à-dire, "lisez" les utilisateurs d'abord du fournisseur d'appartenance dans une liste, puis démarrez la transaction quand vous avez réellement besoin de commencer à faire des modifications.

  2. Supprimer complètement les appels GetUser et lire les informations utilisateur directement à partir de la base de données, sur le même DataContext ou au moins la même connexion.

  3. Activer DTC sur tous les serveurs participant à la transaction (les performances seront affectées lors de la promotion d'une transaction).

Je pense que l'option 1 sera la meilleure dans ce scénario; Il est très improbable que les données que vous devez lire auprès du fournisseur d'adhésion soient modifiées entre le moment où vous les lisez et le moment où vous commencez la transaction.

+1

Un autre aperçu précieux! J'ai déjà un bloc try-catch * en dehors du bloc using. Je vais le déplacer à l'intérieur, éditer mon code ci-dessus en conséquence puis rapporter sous peu. –

+0

... non. Toujours la même exception, même ligne. À noter - commenter le TransactionScope et cela fonctionne bien. A noter également - message d'exception interne "Echec lors de la tentative de promotion de la transaction" –

+0

@Bob Kaufman: En fait, cette exception interne est la plus importante. Je vais vous expliquer pourquoi dans un instant ... – Aaronaught

1

À un niveau, c'est correct; la transaction est toujours annulée (vous n'appelez pas Complete()). Est-ce le code exact?

De plus, ayant la DataContexten dehors le TransactionScope me fait soupçonner que cela pourrait être fait des choses bizarres car la transaction ne sont pas là quand les données du contexte premier est créé. Avez-vous essayé (les deux):

  • inverser l'ordre de création, de sorte que le TransactionScope enjambe la DataContext
  • appelant Complete

?

using (TransactionScope transactionScope = new TransactionScope()) 
using (MyDataContext context = new MyDataContext()) 
{ 
    /* ... */ 
    transactionScope.Complete(); 
} 
+0

J'ai transposé les contrôles TransactionScope et DataContext selon votre suggestion, ici et dans mon code, en vain. J'ai également ajouté l'appel à Complete() comme dans mon code pour mieux refléter mon code sans introduire un *** *** de bruit provenant de ma source d'origine. –

Questions connexes