共用方式為


使用交易

事務允許以原子方式處理多個資料庫操作。 當交易被認可時,所有作業都會成功套用到資料庫。 如果交易回滾,則不會將任何操作套用至資料庫。

小提示

您可以在 GitHub 上檢視本文 範例

預設交易行為

根據預設,如果資料庫提供者支援交易,則單一呼叫 SaveChanges 中的所有變更都會套用在交易中。 如果有任何變更失敗,則會回復交易,且不會將任何變更套用至資料庫。 這表示 SaveChanges 保證會完全成功,或在發生錯誤時讓資料庫保持未修改。

對於大部分的應用程式而言,此預設行為就已足夠。 只有在應用程式需求認為有必要時,才應該手動控制交易。

控制交易

您可以使用 DbContext.Database API 來開始、認可和回復交易。 下列範例顯示兩個 SaveChanges 作業,以及單一交易中執行的LINQ查詢:

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
}

雖然所有關係型資料庫提供者都支援交易,但呼叫交易 API 時,其他類型的提供者可能會擲出錯誤或返回 no-op。

備註

以這種方式手動控制交易與隱含叫用的重試執行策略不相容。 如需詳細資訊,請參閱 連線復原

儲存點

當叫用 SaveChanges 且在上下文中已經進行交易時,EF 會在儲存任何資料之前,自動建立 儲存點。 儲存點是資料庫交易內的點,如果發生錯誤,或基於任何其他原因,稍後可能會回復至此點。 如果 SaveChanges 發生任何錯誤,它會自動將交易回復至儲存點,使交易處於與從未啟動相同的狀態。 這可讓您修正問題並重試儲存,特別是在發生樂觀並行控制問題時。

警告

儲存點與 SQL Server 的多個作用中結果集 (MARS) 不相容。 在連線上啟用MARS時,EF不會建立儲存點,即使MARS未主動使用也一樣。 如果在 SaveChanges 期間發生錯誤,交易可能會處於未知狀態。

您也可以手動管理儲存點,就如同交易一樣。 以下範例在交易中建立一個儲存點,若發生錯誤則回滾至該儲存點:

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
}

跨上下文交易

您也可以跨多個內容實例共用交易。 只有在使用關係資料庫提供者時,才能使用此功能,因為它需要使用關係資料庫特定的 DbTransactionDbConnection

若要共用交易,上下文必須同時共用 DbConnectionDbTransaction

允許外部提供連線

共用 DbConnection 需要具備在構建時將連線嵌入上下文的能力。

若要允許外部提供 DbConnection,最簡單的方法是停止使用 DbContext.OnConfiguring 方法來設定內容,並在外部建立 DbContextOptions,並將其傳遞至內容建構函式。

小提示

DbContextOptionsBuilder 是您在 DbContext.OnConfiguring 中用來設定內容的 API,您現在會將其用於外部來建立 DbContextOptions

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

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

替代方法是繼續使用 DbContext.OnConfiguring,但接受儲存的 DbConnection,然後在 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);
    }
}

共用連線和交易

您現在可以建立多個共用相同連線的內容實例。 然後使用 DbContext.Database.UseTransaction(DbTransaction) API,將兩個上下文登記到相同的交易中。

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
}

使用外部 DbTransactions (僅限關係資料庫)

如果您使用多個數據存取技術來存取關係資料庫,您可能會想要在這些不同技術所執行的作業之間共用交易。

下列範例示範如何在相同的交易中執行 ADO.NET SqlClient 作業和 Entity Framework Core 作業。

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
}

使用 System.Transactions

如果您需要在更大範圍內協調,則可以使用環境式交易。

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
    }
}

您也可以加入明確交易。

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
    }
}

備註

如果您使用異步 API,請務必在 建構函式中指定 TransactionScope,以確保環境交易在異步呼叫之間流動。

如需 TransactionScope 和環境交易的詳細資訊,請參閱本檔

System.Transactions 的限制

  1. EF Core 依賴資料庫提供者來實作 System.Transactions 的支援。 如果提供者未實作 System.Transactions 的支援,可能會完全忽略對這些 API 的呼叫。 SqlClient 支援它。

    這很重要

    建議您先測試 API 是否與提供者正確運作,再依賴它來管理交易。 如果資料庫提供者未這樣做,建議您連絡資料庫提供者的維護者。

  2. System.Transactions 中的分散式交易支援僅新增至適用於 Windows 的 .NET 7.0。 任何嘗試在舊版 .NET 或非 Windows 平臺上使用分散式交易都會失敗。

  3. TransactionScope 不支持異步提交/回滾;這表示同步處置它會封鎖執行緒,直到操作完成為止。