使用交易

注意

僅限 EF6 及更新版本 - Entity Framework 6 已引進此頁面中所討論的功能及 API 等等。 如果您使用的是較早版本,則不適用部分或全部的資訊。

本檔將說明在 EF6 中使用交易,包括自 EF5 以來新增的增強功能,讓使用交易變得容易。

EF 預設會執行哪些工作

在所有 Entity Framework 版本中,每當您執行 SaveChanges() 以插入、更新或刪除資料庫時,架構就會將該作業包裝在交易中。 此交易只持續足夠的時間來執行作業,然後完成。 當您執行另一項這類作業時,就會啟動新的交易。

從 EF6 Database.ExecuteSqlCommand() 開始,如果尚未存在命令,預設會將命令包裝在交易中。 有一個方法的多載可讓您視需要覆寫此行為。 此外,在 EF6 中,透過 ObjectCoNtext.ExecuteFunction() API 執行模型中所包含的預存程式也會執行相同動作(不同之處在于目前無法覆寫預設行為)。

不論是哪一種情況,交易的隔離等級都是資料庫提供者認為其預設設定的任何隔離等級。 例如,在 SQL Server 上,這是 READ COMMITTED。

Entity Framework 不會將查詢包裝在交易中。

此預設功能適用于許多使用者,如果不需要在 EF6 中執行任何不同動作,則為 ;只要像您一樣撰寫程式碼。

不過,有些使用者需要更充分地控制其交易, 這在下列各節中涵蓋。

API 的運作方式

在 EF6 Entity Framework 之前,堅持開啟資料庫連接本身(如果已傳遞已開啟的連接,則會擲回例外狀況)。 由於交易只能在開啟的連線上啟動,這表示使用者將數個作業包裝成一筆交易的唯一方式是使用 TransactionScope 或使用 ObjectCoNtext.連線ion 屬性並開始直接在傳 回的 Entity連線ion 物件上呼叫 Open() BeginTransaction( )。 此外,如果您已自行在基礎資料庫連接上啟動交易,連絡資料庫的 API 呼叫將會失敗。

注意

Entity Framework 6 已移除只接受已關閉連線的限制。 如需詳細資訊,請參閱 連線ion Management

從 EF6 開始,架構現在提供:

  1. Database.BeginTransaction() :讓使用者在現有的 DbCoNtext 內啟動和完成交易本身的較簡單方法,可讓數個作業在相同的交易內合併,因此所有認可或全部回復為一個。 它也允許使用者更輕鬆地指定交易的隔離等級。
  2. Database.UseTransaction() :可讓 DbCoNtext 使用在 Entity Framework 外部啟動的交易。

將數個作業合併成相同內容內的一個交易

Database.BeginTransaction() 有兩個覆寫 - 一個採用明確的 IsolationLevel,另一個採用無引數,並使用基礎資料庫提供者的預設 IsolationLevel 。 這兩個 覆寫都會傳回 DbCoNtextTransaction 物件,該物件會提供 Commit() Rollback() 方法,這些方法會在基礎存放區交易上執行認可和回復。

DbCoNtextTransaction 是要在認可或回復之後處置。 完成此作業的其中一個簡單方式是 using(...) {...} 語法,當 using 區塊完成時,它會自動呼叫 Dispose()

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;

namespace TransactionsExamples
{
    class TransactionsExample
    {
        static void StartOwnTransactionWithinContext()
        {
            using (var context = new BloggingContext())
            {
                using (var dbContextTransaction = context.Database.BeginTransaction())
                {
                    context.Database.ExecuteSqlCommand(
                        @"UPDATE Blogs SET Rating = 5" +
                            " WHERE Name LIKE '%Entity Framework%'"
                        );

                    var query = context.Posts.Where(p => p.Blog.Rating >= 5);
                    foreach (var post in query)
                    {
                        post.Title += "[Cool Blog]";
                    }

                    context.SaveChanges();

                    dbContextTransaction.Commit();
                }
            }
        }
    }
}

注意

開始交易需要基礎存放區連線已開啟。 因此,如果尚未開啟連接,則呼叫 Database.BeginTransaction() 將會開啟連接。 如果 DbCoNtextTransaction 開啟連線,則會在呼叫 Dispose() 時關閉它。

將現有的交易傳遞至內容

有時候,您希望交易的範圍更廣,而且包含相同資料庫上的作業,但完全不在 EF 之外。 若要完成這項作業,您必須自行開啟連線並啟動交易,然後告訴 EF a) 使用已開啟的資料庫連線,而 b) 使用該連接上的現有交易。

若要這樣做,您必須在內容類別別上定義並使用建構函式,該建構函式繼承自其中一個 DbCoNtext 建構函式,而該建構函式採用 i) 現有的連接參數,而 ii) coNtextOwns連線ion 布林值。

注意

在此案例中呼叫時,coNtextOwns連線ion 旗標必須設定為 false。 這很重要,因為它會通知 Entity Framework,它不應該在完成連線時關閉連線(例如,請參閱下面的第 4 行):

using (var conn = new SqlConnection("..."))
{
    conn.Open();
    using (var context = new BloggingContext(conn, contextOwnsConnection: false))
    {
    }
}

此外,您必須自行啟動交易(如果您想要避免預設設定,請包含 IsolationLevel),並讓 Entity Framework 知道連線上已啟動現有的交易(請參閱下面的第 33 行)。

然後您可以直接在 Sql連線ion 本身或 DbCoNtext 上執行資料庫作業。 所有這類作業都會在一個交易內執行。 您負責認可或回復交易,並在交易上呼叫 Dispose(),以及關閉和處置資料庫連線。 例如:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;

namespace TransactionsExamples
{
     class TransactionsExample
     {
        static void UsingExternalTransaction()
        {
            using (var conn = new SqlConnection("..."))
            {
               conn.Open();

               using (var sqlTxn = conn.BeginTransaction(System.Data.IsolationLevel.Snapshot))
               {
                   var sqlCommand = new SqlCommand();
                   sqlCommand.Connection = conn;
                   sqlCommand.Transaction = sqlTxn;
                   sqlCommand.CommandText =
                       @"UPDATE Blogs SET Rating = 5" +
                        " WHERE Name LIKE '%Entity Framework%'";
                   sqlCommand.ExecuteNonQuery();

                   using (var context =  
                     new BloggingContext(conn, contextOwnsConnection: false))
                    {
                        context.Database.UseTransaction(sqlTxn);

                        var query =  context.Posts.Where(p => p.Blog.Rating >= 5);
                        foreach (var post in query)
                        {
                            post.Title += "[Cool Blog]";
                        }
                       context.SaveChanges();
                    }

                    sqlTxn.Commit();
                }
            }
        }
    }
}

清除交易

您可以將 null 傳遞至 Database.UseTransaction() 以清除 Entity Framework 對目前交易的知識。 當您這樣做時,Entity Framework 不會認可或回復現有的交易,因此請小心使用 ,而且只有在您確定這是您想要執行的動作時,才使用 。

UseTransaction 中的錯誤

如果您在下列情況下傳遞交易,就會看到 Database.UseTransaction() 的例外狀況:

  • Entity Framework 已經有現有的交易
  • Entity Framework 已在 TransactionScope 內運作
  • 傳遞之交易中的連線物件為 Null。 也就是說,交易未與連線相關聯 – 這通常是交易已完成的正負號
  • 所傳遞交易中的連線物件與 Entity Framework 的連接不符。

搭配其他功能使用交易

本節詳細說明上述交易如何與下列專案互動:

  • 恢復連線
  • 非同步方法
  • TransactionScope 交易

連線恢復功能

新的連線復原功能不適用於使用者起始的交易。 如需詳細資訊,請參閱 重試執行策略

非同步程式設計

前幾節所述的方法不需要進一步的選項或設定,才能使用 非同步查詢和儲存方法 。 但請注意,根據您在非同步方法內執行的動作,這可能會導致長時間執行的交易 ,這反過來可能會導致死結或封鎖,這對整體應用程式的效能不利。

TransactionScope Transactions

在 EF6 之前,提供較大範圍交易的建議方式是使用 TransactionScope 物件:

using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;

namespace TransactionsExamples
{
    class TransactionsExample
    {
        static void UsingTransactionScope()
        {
            using (var scope = new TransactionScope(TransactionScopeOption.Required))
            {
                using (var conn = new SqlConnection("..."))
                {
                    conn.Open();

                    var sqlCommand = new SqlCommand();
                    sqlCommand.Connection = conn;
                    sqlCommand.CommandText =
                        @"UPDATE Blogs SET Rating = 5" +
                            " WHERE Name LIKE '%Entity Framework%'";
                    sqlCommand.ExecuteNonQuery();

                    using (var context =
                        new BloggingContext(conn, contextOwnsConnection: false))
                    {
                        var query = context.Posts.Where(p => p.Blog.Rating > 5);
                        foreach (var post in query)
                        {
                            post.Title += "[Cool Blog]";
                        }
                        context.SaveChanges();
                    }
                }

                scope.Complete();
            }
        }
    }
}

Sql連線ion 和 Entity Framework 都會使用環境 TransactionScope 交易,因此會一起認可。

從 .NET 4.5.1 TransactionScope 開始,已更新為透過使用 TransactionScopeAsyncFlowOption 列舉來處理非同步方法:

using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Transactions;

namespace TransactionsExamples
{
    class TransactionsExample
    {
        public static void AsyncTransactionScope()
        {
            using (var scope = new TransactionScope(TransactionScopeAsyncFlowOption.Enabled))
            {
                using (var conn = new SqlConnection("..."))
                {
                    await conn.OpenAsync();

                    var sqlCommand = new SqlCommand();
                    sqlCommand.Connection = conn;
                    sqlCommand.CommandText =
                        @"UPDATE Blogs SET Rating = 5" +
                            " WHERE Name LIKE '%Entity Framework%'";
                    await sqlCommand.ExecuteNonQueryAsync();

                    using (var context = new BloggingContext(conn, contextOwnsConnection: false))
                    {
                        var query = context.Posts.Where(p => p.Blog.Rating > 5);
                        foreach (var post in query)
                        {
                            post.Title += "[Cool Blog]";
                        }

                        await context.SaveChangesAsync();
                    }
                }
                
                scope.Complete();
            }
        }
    }
}

TransactionScope 方法仍有一些限制:

  • 需要 .NET 4.5.1 或更新版本才能使用非同步方法。
  • 除非您確定您有一個且只有一個連線,否則無法在雲端案例中使用它(雲端案例不支援分散式交易)。
  • 無法與先前各節的 Database.UseTransaction() 方法結合。
  • 如果您發出任何 DDL,且尚未透過 MSDTC 服務啟用分散式交易,則會擲回例外狀況。

TransactionScope 方法的優點:

  • 如果您對指定的資料庫進行一個以上的連接,或將一個資料庫的連線與相同交易內不同資料庫的連線結合,它會自動將本機交易升級至分散式交易(注意:您必須將 MSDTC 服務設定為允許分散式交易才能運作)。
  • 容易撰寫程式碼。 如果您偏好交易是環境,並在背景隱含處理,而不是明確在您控制之下,則 TransactionScope 方法可能會更適合您。

總而言之,有了上述新的 Database.BeginTransaction() 和 Database.UseTransaction() API,交易範圍方法就不再需要大多數使用者。 如果您繼續使用 TransactionScope,請注意上述限制。 建議您盡可能改用先前各節中所述的方法。