never下sqlcient
【一】参数的输入
如执行update,我们写的代码应该是
sqlclient.Update("update xx set a.Moblie = '13800138000' where Id in (@Id) and Name = @Name;",new { @Id = new [] { 1,2,43 },@Name = "eee" });
表示更新Id =1,2,3这三行的信息。这里的参数是一个匿名类型:Id是数组类型,Name是string类型。@Id参数是如何变成(@Id1,@Id2,@Id3)的呢?我们跟踪一下
1 /// <summary> 2 /// sql语句拼接 3 /// </summary> 4 public class SqlParamerBuilder 5 { 6 /// <summary> 7 /// 准备参数 8 /// </summary> 9 /// <param name="prefix"></param> 10 /// <param name="parameter"></param> 11 /// <returns></returns> 12 public SqlParamerBuilder Build(string prefix, object @parameter) 13 { 14 if (string.IsNullOrEmpty(this.sql)) 15 return this; 16 17 if (this.builded) 18 return this; 19 20 var table = PreParameters(@parameter); 21 if (table == null || table.Count == 0) 22 { 23 this.parameters = new List<KeyValuePair<string, object>>(); 24 return this; 25 } 26 27 this.parameters = new List<KeyValuePair<string, object>>(table.Count); 28 var keys = new List<string>(table.Count); 29 sql = rxParamers.Replace(sql, m => 30 { 31 var name = m.Groups["name"].Value; 32 if (!table.ContainsKey(name)) 33 { 34 var i = m.Groups["name"].Index; 35 var count = 0; 36 while (i >= 0) 37 { 38 if (this.sql[i] == '\'') 39 { 40 count = 1; 41 break; 42 } 43 i--; 44 } 45 46 if (count > 0) 47 { 48 i = m.Groups["name"].Index; 49 while (i < this.sql.Length) 50 { 51 if (this.sql[i] == '\'') 52 { 53 count = 2; 54 break; 55 } 56 i++; 57 } 58 } 59 60 if (count != 2) 61 throw new Exception(string.Format("当前在sql语句中参数为{0}的值在所提供的参数列表中找不到", name)); 62 63 return string.Concat(this.sql[m.Groups["name"].Index - 1], name); 64 } 65 66 var stValue = table[name] as string; 67 if (stValue != null) 68 { 69 if (!keys.Contains(name)) 70 { 71 keys.Add(name); 72 parameters.Add(new KeyValuePair<string, object>(name, table[name])); 73 } 74 75 return string.Concat(prefix, name); 76 } 77 78 var value = table[name] as IEnumerable; 79 if (value != null) 80 { 81 int totalCount = 0; 82 var ator = value.GetEnumerator(); 83 var newNameList = new List<string>(50); 84 while (ator.MoveNext()) 85 { 86 totalCount++; 87 var newkey = string.Format("{0}{1}x{2}z", prefix, name, totalCount); 88 newNameList.Add(newkey); 89 parameters.Add(new KeyValuePair<string, object>(newkey, ator.Current)); 90 } 91 92 return string.Concat(string.Join(",", newNameList.ToArray())); 93 } 94 95 if (!keys.Contains(name)) 96 { 97 keys.Add(name); 98 parameters.Add(new KeyValuePair<string, object>(name, table[name])); 99 } 100 101 return string.Concat(prefix, name); 102 }); 103 104 this.builded = true; 105 keys.Clear(); 106 return this; 107 } 108 }
通过SqlParamerBuilder的Build方法可以知道:将匿名对象参数parameter转成Hashtable的同时将sql语句变成了"update xx where Id in (@Id1,@Id2,@Id3) and Name = @Name,并且只是遍历传入参数所有的Property属性(并不遍历Field字段)。
@Id根据数组变成(@Id1,@Id2,@Id3) 参数,而@Name会不会变成(@Name1,@Name2,@Name3)?我们的参数@Id传入的是数组,而@Name是字符串(可以认为是char的数组),如果没有对string特殊处理,@Name就是(@Name1,@Name2,@Name3)这三个参数。
1 //注意是string,实现了IEnumerable接口 2 var stValue = table[name] as string; 3 if (stValue != null) 4 { 5 if (!keys.Contains(name)) 6 { 7 keys.Add(name); 8 parameters.Add(new KeyValuePair<string, object>(name, table[name])); 9 } 10 11 return string.Concat(prefix, name); 12 } 13 14 var value = table[name] as IEnumerable; 15 if (value != null) 16 { 17 int totalCount = 0; 18 var ator = value.GetEnumerator(); 19 var newNameList = new List<string>(50); 20 while (ator.MoveNext()) 21 { 22 totalCount++; 23 var newkey = string.Format("{0}{1}x{2}z", prefix, name, totalCount); 24 newNameList.Add(newkey); 25 parameters.Add(new KeyValuePair<string, object>(newkey, ator.Current)); 26 } 27 28 return string.Concat(string.Join(",", newNameList.ToArray())); 29 }
可以知道string是特殊处理,所以可以认为遇上了IEnumerable接口的就是数组参数。
定位到@Id这一位置是用了下面的正则
/// <summary> /// 分析sql语句中的参数 /// </summary> Regex rxParamers = new Regex(@"(?<prefix>(?<![?@:])[?@:](?![?@:]))(?<name>\w+)", RegexOptions.Compiled);
这个正则可以帮我们找到【@?:】这三种开头的前缀,譬如上面的 where Id = @Id and Name = ?Name and Mobile = :Mobile
参数可以是匿名类,也可以对象。
【二】结果的输出
对update,delete执行的方法是ExecuteNonQuery,得到是Int类型的结果。而insert是使用ExecuteScalar,所以这个结果是看sql的语句的,打个比方,我们拿自增Id,不同的sql是不同的写法
sqlserver select @@identity; mysql select last_insert_id(); sqlite select last_insert_rowid();
对于select的结果,我们对QueryForObject<T>方法进行分析
1 /// <summary> 2 /// 查询列表 3 /// </summary> 4 /// <typeparam name="T">返回对象类型</typeparam> 5 /// <param name="command">查询命令</param> 6 /// <param name="closeConnection">关闭数据库连接</param> 7 /// <returns></returns> 8 protected virtual T QueryForObject<T>(IDbCommand command, bool closeConnection) 9 { 10 var @delegate = DataRecordBuilder<T>.Func; 11 IDataReader reader = null; 12 try 13 { 14 using (reader = this.CreateReader(command)) 15 { 16 var rd = new IDataRecordDecorator(reader); 17 if (reader.Read()) 18 { 19 return @delegate(rd.Load(reader)); 20 } 21 } 22 } 23 catch 24 { 25 throw; 26 } 27 finally 28 { 29 if (this.Transaction == null && closeConnection && reader != null && !reader.IsClosed) 30 reader.Close(); 31 } 32 33 return default(T); 34 }
看到这一行代码,是一个Func的委托
var @delegate = DataRecordBuilder<T>.Func;
我们定位到DataRecordBuilder<T>这一个类
1 /// <summary> 2 /// 对对象进行emit操作 3 /// </summary> 4 /// <param name="emit">The emit.</param> 5 public static void BuildObject(EasyEmitBuilder<Func<IDataRecord, T>> emit) 6 { 7 var type = typeof(T); 8 var targetMembers = GetMembers(type); 9 10 /*实例*/ 11 var instanceLocal = emit.DeclareLocal(type); 12 if (type.IsValueType) 13 { 14 if (targetMembers == null || targetMembers.Count == 0) 15 { 16 emit.LoadLocalAddress(instanceLocal); 17 emit.InitializeObject(type); 18 emit.LoadLocal(instanceLocal); 19 emit.Return(); 20 return; 21 } 22 23 emit.LoadLocalAddress(instanceLocal); 24 emit.InitializeObject(type); 25 emit.LoadLocal(instanceLocal); 26 emit.StoreLocal(instanceLocal); 27 goto _Read; 28 } 29 }
可以看到,里面是使用emit技术实现对对象T的属性或字段进行读写与赋值的(emit是后面所有技术点的基础)。
TypeHandler,有时候阻抗失败使用的方式,还是在DataRecordBuilder<T>这一个类中,我们拿属性进行get或set的时候,会对该属性或字段进行查询TypeHandlerAttribute这个attribute,
var attribute = member.GetCustomAttribute<TypeHandlerAttribute>();
那么是怎么用呢?拿demo一个Typehander例子来看其使用
1 public class User 2 { 3 public int Id { get; set; } 4 5 public long UserId { get; set; } 6 7 [Never.SqlClient.TypeHandler(typeof(UserNameTypeHandler))] 8 public char[] UserName { get; set; } 9 } 10 11 public class UserNameTypeHandler : IReadingFromDataRecordToValueTypeHandler<char[]>, ICastingValueToParameterTypeHandler<string> 12 { 13 /// <summary> 14 /// 15 /// </summary> 16 /// <param name="value"></param> 17 /// <returns></returns> 18 public string ToParameter(object value) 19 { 20 if (value == null) 21 return string.Empty; 22 23 return new string((char[])value); 24 } 25 26 /// <summary> 27 /// 获取结果 28 /// </summary> 29 /// <param name="dataRecord">读取器</param> 30 /// <param name="ordinal">column的位置,如果未-1表示没有找到这个值</param> 31 /// <param name="columnName">行名字</param> 32 /// <returns></returns> 33 public char[] ToValue(IDataRecord dataRecord, int ordinal, string columnName) 34 { 35 var value = dataRecord.GetString(ordinal); 36 return value == null ? new char[0] : value.ToCharArray(); 37 } 38 }
这个UserNameTypeHandler对象实现了2个接口,一个是从数据库到程序的类型转换IReadingFromDataRecordToValueTypeHandler<char[]>,一个是将程序的char[]类型换成数据库的string类型ICastingValueToParameterTypeHandler<string>,而User对象的UserName属性恰好也是char[]类型。
总结:组件使用emit实现核心方法,性能不底;并且有TypeHandler的支持,应该来说大部分数据结构应该还是可以应付的。本人没有测试过图片字段的,所以在对binary等字段处理,我想可以通过TypeHandler来实现
文章导航: