不用預先宣告強型別,查詢資料表後直接傳回 dynamic 是 Dapper的強項,例如:var list = cn.Query("SELECT Col1,Col2 FROM T).ToList(); 將傳回 List<dynamic>,用 list.Fisrt().Col1 就能讀取欄位內容,簡潔又方便。
最近有個花式應用,想用通用函式接收 Dapper 查詢結果,自動列舉其中包含屬性(資料庫欄位)。一開始依循 System.Refelction 思維,想說用 GetType().GetProperties() 就可以搞定,不料踢到鐵板,Dapper 傳回的 dynamic 物件 .GetType() 結果為 null:
進一步追查,Dapper 傳回的 dynamic 骨子裡其實是個 DapperRow 型別,特別的是它實做了 IDictionary<string, object> 介面:
還記得既然要動態就動個痛快 - ExpandoObject介紹過 IDictionary<string, object> 的妙用,既然 DapperRow 是個 IDictionary<string, object> 一切好辦,轉型後用 .Keys 取回屬性清單,接下來就簡單了。
staticvoid Main(string[] args)
{
string sql = @"
SELECT 1 AS ColNum, SYSDATE AS ColDate, 'HELLO' AS ColString FROM DUAL UNION
SELECT 2 AS ColNum, SYSDATE AS ColDate, 'WORLD' AS ColString FROM DUAL
";
using (var cn = new OracleConnection(cs))
{
var res = cn.Query(sql);
StringBuilder sb = null;
foreach (dynamic rec in res)
{
var d = rec as IDictionary<string, object>;
if (sb == null)
{
sb = new StringBuilder(string.Join("\t", d.Keys.ToArray()));
sb.AppendLine();
}
sb.AppendLine(string.Join("\t",
d.Keys.Select(n =>
d[n] == null ? string.Empty : d[n].ToString()).ToArray()));
}
Console.WriteLine(sb.ToString());
Console.Read();
}
}
經實測可成功列舉欄位名稱及內容:
COLNUM COLDATE COLSTRING
1 2017/3/22 下午 09:07:30 HELLO
2 2017/3/22 下午 09:07:30 WORLD
最後,回到 DapperRow.GetType() 傳回 null 的謎團上,照理來說,GetType() 源自 Object 物件,查過 DapperRow 的原始碼並沒有覆寫 GetType(),想不出傳回 null 的理由。於是把問題丟上 stackoverflow,很快就有高手出面解惑,原來 DapperRow 實做了 DynamicMetaObject,攔截所有 dynamic 形式的成員存取,只要遇到非欄位名稱就傳回 null,因此不只 GetType(),呼叫 AsEnumerable() 也會傳回 null,所有謎團都解開了,結案!