iT邦幫忙

9

【C#、Dapper】 小技巧、研究 #1 : QueryFirstOrDefault

最近在研究Dapper Source Code想要抽離一些部分封裝自己想要的SQLHelper
做一些簡單筆記整理分享給版友,假如有不足的地方也期待版友留言、討論


使用建議

  1. 當select結構是select top 1 * 形式,建議使用QueryFirstOrDefault
  2. 當用到LINQ FirstOrDefault也建議使用『 QueryFirstOrDefault + 修改SQL為select top 1 』

原因效能快,QueryFirstOrDefault跟Query的效率比較參考,兩者在測試比較是87.80 us比94.05 us。

connection.Query("select * from T").FirstOrDefault();
connection.Query("select top 1 * from T");
改為
connection.QueryFirstOrDefault("select top 1 * from T");

至於要如何手寫一個Dapper QueryFirstOrDefault核心Core
我寫一個簡化版本(不實現Mapping)給讀者參考:

有幾個重點

  1. 一定要在 ExecuteReader使用CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow行為,這意思是告訴資料庫請按順序給我第一列資料就好,這是提高效率關鍵
  2. 請使用單次read讀取第一行資料資料,並使用空動作while把資料流快速讀完 (可以參考底下詳細說明)
  3. 因為藉由IDbConnection介面呼叫建立方法建立command跟reader物件,支持多資料庫
  4. 使用泛型類別判斷,以Func返回類別當結果類別依據,達到強型別維護
using System.Data;
using System.Data.Common;

public static class DapperDemo
{
	public static T QueryFirstOrDefault<T>(this IDbConnection connection,string sql,Func<IDataRecord,T> selector)
	{
		using (var cmd = connection.CreateCommand())
		{
			cmd.CommandText = sql;
			using (var reader = cmd.ExecuteReader(CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow))
			{
				var data = (reader.Read() && reader.FieldCount != 0) ? selector(reader) : default(T);
				while (reader.Read()) { }
				while (reader.NextResult()) { }
				return data;
			}
		}
	}
}

最後做測試Demo

void Main()
{
	using(var conn = Connection){
		conn.Open();
	
		//Test Data
		var cmd = conn.CreateCommand();
		cmd.CommandText = "with cte as ( select '0001' id,'ITWeiHan' name union all select '0002' id,'Henry' name ) select * into #T from cte ;";
		cmd.ExecuteNonQuery();

		//Query
		var result = conn.QueryFirstOrDefault("select top 1 * from #T", reader => new {id=reader.GetString(0),name=reader.GetString(1)});	
	}
}

感謝米歐問題:

資料流沒有讀完就 return,會發生什麼事情?

可以參考Dapper作者回應的ISSUE: Issue #1210 · StackExchange/Dapper

主要避免忽略錯誤,像是在DataReader提早關閉情況,並且前面已經跟系統強調行為是單列資料(CommandBehavior.SingleRow,CommandBehavior.SingleResult),所以不會增加多少成本。

20190319114948-image.png

跟S.O的DataReader底層介紹 c# - How DataReader works? - Stack Overflow


圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
米歐
iT邦新手 3 級 ‧ 2019-03-19 10:21:42

弱弱的問一下,資料流沒有讀完就 return,會發生什麼事情?

看更多先前的回應...收起先前的回應...
暐翰 iT邦大師 1 級 ‧ 2019-03-19 11:15:38 檢舉

這是一個好問題
我這邊反編譯來查看Read方法但底層的call看不到

我這邊在S.O詢問看看
c# - Why Dapper QueryFirst Core DataReader read to end instead of just doing one read - Stack Overflow

米歐 iT邦新手 3 級 ‧ 2019-03-19 11:38:37 檢舉

真的好神奇,期待答案。

暐翰 iT邦大師 1 級 ‧ 2019-03-19 11:56:40 檢舉

米歐 底下更新Dapper作者的回應

Homura iT邦高手 1 級 ‧ 2019-03-19 13:05:12 檢舉

暐翰
作者回真快
看很多套件作者不是不回
就是他自己也解決不了/images/emoticon/emoticon06.gif

暐翰 iT邦大師 1 級 ‧ 2019-03-19 13:23:06 檢舉

真的 用心秒回作者 :D

米歐 iT邦新手 3 級 ‧ 2019-03-19 14:09:25 檢舉

原來是避免忽略錯誤的部分!又學到一點實務上不太會需要的知識XD

Homura iT邦高手 1 級 ‧ 2019-03-19 14:54:28 檢舉

米歐
我覺得倒是不會
現在很多開發者都只會學怎麼用
很多概念根本不了解
導致大家寫出來的都差不多
而沒辦法寫出自己的東西/images/emoticon/emoticon06.gif

米歐 iT邦新手 3 級 ‧ 2019-03-19 15:39:19 檢舉

Homura
哈哈,可能彼此了解不多,這句話讓你誤會了。
我喜歡去鑽這種問題,也才會在這邊留言。
我覺得這內容很有幫助,但實務上真的很難碰到XD

/images/emoticon/emoticon01.gif

主要避免忽略錯誤,像是在DataReader提早關閉錯誤情況

好奇,這在什麼情況下會發生? /images/emoticon/emoticon19.gif

暐翰 iT邦大師 1 級 ‧ 2019-03-20 12:16:57 檢舉

好問題 我想想怎麼模擬

我要留言

立即登入留言