Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Hinweis
Nur EF6 und weiter – Die Funktionen, APIs und mehr, die auf dieser Seite besprochen werden, wurden in Entity Framework 6 eingeführt. Wenn Sie eine frühere Version verwenden, gelten einige oder alle Informationen nicht.
In diesem Dokument werden die Verwendung von Transaktionen in EF6 beschrieben, einschließlich der Verbesserungen, die wir seit EF5 hinzugefügt haben, um die Arbeit mit Transaktionen zu vereinfachen.
Was EF standardmäßig tut
In allen Versionen von Entity Framework werden Vorgänge wie Einfügen, Aktualisieren oder Löschen in der Datenbank bei jedem Aufruf von SaveChanges() durch das Framework in einer Transaktion eingebettet. Diese Transaktion dauert nur lange genug, um den Vorgang auszuführen und dann abzuschließen. Wenn Sie einen anderen solchen Vorgang ausführen, wird eine neue Transaktion gestartet.
Seit EF6 wird der Befehl von Database.ExecuteSqlCommand() standardmäßig in eine Transaktion eingeschlossen, wenn noch keine vorhanden ist. Es gibt Überladungen dieser Methode, mit denen Sie dieses Verhalten bei Bedarf außer Kraft setzen können. Auch bei der EF6-Ausführung gespeicherter Prozeduren, die im Modell enthalten sind, über APIs wie ObjectContext.ExecuteFunction() funktioniert dasselbe (mit der Ausnahme, dass das Standardverhalten derzeit nicht außer Kraft gesetzt werden kann).
In beiden Fällen entspricht die Isolationsebene der Transaktion der Isolationsebene, die der Datenbankanbieter als Standardeinstellung betrachtet. In SQL Server ist dies standardmäßig auf "READ COMMITTED" eingestellt.
Entity Framework umschließt keine Abfragen in einer Transaktion.
Diese Standardfunktionalität eignet sich für viele Benutzer, und wenn ja, ist es nicht erforderlich, etwas anderes in EF6 zu tun; schreiben Sie einfach den Code wie immer.
Einige Benutzer benötigen jedoch eine bessere Kontrolle über ihre Transaktionen – dies wird in den folgenden Abschnitten behandelt.
Funktionsweise der APIs
Vor EF6 bestand das Entity Framework darauf, die Datenbankverbindung selbst zu öffnen (eine Ausnahme wurde ausgelöst, wenn eine bereits geöffnete Verbindung übergeben wurde). Da eine Transaktion nur für eine geöffnete Verbindung gestartet werden kann, bedeutete dies, dass der einzige Weg, wie ein Benutzer mehrere Vorgänge in eine Transaktion umschließen konnte, entweder ein TransactionScope verwendet oder die ObjectContext.Connection-Eigenschaft verwendet und open () und BeginTransaction() direkt für das zurückgegebene EntityConnection-Objekt aufruft. Darüber hinaus schlagen API-Aufrufe, die die Datenbank kontaktiert haben, fehl, wenn Sie eine Transaktion für die zugrunde liegende Datenbankverbindung eigenständig gestartet hatten.
Hinweis
Die Einschränkung, dass nur geschlossene Verbindungen akzeptiert werden, wurde in Entity Framework 6 entfernt. Ausführliche Informationen finden Sie unter "Verbindungsverwaltung".
Ab EF6 bietet das Framework jetzt Folgendes:
- Database.BeginTransaction() : Eine einfachere Methode, mit der ein Benutzer Transaktionen selbst innerhalb eines vorhandenen DbContext-Objekts starten und abschließen kann – sodass mehrere Vorgänge innerhalb derselben Transaktion kombiniert werden können und somit entweder alle festgeschrieben oder alle zurückgesetzt werden. Außerdem ermöglicht es dem Benutzer, die Isolationsstufe für die Transaktion einfacher anzugeben.
- Database.UseTransaction() : Dies ermöglicht es dbContext, eine Transaktion zu verwenden, die außerhalb des Entity Framework gestartet wurde.
Kombinieren mehrerer Vorgänge in einer Transaktion innerhalb desselben Kontexts
Database.BeginTransaction() hat zwei Außerkraftsetzungen – eine, die ein explizites IsolationLevel verwendet und keine Argumente akzeptiert und den Standardisolationslevel vom zugrunde liegenden Datenbankanbieter verwendet. Beide Außerkraftsetzungen geben ein DbContextTransaction-Objekt zurück, das Commit()- und Rollback()-Methoden bereitstellt, die Commit- und Rollback-Vorgänge für die zugrunde liegende Speichertransaktion ausführen.
Die DbContextTransaction soll gelöscht werden, sobald sie zugesichert oder zurückgesetzt wurde. Eine einfache Möglichkeit hierfür ist die "Using-Syntax" (...) {...}, die Dispose() automatisch aufruft, wenn der "Using-Block" abgeschlossen ist.
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void StartOwnTransactionWithinContext()
{
using (var context = new BloggingContext())
{
using (var dbContextTransaction = context.Database.BeginTransaction())
{
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();
}
}
}
}
}
Hinweis
Für den Beginn einer Transaktion muss die zugrunde liegende Speicherverbindung geöffnet sein. Das Aufrufen von Database.BeginTransaction() öffnet die Verbindung, wenn sie noch nicht geöffnet ist. Wenn DbContextTransaction die Verbindung geöffnet hat, wird sie geschlossen, wenn Dispose() aufgerufen wird.
Übergeben einer vorhandenen Transaktion an den Kontext
Manchmal möchten Sie eine Transaktion, die im Bereich noch breiter ist und Vorgänge in derselben Datenbank, aber außerhalb von EF vollständig umfasst. Dazu müssen Sie die Verbindung öffnen und die Transaktion selbst starten und ef a) anweisen, die bereits geöffnete Datenbankverbindung zu verwenden, und b) die vorhandene Transaktion für diese Verbindung zu verwenden.
Dazu müssen Sie einen Konstruktor für Ihre Kontextklasse definieren und verwenden, der von einem der DbContext-Konstruktoren erbt, die i) einen vorhandenen Verbindungsparameter und ii) den contextOwnsConnection boolean verwenden.
Hinweis
Das flag "contextOwnsConnection" muss auf "false" festgelegt werden, wenn es in diesem Szenario aufgerufen wird. Dies ist wichtig, da es Entity Framework informiert, dass sie die Verbindung nicht schließen sollte, wenn sie damit fertig ist (z. B. siehe Zeile 4 unten):
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
}
}
Darüber hinaus müssen Sie die Transaktion selbst starten (einschließlich isolationLevel, wenn Sie die Standardeinstellung vermeiden möchten) und Entity Framework mitteilen, dass bereits eine Transaktion mit der Verbindung gestartet wurde (siehe Zeile 33 unten).
Anschließend können Sie Datenbankvorgänge entweder direkt auf der SqlConnection selbst oder im DbContext ausführen. Alle derartigen Vorgänge werden innerhalb einer Transaktion ausgeführt. Sie übernehmen die Verantwortung für das Commit oder Rollback der Transaktion und für den Aufruf von Dispose() dafür sowie für das Schließen und Löschen der Datenbankverbindung. Beispiel:
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingExternalTransaction()
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
{
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.Transaction = sqlTxn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
context.Database.UseTransaction(sqlTxn);
var query = context.Posts.Where(p => p.Blog.Rating >= 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
sqlTxn.Commit();
}
}
}
}
}
Abwicklung der Transaktion
Sie können NULL an Database.UseTransaction() übergeben, um das Wissen von Entity Framework über die aktuelle Transaktion zu löschen. Entity Framework übernimmt weder Commit noch Rollback der vorhandenen Transaktion, wenn Sie dies tun. Daher nur vorsichtig verwenden und nur, wenn Sie sicher sind, dass dies gewünscht ist.
Fehler in UseTransaction
Wenn Sie eine Transaktion übergeben, wird eine Ausnahme von Database.UseTransaction() angezeigt:
- Entity Framework verfügt bereits über eine vorhandene Transaktion.
- Entity Framework funktioniert bereits in einem TransactionScope
- Das Verbindungsobjekt in der übergebenen Transaktion ist NULL. Das heißt, die Transaktion ist keiner Verbindung zugeordnet – in der Regel ist dies ein Zeichen, dass die Transaktion bereits abgeschlossen wurde.
- Das Verbindungsobjekt in der übergebenen Transaktion stimmt nicht mit der Verbindung von Entity Framework überein.
Verwenden von Transaktionen mit anderen Features
In diesem Abschnitt wird erläutert, wie die oben genannten Transaktionen mit folgenden Transaktionen interagieren:
- Verbindungsstabilität
- Asynchrone Methoden
- TransactionScope-Transaktionen
Verbindungsresilienz
Das neue Feature "Verbindungsresilienz" funktioniert nicht mit vom Benutzer initiierten Transaktionen. Weitere Informationen finden Sie unter Ausführungsstrategien erneut versuchen.
Asynchrone Programmierung
Der in den vorherigen Abschnitten beschriebene Ansatz benötigt keine weiteren Optionen oder Einstellungen, um mit der asynchronen Abfrage zu arbeiten und Methoden zu speichern. Beachten Sie jedoch, dass dies, je nachdem, was Sie innerhalb der asynchronen Methoden tun, zu langen Transaktionen führen kann – was wiederum zu Deadlocks oder Blockierungen führen kann, die für die Leistung der gesamten Anwendung schlecht ist.
TransactionScope-Transaktionen
Vor EF6 war die empfohlene Möglichkeit, Transaktionen mit größerem Umfang bereitzustellen, ein TransactionScope-Objekt zu verwenden:
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
static void UsingTransactionScope()
{
using (var scope = new TransactionScope(TransactionScopeOption.Required))
{
using (var conn = new SqlConnection("..."))
{
conn.Open();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
sqlCommand.ExecuteNonQuery();
using (var context =
new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
context.SaveChanges();
}
}
scope.Complete();
}
}
}
}
SqlConnection und Entity Framework würden beide die Ambient TransactionScope-Transaktion verwenden und daher gemeinsam zugesichert werden.
Ab .NET 4.5.1 wurde TransactionScope aktualisiert, um auch mit asynchronen Methoden mithilfe der TransactionScopeAsyncFlowOption-Aufzählung zu arbeiten.
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;
namespace TransactionsExamples
{
class TransactionsExample
{
public static void AsyncTransactionScope()
{
using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
{
using (var conn = new SqlConnection("..."))
{
await conn.OpenAsync();
var sqlCommand = new SqlCommand();
sqlCommand.Connection = conn;
sqlCommand.CommandText =
@"UPDATE Blogs SET Rating = 5" +
" WHERE Name LIKE '%Entity Framework%'";
await sqlCommand.ExecuteNonQueryAsync();
using (var context = new BloggingContext(conn, contextOwnsConnection: false))
{
var query = context.Posts.Where(p => p.Blog.Rating > 5);
foreach (var post in query)
{
post.Title += "[Cool Blog]";
}
await context.SaveChangesAsync();
}
}
scope.Complete();
}
}
}
}
Es gibt noch einige Einschränkungen für den TransactionScope-Ansatz:
- Erfordert ,NET 4.5.1 oder höher, um mit asynchronen Methoden zu arbeiten.
- Sie kann nicht in Cloudszenarien verwendet werden, es sei denn, Sie sind sicher, dass Sie über eine einzige Verbindung verfügen (Cloudszenarien unterstützen keine verteilten Transaktionen).
- Sie kann nicht mit dem Database.UseTransaction()-Ansatz der vorherigen Abschnitte kombiniert werden.
- Es werden Ausnahmen ausgelöst, wenn Sie DDL-Anweisungen ausgeben und keine verteilten Transaktionen über den MSDTC-Dienst aktiviert haben.
Vorteile des TransactionScope-Ansatzes:
- Es wird automatisch eine lokale Transaktion auf eine verteilte Transaktion aktualisiert, wenn Sie mehr als eine Verbindung mit einer bestimmten Datenbank herstellen oder eine Verbindung mit einer Datenbank mit einer Verbindung zu einer anderen Datenbank innerhalb derselben Transaktion kombinieren (Hinweis: Sie müssen den MSDTC-Dienst so konfiguriert haben, dass verteilte Transaktionen für dies Funktionieren konfiguriert sind).
- Einfache Programmierung. Wenn Sie es vorziehen, dass die Transaktion ambient ist und implizit im Hintergrund behandelt wird, anstatt sie explizit selbst zu steuern, ist der TransactionScope-Ansatz möglicherweise besser geeignet.
Zusammenfassend ist mit den oben genannten apIs "Database.BeginTransaction()" und "Database.UseTransaction()" der TransactionScope-Ansatz für die meisten Benutzer nicht mehr erforderlich. Wenn Sie TransactionScope weiterhin verwenden, beachten Sie die oben genannten Einschränkungen. Wir empfehlen, stattdessen den Ansatz zu verwenden, der in den vorherigen Abschnitten beschrieben ist.