Wycofywanie transakcji Entity Framework 6

82

Dzięki EF6 masz nową transakcję, której możesz użyć na przykład:

using (var context = new PostEntityContainer())
        {
            using (var dbcxtransaction = context.Database.BeginTransaction())
            {
                try
                {
                    PostInformation NewPost = new PostInformation()
                    {
                        PostId = 101,
                        Content = "This is my first Post related to Entity Model",
                        Title = "Transaction in EF 6 beta"
                    };
                    context.Post_Details.Add(NewPost);
                    context.SaveChanges();
                    PostAdditionalInformation PostInformation = new PostAdditionalInformation()
                    {
                        PostId = (101),
                        PostName = "Working With Transaction in Entity Model 6 Beta Version"
                    };

                    context.PostAddtional_Details.Add(PostInformation);
                    context.SaveChanges();

                    dbcxtransaction.Commit();
                }
                catch
                {
                    dbcxtransaction.Rollback();
                }
            }
        }

Czy wycofywanie zmian jest rzeczywiście potrzebne, gdy sprawy idą bokiem? Jestem zaciekawiony, ponieważ opis zatwierdzenia mówi: „Zatwierdza podstawową transakcję sklepu”.

Natomiast opis wycofania mówi: „Wycofuje transakcję w sklepie bazowym”.

To mnie zaciekawia, ponieważ wydaje mi się, że jeśli Commit nie zostanie wywołane, wcześniej wykonane polecenia nie zostaną zapisane (co wydaje mi się logiczne). Ale jeśli tak jest, jaki byłby powód wywołania funkcji wycofywania zmian? W EF5 użyłem TransactionScope, który nie miał funkcji Rollback (tylko Complete), co wydawało mi się logiczne. Z powodów związanych z MS DTC nie mogę już korzystać z TransactionScope, ale nie mogę również używać try catch, jak na powyższym przykładzie (tj. Potrzebuję tylko Commit).

Pies z ciasteczkami
źródło
1
Czy czytałeś informacje o transakcjach w sql ? EF próbuje to naśladować. AFAIK, jeśli nie zatwierdzisz transakcji w sql, zostanie ona wycofana.
gunr2171
Zobacz także to pytanie .
gunr2171
Tak, wiem o transakcjach w samym SQL. Byłem ciekawy, co robi EF, ale jeśli to naśladują, ma to sens. Zobaczę, czy uda mi się to obejść. Dziękuję Ci!
The Cookies Dog
SaveChanges () zawsze ma miejsce w transakcji, która zostanie wycofana, jeśli wystąpi wyjątek. W Twoim przypadku nie ma potrzeby ręcznej obsługi tego problemu (w tym konkretnym przypadku jeszcze lepiej byłoby najpierw dodać wszystkie elementy i SaveChangestylko raz).
Paweł
Chcę, aby elementy były zapisywane z obu SaveChanges tylko wtedy, gdy oba nie zawiodły, więc tak, potrzebuję jednej transakcji dla obu z nich.
The Cookies Dog,

Odpowiedzi:

117

Nie musisz dzwonić Rollbackręcznie, ponieważ używasz usinginstrukcji.

DbContextTransaction.Disposezostanie wywołana na końcu usingbloku. I automatycznie wycofa transakcję, jeśli transakcja nie zostanie pomyślnie zatwierdzona (nie wywołano lub nie napotkano wyjątków). Poniżej znajduje się kod źródłowy SqlInternalTransaction.Disposemetody ( DbContextTransaction.Disposeostatecznie deleguje do niej przy użyciu dostawcy SqlServer):

private void Dispose(bool disposing)
{
    // ...
    if (disposing && this._innerConnection != null)
    {
        this._disposing = true;
        this.Rollback();
    }
}

Widzisz, sprawdza, czy _innerConnectionnie jest null, jeśli nie, wycofuje transakcję (jeśli została zatwierdzona, _innerConnectionbędzie zerowa). Zobaczmy, co Commitrobi:

internal void Commit() 
{
    // Ignore many details here...

    this._innerConnection.ExecuteTransaction(...);

    if (!this.IsZombied && !this._innerConnection.IsYukonOrNewer)
    {
        // Zombie() method will set _innerConnection to null
        this.Zombie();
    }
    else
    {
        this.ZombieParent();
    }

    // Ignore many details here...
}

internal void Zombie()
{
    this.ZombieParent();

    SqlInternalConnection innerConnection = this._innerConnection;

    // Set the _innerConnection to null
    this._innerConnection = null;

    if (innerConnection != null)
    {
        innerConnection.DisconnectTransaction(this);
    }
}
Mouhong Lin
źródło
24

Dopóki zawsze będziesz używać SQL Server z EF, nie ma potrzeby jawnego używania catch, aby wywołać metodę Rollback. Zezwolenie blokowi using na automatyczne wycofywanie wszystkich wyjątków zawsze będzie działać.

Jednak gdy myślisz o tym z punktu widzenia Entity Framework, możesz zobaczyć, dlaczego wszystkie przykłady używają jawnego wywołania wycofania transakcji. W przypadku EF dostawca bazy danych jest dowolny i można go podłączyć, a dostawcę można zastąpić MySQL lub dowolną inną bazą danych, która ma implementację dostawcy EF. Dlatego z punktu widzenia EF nie ma gwarancji, że dostawca automatycznie wycofa usuniętą transakcję, ponieważ EF nie wie o implementacji dostawcy bazy danych.

Dlatego zgodnie z najlepszą praktyką dokumentacja EF zaleca jawne wycofywanie zmian - na wypadek, gdyby pewnego dnia zmieniono dostawców na implementację, która nie jest automatycznie przywracana przy usuwaniu.

Moim zdaniem każdy dobry i dobrze napisany dostawca automatycznie wycofa transakcję w dyspozycji, więc dodatkowy wysiłek, aby zawinąć wszystko w bloku using za pomocą try-catch-rollback, jest przesadą.

Rwb
źródło
1
Dzięki za ten wgląd. Zanurzyłem się w kodzie i kończysz w Dispose klasy abstrakcyjnej DbTransaction, która jest nadpisywana w SqlTransaction, która sama wywołuje SqlInternalTransaction wspomnianą przez Mouhong Lin.
ShawnFumo
4
  1. Ponieważ napisałeś blok „using” w celu utworzenia instancji transakcji, nie musisz jawnie wspominać o funkcji Rollback, ponieważ zostanie ona automatycznie wycofana (chyba że została zatwierdzona) w momencie zbycia.
  2. Ale jeśli utworzysz go bez bloku using, w takim przypadku konieczne jest wycofanie transakcji w przypadku wyjątku (dokładnie w bloku catch), a także z sprawdzeniem wartości null dla bardziej niezawodnego kodu. Działanie BeginTransaction różni się od zakresu transakcji (który wymaga tylko pełnej funkcji, jeśli wszystkie operacje zostały pomyślnie zakończone). Zamiast tego jest podobny do działania transakcji Sql.
roopaliv
źródło