Використання транзакцій чи SaveChanges (помилкових) та AcceptAllChanges ()?


346

Я досліджувала угоди і, здається , що вони піклуються про себе в EF тих пір , поки я проходжу falseдо SaveChanges()і потім викликати , AcceptAllChanges()якщо немає помилок:

SaveChanges(false);
// ...
AcceptAllChanges();

Що робити, якщо щось піде погано? чи не потрібно мені відмовлятись, або, як тільки мій метод виходить із сфери застосування, транзакція закінчується?

Що трапиться з стовпчиками indentiy, яким було призначено на півдорозі транзакції? Я припускаю, якщо хтось ще додав запис після мого, перш ніж міна пішла погано, то це означає, що буде відсутнім значенням Identity.

Чи є причина використовувати стандартний TransactionScopeклас у своєму коді?


1
Це допомогло мені зрозуміти, чому SaveChanges(fase); ... AcceptAllChanges();в першу чергу був візерунок. Зауважте, як прийнята відповідь на вищезазначене запитання написана автором блогу - і на який блог посилається інше питання. Все це поєднується.
Червоний горох

Відповіді:


451

З Entity Framework більшість часу SaveChanges()достатньо. Це створює транзакцію або залучає до будь-якої навколишньої транзакції і виконує всю необхідну роботу в цій транзакції.

Іноді, хоча SaveChanges(false) + AcceptAllChanges()спарювання корисне.

Найбільш корисне місце для цього - у ситуаціях, коли ви хочете зробити розподілену транзакцію в двох різних контекстах.

Тобто щось подібне (погано):

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save and discard changes
    context1.SaveChanges();

    //Save and discard changes
    context2.SaveChanges();

    //if we get here things are looking good.
    scope.Complete();
}

Якщо це context1.SaveChanges()вдалося, але context2.SaveChanges()не вдалося, вся розподілена транзакція припиняється. Але, на жаль, Entity Framework вже відмовився від змін context1, тому ви не можете відтворити або ефективно зареєструвати помилку.

Але якщо ви змінили код, щоб він виглядав так:

using (TransactionScope scope = new TransactionScope())
{
    //Do something with context1
    //Do something with context2

    //Save Changes but don't discard yet
    context1.SaveChanges(false);

    //Save Changes but don't discard yet
    context2.SaveChanges(false);

    //if we get here things are looking good.
    scope.Complete();
    context1.AcceptAllChanges();
    context2.AcceptAllChanges();

}

Поки виклик SaveChanges(false)надсилає необхідні команди в базу даних, сам контекст не змінюється, тому ви можете зробити це ще раз, якщо потрібно, або можете допитати, ObjectStateManagerякщо хочете.

Це означає, що якщо транзакція насправді викидає виняток, який ви можете компенсувати, повторно пробуючи або записуючи стан кожного контексту ObjectStateManagerдесь.

Докладніше див. У моєму блозі .


3
Це чудово, дякую ... Тож якщо щось не вдасться, мені не доведеться відкатати ?? SaveChanges, позначає її як збережену, але насправді не виконує, поки я не прийму змін. Але якщо щось піде не так .. Мені потрібно буде відкат, чи не повернеться я до правильного стану?
Марк Сміт

33
@ Марк: якщо ви маєте на увазі повернення об'єктів до стану, у якому вони є в базі даних, то ні, ви цього не хочете робити, тому що втратите всі зміни користувачів на об'єкти. . SaveChanges(false)робить фактичне оновлення бази даних, при цьому AcceptAllChanges()каже EF: "Гаразд, ви можете забути, які речі потрібно зберегти, оскільки вони були успішно збережені". Якщо SaveChanges(false)не вдасться, AcceptAllChanges()ніколи не буде викликано, і EF все одно вважатиме ваш об’єкт таким, що має властивості, які були змінені та потребують збереження назад у базі даних.
BlueRaja - Danny Pflughoeft

Чи можете ви порадити, як це зробити за допомогою Code First? Немає параметра для методу SaveChanges або AcceptAllChanges
Kirsten Greed

2
Я задав питання про використання цієї методики з Code First Here
Кірстен Жадібний

13
У EF 6.1 це більше неможливо. Чи знаєте ви, які саме коригування потрібно внести для роботи зараз?
Олексій Дресько

113

Якщо ви використовуєте EF6 (Entity Framework 6+), це змінилося для викликів бази даних до SQL.
Дивіться: http://msdn.microsoft.com/en-us/data/dn456843.aspx

використовувати context.Database.BeginTransaction.

Від MSDN:

using (var context = new BloggingContext()) 
{ 
    using (var dbContextTransaction = context.Database.BeginTransaction()) 
    { 
        try 
        { 
            context.Database.ExecuteSqlCommand( 
                @"UPDATE Blogs SET Rating = 5" + 
                    " WHERE Name LIKE '%Entity Framework%'" 
                ); 

            var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
            foreach (var post in query) 
            { 
                post.Title += "[Cool Blog]"; 
            } 

            context.SaveChanges(); 

            dbContextTransaction.Commit(); 
        } 
        catch (Exception) 
        { 
            dbContextTransaction.Rollback(); //Required according to MSDN article 
            throw; //Not in MSDN article, but recommended so the exception still bubbles up
        } 
    } 
} 

52
спроба лову з відкатом не потрібна, коли ви використовуєте "використання" для транзакції.
Роберт

12
Я беру виняток, щоб захопити такий виняток. Це призводить до того, що робота бази даних мовчки не працює. Через характер SO, хтось може взяти цей приклад і використовувати його у виробничому додатку.
B2K

3
@ B2K: Добре, але цей код скопійовано із пов'язаної статті Microsoft. Сподіваюся, ніхто не використовує свій код у виробництві :)
J Bryan Ціна

6
@Robert Відповідно до статті MSDN, відкат () необхідний. Вони цілеспрямовано залишають команду відкоту для прикладу TransactionScope. @ B2K Я додав у throw;фрагмент MSDN і чітко вказав, що це не оригінал із статті MSDN.
Тодд

6
(Якщо правильно) Це може прояснити: звуки, схожі на EF + MSSQL, не потребують відкату, але, можливо, EF + інші постачальники SQL. Оскільки EF має бути агностиком, з якою базою даних він спілкується, Rollback()викликається у випадку, якщо він спілкується з MySql або чимось, що не має такої автоматичної поведінки.
Слова як Джаред

-5

Оскільки деяка база даних може кинути виняток у dbContextTransaction.Commit (), тож краще це:

using (var context = new BloggingContext()) 
{ 
  using (var dbContextTransaction = context.Database.BeginTransaction()) 
  { 
    try 
    { 
      context.Database.ExecuteSqlCommand( 
          @"UPDATE Blogs SET Rating = 5" + 
              " WHERE Name LIKE '%Entity Framework%'" 
          ); 

      var query = context.Posts.Where(p => p.Blog.Rating >= 5); 
      foreach (var post in query) 
      { 
          post.Title += "[Cool Blog]"; 
      } 

      context.SaveChanges(false); 

      dbContextTransaction.Commit(); 

      context.AcceptAllChanges();
    } 
    catch (Exception) 
    { 
      dbContextTransaction.Rollback(); 
    } 
  } 
} 

7
Я беру виняток, щоб захопити такий виняток. Це призводить до того, що робота бази даних мовчки не працює. Через характер SO, хтось може взяти цей приклад і використовувати його у виробничому додатку.
B2K

6
Хіба це не є таким самим, як цей інший відповідь, який дав атрибуцію на сторінці MSDN, яку він цитує? Єдина різниця, яку я бачу, - це те, що ти переходиш falseдо context.SaveChanges();та додатково дзвониш context.AcceptAllChanges();.
Вай Ха Лі

@ B2K відкат не потрібен - якщо транзакція не працює, нічого не робиться. Також явний дзвінок на відкат може не вдатися - дивіться мою відповідь тут stackoverflow.com/questions/41385740/…
Кен

Відкат - це не те, що я заперечую. Автор цієї відповіді оновив свій код, щоб повторно скинути виняток, вирішивши таким чином те, що я заперечував.
В2К

Вибачте, я прокоментував із телефону. Todd перекидає виняток, eMeL - ні. У уловці повинно бути щось, що сповіщає або розробника, або користувача про проблему, що викликає відкат. Це може бути запис у файл журналу, повторне скидання винятку або повернення повідомлення користувачеві.
B2K
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.