3

The following code throws a TransactionAbortedException with message "The transaction has aborted" and an inner TransactionPromotionException with message "Failure while attempting to promote transaction":

    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 ...]
        }
    }

The error occurs at the call to Membership.GetUser().

My Connection String is:

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

Everything I've read tells me that TransactionScope should just get magically applied to the Membership calls. The user exists (I'd expect a null return otherwise.)

Bob Kaufman
  • 12,864
  • 16
  • 78
  • 107
  • What does `GetUser` do? If you are merely querying for things, what is the need of `TransactionScope`? – shahkalpesh Mar 21 '10 at 14:49
  • GetUser() is part of the Microsoft Membership API. I've deleted a lot of code that follows that deletes all logins for an account then the account itself, for readability's sake. – Bob Kaufman Mar 21 '10 at 15:18

2 Answers2

6

The TransactionScope class masks exceptions. Most likely what's happening is that something inside that scope is failing (throwing an exception), and the TransactionAbortedException is simply a side-effect that occurs when control exits the using block.

Try wrapping everything inside the TransactionScope in a try-catch block, with a rethrow inside the catch, and set a breakpoint there; you should be able to see what the real error is.

One other thing, TransactionScope.Complete should be the last statement executed before the end of the using block containing the TransactionScope. In this case you should probably be alright, since you're not actually doing any work afterward, but putting the call to Complete inside an inner scope tends to make for more bug-prone code.


Update:

Now that we know what the inner exception is (failure promoting transaction), it's more clear what's going on.

The problem is that inside the TransactionScope, you are actually opening up another database connection with GetUser. The membership provider doesn't know how to re-use the DataContext you already have open; it has to open its own connection, and when the TransactionScope sees this, it tries to promote to a distributed transaction.

It's failing because you probably have MSDTC disabled on either the web server, the database server, or both.

There's no way to avoid the distributed transaction if you are going to be opening two separate connections, so there are really a few ways around this issue:

  1. Move the GetUser calls outside the TransactionScope. That is, "read" the users first from the membership provider into a list, then start the transaction when you actually need to start making modifications.

  2. Remove the GetUser calls altogether and read the user information directly from the database, on the same DataContext or at least the same connection.

  3. Enable DTC on all servers participating in the transaction (performance will be impacted when a transaction promotes).

I think that option #1 is going to be the best in this scenario; it's very unlikely that the data you need to read from the membership provider will be changed between the time you read it and the time you begin the transaction.

Aaronaught
  • 120,909
  • 25
  • 266
  • 342
  • 1
    Another valuable insight! I've already got a try-catch block *outside* the using block. I'll move it inside, edit my code above accordingly then report back shortly. – Bob Kaufman Mar 21 '10 at 16:52
  • ... nope. Still the same exception, same line. Of note -- commenting out the TransactionScope and it works fine. Also of note -- inner exception message "Failure while attempting to promote transaction" – Bob Kaufman Mar 21 '10 at 16:57
  • @Bob Kaufman: Actually, that inner exception is the important one. Will explain why in a moment... – Aaronaught Mar 21 '10 at 16:59
  • I successfully went with option #2. Thank you! #1 isn't practical; I'm selecting an "Account" then enumerating over and deleting all the Logins under that account. #3 - we're both in agreement that this is bad news on many levels. – Bob Kaufman Mar 21 '10 at 17:21
  • I have DTC enabled on the server but I am still getting this error when calling a web method. Does anyone have any idea why? Basically I did 3 on the list and 1 or 2 don't apply to me and it still doesn't work. – Para Apr 17 '12 at 10:48
  • @Para, if a large part of this answer doesn't apply to you then it sounds like you need to ask a new question. Neither I nor anyone else can be of any help to you given so little information. – Aaronaught Apr 17 '12 at 23:45
  • @Aaronaught Hi, yes I have posted a new question http://stackoverflow.com/questions/10195925/nhibernate-and-transactionscope-transaction-aborted-spontaniously – Para Apr 18 '12 at 04:02
1

At one level, it is correct; the transaction is always aborted (you aren't calling Complete()). Is that the exact code?

Additionally, having the DataContext outside the TransactionScope makes me suspect that it might be doing some odd things since the transaction isn't there when the data-context first gets created. Have you tried (both of):

  • reversing the creation order, so the TransactionScope spans the DataContext
  • calling Complete

?

using ( TransactionScope transactionScope = new TransactionScope() )
using ( MyDataContext context = new MyDataContext() )
{
    /* ... */
    transactionScope.Complete();
}
Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
  • I've transposed the TransactionScope and DataContext controls per your suggestion, here and in my code, to no avail. I've also added the call to Complete() as in my code to better reflect my code without introducing a ***lot*** of noise from my original source. – Bob Kaufman Mar 21 '10 at 16:17