連線復原和重試邏輯

注意

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

由於後端失敗和網路不穩定,連線到資料庫伺服器的應用程式一直容易受到連線中斷的影響。 不過,在針對專用資料庫伺服器運作的 LAN 型環境中,這些錯誤很少見,因此不需要額外的邏輯來處理這些失敗。 隨著 Windows Azure SQL 資料庫等雲端式資料庫伺服器的興起,以及透過較不可靠的網路進行連線,連線中斷現在更為常見。 這可能是因為雲端資料庫用來確保服務公平性的防禦技術,例如連線節流,或網路不穩定導致間歇性逾時和其他暫時性錯誤。

連線復原是指 EF 能夠自動重試因這些連線中斷而失敗的任何命令。

執行策略

IDbExecutionStrategy 介面的實作會處理連線重試。 IDbExecutionStrategy 的實作將負責接受作業,如果發生例外狀況,則判斷重試是否適當,並重試。 EF 隨附四種執行策略:

  1. DefaultExecutionStrategy :此執行策略不會重試任何作業,這是 SQL Server 以外的資料庫預設值。
  2. DefaultSqlExecutionStrategy :這是預設使用的內部執行策略。 不過,此策略完全不會重試,它會包裝任何可能暫時性的例外狀況,以通知使用者他們可能想要啟用連線復原功能。
  3. DbExecutionStrategy :這個類別適合做為其他執行策略的基類,包括您自己的自訂策略。 它會實作指數重試原則,其中初始重試會發生零延遲,而延遲會以指數方式增加,直到達到最大重試計數為止。 這個類別具有抽象的 ShouldRetryOn 方法,可在衍生的執行策略中實作,以控制應重試哪些例外狀況。
  4. SqlAzureExecutionStrategy :此執行策略繼承自 DbExecutionStrategy,並會在使用 Azure SQL 資料庫時已知可能是暫時性的例外狀況重試。

注意

執行策略 2 和 4 包含在 EF 隨附的 Sql Server 提供者中,該提供者位於 EntityFramework.SqlServer 元件中,且設計為使用 SQL Server。

啟用執行策略

告知 EF 使用執行策略最簡單的方式,是搭配 DbConfiguration 類別的 SetExecutionStrategy 方法:

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetExecutionStrategy("System.Data.SqlClient", () => new SqlAzureExecutionStrategy());
    }
}

此程式碼會告知 EF 在連線到 SQL Server 時使用 SqlAzureExecutionStrategy。

設定執行策略

SqlAzureExecutionStrategy 的建構函式可以接受兩個參數 MaxRetryCount 和 MaxDelay。 MaxRetry 計數是策略將重試的最大次數。 MaxDelay 是 TimeSpan,代表執行策略將使用之重試之間的最大延遲。

若要將重試次數上限設定為 1,並將延遲上限設定為 30 秒,請執行下列動作:

public class MyConfiguration : DbConfiguration
{
    public MyConfiguration()
    {
        SetExecutionStrategy(
            "System.Data.SqlClient",
            () => new SqlAzureExecutionStrategy(1, TimeSpan.FromSeconds(30)));
    }
}

SqlAzureExecutionStrategy 會在第一次發生暫時性失敗時立即重試,但會在每次重試之間延遲較長的時間,直到超過最大重試限制或達到最大延遲的總時間為止。

執行策略只會重試一些通常為暫時性的例外狀況,您仍然需要處理其他錯誤,以及攔截 RetryLimitExceeded 例外狀況,以防發生錯誤不是暫時性或需要太長的時間才能自行解決。

使用重試執行策略時,有一些已知的限制:

不支援串流查詢

根據預設,EF6 和更新版本會緩衝處理查詢結果,而不是進行串流處理。 如果您想要將結果串流處理,您可以使用 AsStreaming 方法將 LINQ to Entities 查詢變更為串流。

using (var db = new BloggingContext())
{
    var query = (from b in db.Blogs
                orderby b.Url
                select b).AsStreaming();
    }
}

註冊重試執行策略時,不支援串流。 此限制存在,因為連線可能會卸載傳回結果的一部分。 發生此情況時,EF 必須重新執行整個查詢,但沒有可靠的方法來知道哪些結果已經傳回(資料在傳送初始查詢後可能已經變更,結果可能會以不同的順序傳回,結果可能沒有唯一識別碼等等)。

不支援使用者起始的交易

當您設定會導致重試的執行策略時,交易的使用有一些限制。

根據預設,EF 會在交易內執行任何資料庫更新。 您不需要執行任何動作即可啟用此功能,EF 一律會自動執行此動作。

例如,在下列程式碼中,SaveChanges 會自動在交易內執行。 如果 SaveChanges 在插入其中一個新月臺之後失敗,則會回復交易,且不會套用至資料庫。 內容也會保持狀態,允許再次呼叫 SaveChanges 以重試套用變更。

using (var db = new BloggingContext())
{
    db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" });
    db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" });
    db.SaveChanges();
}

不使用重試執行策略時,您可以在單一交易中包裝多個作業。 例如,下列程式碼會在單一交易中包裝兩個 SaveChanges 呼叫。 如果任一作業的任何部分失敗,則不會套用任何變更。

using (var db = new BloggingContext())
{
    using (var trn = db.Database.BeginTransaction())
    {
        db.Blogs.Add(new Site { Url = "http://msdn.com/data/ef" });
        db.Blogs.Add(new Site { Url = "http://blogs.msdn.com/adonet" });
        db.SaveChanges();

        db.Blogs.Add(new Site { Url = "http://twitter.com/efmagicunicorns" });
        db.SaveChanges();

        trn.Commit();
    }
}

使用重試執行策略時不支援這種情況,因為 EF 並不知道任何先前的作業,以及如何重試它們。 例如,如果第二個 SaveChanges 失敗,EF 就不再具有重試第一個 SaveChanges 呼叫所需的資訊。

解決方案:手動呼叫執行策略

解決方案是手動使用執行策略,並為其提供要執行的整個邏輯集,以便在其中一個作業失敗時重試所有專案。 當衍生自 DbExecutionStrategy 的執行策略正在執行時,它會暫停 SaveChanges 中使用的隱含執行策略。

請注意,應該在程式碼區塊內建構任何內容,以重試。 這可確保我們會從每次重試的全新狀態開始。

var executionStrategy = new SqlAzureExecutionStrategy();

executionStrategy.Execute(
    () =>
    {
        using (var db = new BloggingContext())
        {
            using (var trn = db.Database.BeginTransaction())
            {
                db.Blogs.Add(new Blog { Url = "http://msdn.com/data/ef" });
                db.Blogs.Add(new Blog { Url = "http://blogs.msdn.com/adonet" });
                db.SaveChanges();

                db.Blogs.Add(new Blog { Url = "http://twitter.com/efmagicunicorns" });
                db.SaveChanges();

                trn.Commit();
            }
        }
    });