您的位置:首頁>正文

關於Dapper.NET的相關論述

年少時, 為何不為自己的夢想去拼搏一次呢?縱使頭破血流, 也不悔有那年少輕狂。 感慨很多, 最近事情也很多, 博客也很少更新了, 畢竟每個人都需要為自己的生活去努力。

最近在一個群裡遇到一個人說的話, 在這裡不再贅述, 大概意思就是自己各種精通各種懂, 面試時各種裝逼各種吊, 本人真誠的求教了一下他, 問他是否懂這些東西的底層原理, 是否瞭解過底層源碼, 能否根據實際情況修改源碼, 誰知被他吐槽說裝逼, 說知識那麼多不能什麼都看源碼和理解原理吧。 但是我只想說, 這可是你自己說自己精通,

難道精通的框架不該瞭解源碼和原理嗎?難道精通就是只知道怎麼簡單的應用嗎?難道是我聊天的方式不對?

最近遇到一個問題, 那就是有關Dapper.NET的一些問題, Dapper.NET的效率為何很高?該組件的運行原理是什麼?說句實話, 我找了很久都沒有發現類似的文章, 不知道是不是我的搜素方式不對, 還希望發現類似好的文章的朋友發給我看看, 知識在於分享嘛, 不要吝嗇你的知識, 讓我們一起進步吧。

在這裡簡單介紹一下其原理

一.Dapper.NET概述:

專案開發時, 我們都是需要考慮專案的技術架構, 尤其是對資料庫底層的考慮比較多。 現在對於資料庫的訪問有ADO.NET, EF, Dapper.NET等等, 不同的情況會有不同的選擇, 討論的時候都會說到“xx很牛逼,

xx效率很高”等等, 總之需要幹一場, 才算我們開過會。 (很多時候, 在開會前專案選什麼技術早就定了, 但是不開個會就顯得做事不嚴謹...), 在選用Dapper.NET時, 有人說到Dapper.NET效率高, 很牛逼, 也不知道那個新人說了一句“為什麼Dapper.NET效率高?”

好尷尬...

Dapper.NET是一個簡單的ORM, 專門從SQL查詢結果中快速生成對象。 Dapper.Net支持執行sql查詢並將其結果映射到強類型清單或動態物件清單。 Dapper.Net緩存每個查詢的資訊。 這種全面的緩存有助於從大約兩倍於LINQ到SQL的查詢生成物件。 當前緩存由兩個ConcurrentDictionary物件處理, 它們從不被清除。

Dapper.Net通過擴展方法將兩個映射函數添加到IDbConnection介面, 這兩個函數都命名為ExecuteMapperQuery。 第一個映射結果是一個強類型列表, 而第二個映射結果是一個動態物件清單。 ExecuteMapperCommand執行並且不返回結果集。

所有三個方法都將參數接受為匿名類, 其中屬性值映射到同名的SQL參數。

Dapper.Net旨在僅處理結果集到物件映射。 它不處理物件之間的關係, 它不會自動生成任何類型的SQL查詢。

二.Dapper.NET原理淺析:

通過Dapper.NET的源碼我們可以發現其主要是通過“分部方法和分部類”進行可擴展設計的, 有關於“分部方法和分部類”的知識可以看這篇博客:http://www.cnblogs.com/pengze0902/p/6369541.html。 Dapper.Net也假定連接已打開並準備就緒, Dapper.NET通過對IDbConnection介面進行擴展。 在Dapper.NET對資料庫連接完成後, 可以進行相關的操作, 接下來我們就來看一下這些操作的實現方式。

1.Query方法:

Query(this IDbConnection cnn, string sql, object param = null,

改方法表示執行查詢, 返回按T輸入的資料。 該方法是Query方法的泛型方法, 有7個參數, 第一個參數為IDbConnection擴展類, 表示對IDbConnection介面進行擴展, 該方法使用了可選參數,

提高方法的擴展性。 在Query方法的實現中, 有一個CommandDefinition類, 用來表示sql操作的關鍵方面。 在該類下有一個GetInit方法。

2.GetInit方法:

我們都知道Dapper.NET通過Emit反射IDataReader的序列佇列, 來快速的得到和產生物件。 GetInit方法是一個靜態方法, 該方法的“Type commandType”參數表示連接關聯的Command物件, 返回一個Action委託。

我們就具體看一下是如何通過Emit反射IDataReader的序列佇列的。

if (SqlMapper.Link.TryGet(commandInitCache, commandType, out action)){ return action; }

Link是一個泛型分佈類, 這是一個微緩存, 查看是否存在一個Action的委託。

var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool)); var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int));

以上兩個操作主要獲取BindByName和InitialLONGFetchSize的獲取基本屬性設置。

if (bindByName != null || initialLongFetchSize != null) { var method = new DynamicMethod(commandType.Name + "_init", null, new Type { typeof(IDbCommand) }); var il = method.GetILGenerator; if (bindByName != null) { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, commandType); il.Emit(OpCodes.Ldc_I4_1); il.EmitCall(OpCodes.Callvirt, bindByName, null); } if (initialLongFetchSize != null) { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, commandType); il.Emit(OpCodes.Ldc_I4_M1); il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null); } il.Emit(OpCodes.Ret); action = (Action)method.CreateDelegate(typeof(Action)); }

這一步是該操作的核心部分, 利用Emit反射操作。 根據上一步獲取的對應名稱的基本屬性設置, 採用DynamicMethod物件, 定義和表示一個可以編譯, 執行和丟棄的動態方法。 丟棄的方法可用於垃圾回收。 調用該物件的GetILGenerator方法, 返回方法的Microsoft中間語言(MSIL)生成器,

默認的MSIL流大小為64位元組。 判斷基本屬性設置不為空後, 調用ILGenerator類的Emit方法, Emit將指定的指令放在指令流上, 該方法接收一個IL流。 EmitCall將 call或callvirt指令置於 Microsoft 中間語言 (MSIL) 流, 以調用varargs方法。 我們看到OpCodes類, 該類描述中間語言 (IL) 指令。 CreateDelegate完成動態方法並創建一個可用於執行它的委託。

通過以上的反射操作構建好物件後, 就會接著執行對應的資料庫操作。

3.QueryImpl:

private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefinition command, Type effectiveType) { object param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param == null ? null : param.GetType, null); var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; IDataReader reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { cmd = command.SetupCommand(cnn, info.ParamReader); if (wasClosed) cnn.Open; reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection | CommandBehavior.SequentialAccess : CommandBehavior.SequentialAccess); wasClosed = false; var tuple = info.Deserializer; int hash = GetColumnHash(reader); if (tuple.Func == null || tuple.Hash != hash) { if (reader.FieldCount == 0) yield break; tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (reader.Read) { object val = func(reader); if (val == null || val is T) { yield return (T)val; } else { yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); } } while (reader.NextResult) { } reader.Dispose; reader = null; command.OnCompleted; } finally { if (reader != null) { if (!reader.IsClosed) try { cmd.Cancel; } catch { /* don't spoil the existing exception */ } reader.Dispose; } if (wasClosed) cnn.Close; if (cmd != null) cmd.Dispose; } }

該方法為執行查詢操作的核心方法, 通過CommandDefinition類的相關操作後, 獲取到相應的物件後, 執行這一步操作。 該方法是IDbConnection的擴展方法, CommandDefinition表示sql的相關操作物件, Type表示傳入的一個有效的類型。 Identity物件表示Dapper中的緩存查詢的標識, 該類是一個分部類, 可以對其進行相應的擴展。 GetCacheInfo獲取緩存資訊。

三.Dapper.NET擴展:

這一部分是借花獻佛, 該部分代碼是對Dapper.NET代碼做一封裝,可以類似於操作其他ORM的方式,需要者可以自取,就不要到處去找這些東西了。

四.總結:

這篇博文是我硬著頭皮寫的,因為基本沒有類似的文章,連參考的資料都沒有,最多的就是調用代碼的demo,對於原理和底層源碼解析基本沒有,在這裡就用這篇博文引出大神對其全面的解析。希望對大家有一點幫助,也算是盡力了。

該部分代碼是對Dapper.NET代碼做一封裝,可以類似於操作其他ORM的方式,需要者可以自取,就不要到處去找這些東西了。

四.總結:

這篇博文是我硬著頭皮寫的,因為基本沒有類似的文章,連參考的資料都沒有,最多的就是調用代碼的demo,對於原理和底層源碼解析基本沒有,在這裡就用這篇博文引出大神對其全面的解析。希望對大家有一點幫助,也算是盡力了。

同類文章
Next Article
喜欢就按个赞吧!!!
点击关闭提示