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.
Transaktionen ermöglichen eine atomare Verarbeitung mehrerer Datenbankvorgänge. Wenn die Transaktion zugesichert wird, werden alle Vorgänge erfolgreich auf die Datenbank angewendet. Wenn die Transaktion zurückgesetzt wird, werden keine Vorgänge auf die Datenbank angewendet.
Tipp
Sie können das Beispiel dieses Artikels auf GitHub anzeigen.
Standardtransaktionsverhalten
Wenn der Datenbankanbieter Transaktionen unterstützt, werden standardmäßig alle Änderungen eines einzelnen Aufrufs an SaveChanges
in einer Transaktion angewendet. Wenn eine der Änderungen fehlschlägt, wird die Transaktion zurückgesetzt, und keine der Änderungen wird auf die Datenbank angewendet. Dies bedeutet, dass SaveChanges
entweder vollständig erfolgreich ist oder die Datenbank unverändert bleibt, wenn ein Fehler auftritt.
Für die meisten Anwendungen reicht dieses Standardverhalten aus. Sie sollten Transaktionen nur manuell steuern, wenn ihre Anwendungsanforderungen dies als notwendig erahnen.
Kontrollieren von Transaktionen
Sie können die DbContext.Database
-API zum Starten, Commit und Rollback von Transaktionen verwenden. Das folgende Beispiel zeigt zwei SaveChanges
Vorgänge und eine LINQ-Abfrage, die in einer einzelnen Transaktion ausgeführt wird:
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
await context.SaveChangesAsync();
var blogs = await context.Blogs
.OrderBy(b => b.Url)
.ToListAsync();
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
Während alle Anbieter relationaler Datenbanken Transaktionen unterstützen, können andere Anbieter möglicherweise beim Aufrufen von Transaktions-APIs Fehler auslösen oder dies nicht ausführen.
Hinweis
Das manuelle Steuern von Transaktionen auf diese Weise ist nicht mit den implizit aufgerufenen Ausführungsstrategie für die Wiederholung kompatibel. Weitere Informationen finden Sie unter Verbindungsresilienz.
Sicherungspunkte
Wenn SaveChanges
aufgerufen wird und eine Transaktion bereits im Kontext ausgeführt wird, erstellt EF automatisch einen Speicherpunkt vor dem Speichern von Daten. Savepoints sind Punkte innerhalb einer Datenbanktransaktion, auf die später zurückgegangen werden kann, wenn ein Fehler auftritt oder aus einem anderen Grund. Wenn ein Fehler bei SaveChanges
auftritt, wird die Transaktion automatisch zum Speicherpunkt zurückgesetzt, sodass sie sich in demselben Zustand befindet, als wäre sie nie gestartet worden. Dies ermöglicht Ihnen, mögliche Probleme zu beheben und den Speichervorgang zu wiederholen, insbesondere wenn Probleme mit der optimistischen Nebenläufigkeit auftreten.
Warnung
Savepoints sind nicht mit den multiplen Active Result Sets (MARS) von SQL Server kompatibel. Savepoints werden nicht von EF erstellt, wenn MARS für die Verbindung aktiviert ist, auch wenn MARS nicht aktiv verwendet wird. Wenn während SaveChanges ein Fehler auftritt, wird die Transaktion möglicherweise in einem unbekannten Zustand verbleiben.
Es ist auch möglich, Speicherpunkte manuell zu verwalten, genau wie bei Transaktionen. Im folgenden Beispiel wird ein Sicherungspunkt innerhalb einer Transaktion erstellt und bei einem Fehler ein Rollback dahin ausgeführt:
using var context = new BloggingContext();
await using var transaction = await context.Database.BeginTransactionAsync();
try
{
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
await context.SaveChangesAsync();
await transaction.CreateSavepointAsync("BeforeMoreBlogs");
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
await context.SaveChangesAsync();
await transaction.CommitAsync();
}
catch (Exception)
{
// If a failure occurred, we rollback to the savepoint and can continue the transaction
await transaction.RollbackToSavepointAsync("BeforeMoreBlogs");
// TODO: Handle failure, possibly retry inserting blogs
}
Kontextübergreifende Transaktion
Sie können eine Transaktion auch für mehrere Kontextinstanzen freigeben. Diese Funktionalität ist nur bei Verwendung eines relationalen Datenbankanbieters verfügbar, da sie die Verwendung von DbTransaction
und DbConnection
erfordert, die speziell für relationale Datenbanken sind.
Um eine Transaktion freigeben zu können, müssen die Kontexte DbConnection
und DbTransaction
verwenden.
Zulassen, dass die Verbindung extern bereitgestellt wird
Um DbConnection
freigeben zu können, muss eine Verbindung an einen Kontext übergeben werden können, während dieser erstellt wird.
Die einfachste Möglichkeit, DbConnection
extern bereitgestellt zu werden, besteht darin, die Verwendung der DbContext.OnConfiguring
-Methode zu beenden, um den Kontext zu konfigurieren und extern DbContextOptions
zu erstellen und an den Kontextkonstruktor zu übergeben.
Tipp
DbContextOptionsBuilder
ist die API, die Sie in DbContext.OnConfiguring
zum Konfigurieren des Kontexts verwendet haben. Jetzt werden Sie sie extern verwenden, um DbContextOptions
zu erstellen.
public class BloggingContext : DbContext
{
public BloggingContext(DbContextOptions<BloggingContext> options)
: base(options)
{
}
public DbSet<Blog> Blogs { get; set; }
}
Eine Alternative besteht darin, DbContext.OnConfiguring
weiterhin zu verwenden, jedoch eine DbConnection
zu akzeptieren, die gespeichert und dann in DbContext.OnConfiguring
verwendet wird.
public class BloggingContext : DbContext
{
private DbConnection _connection;
public BloggingContext(DbConnection connection)
{
_connection = connection;
}
public DbSet<Blog> Blogs { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(_connection);
}
}
Freigeben der Verbindung und der Transaktion
Sie können jetzt mehrere Kontextinstanzen erstellen, die dieselbe Verbindung gemeinsam nutzen. Verwenden Sie dann die DbContext.Database.UseTransaction(DbTransaction)
-API, um beide Kontexte in derselben Transaktion auflisten.
using var connection = new SqlConnection(connectionString);
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using var context1 = new BloggingContext(options);
await using var transaction = await context1.Database.BeginTransactionAsync();
try
{
context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context1.SaveChangesAsync();
using (var context2 = new BloggingContext(options))
{
await context2.Database.UseTransactionAsync(transaction.GetDbTransaction());
var blogs = await context2.Blogs
.OrderBy(b => b.Url)
.ToListAsync();
context2.Blogs.Add(new Blog { Url = "http://dot.net" });
await context2.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
Verwenden externer DbTransactions (nur relationale Datenbanken)
Wenn Sie mehrere Datenzugriffstechnologien für den Zugriff auf eine relationale Datenbank verwenden, können Sie eine Transaktion zwischen Vorgängen freigeben, die von diesen verschiedenen Technologien ausgeführt werden.
Das folgende Beispiel zeigt, wie Sie einen ADO.NET SqlClient-Vorgang und einen Entity Framework Core-Vorgang in derselben Transaktion ausführen.
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
await using var transaction = (SqlTransaction)await connection.BeginTransactionAsync();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.Transaction = transaction;
command.CommandText = "DELETE FROM dbo.Blogs";
command.ExecuteNonQuery();
// Run an EF Core command in the transaction
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
await context.Database.UseTransactionAsync(transaction);
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
await transaction.CommitAsync();
}
catch (Exception)
{
// TODO: Handle failure
}
Verwenden von 'System.Transactions'
Es ist möglich, Umgebungstransaktionen zu verwenden, wenn Sie einen größeren Bereich koordinieren müssen.
using (var scope = new TransactionScope(
TransactionScopeOption.Required,
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();
try
{
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
await command.ExecuteNonQueryAsync();
// Run an EF Core command in the transaction
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
scope.Complete();
}
catch (Exception)
{
// TODO: Handle failure
}
}
Es ist auch möglich, sich an einer expliziten Transaktion zu beteiligen.
using (var transaction = new CommittableTransaction(
new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
var connection = new SqlConnection(connectionString);
try
{
var options = new DbContextOptionsBuilder<BloggingContext>()
.UseSqlServer(connection)
.Options;
using (var context = new BloggingContext(options))
{
await context.Database.OpenConnectionAsync();
context.Database.EnlistTransaction(transaction);
// Run raw ADO.NET command in the transaction
var command = connection.CreateCommand();
command.CommandText = "DELETE FROM dbo.Blogs";
await command.ExecuteNonQueryAsync();
// Run an EF Core command in the transaction
context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
await context.SaveChangesAsync();
await context.Database.CloseConnectionAsync();
}
// Commit transaction if all commands succeed, transaction will auto-rollback
// when disposed if either commands fails
transaction.Commit();
}
catch (Exception)
{
// TODO: Handle failure
}
}
Hinweis
Wenn Sie asynchrone APIs verwenden, müssen Sie TransactionScopeAsyncFlowOption.Enabled im TransactionScope
-Konstruktor angeben, um sicherzustellen, dass die Umgebungstransaktion über asynchrone Aufrufe hinweg fließt.
Weitere Informationen zu TransactionScope
und Umgebungstransaktionen finden Sie in dieser Dokumentation.
Einschränkungen von System.Transactions
EF Core basiert auf Datenbankanbietern, um Unterstützung für System.Transactions zu implementieren. Wenn ein Anbieter keine Unterstützung für System.Transactions implementiert, wird der Aufruf dieser APIs möglicherweise vollständig ignoriert. SqlClient unterstützt es.
Wichtig
Es wird empfohlen, zu testen, ob sich die API ordnungsgemäß mit Ihrem Anbieter verhält, bevor Sie sie für die Verwaltung von Transaktionen verwenden. Sie werden aufgefordert, sich an den Betreuer des Datenbankanbieters zu wenden, wenn dies nicht der Fall ist.
Unterstützung für verteilte Transaktionen in System.Transactions wurde nur zu .NET 7.0 für Windows hinzugefügt. Jeder Versuch, verteilte Transaktionen auf älteren .NET-Versionen oder auf Nicht-Windows-Plattformen zu verwenden, schlägt fehl.
TransactionScope unterstützt kein asynchrones Commit/Rollback, d. h. die synchrone Disposition blockiert den ausführenden Thread, bis der Vorgang abgeschlossen ist.