使用交易

交易可讓系統以不可部分完成的方式處理數個資料庫作業。 如果認可交易,就會對資料庫成功套用所有作業。 如果復原交易,則不會對資料庫套用任何作業。

提示

您可以檢視本文中的 GitHut 範例

預設交易行為

根據預設,如果資料庫提供者支援交易,就會在交易中套用對 SaveChanges 單一呼叫中的所有變更。 如果其中任何一項變更失敗,系統就會復原交易,而不會對資料庫套用任何變更。 這意謂著會保證 SaveChanges 要不就是完全成功,要不就是發生錯誤時讓資料庫維持原封不動。

對大多數應用程式來說,此預設行為已足以應付需求。 您應該只有在應用程式需求認為有必要時,才手動控制交易。

控制交易

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

using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();

try
{
    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
    context.SaveChanges();

    context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/visualstudio" });
    context.SaveChanges();

    var blogs = context.Blogs
        .OrderBy(b => b.Url)
        .ToList();

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    transaction.Commit();
}
catch (Exception)
{
    // TODO: Handle failure
}

雖然所有關系資料庫提供者都支援交易,但呼叫交易 API 時,其他提供者類型可能會擲回或無作業。

注意

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

儲存點

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

警告

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

您也可以手動管理儲存點,就如同交易一樣。 下列範例會在交易內建立儲存點,並在失敗時回復至它:

using var context = new BloggingContext();
using var transaction = context.Database.BeginTransaction();

try
{
    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/dotnet/" });
    context.SaveChanges();

    transaction.CreateSavepoint("BeforeMoreBlogs");

    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/visualstudio/" });
    context.Blogs.Add(new Blog { Url = "https://devblogs.microsoft.com/aspnet/" });
    context.SaveChanges();

    transaction.Commit();
}
catch (Exception)
{
    // If a failure occurred, we rollback to the savepoint and can continue the transaction
    transaction.RollbackToSavepoint("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);
using var transaction = context1.Database.BeginTransaction();
try
{
    context1.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
    context1.SaveChanges();

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

        var blogs = context2.Blogs
            .OrderBy(b => b.Url)
            .ToList();
            
        context2.Blogs.Add(new Blog { Url = "http://dot.net" });
        context2.SaveChanges();
    }

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    transaction.Commit();
}
catch (Exception)
{
    // TODO: Handle failure
}

使用外部 DbTransactions (僅適用於關聯式資料庫)

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

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

using var connection = new SqlConnection(connectionString);
connection.Open();

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";
    command.ExecuteNonQuery();

    // Run an EF Core command in the transaction
    var options = new DbContextOptionsBuilder<BloggingContext>()
        .UseSqlServer(connection)
        .Options;

    using (var context = new BloggingContext(options))
    {
        context.Database.UseTransaction(transaction);
        context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
        context.SaveChanges();
    }

    // Commit transaction if all commands succeed, transaction will auto-rollback
    // when disposed if either commands fails
    transaction.Commit();
}
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);
    connection.Open();

    try
    {
        // Run raw ADO.NET command in the transaction
        var command = connection.CreateCommand();
        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))
        {
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();
        }

        // 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))
        {
            context.Database.OpenConnection();
            context.Database.EnlistTransaction(transaction);

            // Run raw ADO.NET command in the transaction
            var command = connection.CreateCommand();
            command.CommandText = "DELETE FROM dbo.Blogs";
            command.ExecuteNonQuery();

            // Run an EF Core command in the transaction
            context.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/dotnet" });
            context.SaveChanges();
            context.Database.CloseConnection();
        }

        // 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 指定 TransactionScopeAsyncFlowOption.Enabled ,以確保環境交易在非同步呼叫之間流動。

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

System.Transactions 的限制

  1. EF Core 需倚賴資料庫提供者實作對 System.Transactions 的支援。 如果提供者未實作對 System.Transactions 的支援,則對這些 API 發出的呼叫將可能完全被忽略。 SqlClient 支援它。

    重要

    建議您先測試該 API 是否可與您的提供者正確搭配運作,再倚賴它來管理交易。 如果無法正確搭配運作,建議您與資料庫提供者的維護人員連絡。

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

  3. TransactionScope 不支援非同步認可/回復;這表示處置它會同步封鎖執行執行緒,直到作業完成為止。