摘要:試試 Entity Framework v6 新增的「連線彈性」功能。

簡介

Entity Framework v6 在資料庫連線管理方面有一些改進和新增功能,其中一項新功能叫做連線彈性(Connection Resilience),或稱為連線重試。會需要這項功能,是因為資料庫有時會處於短暫無法連接或無法服務的情況,例如網路不穩定或伺服器負載過重導致 SQL Server 突然暫時無法連接,但過了十秒之後就恢復正常。以往碰到這種狀況,開發人員可能得自行實作重試 邏輯。現在 EF 6 內建此功能,提供我們更多彈性來實作資料庫操作失敗時的重試機制。

更新:就在剛發布完這篇,得知 EF 6.1 RTM 了。Ya!

小實驗

我用一個簡單的 Console 程式來做點小實驗,程式碼如下:

static void Main(string[] args)
{

    var db = new Model.NorthwindEntities();
    using (db)
    {
        var qry = from t in db.Customers
                  select t;
        Console.WriteLine("Before connecting: {0:HH:mm:ss}", DateTime.Now);
        try
        {
            var customer = qry.FirstOrDefault();
            Console.WriteLine("Done: {0:HH:mm:ss}", DateTime.Now);
        }
        catch (Exception ex)
        {
            Console.WriteLine("Connection failed: {0:HH:mm:ss}", DateTime.Now);
            Console.WriteLine("Error: {0}", ex.Message);
        }                
    }
}

執行此程式之前,先把 Northwind 資料庫設定成離線狀態,然後觀察程式執行的結果,如下圖:


從執行結果可以發現,當資料庫連不上的時候,很快就傳回錯誤了,中間不過一秒的時間(即使在連線字串中指定 Connection Timeout 也一樣)。

在加入 EF 6 的失敗自動重試功能之前,先來了解一個相關概念:執行策略。

EF 6 的重試功能

EF 6 的連線重試邏輯是由 IDbExecutionStrategy 介面來定義執行策略的基礎操作,而實作此介面的類別必須負責提供重試機制,亦即當執行的動作失敗時,該類別必須決定是否需要重試。

EF 6 內建四種執行策略:
  • DefaultExecutionStrategy:完全不重試任何失敗的操作。這是 SQL Server 資料庫的預設執行策略。
  • DefaultSqlExecutionStrategy:預設的執行策略。它不會重試失敗的操作,但會把可能屬於短暫失效的狀況包在 exception 物件裡面,讓使用者注意到可能需要使用重試機制。
  • DbExecutionStrategy:此類別實作了「指數型遞增重試策略」,當操作失敗時,第一次的重試會在間隔 0 秒之後重試(亦即立刻重試);此後的重試間隔時間會以指數的幅度遞增(例如 1 秒、2 秒、4 秒....),直到重試次數已達最高次數限制。此類別有個抽象方法 ShouldRetryOn,讓子類別可以在此方法中根據當時的錯誤狀況來決定要不要重試。
  • SqlAzureExecutionStrategy:繼承自 DbExecutionStrategy,會針對 SqlAzure 的一些已知的暫時失效狀況進行重試。


加入重試邏輯

欲使用執行策略,必須要先設定 EF 的組態。一個比較簡單的方法,就是透過 DbConfiguration 類別的 SetExecutionStrategy 方法。

首先,在你的 DbContext 類別所在的專案中加入一個新類別,類別名稱假設叫做 MyEFConfig。程式碼如下:

public class MyEFConfig : DbConfiguration
{
    public MyEFConfig()
    {
        SetExecutionStrategy("System.Data.SqlClient",
            () => new MySqlAzureExecutionStrategy(4, TimeSpan.FromSeconds(15)));
    }
}


在此範例中,MyEFConfig 類別的建構函式呼叫了 SetExecutionStrategy 方法來指定執行策略。這裡使用的執行策略是另一個自訂類別 MySqlAzureExecutionStrategy 的執行個體,而且建立此執行策略物件時,明確指定最多重試 4 次,且最長的重試間隔時間不可超過 15 秒。也就是說,重試達 4 次或者重試間隔時間已經遞增到超過 15 秒時,就不再重試。注意:MyEFConfig 類別必須跟你的 DbContext 類別放在同一個組件裡

底下是 MySqlAzureExecutionStrategy 類別的程式碼:

public class MySqlAzureExecutionStrategy : SqlAzureExecutionStrategy
{
    private List<int> _errorCodesToRetry = new List<int>
    {
        // 把所有想要重試的錯誤代碼加入此串列.
        18456
    };

    public MySqlAzureExecutionStrategy(int maxRetryCount, TimeSpan maxDelay) : base(maxRetryCount, maxDelay)
    {

    }

    protected override bool ShouldRetryOn(Exception exception)
    {
        var sqlException = exception as SqlException;
        if (sqlException != null)
        {
            foreach (SqlError err in sqlException.Errors)
            {
                // 如果不知道哪些錯誤狀況需要重試,先用下面這行查看錯誤代碼,然後將它加入 _errorCodesToRetry 串列.
                // Console.WriteLine("ShouldRetryOn: " + err.Message + " , " + err.Number);

                if (_errorCodesToRetry.Contains(err.Number))
                {
                    return true;
                }
            }
        }
        return base.ShouldRetryOn(exception);
    }
}

OK! 這樣就行了,原本 Main 函式中的程式碼完全不用更動。再執行一次看看,結果如下圖:


從圖中可以看到,這次是花了超過一分鐘才放棄連線,其中經過了四次重試。

看樣子,這機制正好可以用來解決最近碰到的 SQL Server 暫時無法連線的問題。不過,自動重試機制有些限制,使用時必須特別注意,包括:
  1. 不支援串流操作;
  2. 不支援手動交易管理(明確呼叫 DbContext 物件的 Database.BeginTransaction 方法來啟動交易)。
管理交易方面的限制有法可解,詳情參見官方文件:Limitations with Retrying Execution Strategies

Happy coding!