• Dapper.net 在Parameterized时对于String的扩展(转)


    虽然Dapper通过提供的DbString本身支持对于String的指定Parameterized,但这方法明显不够,当Insert时,我们更希望是把一个Poco直接传递过去,而不是来new一个匿名函数,对于string类型的属性,转化成DbString,然后一个一个的属性再写一遍,这多苦逼

    通过代码,可以看到有这么一段方法

    public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused)


    这段代码就是用来构建Param参数的,内部通过Emit来实现,在里面可以找到遍历属性的代码,其内部有一些判断,这些就是可以直接增加代码来控制生成何种Param的地方,具体是两个地方

    if (dbType != DbType.Time)
    {
        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]
    }
    
    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] 
    }


    我们只要修改第一段代码部分的

    EmitInt32(il, (int)dbType);

    通过修改(int)dbType来控制AnsiString、AnsiStringFixedLength、String、StringFixedLength

    通过修改第二段代码部分的两个4000来控制字符串Param长度(其实修改第二个4000就能达到目标,但为啥还要第一个,整段Emit代码又是什么意思。。。完全没看懂!!)

    具体怎么改呢

    1、可以通过Attribute,这个方法比较简单,但坏处就是相当于破坏了Dapper不需要修改原代码的事实,如果Orm每次通过工具生成一次,就要修改一次Poco,当然你也可以修改Orm的生成工具,为属性加上相应的Length限制,好处就是可以直接应用Dapper自己提供的SqlMapperExtensions,而且相对一致,都是通过Attribute进行控制

    2、通过Mapping,这个就是要额外增加控制的类,下面是一个简单的类

    public class DapperStringParameterized
        {
            private Dictionary<string, KeyValuePair<DbType, int>> _dic = new Dictionary<string, KeyValuePair<DbType, int>>();
            /// <summary>
            /// 添加字符串参数化映射
            /// </summary>
            /// <param name="name">属性名</param>
            /// <param name="type">必须为AnsiString、AnsiStringFixedLength、String、StringFixedLength</param>
            /// <param name="len">必须为1~8000</param>
            public virtual void Add(string name, DbType type = DbType.AnsiString, int len = 50)
            {
                if (len <= 0 || len > 8000)
                {//长度范围1~8000,此处暂时对应sql,如果其它关系型数据库长度范围与此不一致,可继承修改
                    throw new ArgumentException("The param len's value must between 1 and 8000.");
                }
                if (type != DbType.AnsiString && type != DbType.AnsiStringFixedLength && type != DbType.String && type != DbType.StringFixedLength)
                {
                    return;
                }
                if (!string.IsNullOrEmpty(name))
                {
                    if (this._dic.ContainsKey(name))
                    {
                        throw new ArgumentException(string.Format("The param name '{0}' has aready existed!", name));
                    }
                    else
                    {
                        this._dic.Add(name, new KeyValuePair<DbType, int>(type, len));
                    }
                }
            }
            public void Remove(string name)
            {
                if (!string.IsNullOrWhiteSpace(name))
                {
                    if (this._dic.ContainsKey(name))
                    {
                        this._dic.Remove(name);
                    }
                }
            }
            public KeyValuePair<DbType, int>? GetParameterizedData(string name)
            {
                if (!string.IsNullOrWhiteSpace(name) && this._dic.ContainsKey(name))
                {
                    return this._dic[name];
                }
                return null;
            }
        }
    
        public class DapperStringParameterizedManager
        { 
            private static readonly DapperStringParameterizedManager manager = new DapperStringParameterizedManager();
            private static Dictionary<Type, DapperStringParameterized> dic = new Dictionary<Type, DapperStringParameterized>();
            private static object locObj = new object();
            private DapperStringParameterizedManager() { }
    
            public static DapperStringParameterizedManager Instance
            {
                get { return manager; }
            }
            /// <summary>
            /// 添加映射关系
            /// </summary>
            /// <returns></returns>
            public void AddMapping<T>(DapperStringParameterized mapping)
                where T : class
            {
                if (mapping != null)
                {
                    lock (locObj)
                    {
                        DapperStringParameterized tmpmapping = this.GetMapping(typeof(T));
                        if (tmpmapping == null)
                        {
                            dic.Add(typeof(T), mapping);
                        }
                        else
                        {
                            throw new ArgumentException(string.Format("The POCO Mapping {0} has aready existed!", typeof(T)));
                        }
                    }
                }
            }
    
            public DapperStringParameterized GetMapping(Type type)
            {
                if (type != null && dic.ContainsKey(type))
                {
                    return dic[type];
                }
                return null;
            }
        }


    使用时就是在CreateParamInfoGenerator方法中,foreach (var prop in props)之前添加代码

    DapperStringParameterized dsp = DapperStringParameterizedManager.Instance.GetMapping(identity.type);

    在获取DbType的地方增加代码

    DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
    KeyValuePair<DbType, int>? kvp = null;
    if (dbType == DbType.String && dsp != null)//默认所有字符串在Dapper中被param成 DbType.String
    {
        kvp = dsp.GetParameterizedData(prop.Name);
    }

    第一段代码部分修改为

    if (dbType != DbType.Time) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
    {
       //string parameter extensions  对于字符串参数化的扩展
       int dbTypeValue = (int)dbType;
       if (kvp.HasValue)
       {
           dbTypeValue = (int)kvp.Value.Key;
       }
    
       il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
       EmitInt32(il, dbTypeValue);// 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]
    }


    因为我们设定了字符串允许的最大长度,所以第二部分判断大小的代码直接注销,然后将下面另一段判断string的代码

    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);
    }


    修改为

    if (prop.PropertyType == typeof(string) && kvp.HasValue)
    {
         il.Emit(OpCodes.Dup);
         EmitInt32(il, kvp.Value.Value);
         il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter]
    }

    这样如果有设定字符串长度,则此部分代码会进行size设定,否则不设定

     

     

    实际用的地方只要在static构造函数中添加相应的初始化设定就可以了,建议将此部分代码写在相应的Repository部分,如果是三层则写在DAL部分,比如

    static _Default()
    {
        DapperStringParameterized dsp = new DapperStringParameterized();
    
        DapperStringParameterizedManager manager = DapperStringParameterizedManager.Instance;
        manager.AddMapping<Customer>(dsp);
    
        dsp.Add("UserName", DbType.String, 20);
        dsp.Add("Contact", DbType.String, 25);
    }

    好吧。。这样子做了之后只针对Query<T>起了作用,对于Execute没起作用,因为这个方法没指定类型T,在创建Identity时它直接将Type设为了null,那就添加ExecuteQ<T>方法,因为指定了T,所以将Execute的代码复制一份,然后将new Identity的地方将cnn后面的第一个null改为typeof(T)就可以了

  • 相关阅读:
    递归算法介绍及Java应用实战
    常用缓存淘汰算法(LFU、LRU、ARC、FIFO、MRU)
    阿里巴巴高级Java面试题(首发,70道)
    2017阿里技术年度精选(全)
    10年老兵给程序员的10条建议!
    8条关于Web前端性能的优化建议
    (16)约束
    (15)oracle序列
    (14)oracle数据字典
    (13)oracle导出、导入
  • 原文地址:https://www.cnblogs.com/tonykan/p/3463469.html
Copyright © 2020-2023  润新知