Verwenden von TransaktionenUsing Transactions

Mit Transaktionen können mehrere Datenbankvorgänge einzeln verarbeitet werden.Transactions allow several database operations to be processed in an atomic manner. Wenn die Transaktion committet wird, werden alle Vorgänge erfolgreich auf die Datenbank angewendet.If the transaction is committed, all of the operations are successfully applied to the database. Falls für die Transaktion ein Rollback ausgeführt wird, wird keiner der Vorgänge für die Datenbank übernommen.If the transaction is rolled back, none of the operations are applied to the database.

Tipp

Das in diesem Artikel verwendete Beispiel finden Sie auf GitHub.You can view this article's sample on GitHub.

StandardtransaktionsverhaltenDefault transaction behavior

Wenn der Datenbankanbieter Transaktionen unterstützt, werden standardmäßig alle Änderungen in einem einzigen Aufruf von SaveChanges in einer Transaktion angewendet.By default, if the database provider supports transactions, all changes in a single call to SaveChanges are applied in a transaction. Wenn bei einer der Änderungen ein Fehler auftritt, wird ein Rollback ausgeführt, und keine der Änderungen wird für die Datenbank übernommen.If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. SaveChanges wird also entweder vollständig ausgeführt oder überhaupt nicht, wenn ein Fehler auftritt. Die Datenbank bleibt in diesem Fall unverändert.This means that SaveChanges is guaranteed to either completely succeed, or leave the database unmodified if an error occurs.

Bei den meisten Anwendungen ist dieses Standardverhalten ausreichend.For most applications, this default behavior is sufficient. Sie sollten Transaktionen nur manuell steuern, wenn die Anforderungen Ihrer Anwendung dies notwendig machen.You should only manually control transactions if your application requirements deem it necessary.

Steuern von TransaktionenControlling transactions

Mit der DbContext.Database-API können Sie Transaktionen beginnen, committen und ein Rollback dafür ausführen.You can use the DbContext.Database API to begin, commit, and rollback transactions. Im folgenden Beispiel werden zwei SaveChanges-Vorgänge und eine LINQ-Abfrage dargestellt, die in einer einzelnen Transaktion ausgeführt werden:The following example shows two SaveChanges operations and a LINQ query being executed in a single transaction:

await 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.While all relational database providers support transactions, other providers types may throw or no-op when transaction APIs are called.

SicherungspunkteSavepoints

Wenn SaveChanges aufgerufen und im Kontext bereits eine Transaktion ausgeführt wird, erstellt EF automatisch einen Sicherungspunkt, bevor Daten gespeichert werden.When SaveChanges is invoked and a transaction is already in progress on the context, EF automatically creates a savepoint before saving any data. Sicherungspunkte sind Punkte innerhalb einer Datenbanktransaktion, für die später ein Rollback ausgeführt werden kann, wenn ein Fehler auftritt, oder aus einem anderen Grund.Savepoints are points within a database transaction which may later be rolled back to, if an error occurs or for any other reason. Wenn bei SaveChanges ein Fehler auftritt, wird die Transaktion automatisch an den Sicherungspunkt zurückgesetzt, sodass die Transaktion sich in demselben Zustand befindet, als sei sie nie gestartet worden.If SaveChanges encounters any error, it automatically rolls the transaction back to the savepoint, leaving the transaction in the same state as if it had never started. Dies ermöglicht Ihnen, mögliche Probleme zu beheben und den Speichervorgang zu wiederholen, insbesondere wenn Probleme mit der optimistischen Nebenläufigkeit auftreten.This allows you to possibly correct issues and retry saving, in particular when optimistic concurrency issues occur.

Es ist auch möglich, Sicherungspunkte wie bei Transaktionen manuell zu verwalten.It's also possible to manually manage savepoints, just as it is with transactions. Im folgenden Beispiel wird ein Sicherungspunkt innerhalb einer Transaktion erstellt und bei einem Fehler ein Rollback dahin ausgeführt:The following example creates a savepoint within a transaction, and rolls back to it on failure:

await 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 TransaktionCross-context transaction

Sie können Transaktionen auch für mehrere Kontextinstanzen freigeben.You can also share a transaction across multiple context instances. Diese Funktion ist nur bei Anbietern von relationalen Datenbanken verfügbar, da DbTransaction und DbConnection verwendet werden müssen, die charakteristisch für relationale Datenbanken sind.This functionality is only available when using a relational database provider because it requires the use of DbTransaction and DbConnection, which are specific to relational databases.

Um eine Transaktion freigeben zu können, müssen die Kontexte DbConnection und DbTransaction verwenden.To share a transaction, the contexts must share both a DbConnection and a DbTransaction.

Zulassen von extern bereitgestellten VerbindungenAllow connection to be externally provided

Um DbConnection freigeben zu können, muss eine Verbindung an einen Kontext übergeben werden können, während dieser erstellt wird.Sharing a DbConnection requires the ability to pass a connection into a context when constructing it.

Die einfachste Möglichkeit, eine externe Bereitstellung von DbConnection zuzulassen, ist, den Kontext nicht mehr mit der DbContext.OnConfiguring-Methode zu konfigurieren, sondern extern DbContextOptions zu erstellen und es an den Kontextkonstruktor zu übergeben.The easiest way to allow DbConnection to be externally provided, is to stop using the DbContext.OnConfiguring method to configure the context and externally create DbContextOptions and pass them to the context constructor.

Tipp

DbContextOptionsBuilder ist die API, die Sie in DbContext.OnConfiguring zum Konfigurieren des Kontexts verwendet haben. Jetzt werden Sie es extern verwenden und DbContextOptions erstellen.DbContextOptionsBuilder is the API you used in DbContext.OnConfiguring to configure the context, you are now going to use it externally to create DbContextOptions.

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    { }

    public DbSet<Blog> Blogs { get; set; }
}

Alternativ können Sie weiterhin DbContext.OnConfiguring verwenden und eine DbConnection akzeptieren, die gespeichert und dann in DbContext.OnConfiguring verwendet wird.An alternative is to keep using DbContext.OnConfiguring, but accept a DbConnection that is saved and then used in DbContext.OnConfiguring.

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 TransaktionShare connection and transaction

Sie können nun mehrere Kontextinstanzen erstellen, die die gleiche Verbindung verwenden.You can now create multiple context instances that share the same connection. Anschließend tragen Sie mit der DbContext.Database.UseTransaction(DbTransaction)-API beide Kontexte in derselben Transaktion ein.Then use the DbContext.Database.UseTransaction(DbTransaction) API to enlist both contexts in the same transaction.

var options = new DbContextOptionsBuilder<BloggingContext>()
    .UseSqlServer(new SqlConnection(connectionString))
    .Options;

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

    await using (var context2 = new BloggingContext(options))
    {
        context2.Database.UseTransaction(transaction.GetDbTransaction());

        var blogs = await context2.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
}

Verwenden von externen DbTransactions (nur relationale Datenbanken)Using external DbTransactions (relational databases only)

Wenn Sie mit verschiedenen Datenzugriffstechnologien auf eine relationale Datenbank zugreifen, sollten Sie eine gemeinsame Transaktion für die Vorgänge einrichten, die von diesen verschiedenen Technologien ausgeführt werden.If you are using multiple data access technologies to access a relational database, you may want to share a transaction between operations performed by these different technologies.

Im folgenden Beispiel wird gezeigt, wie ein ADO.NET SqlClient-Vorgang und ein Entity Framework Core-Vorgang in derselben Transaktion ausgeführt werden.The following example, shows how to perform an ADO.NET SqlClient operation and an Entity Framework Core operation in the same transaction.

await using var connection = new SqlConnection(connectionString);
await connection.OpenAsync();

await using var transaction = connection.BeginTransaction();
try
{
    // Run raw ADO.NET command in the transaction
    var command = connection.CreateCommand();
    command.Transaction = transaction;
    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;

    await using (var context = new BloggingContext(options))
    {
        context.Database.UseTransaction(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 (System.Exception)
{
    // TODO: Handle failure
}

Verwenden von 'System.Transactions'Using System.Transactions

Es ist möglich, Ambient-Transaktionen zu verwenden, wenn Sie einen größeren Bereich koordinieren müssen.It is possible to use ambient transactions if you need to coordinate across a larger scope.

using (var scope = new TransactionScope(
    TransactionScopeOption.Required,
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    await 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;

        await 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 (System.Exception)
    {
        // TODO: Handle failure
    }
}

Sie können auch eine Eintragung in einer expliziten Transaktion vornehmen.It is also possible to enlist in an explicit transaction.

using (var transaction = new CommittableTransaction(
    new TransactionOptions { IsolationLevel = IsolationLevel.ReadCommitted }))
{
    var connection = new SqlConnection(connectionString);

    try
    {
        var options = new DbContextOptionsBuilder<BloggingContext>()
            .UseSqlServer(connection)
            .Options;

        await 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 (System.Exception)
    {
        // TODO: Handle failure
    }
}

Einschränkungen von System.TransactionsLimitations of System.Transactions

  1. In EF Core müssen die Datenbankanbieter die Unterstützung für System.Transactions implementieren.EF Core relies on database providers to implement support for System.Transactions. Wenn ein Anbieter keine Unterstützung für System.Transactions implementiert, ist es möglich, dass Aufrufe dieser APIs vollständig ignoriert werden.If a provider does not implement support for System.Transactions, it is possible that calls to these APIs will be completely ignored. SqlClient unterstützt dies.SqlClient supports it.

    Wichtig

    Daher sollten Sie testen, ob die API ordnungsgemäß mit Ihrem Anbieter funktioniert, bevor Sie sie für die Verwaltung von Transaktionen einsetzen.It is recommended that you test that the API behaves correctly with your provider before you rely on it for managing transactions. Sollte die API nicht funktionieren, wenden Sie sich bitte an den Maintainer des Datenbankanbieters.You are encouraged to contact the maintainer of the database provider if it does not.

  2. Ab .NET Core 2.1 enthält die System.Transactions-Implementierung keine Unterstützung für verteilte Transaktionen. Aus diesem Grund können Sie weder mit TransactionScope noch mit CommittableTransaction Transaktionen in mehreren Ressourcen-Managern koordinieren.As of .NET Core 2.1, the System.Transactions implementation does not include support for distributed transactions, therefore you cannot use TransactionScope or CommittableTransaction to coordinate transactions across multiple resource managers.