Używasz transakcji lub SaveChanges (false) i AcceptAllChanges ()?

346

Badam transakcje i wygląda na to, że dbają o siebie w EF, dopóki przechodzę falsedo, SaveChanges()a następnie dzwonię, AcceptAllChanges()jeśli nie ma błędów:

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

Co jeśli coś pójdzie nie tak? czy nie muszę wycofywać lub gdy transakcja wykracza poza zakres, czy transakcja jest zakończona?

Co stanie się z dowolnymi kolumnami indentiy, które zostały przypisane w połowie transakcji? Zakładam, że jeśli ktoś inny dodał mój rekord po moim, zanim mój się zepsuł, oznacza to, że będzie brakowało wartości tożsamości.

Czy jest jakiś powód, aby używać standardowej TransactionScopeklasy w moim kodzie?

Mark Smith
źródło
1
To pomogło mi zrozumieć, dlaczego SaveChanges(fase); ... AcceptAllChanges();był to wzorzec. Zwróć uwagę, w jaki sposób autor bloga napisał zaakceptowaną odpowiedź na powyższe pytanie - a do tego bloga odwołuje się drugie pytanie. Wszystko się łączy.
The Red Pea,

Odpowiedzi:

451

Dzięki Entity Framework większość czasu SaveChanges()jest wystarczająca. To tworzy transakcję lub powiększa się w dowolnej transakcji otoczenia i wykonuje wszystkie niezbędne prace w tej transakcji.

Czasami SaveChanges(false) + AcceptAllChanges()parowanie jest przydatne.

Najbardziej użytecznym miejscem do tego są sytuacje, w których chcesz dokonać transakcji rozproszonej w dwóch różnych kontekstach.

Tzn. Coś takiego (źle):

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();
}

Jeśli się context1.SaveChanges()powiedzie, ale się context2.SaveChanges()nie powiedzie, cała rozproszona transakcja zostanie przerwana. Ale niestety Entity Framework już odrzucił zmiany context1, więc nie można odtwarzać ani skutecznie rejestrować awarii.

Ale jeśli zmienisz kod, aby wyglądał tak:

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();

}

Podczas gdy wezwanie do SaveChanges(false)wysłania niezbędnych poleceń do bazy danych, sam kontekst nie ulega zmianie, więc możesz to zrobić ponownie, jeśli to konieczne, lub możesz przesłuchać, ObjectStateManagerjeśli chcesz.

Oznacza to, że jeśli transakcja faktycznie zgłasza wyjątek, który można zrekompensować, albo próbując ponownie, albo rejestrując stan każdego kontekstu ObjectStateManagergdzieś.

Zobacz mój post na blogu, aby uzyskać więcej.

Alex James
źródło
3
To świetnie, dzięki ... Więc jeśli coś się nie powiedzie, nie muszę się wycofywać ?? SaveChanges, oznacza to, że jest zapisywane, ale tak naprawdę nie zatwierdza, dopóki nie zaakceptuję wszystkich zmian ... ale jeśli coś pójdzie nie tak ... będę musiał wycofać, czy nie, aby mój obiekt powrócił do prawidłowego stanu?
Mark Smith
33
@ Mark: jeśli przez „wycofanie” rozumiesz, przywróć swoje obiekty do stanu, w którym znajdują się one w bazie danych, to nie, nie chciałbyś tego robić, ponieważ stracisz wszystkie zmiany użytkownika w obiektach . SaveChanges(false)dokonuje faktycznej aktualizacji bazy danych, a jednocześnie AcceptAllChanges()mówi EF: „Dobra, możesz zapomnieć, które rzeczy trzeba zapisać, ponieważ zostały pomyślnie zapisane”. Jeśli się SaveChanges(false)nie powiedzie, AcceptAllChanges()nigdy nie zostanie wywołany, a EF nadal będzie uważał, że Twój obiekt ma właściwości, które zostały zmienione i muszą zostać zapisane z powrotem w bazie danych.
BlueRaja - Danny Pflughoeft
Czy możesz doradzić, jak to zrobić za pomocą Code First? Nie ma parametru do metody SaveChanges lub AcceptAllChanges
Kirsten Greed
2
Zadałem pytanie dotyczące stosowania tej techniki z Code First tutaj
Kirsten Greed
13
Nie jest to już możliwe w EF 6.1. Czy wiesz, jakie zmiany należy teraz wprowadzić, aby działały?
Alex Dresko
113

Jeśli używasz EF6 (Entity Framework 6+), zmieniło się to w przypadku wywołań bazy danych do SQL.
Zobacz: http://msdn.microsoft.com/en-us/data/dn456843.aspx

użyj context.Database.BeginTransaction.

Z 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
        } 
    } 
} 
użytkownik3885816
źródło
52
Try-catch z funkcją Roolback nie jest potrzebny, gdy używasz „transakcji” na transakcji.
Robert,
12
Biorę wyjątek do pułapkowania takiego wyjątku. Powoduje to po cichu działanie bazy danych. Ze względu na naturę SO ktoś może wziąć ten przykład i użyć go w aplikacji produkcyjnej.
B2K
3
@ B2K: Dobrze, ale ten kod został skopiowany z powiązanego artykułu Microsoft. Mam nadzieję, że nikt nie używa ich kodu w produkcji :)
J Bryan Price
6
@Robert Zgodnie z artykułem MSDN konieczne jest przywrócenie (). Celowo pomijają polecenie Cofnij dla przykładu TransactionScope. @ B2K Dodałem throw;fragment kodu MSDN i wyraźnie wskazałem, że nie jest to oryginał z artykułu MSDN.
Todd,
6
(Jeśli jest poprawny) Może to wyjaśnić: Wygląda na to, że EF + MSSQL nie wymaga wycofania, ale EF + inni dostawcy SQL mogą. Ponieważ EF ma być niezależny od bazy danych, z którą się rozmawia, Rollback()jest wywoływany na wypadek, gdyby rozmawiał z MySql lub czymś, co nie ma takiego automatycznego zachowania.
Słowa jak Jared
-5

Ponieważ niektóre bazy danych mogą zgłaszać wyjątek w dbContextTransaction.Commit (), więc lepiej to:

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(); 
    } 
  } 
} 
eMeL
źródło
7
Biorę wyjątek do pułapkowania takiego wyjątku. Powoduje to po cichu działanie bazy danych. Ze względu na naturę SO ktoś może wziąć ten przykład i użyć go w aplikacji produkcyjnej.
B2K
6
Czy to nie jest w zasadzie to samo, co ta inna odpowiedź, która podała przypisanie cytowanej przez niego strony MSDN ? Jedyną różnicą jest to, że widzę zdać falsesię context.SaveChanges();, a dodatkowo zadzwonić context.AcceptAllChanges();.
Wai Ha Lee,
@ B2K wycofanie nie jest wymagane - jeśli transakcja nie działa, nic się nie dzieje. Również wyraźne wezwanie do wycofania może się nie powieść - patrz moja odpowiedź tutaj stackoverflow.com/questions/41385740/…
Ken
Cofanie się nie jest tym, czemu się sprzeciwiam. Autor tej odpowiedzi zaktualizował swój kod, aby ponownie zgłosić wyjątek, rozwiązując w ten sposób to, czemu się sprzeciwiłem.
B2K
Przepraszam, skomentowałem z mojego telefonu. Todd ponownie zgłasza wyjątek, eMeL nie. W haczyku powinno być coś, co powiadomi programistę lub użytkownika o problemie powodującym wycofanie. Może to być zapisywanie do pliku dziennika, ponowne zgłaszanie wyjątku lub zwracanie wiadomości do użytkownika.
B2K,