這篇將帶讀者了解Dapper如何在底層利用CommandBehavior優化查詢效率,如何選擇正確Behavior在特定時機。
我這邊整理了各方法對應的Behavior表格 :
| 方法 | Behavior |
|---|---|
| Query | CommandBehavior.SequentialAccess & CommandBehavior.SingleResult |
| QueryFirst | CommandBehavior.SequentialAccess & CommandBehavior.SingleResult & CommandBehavior.SingleRow |
| QueryFirstOrDefault | CommandBehavior.SequentialAccess & CommandBehavior.SingleResult & CommandBehavior.SingleRow |
| QuerySingle | CommandBehavior.SingleResult & CommandBehavior.SequentialAccess |
| QuerySingleOrDefault | CommandBehavior.SingleResult & CommandBehavior.SequentialAccess |
| QueryMultiple | CommandBehavior.SequentialAccess |
首先可以看到每個方法都使用CommandBehavior.SequentialAccess,該標籤主要功能 使DataReader順序讀取行和列,行和列不緩衝,讀取一列後,它會從內存中刪除。,有以下優點 :
避免二進制大資源一次性讀取到記憶體,尤其是Blob或是Clob會配合GetBytes 或 GetChars 方法限制緩衝區大小,微軟官方也特別標註注意 :
加快查詢效率
但它卻不是DataReader的預設行為,系統預設是CommandBehavior.Default
CommandBehavior.Default有著以下特性 :
多個結果集(Multi Result)這兩個特性跟生產環境情況差滿多,畢竟大多時刻是只需要一組結果集配合有限的記憶體,所以除了SequentialAccess外Dapper還特別在大多方法使用了CommandBehavior.SingleResult,滿足只需一組結果就好避免浪費資源。
這段還有一段細節的處理,查看源碼可以發現除了標記SingleResult外,Dapper還特別加上一段代碼在結尾while (reader.NextResult()){},而不是直接Return(如圖片)

早些前我有特別發Issue(連結#1210)詢問過作者,這邊是回答 : 主要避免忽略錯誤,像是在DataReader提早關閉情況
有時候我們會遇到select top 1知道只會讀取一行資料的情況,這時候可以使用QueryFirst。它使用CommandBehavior.SingleRow可以避免浪費資源只讀取一行資料。
另外可以發現此段除了while (reader.NextResult()){}外還有while (reader.Read()) {},同樣是避免忽略錯誤,這是一些公司自行土炮ORM會忽略的地方。
兩者差別在QuerySingle沒有使用CommandBehavior.SingleRow,至於為何沒有使用,是因為需要有多行資料才能判斷是否不符合條件並拋出Exception告知使用者。
這段有一個特別好玩小技巧可以學,錯誤處理直接沿用對應LINQ的Exception,舉例:超過一行資料錯誤,使用new int[2].Single(),這樣不用另外維護Exceptiono類別,還可以擁有i18N多國語言化。
