Membership.GetUser() в TransactionScope вызывает исключение TransactionPromotionException

Следующий код выдает TransactionAbortedException с сообщением «Транзакция прервана» и внутренний 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 ...]
        }
    }

Ошибка возникает при вызове Membership.GetUser().

Моя строка подключения:

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

Все, что я прочитал, говорит мне о том, что TransactionScope должно просто волшебным образом применяться на членские звонки. Пользователь существует (в противном случае я ожидаю возврата null.)


person Bob Kaufman    schedule 21.03.2010    source источник
comment
Что делает GetUser? Если вы просто спрашиваете о вещах, зачем нужен TransactionScope?   -  person shahkalpeshp    schedule 21.03.2010
comment
GetUser() является частью API членства Microsoft. Я удалил много следующего кода, который удаляет все входы в систему для учетной записи, а затем саму учетную запись, для удобочитаемости.   -  person Bob Kaufman    schedule 21.03.2010


Ответы (2)


Класс TransactionScope маскирует исключения. Скорее всего, что-то внутри этой области действия дает сбой (выдает исключение), а TransactionAbortedException — это просто побочный эффект, возникающий, когда управление выходит из блока using.

Попробуйте обернуть все внутри TransactionScope в блок try-catch с повторным броском внутри catch и установить там точку останова; вы должны увидеть реальную ошибку.

Еще одна вещь: TransactionScope.Complete должен быть последним оператором, выполняемым перед концом блока using, содержащего TransactionScope. В этом случае вы, вероятно, должны быть в порядке, поскольку после этого вы на самом деле не выполняете никакой работы, но размещение вызова Complete во внутренней области, как правило, делает код более подверженным ошибкам.


Обновление:

Теперь, когда мы знаем, что такое внутреннее исключение (сбой продвижения транзакции), становится более ясно, что происходит.

Проблема в том, что внутри TransactionScope вы фактически открываете другое соединение с базой данных с GetUser. Поставщик членства не знает, как повторно использовать DataContext, который у вас уже открыт; он должен открыть свое собственное соединение, и когда TransactionScope увидит это, он попытается перейти к распределенной транзакции.

Это не удается, потому что вы, вероятно, отключили MSDTC на веб-сервере, сервере базы данных или на обоих.

Невозможно избежать распределенной транзакции, если вы собираетесь открывать два отдельных соединения, поэтому есть несколько способов обойти эту проблему:

  1. Переместите вызовы GetUser за пределы TransactionScope. То есть сначала «прочитайте» пользователей из поставщика членства в список, а затем запустите транзакцию, когда вам действительно нужно начать вносить изменения.

  2. Полностью удалите вызовы GetUser и считывайте информацию о пользователе непосредственно из базы данных по тому же DataContext или хотя бы тому же соединению.

  3. Включите DTC на всех серверах, участвующих в транзакции (на производительность повлияет продвижение транзакции).

Я думаю, что вариант № 1 будет лучшим в этом сценарии; очень маловероятно, что данные, которые вам нужно прочитать от поставщика членства, будут изменены между временем, когда вы их читаете, и временем, когда вы начинаете транзакцию.

person Aaronaught    schedule 21.03.2010
comment
Еще одна ценная информация! У меня уже есть блок try-catch снаружи блока using. Я перенесу его внутрь, соответствующим образом отредактирую код выше и вскоре сообщу. - person Bob Kaufman; 21.03.2010
comment
... Неа. Все то же исключение, та же строка. Следует отметить, что комментирование TransactionScope и работает нормально. Также следует отметить: внутреннее сообщение об исключении Ошибка при попытке продвигать транзакцию - person Bob Kaufman; 21.03.2010
comment
@ Боб Кауфман: На ​​самом деле, это внутреннее исключение является важным. Сейчас объясню почему... - person Aaronaught; 21.03.2010
comment
Я успешно пошел с вариантом № 2. Спасибо! № 1 непрактично; Я выбираю учетную запись, затем перечисляю и удаляю все логины под этой учетной записью. № 3 — мы оба согласны с тем, что это плохие новости на многих уровнях. - person Bob Kaufman; 21.03.2010
comment
У меня включен DTC на сервере, но я все еще получаю эту ошибку при вызове веб-метода. Кто-нибудь знает, почему? В основном я сделал 3 из списка, а 1 или 2 ко мне не относятся, и это все еще не работает. - person Para; 17.04.2012
comment
@Para, если большая часть этого ответа к вам не относится, похоже, вам нужно задать новый вопрос. Ни я, ни кто-либо другой не может вам чем-то помочь, учитывая так мало информации. - person Aaronaught; 18.04.2012
comment
@Aaronaught Привет, да, я опубликовал новый вопрос stackoverflow.com/questions/10195925/ - person Para; 18.04.2012

На одном уровне это правильно; транзакция всегда прерывается (вы не вызываете Complete()). Это точный код?

Кроме того, наличие DataContext вне TransactionScope заставляет меня подозревать, что он может делать какие-то странные вещи, поскольку транзакция отсутствует, когда впервые создается контекст данных. Вы пробовали (оба):

  • меняет порядок создания, поэтому TransactionScope охватывает DataContext
  • звоню Complete

?

using ( TransactionScope transactionScope = new TransactionScope() )
using ( MyDataContext context = new MyDataContext() )
{
    /* ... */
    transactionScope.Complete();
}
person Marc Gravell    schedule 21.03.2010
comment
Я переставил элементы управления TransactionScope и DataContext в соответствии с вашим предложением здесь и в моем коде, но безрезультатно. Я также добавил вызов Complete(), как в своем коде, чтобы лучше отразить мой код, не внося много шума из моего исходного кода. - person Bob Kaufman; 21.03.2010