Priya-Kiko Ответов: 2

Откат транзакции Entity framework не работает


DbUpdateJob_DoWork(object sender, DoWorkEventArgs e)
{
    using (DbContextTransaction mdbtrans =  
    Context.Database.BeginTransaction(IsolationLevel.Serializable))
    {
       try
       {
            --- db operations with entityA  - 1  ( Insert records )
            DbUpdateJob.ReportProgress(20);

            --- db operations with entityB - 2 ( Insert records)
            DbUpdateJob.ReportProgress(40);

            --- db operations with entityC - 3 (Update records )
            DbUpdateJob.ReportProgress(60);

            --- db operations wiwth entityD - 4 (Insert records)
            DbUpdateJob.ReportProgress(80);

            Context.SaveChanges();
            mdbtrans.Commit();
            DbUpdateJob.ReportProgress(100);
       }
       catch(Exception ex)
       {
           mdbtrans.Rollback();
           DbUpdateJob.ReportProgress(0);
       }
   }
}

DbUpdateJob_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
   pbupdate.Value = e.ProgressPercentage;
   if (e.ProgressPercentage == 0)
      MessageBox.Show(e.Message); 
   if (e.ProgressPercentage == 100)
      MessageBox.Show("Success..!!"); 

}


Использование SQL Server 2014, Entity Framework 6.0, .Net Framework 4.7.2, C# Winforms.

В приведенном выше коде, если исключение генерируется где-то между ними, оно приходит в блок catch и показывает сообщение об ошибке. Но транзакция не откатывает изменения, внесенные в таблицы до строки исключения.

Что может быть причиной такого поведения ? Пожалуйста, посоветуйте. Заранее спасибо.

Что я уже пробовал:

Ранее я использовал SqlTransaction, изменил его на DBContextTransaction. Удалены все внешние ссылки на библиотеки внутри блока транзакций. Не удалось найти соответствующую ссылку на решение в поиске Google. Большинство ссылок предполагают такую же логику ... но она работает не так, как ожидалось для меня.

0x01AA

И вы уверены, что исключение не происходит в DbUpdateJob.ReportProgress(100) что происходит после совершения коммита?

Priya-Kiko

Спасибо за ваш ответ. Я пропустил несколько строк при копировании кода здесь... в любом случае фактический код имеет некоторое исключение в операциях БД 1 или 2 внутри блока try.

2 Ответов

Рейтинг:
18

Dave Kreskowiak

Вы определяете контекст где-то еще? Он должен быть создан внутри DoWork, где происходит вся ваша работа с базой данных.

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

Там, где вы работаете с базой данных, шаблон должен быть открыт контекст как можно позже, выполнять работу с базой данных и закрывать/утилизировать контекст как можно раньше.

Кроме того, ваш блок try/catch нигде не сообщает об ошибке, поэтому он скрывает исключение и детали, которые его вызвали. Это значительно усложняет поиск и устранение неисправностей, таких как проблема, с которой вы столкнулись сейчас.

Для целей отладки либо избавьтесь от блока try/catch, либо зарегистрируйте сведения об исключении где-нибудь, где вы можете их прочитать.


Priya-Kiko

Спасибо, что пролили немного света. Контекст действительно был определен "где-то еще" глобально. Что касается отчетов об ошибках, то у нас есть процедура обработки ошибок. Это всего лишь пример кода.

Dave Kreskowiak

Ваш "пример кода" не помогает решить проблему.

Причина, по которой "глобальный" контекст является проблемой, заключается в том, что вы не можете иметь два метода, использующих контекст одновременно. Например, наличие одного метода, возвращающего записи из перечисления, и наличие второго метода, пытающегося использовать контекст для изменения записей, не будут работать и будут выбрасываться.

Priya-Kiko

Хорошо. Может быть, вы посоветуете мне вообще избавиться от глобального контекста?

Разве не правильно ограничивать глобальный контекст только операциями чтения для отчетов и т. д. и окончательно избавляться от него при выходе, в то время как мы используем другой локальный контекстный объект внутри using() для вставки, обновления и удаления БД ??

Теперь изменили код обновления следующим образом :

DbUpdateJob_DoWork(отправитель объекта, DoWorkEventArgs e)
{
использование (MyContext dbContext = new MyContext())
{
использование (DbContextTransaction mdbtrans =
DbContext можно.Базы данных.BeginTransaction(Уровень Изоляции.Упорядочиваемый))
{
пробовать
{
--- операции БД с entityA - 1 ( вставка записей )
DbUpdateJob.ReportProgress(20);

--- операции с БД с entityB - 2 ( вставка записей)
DbUpdateJob.ReportProgress(40);

--- операции БД с entityC - 3 (обновление записей )
DbUpdateJob.ReportProgress(60);

--- операции с БД wiwth entityD - 4 (вставка записей)
DbUpdateJob.ReportProgress(80);

DbContext можно.Метод SaveChanges();
мдбтранс.Совершать();
DbUpdateJob.ReportProgress(100);
}
поймать(исключение бывший)
{
мдбтранс.Отмена();
DbUpdateJob.ReportProgress(0);
}
}
}
}

DbUpdateJob_ProgressChanged(отправитель объекта, ProgressChangedEventArgs e)
{
pbupdate.Значение = e.ProgressPercentage;
если (например, ProgressPercentage == 0)
Ящик для сообщений.Показать(например,сообщение);
если (например, ProgressPercentage == 100)
Ящик для сообщений.Шоу("Успех..!!");

}

После создания локального контекста откат происходит совершенно нормально. Еще раз благодарю вас.

Dave Kreskowiak

Избавьтесь от глобального контекста. То, о чем вы не думаете, - это то, что вы захватываете ресурс подключения на SQL server для жизни вашего объекта BackgroundWorker. Ресурсы подключения на SQL server ограничены. Если ваш класс держит соединение открытым в течение всей жизни экземпляра, это плохо.

Всегда открывайте как можно позже, делайте свою работу как можно быстрее и закрывайте/избавляйтесь от контекста как можно раньше.

Priya-Kiko

Хорошо. Спасибо.

Dave Kreskowiak

Причина, по которой глобальный контекст так плох, заключается в разделении интересов и контроле времени жизни объекта. Используя глобальный контекст на уровне класса, вы предоставляете вызывающему объекту контроль над временем жизни контекста, а не над классом, который его использует. Это наоборот. Класс, использующий контекст, который выполняет работу с базой данных, должен иметь контроль над временем жизни контекста.

Priya-Kiko

Я получил очень ясную картину из вашего подробного объяснения, которое также привело к нескольким дальнейшим чтениям. Большое спасибо за это. Удалили глобальный контекст и, как вы предложили, объявили его внутри using (), где когда-либо выполняется работа с базой данных. Спасибо снова.

Рейтинг:
1

Gerry Schmitz

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

Работа с транзакциями - EF6 | Microsoft Docs[^]