使用交易Using Transactions

交易可讓數個資料庫作業,以不可部分完成的方式處理。Transactions allow several database operations to be processed in an atomic manner. 認可交易時,如果所有的作業會成功套用至資料庫中。If the transaction is committed, all of the operations are successfully applied to the database. 如果回復交易,所有作業都不會套用至資料庫。If the transaction is rolled back, none of the operations are applied to the database.

提示

您可以在 GitHub 上檢視此文章的範例 (英文)。You can view this article's sample on GitHub.

預設的交易行為Default transaction behavior

根據預設,如果資料庫提供者支援交易,所有變更的單一呼叫中SaveChanges()會套用在交易中。By default, if the database provider supports transactions, all changes in a single call to SaveChanges() are applied in a transaction. 如果有任何變更失敗,然後回復交易,並變更不會套用至資料庫。If any of the changes fail, then the transaction is rolled back and none of the changes are applied to the database. 這表示SaveChanges()保證完全成功,或讓資料庫發生錯誤時,不想修改。This means that SaveChanges() is guaranteed to either completely succeed, or leave the database unmodified if an error occurs.

對於大部分的應用程式,此預設行為已足夠。For most applications, this default behavior is sufficient. 如果應用程式的需求覺得有必要,您應該僅以手動方式控制交易。You should only manually control transactions if your application requirements deem it necessary.

控制交易Controlling transactions

您可以使用DbContext.DatabaseAPI 來開始、 認可和回復交易。You can use the DbContext.Database API to begin, commit, and rollback transactions. 下列範例示範兩個SaveChanges()作業和 LINQ 查詢在單一交易中執行。The following example shows two SaveChanges() operations and a LINQ query being executed in a single transaction.

並非所有的資料庫提供者支援交易。Not all database providers support transactions. 某些提供者可能會擲回或無作業 Api 呼叫交易時。Some providers may throw or no-op when transaction APIs are called.

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

跨內容交易 (只有關聯式資料庫)Cross-context transaction (relational databases only)

您也可以在多個內容執行個體之間共用的交易。You can also share a transaction across multiple context instances. 這項功能時才使用關聯式資料庫提供者,因為它需要使用DbTransactionDbConnection,這專屬於關聯式資料庫。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.

若要共用交易,內容必須共用兩者DbConnectionDbTransactionTo share a transaction, the contexts must share both a DbConnection and a DbTransaction.

允許從外部提供的連線Allow connection to be externally provided

共用DbConnection需要建構它時,將連接傳遞到內容的功能。Sharing a DbConnection requires the ability to pass a connection into a context when constructing it.

允許的最簡單方式DbConnection外部提供,是要停止使用DbContext.OnConfiguring方法來設定內容和外部建立DbContextOptions並將其傳遞至內容的建構函式。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.

提示

DbContextOptionsBuilder 是您在中使用的 APIDbContext.OnConfiguring若要設定內容,您現在要建立外部使用DbContextOptionsDbContextOptionsBuilder 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; }
}

若要繼續使用替代方法是DbContext.OnConfiguring,但接受DbConnection,是儲存並接著用於DbContext.OnConfiguringAn 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);
    }
}

共用連接及交易Share connection and transaction

您現在可以建立多個共用相同連接的內容執行個體。You can now create multiple context instances that share the same connection. 然後使用DbContext.Database.UseTransaction(DbTransaction)API 來編列這兩個相同交易中的內容。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;

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

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

使用外部 DbTransactions (只有關聯式資料庫)Using external DbTransactions (relational databases only)

如果您使用多個資料存取技術來存取關聯式資料庫,您可能想要共用這些不同的技術所執行作業之間的交易。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.

下列範例中,示範如何在相同交易中執行 ADO.NET SqlClient 和 Entity Framework Core 作業。The following example, shows how to perform an ADO.NET SqlClient operation and an Entity Framework Core operation in the same transaction.

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

使用 System.TransactionsUsing System.Transactions

注意

這項功能是在 EF 核心 2.1 中新功能。This feature is new in EF Core 2.1.

您可使用環境交易,如果您需要在較大範圍協調。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 }))
{
    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 (System.Exception)
    {
        // TODO: Handle failure
    }
}

它也可在明確交易中登記。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;

        using (var context = new BloggingContext(options))
        {
            context.Database.EnlistTransaction(transaction);
            context.Database.OpenConnection();

            // 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();
        }

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

System.Transactions 的限制Limitations of System.Transactions

  1. EF 核心依賴資料庫提供者實作 System.Transactions 的支援。EF Core relies on database providers to implement support for System.Transactions. 雖然支援是很常見的 ADO.NET 提供者的.NET Framework API 只已最近新增到.NET Core,因此支援是無法為廣泛。Although support is quite common among ADO.NET providers for .NET Framework, the API has only been recently added to .NET Core and hence support is not be as widespread. 如果提供者未實作 system.transactions 的支援,則可能會完全忽略這些 Api 的呼叫。If a provider does not implement support for System.Transactions, it is possible that calls to these APIs will be completely ignored. 適用於.NET Core 的 SqlClient 支援它從 2.1 及更新版本。SqlClient for .NET Core does support it from 2.1 onwards. 適用於.NET Core 2.0 SqlClient 將會擲回例外狀況的您嘗試使用此功能。SqlClient for .NET Core 2.0 will throw an exception of you attempt to use the feature.

    重要

    我們建議您測試的資料,應用程式開發介面的行為會正確地與您的提供者之前您仰賴它來管理交易。It is recommended that you test that the API behaves correctly with your provider before you rely on it for managing transactions. 您是我們建議如果不存在,請連絡資料庫提供者維護程式。You are encouraged to contact the maintainer of the database provider if it does not.

  2. 從 2.1 版開始,.NET Core 的 System.Transactions 實作不包含分散式交易支援,因此您無法使用TransactionScopeCommitableTransaction跨多個資源管理員協調的交易。As of version 2.1, the System.Transactions implementation in .NET Core does not include support for distributed transactions, therefore you cannot use TransactionScope or CommitableTransaction to coordinate transactions across multiple resource managers.