• 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入参进行的分析,出参还没看呢,先这样吧,有空再说.

  • 相关阅读:
    【WPF】ListBox GridViewColumn Header 文字换行、文字多行显示
    【Unity】讯飞语音识别SDK
    【Unity】UGUI无法修改UI元素的Pivot锚点位置
    【Unity】EasyTouch5触屏检测
    【Unity】序列化字典Dictionary的问题
    【WPF】自定义形状的按钮Button
    【WPF】图片按钮的单击与双击事件
    【Unity】ShareSDK、SMSSDK的基本使用与常见问题
    【Unity】不能新建项目
    【Java】移动JDK路径后,修改环境变量不生效 Error: could not open `C:Program FilesJavajre1.8.0_131libamd64jvm.cfg'
  • 原文地址:https://www.cnblogs.com/deeround/p/6612213.html
Copyright © 2020-2023  润新知