Dapper源碼學習和源碼修改


目錄:

Dapper源碼學習和源碼修改(上篇主要講解入參解析)

Dapper源碼學習和源碼修改(下篇主要講解出參解析)

 

之前ORM比較火熱,自己也搞了個WangSql,但是感覺比較low,大家都說Dapper性能好,所以現在學習學習Dapper,下面簡單從宏觀層面講講我學習的Dapper。

再了解一個東西前,先得學會使用,我也不再贅述怎么使用,接轉一個文章吧

http://www.cnblogs.com/yankliu-vip/p/4182892.html

好就當學習了吧,該去看看源碼了,到底怎么實現和好在哪呢。

先上一張圖,已經把SqlMapper.cs按類拆分了,同時我自己在學習過程中也刪了加了改了一些類。

 

當然最重要的類還是SqlMapper.cs這個類,那就開始吧。

本來想把這個SqlMapper.cs類代碼全部粘貼的發現太長了,就算了吧,就把一些關鍵代碼粘貼過來。

        private static CacheInfo GetCacheInfo(Identity identity)
        {
            CacheInfo info;
            if (!TryGetQueryCache(identity, out info))
            {
                info = new CacheInfo();
                if (identity.parametersType != null)
                {
                    if (typeof(string).IsAssignableFrom(identity.parametersType))
                    {
                        info.ParamReader = delegate(IDbCommand cmd, object obj) { (new StringParameters() as IDynamicParameters).AddParameters(cmd, identity, obj); };
                    }
                    else if (typeof(IDictionary).IsAssignableFrom(identity.parametersType))
                    {
                        info.ParamReader = delegate(IDbCommand cmd, object obj) { (new DictionaryParameters() as IDynamicParameters).AddParameters(cmd, identity, obj); };
                    }
                    else
                    {
                        info.ParamReader = CreateParamInfoGenerator(identity);
                    }
                }
                SetQueryCache(identity, info);
            }
            return info;
        }

來來來,划重點了 info.ParamReader = CreateParamInfoGenerator(identity); 看到沒,這貨是干嘛的啊,哪里用的呢?

就這里用的,其實就是那是一個委托,主要用來創建Command的DataParameter的,不信看下面

        private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
        {
            var cmd = cnn.CreateCommand();
            var bindByName = GetBindByName(cmd.GetType());
            if (bindByName != null) bindByName(cmd, true);
            cmd.Transaction = transaction;
            cmd.CommandText = FormatSql(sql);
            if (commandTimeout.HasValue)
                cmd.CommandTimeout = commandTimeout.Value;
            if (commandType.HasValue)
                cmd.CommandType = commandType.Value;
            if (paramReader != null)
            {
                paramReader(cmd, obj);
            }
            return cmd;
        }

又划重點了, paramReader(cmd, obj); 這里就是執行委托創建Command的DataParameter了。

如果有人問為什么要這個呢,那就是你上面連Dapper基本使用都沒看啊,滾回去看看先。

舉個例子:

                sql = "insert into Teacher(Id,Name) values(@Id,@Name)";
                string tid = Guid.NewGuid().ToString();
                teacher = new Teacher()
                {
                    Id = tid,
                    Name = "wang"
                };
                intResult = SqlMapper.Execute(conn, sql, teacher);

這里 SqlMapper.Execute(conn, sql, teacher); 參數teacher就是上面 paramReader(cmd, obj);對應的參數obj,這個委托呢就是將自定義實體teacher變成cmd的Parameters。

那么你又要問了,怎么變的?額,這么嘛就是難點了....

回到上面看看,委托的創建 info.ParamReader = CreateParamInfoGenerator(identity); 

關鍵點也是難點啊同學們CreateParamInfoGenerator這個方法是干嘛的啊,就是創建委托的啊,你個白痴。

        public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity)
        {
            Type type = identity.parametersType;
            bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text;

            var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);

            var il = dm.GetILGenerator();

            il.DeclareLocal(type); // 0
            bool haveInt32Arg1 = false;
            il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
            il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param]
            il.Emit(OpCodes.Stloc_0);// stack is now empty

            il.Emit(OpCodes.Ldarg_0); // stack is now [command]
            il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters]

            IEnumerable<PropertyInfo> props = type.GetProperties();
            if (filterParams)
            {
                props = FilterParameters(props, identity.sql);
            }
            foreach (var prop in props)
            {
                if (filterParams)
                {
                    if (identity.sql.IndexOf(dbProvider.ParameterPrefix + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0
                      && identity.sql.IndexOf("#" + prop.Name + "#", StringComparison.InvariantCultureIgnoreCase) < 0
                        )
                    { // can't see the parameter in the text (even in a comment, etc) - burn it with fire
                        continue;
                    }
                }
                //if (prop.PropertyType == typeof(DbString))
                //{
                //    il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
                //    il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring]
                //    il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command]
                //    il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name]
                //    il.EmitCall(OpCodes.Callvirt, typeof(DbString).GetMethod("AddParameter"), null); // stack is now [parameters]
                //    continue;
                //}
                DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
                if (dbType == DbType.Xml)
                {
                    // this actually represents special handling for list types;
                    il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
                    il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name]
                    il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
                    il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
                    if (prop.PropertyType.IsValueType)
                    {
                        il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
                    }
                    il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters]
                    continue;
                }
                il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters]

                il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command]
                il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter]

                il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
                il.Emit(OpCodes.Ldstr, FormatNameForParameter(prop.Name)); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
                il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]

                il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
                EmitInt32(il, (int)dbType);// stack is now [parameters] [parameters] [parameter] [parameter] [db-type]

                il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]

                il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
                EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [parameters] [parameter] [parameter] [dir]
                il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]

                il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
                il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [parameters] [parameter] [parameter] [typed-param]
                il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [parameters] [parameter] [parameter] [typed-value]
                bool checkForNull = true;
                if (prop.PropertyType.IsValueType)
                {
                    il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [parameters] [parameter] [parameter] [boxed-value]
                    if (Nullable.GetUnderlyingType(prop.PropertyType) == null)
                    {   // struct but not Nullable<T>; boxed value cannot be null
                        checkForNull = false;
                    }
                }
                if (checkForNull)
                {
                    if (dbType == DbType.String && !haveInt32Arg1)
                    {
                        il.DeclareLocal(typeof(int));
                        haveInt32Arg1 = true;
                    }
                    // relative stack: [boxed value]
                    il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
                    Label notNull = il.DefineLabel();
                    Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null;
                    il.Emit(OpCodes.Brtrue_S, notNull);
                    // relative stack [boxed value = null]
                    il.Emit(OpCodes.Pop); // relative stack empty
                    il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull]
                    if (dbType == DbType.String)
                    {
                        EmitInt32(il, 0);
                        il.Emit(OpCodes.Stloc_1);
                    }
                    if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value);
                    il.MarkLabel(notNull);
                    if (prop.PropertyType == typeof(string))
                    {
                        il.Emit(OpCodes.Dup); // [string] [string]
                        il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length]
                        EmitInt32(il, 4000); // [string] [length] [4000]
                        il.Emit(OpCodes.Cgt); // [string] [0 or 1]
                        Label isLong = il.DefineLabel(), lenDone = il.DefineLabel();
                        il.Emit(OpCodes.Brtrue_S, isLong);
                        EmitInt32(il, 4000); // [string] [4000]
                        il.Emit(OpCodes.Br_S, lenDone);
                        il.MarkLabel(isLong);
                        EmitInt32(il, -1); // [string] [-1]
                        il.MarkLabel(lenDone);
                        il.Emit(OpCodes.Stloc_1); // [string] 
                    }
                    if (allDone != null) il.MarkLabel(allDone.Value);
                    // relative stack [boxed value or DBNull]
                }
                il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]

                if (prop.PropertyType == typeof(string))
                {
                    var endOfSize = il.DefineLabel();
                    // don't set if 0
                    il.Emit(OpCodes.Ldloc_1); // [parameters] [parameters] [parameter] [size]
                    il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [parameters] [parameter]

                    il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
                    il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [parameters] [parameter] [parameter] [size]
                    il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]

                    il.MarkLabel(endOfSize);
                }

                il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters]
                il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
            }
            // stack is currently [command]
            il.Emit(OpCodes.Pop); // stack is now empty
            il.Emit(OpCodes.Ret);
            return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
        }

 

好了,方法給你了,自己看吧,注釋寫的多詳細的啊。恩....

WTF,英文的啊,不要緊有翻譯的,反正自己看吧,我來給你講怕誤人子弟啊。

就我刪減后的Dapper 來說入參解析部分其實就到此結束,什么鬼,毫無亮點,要你講有何用。

別急,別急,下面講講我修改部分。

使用中,我發現參數的這么寫 insert into Student(Id,Name,Tid) values(@Id,@Name,@Tid) 那如果我換了數據庫比如MySql又得改@為?,換成Oracle又得把@改成:,這是我不能忍受的其一。

使用中,我發現 sql = "delete from Student where Id=#Id#"; intResult = SqlMapper.Execute(conn, sql, "jkajskajsk"); 報錯,為毛啊,連字符串入參傳入你都不認識,這是我不能忍受其二。

以上問題,可能是我版本問題,我用的是Dapper NET2.0版本。

不管為什么,先解決這個兩個痛點。

 

問題一:

原因分析,主要是不知道數據庫類型造成的。

解決辦法,那我就提前告訴你,數據庫相關信息,我們采用一種驅動方式來設置數據庫相關信息。

我新建了個類DbProvider.cs

    internal class DbProvider
    {
        public bool UseParameterPrefixInSql { get; set; }
        public bool UseParameterPrefixInParameter { get; set; }
        public string ParameterPrefix { get; set; }
    }

 

很簡單,后期你可以自己擴展,  ParameterPrefix  這個就是數據庫參數前綴,比如@ ? :

            //dbProvider
            dbProvider = new DbProvider()
            {
                UseParameterPrefixInSql = true,
                UseParameterPrefixInParameter = true,
                ParameterPrefix = "@"
            };

 

在SqlMapper創建一個 dbProvider 構造函數里面 對其初始化,具體值最好寫在web.config里面,初始化的時候去讀配置文件。

有了這個之前的SQL我們可以改改了 insert into Student(Id,Name,Tid) values(#Id#,#Name#,#Tid#)當然原來的寫法也是支持的,只不過現在這種寫法,保證的SQL參數的統一性,以后切換數據庫也容易多了。 

可是這樣寫了,能正常運行嗎?答案是NO,所以還需要下面的方法。

        //我寫的
        internal static string FormatNameForSql(string parameterName)
        {
            return dbProvider.UseParameterPrefixInSql ? (dbProvider.ParameterPrefix + parameterName) : parameterName;
        }
        internal static string FormatNameForParameter(string parameterName)
        {
            return dbProvider.UseParameterPrefixInParameter ? (dbProvider.ParameterPrefix + parameterName) : parameterName;
        }
        internal static string FormatSql(string sql)
        {
            Regex regex = new Regex("#([a-zA-Z0-9_]+?)#", RegexOptions.IgnoreCase | RegexOptions.Multiline);
            var ms = regex.Matches(sql);
            foreach (Match item in ms)
            {
                sql = sql.Replace(item.Value, FormatNameForSql(item.Groups[1].Value));
            }
            return sql;
        }

 

主要是這個方法 FormatSql 什么時候調呢,在這里

好了,問題一,反正是解決了,下面看看問題二了。

 

問題二:

原因分析,

來來來,划重點了 info.ParamReader = CreateParamInfoGenerator(identity); 看到沒,這貨是干嘛的啊,哪里用的呢?

就這里用的,其實就是那是一個委托,主要用來創建Command的DataParameter的

引用的上面的,那個委托啊CreateParamInfoGenerator不支持String、Dictionary這種入參造成的。

解決辦法,既然那個委托不支持,我就給不同的類型創建不同的委托就行了啥。

我為繼承string類型的創建了一個委托,委托是執行StringParameters實例的AddParameters方法。

我為繼承IDictionary類型的創建了一個委托,委托是執行DictionaryParameters實例的AddParameters方法。

通過不同的委托就能實現不同入參實現給Command的Parameters創建賦值了,哈哈哈哈哈....當然你要實現int double ...都一樣的方法,加個類繼承IDynamicParameters即可。

現在這樣子都可以正常使用了

                sql = "delete from Student where Id=#Id#";
                intResult = SqlMapper.Execute(conn, sql, "jkajskajsk");


                sql = "delete from Student where Id=#Id# and Name=@Name and Name=@Name1";
                Hashtable dic = new Hashtable();
                dic.Add("Id", "123");
                dic.Add("Name", "s1234");
                dic.Add("Name1", "d12345");
                intResult = SqlMapper.Execute(conn, sql, dic);

 

 

總結:

這篇文章只是對Dapper入參進行的分析,出參還沒看呢,先這樣吧,有空再說.

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM