iT邦幫忙

第 11 屆 iT 邦幫忙鐵人賽

DAY 3
2

有了前面簡單ExpandoObject Dynamic Query例子的概念後,接著進到底層來了解Dapper如何細節處理,為何要自訂義DynamicMetaObjectProvider。

首先掌握Dynamic Query流程邏輯 :

假設使用下面代碼

using (var cn = new SqlConnection(@"Data Source=(localdb)\MSSQLLocalDB;Integrated Security=SSPI;Initial Catalog=master;"))
{
    var result = cn.Query("select N'暐翰' Name,26 Age").First();
    Console.WriteLine(result.Name);
}

取值的過程會是 : 建立動態Func > 保存在緩存 > 使用result.Name > 轉成呼叫 ((DapperRow)result)["Name"] > 從DapperTable.Values陣列中以"Name"欄位對應的Index取值

接著查看源碼GetDapperRowDeserializer方法,它掌管dynamic如何運行的邏輯,並動態建立成Func給上層API呼叫、緩存重複利用。
20191003190836.png

此段Func邏輯 :

  1. DapperTable雖然是方法內的局部變數,但是被生成的Func引用,所以不會被GC一直保存在記憶體內重複利用。
    20191003182219.png

  2. 因為是dynamic不需要考慮類別Mapping,這邊直接使用GetValue(index)向資料庫取值

var values = new object[select欄位數量];
for (int i = 0; i < values.Length; i++)
{
    object val = r.GetValue(i);
    values[i] = val is DBNull ? null : val;
}
  1. 將資料保存到DapperRow內
public DapperRow(DapperTable table, object[] values)
{
    this.table = table ?? throw new ArgumentNullException(nameof(table));
    this.values = values ?? throw new ArgumentNullException(nameof(values));
}
  1. DapperRow 繼承 IDynamicMetaObjectProvider 並實作 GetMetaObject 方法,實作邏輯是返回DapperRowMetaObject物件。
private sealed partial class DapperRow : System.Dynamic.IDynamicMetaObjectProvider
{
    DynamicMetaObject GetMetaObject(Expression parameter)
    {
        return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
    }
}
  1. DapperRowMetaObject主要功能是定義行為,藉由override BindSetMember、BindGetMember方法,Dapper定義了Get、Set的行為分別使用IDictionary<string, object> - GetItem方法DapperRow - SetValue方法
    20191003210351.png
    20191003210547.png

  2. 最後Dapper利用DataReader的欄位順序性,先利用欄位名稱取得Index,再利用Index跟Values取得值

20191003211448.png

為何要繼承IDictionary<string,object>?

可以思考一個問題 : 在DapperRowMetaObject可以自行定義Get跟Set行為,那麼不使用Dictionary - GetItem方法,改用其他方式,是否代表不需要繼承IDictionary<string,object>?

Dapper這樣做的原因之一跟開放原則有關,DapperTable、DapperRow都是底層實作類別,基於開放封閉原則不應該開放給使用者,所以設為private權限。

private class DapperTable{/*略*/}
private class DapperRow :IDictionary<string, object>, IReadOnlyDictionary<string, object>,System.Dynamic.IDynamicMetaObjectProvider{/*略*/}

那麼使用者想要知道欄位名稱怎麼辦?
因為DapperRow實作IDictionary所以可以向上轉型為IDictionary<string, object>,利用它為公開介面特性取得欄位資料。

public interface IDictionary<TKey, TValue> : ICollection<KeyValuePair<TKey, TValue>>, IEnumerable<KeyValuePair<TKey, TValue>>, IEnumerable{/*略*/}

舉個例子,筆者有做一個小工具HtmlTableHelper就是利用這特性,自動將Dapper Dynamic Query轉成Table Html,如以下代碼跟圖片

using (var cn = "Your Connection")
{
	var sourceData = cn.Query(@"select 'ITWeiHan' Name,25 Age,'M' Gender");
	var tablehtml = sourceData.ToHtmlTable(); //Result : <table><thead><tr><th>Name</th><th>Age</th><th>Gender</th></tr></thead><tbody><tr><td>ITWeiHan</td><td>25</td><td>M</td></tr></tbody></table>
}

20191003212846.png


上一篇
【深入Dapper.NET源碼】Dynamic Query 原理 Part1
下一篇
【深入Dapper.NET源碼】 Strongly Typed Mapping 原理 Part1 : ADO.NET對比Dapper
系列文
🌊 進階學習 ADO.NET、Dapper、Entity Framework 30

尚未有邦友留言

立即登入留言