真心想说:其实。。。我不想用Dapper,如果OrmLite.Net支持参数化的话,也就没Dapper的什么事情了,对于OrmLite.Net只能做后续跟踪......

这个其实是看了Dapper作者的扩展后觉得不爽,然后自己按照他的设计思路重写了代码,只支持单个数据的增删改查,根据Expression来查的真心无能为力......

另外作者似乎已经支持了属性、字段等与数据库中的映射.....

具体包含了

1、对字符串的扩展

2、对主键的定义,支持单或多主键,当单主键并且类型为数字时,认为该主键为自增列

3、对表名的定义

实际代码如下:

DapperExtensions部分

[csharp] view plaincopy
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Data;
  6. using System.Collections.Concurrent;
  7. using System.Reflection;
  8. namespace Dapper
  9. {
  10. /// <summary>
  11. /// Dapper扩展POCO信息类,为应用此扩展,在原Dapper的第1965行增加了对此类的应用
  12. /// </summary>
  13. public class DapperPocoInfo
  14. {
  15. private Type _type;
  16. private ConcurrentDictionary<string, KeyValuePair<DbType, int>> _stringColumns
  17. = new ConcurrentDictionary<string, KeyValuePair<DbType, int>>();
  18. private IEnumerable<PropertyInfo> _typeProperties;
  19. private IList<PropertyInfo> _keyProperties = new List<PropertyInfo>();
  20. private IEnumerable<PropertyInfo> _defaultKeyPropeties;
  21. private string _tableName;
  22. /// <summary>
  23. /// 对应表名
  24. /// </summary>
  25. public string TableName
  26. {
  27. get
  28. {
  29. if (string.IsNullOrWhiteSpace(this._tableName))
  30. {
  31. this._tableName = this._type.Name;
  32. }
  33. return this._tableName;
  34. }
  35. set
  36. {
  37. this._tableName = value;
  38. }
  39. }
  40. /// <summary>
  41. /// 所有Public的属性集合
  42. /// </summary>
  43. public IEnumerable<PropertyInfo> TypeProperties
  44. {
  45. get { return this._typeProperties; }
  46. }
  47. /// <summary>
  48. /// 获取主键集合,如果主键数量大于1,则认为是联合主键,等于0认为不存在主键,等于1并且类型为数字型则认为自增主键
  49. /// </summary>
  50. /// <returns></returns>
  51. public IEnumerable<PropertyInfo> KeyProperties
  52. {
  53. get
  54. {
  55. if (this._keyProperties.Count == 0)
  56. {//如果未设定KeyProperties,则默认认为第一个后缀为ID的PropertyInfo为主键
  57. if (this._defaultKeyPropeties == null)
  58. {
  59. this._defaultKeyPropeties = this._typeProperties.Where(p => p.Name.ToLower().EndsWith("id")).Take(1).ToArray();
  60. }
  61. return this._defaultKeyPropeties;
  62. }
  63. else
  64. {
  65. return this._keyProperties;
  66. }
  67. }
  68. }
  69. /// <summary>
  70. /// .oct
  71. /// </summary>
  72. /// <param name="type"></param>
  73. internal DapperPocoInfo(Type type)
  74. {
  75. if (type == null)
  76. {
  77. throw new ArgumentNullException();
  78. }
  79. this._type = type;
  80. this._typeProperties = type.GetProperties();
  81. }
  82. /// <summary>
  83. /// 添加字符串参数化映射
  84. /// </summary>
  85. /// <param name="propertyName">属性名</param>
  86. /// <param name="dbType">必须为AnsiString、AnsiStringFixedLength、String、StringFixedLength</param>
  87. /// <param name="len">值范围为1~8000</param>
  88. public virtual void AddStringColumnMap(string propertyName, DbType dbType = DbType.AnsiString, int len = 50)
  89. {
  90. this.GetProperty(propertyName);
  91. if (len <= 0 || len > 8000)
  92. {//长度范围1~8000,此处暂时对应sql,如果其它关系型数据库长度范围与此不一致,可继承修改
  93. throw new ArgumentException("The param len's value must between 1 and 8000.");
  94. }
  95. if (dbType != DbType.AnsiString && dbType != DbType.AnsiStringFixedLength && dbType != DbType.String && dbType != DbType.StringFixedLength)
  96. {
  97. return;
  98. }
  99. this._stringColumns.TryAdd(propertyName, new KeyValuePair<DbType, int>(dbType, len));
  100. }
  101. /// <summary>
  102. /// 获取字符串映射
  103. /// </summary>
  104. /// <param name="propertyName"></param>
  105. /// <returns></returns>
  106. public KeyValuePair<DbType, int>? GetStringColumnMap(string propertyName)
  107. {
  108. KeyValuePair<DbType, int> kvp;
  109. if (this._stringColumns.TryGetValue(propertyName, out kvp))
  110. {
  111. return kvp;
  112. }
  113. return null;
  114. }
  115. private PropertyInfo GetProperty(string propertyName)
  116. {
  117. if (string.IsNullOrWhiteSpace(propertyName))
  118. {
  119. throw new ArgumentNullException("propertyName can not be null or empty value");
  120. }
  121. PropertyInfo pi = this._typeProperties.Where(p => p.Name.ToLower() == propertyName.ToLower()).FirstOrDefault();
  122. if (pi == null)
  123. {
  124. throw new ArgumentOutOfRangeException(string.Format("The class '{0}' does not contains property '{1}'.", this._type.FullName, propertyName));
  125. }
  126. return pi;
  127. }
  128. /// <summary>
  129. /// 添加主键映射
  130. /// </summary>
  131. /// <param name="propertyName"></param>
  132. public void AddKeyMap(string propertyName)
  133. {
  134. var pi = this.GetProperty(propertyName);
  135. if (this._keyProperties.Where(p => p.Name == pi.Name).FirstOrDefault() == null)
  136. {
  137. this._keyProperties.Add(pi);
  138. this._unWriteKey = null;//赋值时取消已经确认的是否可写键值
  139. }
  140. }
  141. /// <summary>
  142. /// 不需要插入数据的主键类型,除了Guid,其它均认为自增
  143. /// </summary>
  144. private static Type[] UnWriteTypes = { typeof(int), typeof(short), typeof(long), typeof(byte), typeof(Guid) };
  145. private bool? _unWriteKey;
  146. /// <summary>
  147. /// 主键是否可写
  148. /// </summary>
  149. /// <returns></returns>
  150. public bool IsUnWriteKey()
  151. {
  152. if (!this._unWriteKey.HasValue)
  153. {
  154. this._unWriteKey = false;
  155. IList<PropertyInfo> keys = this.KeyProperties.ToList();
  156. if (keys.Count == 1)
  157. {
  158. this._unWriteKey = UnWriteTypes.Contains(keys[0].PropertyType);
  159. }
  160. }
  161. return this._unWriteKey.Value;
  162. }
  163. }
  164. /// <summary>
  165. /// Dapper扩展类
  166. /// </summary>
  167. public static class DapperExtensions
  168. {
  169. private static ConcurrentDictionary<RuntimeTypeHandle, DapperPocoInfo> PocoInfos
  170. = new ConcurrentDictionary<RuntimeTypeHandle, DapperPocoInfo>();
  171. /// <summary>
  172. /// 已实现的ISqlAdapter集合
  173. /// </summary>
  174. private static readonly Dictionary<string, ISqlAdapter> AdapterDictionary
  175. = new Dictionary<string, ISqlAdapter>() {
  176. {"sqlconnection", new MsSqlServerAdapter()}
  177. };
  178. public static DapperPocoInfo GetPocoInfo<T>()
  179. where T : class
  180. {
  181. return GetPocoInfo(typeof(T));
  182. }
  183. public static DapperPocoInfo GetPocoInfo(this Type type)
  184. {
  185. DapperPocoInfo pi;
  186. RuntimeTypeHandle hd = type.TypeHandle;
  187. if (!PocoInfos.TryGetValue(hd, out pi))
  188. {
  189. pi = new DapperPocoInfo(type);
  190. PocoInfos[hd] = pi;
  191. }
  192. return pi;
  193. }
  194. public static ISqlAdapter GetSqlAdapter(this IDbConnection connection)
  195. {
  196. string name = connection.GetType().Name.ToLower();
  197. ISqlAdapter adapter;
  198. if (!AdapterDictionary.TryGetValue(name, out adapter))
  199. {
  200. throw new NotImplementedException(string.Format("Unknow sql connection '{0}'", name));
  201. }
  202. return adapter;
  203. }
  204. /// <summary>
  205. /// 新增数据,如果T只有一个主键,且新增的主键为数字,则会将新增的主键赋值给entity相应的字段,如果为多主键,或主键不是数字和Guid,则主键需输入
  206. /// 如果要进行匿名类型新增,因为匿名类型无法赋值,需调用ISqlAdapter的Insert,由数据库新增的主键需自己写方法查询
  207. /// </summary>
  208. /// <typeparam name="T"></typeparam>
  209. /// <param name="connection"></param>
  210. /// <param name="entity"></param>
  211. /// <param name="transaction"></param>
  212. /// <param name="commandTimeout"></param>
  213. /// <returns></returns>
  214. public static bool Insert<T>(this IDbConnection connection, T entity, IDbTransaction transaction = null, int? commandTimeout = null)
  215. where T : class
  216. {
  217. ISqlAdapter adapter = GetSqlAdapter(connection);
  218. return adapter.Insert<T>(connection, entity, transaction, commandTimeout);
  219. }
  220. /// <summary>
  221. /// 更新数据,如果entity为T,则全字段更新,如果为匿名类型,则修改包含的字段,但匿名类型必须包含主键对应的字段
  222. /// </summary>
  223. /// <typeparam name="T"></typeparam>
  224. /// <param name="connection"></param>
  225. /// <param name="entity"></param>
  226. /// <param name="transaction"></param>
  227. /// <param name="commandTimeout"></param>
  228. /// <returns></returns>
  229. public static bool Update<T>(this IDbConnection connection, object entity, IDbTransaction transaction = null, int? commandTimeout = null)
  230. where T : class
  231. {
  232. ISqlAdapter adapter = GetSqlAdapter(connection);
  233. return adapter.UpdateByKey<T>(connection, entity, transaction, commandTimeout);
  234. }
  235. /// <summary>
  236. /// 删除数据,支持匿名,但匿名类型必须包含主键对应的字段
  237. /// </summary>
  238. /// <typeparam name="T"></typeparam>
  239. /// <param name="connection"></param>
  240. /// <param name="param"></param>
  241. /// <param name="transaction"></param>
  242. /// <param name="commandTimeout"></param>
  243. /// <returns></returns>
  244. public static bool DeleteByKey<T>(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null)
  245. where T : class
  246. {
  247. ISqlAdapter adapter = GetSqlAdapter(connection);
  248. return adapter.DeleteByKey<T>(connection, param, transaction, commandTimeout);
  249. }
  250. /// <summary>
  251. /// 查询数据,支持匿名,但匿名类型必须包含主键对应的字段
  252. /// </summary>
  253. /// <typeparam name="T"></typeparam>
  254. /// <param name="connection"></param>
  255. /// <param name="param"></param>
  256. /// <param name="transaction"></param>
  257. /// <param name="commandTimeout"></param>
  258. /// <returns></returns>
  259. public static T QueryByKey<T>(this IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null)
  260. where T : class
  261. {
  262. ISqlAdapter adapter = GetSqlAdapter(connection);
  263. return adapter.QueryByKey<T>(connection, param, transaction, commandTimeout);
  264. }
  265. /// <summary>
  266. /// 获取最后新增的ID值,仅对Indentity有效
  267. /// </summary>
  268. /// <typeparam name="T"></typeparam>
  269. /// <param name="connection"></param>
  270. /// <returns></returns>
  271. public static long GetLastInsertIndentityID<T>(this IDbConnection connection)
  272. where T : class
  273. {
  274. ISqlAdapter adapter = GetSqlAdapter(connection);
  275. return adapter.GetLastInsertID<T>(connection);
  276. }
  277. }
  278. public interface ISqlAdapter
  279. {
  280. string GetFullQueryByKeySql(Type type);
  281. string GetFullInsertSql(Type type);
  282. string GetFullUpdateByKeySql(Type type);
  283. string GetDeleteByKeySql(Type type);
  284. bool Insert<T>(IDbConnection connection, object entity, IDbTransaction transaction, int? commandTimeout)
  285. where T : class;
  286. bool UpdateByKey<T>(IDbConnection connection, object entity, IDbTransaction transaction, int? commandTimeout)
  287. where T : class;
  288. bool DeleteByKey<T>(IDbConnection connection, object param, IDbTransaction transaction, int? commandTimeout)
  289. where T : class;
  290. T QueryByKey<T>(IDbConnection connection, object param, IDbTransaction transaction, int? commandTimeout)
  291. where T : class;
  292. long GetLastInsertID<T>(IDbConnection connection)
  293. where T : class;
  294. }
  295. internal class BasicSql
  296. {
  297. public string FullQueryByKeySql { get; set; }
  298. public string FullInsertSql { get; set; }
  299. public string FullUpdateByKeySql { get; set; }
  300. public string DeleteByKeySql { get; set; }
  301. }
  302. public class MsSqlServerAdapter : ISqlAdapter
  303. {
  304. private static ConcurrentDictionary<RuntimeTypeHandle, BasicSql> BasicSqls
  305. = new ConcurrentDictionary<RuntimeTypeHandle, BasicSql>();
  306. private static readonly char SqlParameterChar = '@';
  307. internal MsSqlServerAdapter() { }
  308. private string GetParameterName(PropertyInfo pi)
  309. {
  310. return string.Format("{0}{1}", SqlParameterChar, pi.Name);
  311. }
  312. private BasicSql GetBasicSql(Type type)
  313. {
  314. BasicSql basicSql;
  315. RuntimeTypeHandle hd = type.TypeHandle;
  316. if (!BasicSqls.TryGetValue(hd, out basicSql))
  317. {
  318. basicSql = new BasicSql();
  319. BasicSqls[hd] = basicSql;
  320. }
  321. return basicSql;
  322. }
  323. private void AppendKeyParameter(StringBuilder tmp, IEnumerable<PropertyInfo> keys)
  324. {
  325. if (keys.Any())
  326. {
  327. tmp.AppendLine(" WHERE");
  328. foreach (PropertyInfo key in keys)
  329. {
  330. tmp.Append(key.Name);
  331. tmp.Append("=");
  332. tmp.Append(this.GetParameterName(key));
  333. tmp.Append(" AND ");
  334. }
  335. tmp.Remove(tmp.Length - 5, 5);
  336. }
  337. }
  338. public string GetFullQueryByKeySql(Type type)
  339. {
  340. BasicSql basicSql = this.GetBasicSql(type);
  341. if (string.IsNullOrEmpty(basicSql.FullQueryByKeySql))
  342. {
  343. DapperPocoInfo dpi = type.GetPocoInfo();
  344. StringBuilder tmp = new StringBuilder();
  345. tmp.Append("SELECT * FROM ");
  346. tmp.Append(dpi.TableName);
  347. tmp.Append(" (NOLOCK) ");
  348. this.AppendKeyParameter(tmp, dpi.KeyProperties);
  349. basicSql.FullQueryByKeySql = tmp.ToString();
  350. }
  351. return basicSql.FullQueryByKeySql;
  352. }
  353. public string GetFullInsertSql(Type type)
  354. {
  355. BasicSql basicSql = this.GetBasicSql(type);
  356. if (string.IsNullOrEmpty(basicSql.FullInsertSql))
  357. {
  358. DapperPocoInfo dpi = type.GetPocoInfo();
  359. basicSql.FullInsertSql = this.GetInsertSql(dpi, dpi.TypeProperties);
  360. }
  361. return basicSql.FullInsertSql;
  362. }
  363. private string GetInsertSql(DapperPocoInfo dpi, IEnumerable<PropertyInfo> props)
  364. {
  365. StringBuilder tmp = new StringBuilder();
  366. tmp.Append("INSERT INTO ");
  367. tmp.AppendLine(dpi.TableName);
  368. tmp.Append('(');
  369. IEnumerable<PropertyInfo> valueProps = props;
  370. if (dpi.IsUnWriteKey())
  371. {
  372. valueProps = this.GetExceptProps(props, dpi.KeyProperties);
  373. }
  374. this.AppendColumnList(tmp, valueProps, '\0');
  375. tmp.Append(')');
  376. tmp.AppendLine(" VALUES ");
  377. tmp.Append('(');
  378. this.AppendColumnList(tmp, valueProps, SqlParameterChar);
  379. tmp.Append(')');
  380. return tmp.ToString();
  381. }
  382. private void AppendColumnList(StringBuilder tmp, IEnumerable<PropertyInfo> valueProps, char addChar)
  383. {
  384. foreach (PropertyInfo p in valueProps)
  385. {
  386. tmp.Append(addChar);
  387. tmp.Append(p.Name);
  388. tmp.Append(',');
  389. }
  390. tmp.Remove(tmp.Length - 1, 1);
  391. }
  392. private IEnumerable<PropertyInfo> GetExceptProps(IEnumerable<PropertyInfo> props1, IEnumerable<PropertyInfo> props2)
  393. {
  394. //return props1.Except(props2, new EqualityCompareProperty()).ToArray();
  395. IList<PropertyInfo> list = new List<PropertyInfo>();
  396. foreach (PropertyInfo pi in props1)
  397. {
  398. string name = pi.Name.ToLower();
  399. if (!props2.Any(p => p.Name.ToLower() == name))
  400. {
  401. list.Add(pi);
  402. }
  403. }
  404. return list;
  405. }
  406. private string GetUpdateSql(DapperPocoInfo dpi, IEnumerable<PropertyInfo> props)
  407. {
  408. StringBuilder tmp = new StringBuilder();
  409. tmp.Append("UPDATE ");
  410. tmp.AppendLine(dpi.TableName);
  411. tmp.Append("SET ");
  412. IEnumerable<PropertyInfo> valueProps = this.GetExceptProps(props, dpi.KeyProperties);
  413. foreach (PropertyInfo p in valueProps)
  414. {
  415. tmp.Append(p.Name);
  416. tmp.Append('=');
  417. tmp.Append(SqlParameterChar);
  418. tmp.Append(p.Name);
  419. tmp.Append(',');
  420. }
  421. tmp.Remove(tmp.Length - 1, 1);
  422. tmp.AppendLine();
  423. this.AppendKeyParameter(tmp, dpi.KeyProperties);
  424. return tmp.ToString();
  425. }
  426. public string GetFullUpdateByKeySql(Type type)
  427. {
  428. BasicSql basicSql = this.GetBasicSql(type);
  429. if (string.IsNullOrEmpty(basicSql.FullUpdateByKeySql))
  430. {
  431. DapperPocoInfo dpi = type.GetPocoInfo();
  432. basicSql.FullUpdateByKeySql = this.GetUpdateSql(dpi, dpi.TypeProperties);
  433. }
  434. return basicSql.FullUpdateByKeySql;
  435. }
  436. public string GetDeleteByKeySql(Type type)
  437. {
  438. BasicSql basicSql = this.GetBasicSql(type);
  439. if (string.IsNullOrEmpty(basicSql.DeleteByKeySql))
  440. {
  441. DapperPocoInfo dpi = type.GetPocoInfo();
  442. StringBuilder tmp = new StringBuilder();
  443. tmp.Append("DELETE FROM ");
  444. tmp.AppendLine(dpi.TableName);
  445. this.AppendKeyParameter(tmp, dpi.KeyProperties);
  446. basicSql.DeleteByKeySql = tmp.ToString();
  447. }
  448. return basicSql.DeleteByKeySql;
  449. }
  450. public bool Insert<T>(IDbConnection connection, object entity, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  451. {
  452. Type type = typeof(T);
  453. string insertSql;
  454. Type entityType = entity.GetType();
  455. DapperPocoInfo dpi = type.GetPocoInfo();
  456. if (entityType.IsAssignableFrom(type))
  457. {
  458. insertSql = this.GetFullInsertSql(type);
  459. }
  460. else
  461. {
  462. insertSql = this.GetInsertSql(dpi, entityType.GetProperties());
  463. }
  464. if (connection.Execute<T>(insertSql, entity, transaction, commandTimeout) > 0)
  465. {
  466. if (entityType.IsAssignableFrom(type) && dpi.IsUnWriteKey())
  467. {
  468. PropertyInfo key = dpi.KeyProperties.First();
  469. if (key.PropertyType != typeof(Guid))
  470. {
  471. var idValue = this.GetLastInsertID(connection, dpi, transaction, commandTimeout);
  472. key.SetValue(entity, idValue, null);
  473. }
  474. }
  475. return true;
  476. }
  477. return false;
  478. }
  479. public bool UpdateByKey<T>(IDbConnection connection, object entity, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  480. {
  481. Type type = typeof(T);
  482. string updateSql;
  483. Type entityType = entity.GetType();
  484. DapperPocoInfo dpi = type.GetPocoInfo();
  485. if (entityType.IsAssignableFrom(type))
  486. {
  487. updateSql = this.GetFullUpdateByKeySql(type);
  488. }
  489. else
  490. {
  491. updateSql = this.GetUpdateSql(dpi, entityType.GetProperties());
  492. }
  493. return connection.Execute<T>(updateSql, entity, transaction, commandTimeout) > 0;
  494. }
  495. public bool DeleteByKey<T>(IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  496. {
  497. string deleteSql = this.GetDeleteByKeySql(typeof(T));
  498. return connection.Execute<T>(deleteSql, param, transaction: transaction, commandTimeout: commandTimeout) > 0;
  499. }
  500. public T QueryByKey<T>(IDbConnection connection, object param, IDbTransaction transaction = null, int? commandTimeout = null) where T : class
  501. {
  502. string querySql = this.GetFullQueryByKeySql(typeof(T));
  503. return connection.Query<T>(querySql, param, transaction: transaction, commandTimeout: commandTimeout).FirstOrDefault();
  504. }
  505. private object GetLastInsertID(IDbConnection connection, DapperPocoInfo dpi, IDbTransaction transaction = null, int? commandTimeout = null)
  506. {
  507. var r = connection.Query("SELECT IDENT_CURRENT('" + dpi.TableName + "') ID", transaction: transaction, commandTimeout: commandTimeout);
  508. return Convert.ChangeType(r.First().ID, dpi.KeyProperties.First().PropertyType);
  509. }
  510. public long GetLastInsertID<T>(IDbConnection connection)
  511. where T : class
  512. {
  513. DapperPocoInfo dpi = typeof(T).GetPocoInfo();
  514. return Convert.ToInt64(this.GetLastInsertID(connection, dpi));
  515. }
  516. }
  517. }

原Dapper部分,在1965行开始做了些修改:

[csharp] view plaincopy
  1. /*
  2. License: http://www.apache.org/licenses/LICENSE-2.0
  3. Home page: http://code.google.com/p/dapper-dot-net/
  4. Note: to build on C# 3.0 + .NET 3.5, include the CSHARP30 compiler symbol (and yes,
  5. I know the difference between language and runtime versions; this is a compromise).
  6. */
  7. using System;
  8. using System.Collections;
  9. using System.Collections.Generic;
  10. using System.ComponentModel;
  11. using System.Data;
  12. using System.Linq;
  13. using System.Reflection;
  14. using System.Reflection.Emit;
  15. using System.Text;
  16. using System.Threading;
  17. using System.Text.RegularExpressions;
  18. using System.Diagnostics;
  19. namespace Dapper
  20. {
  21. /// <summary>
  22. /// Dapper, a light weight object mapper for ADO.NET
  23. /// </summary>
  24. static partial class SqlMapper
  25. {
  26. /// <summary>
  27. /// Implement this interface to pass an arbitrary db specific set of parameters to Dapper
  28. /// </summary>
  29. public partial interface IDynamicParameters
  30. {
  31. /// <summary>
  32. /// Add all the parameters needed to the command just before it executes
  33. /// </summary>
  34. /// <param name="command">The raw command prior to execution</param>
  35. /// <param name="identity">Information about the query</param>
  36. void AddParameters(IDbCommand command, Identity identity);
  37. }
  38. /// <summary>
  39. /// Implement this interface to pass an arbitrary db specific parameter to Dapper
  40. /// </summary>
  41. public interface ICustomQueryParameter
  42. {
  43. /// <summary>
  44. /// Add the parameter needed to the command before it executes
  45. /// </summary>
  46. /// <param name="command">The raw command prior to execution</param>
  47. /// <param name="name">Parameter name</param>
  48. void AddParameter(IDbCommand command, string name);
  49. }
  50. /// <summary>
  51. /// Implement this interface to change default mapping of reader columns to type memebers
  52. /// </summary>
  53. public interface ITypeMap
  54. {
  55. /// <summary>
  56. /// Finds best constructor
  57. /// </summary>
  58. /// <param name="names">DataReader column names</param>
  59. /// <param name="types">DataReader column types</param>
  60. /// <returns>Matching constructor or default one</returns>
  61. ConstructorInfo FindConstructor(string[] names, Type[] types);
  62. /// <summary>
  63. /// Gets mapping for constructor parameter
  64. /// </summary>
  65. /// <param name="constructor">Constructor to resolve</param>
  66. /// <param name="columnName">DataReader column name</param>
  67. /// <returns>Mapping implementation</returns>
  68. IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName);
  69. /// <summary>
  70. /// Gets member mapping for column
  71. /// </summary>
  72. /// <param name="columnName">DataReader column name</param>
  73. /// <returns>Mapping implementation</returns>
  74. IMemberMap GetMember(string columnName);
  75. }
  76. /// <summary>
  77. /// Implements this interface to provide custom member mapping
  78. /// </summary>
  79. public interface IMemberMap
  80. {
  81. /// <summary>
  82. /// Source DataReader column name
  83. /// </summary>
  84. string ColumnName { get; }
  85. /// <summary>
  86. ///  Target member type
  87. /// </summary>
  88. Type MemberType { get; }
  89. /// <summary>
  90. /// Target property
  91. /// </summary>
  92. PropertyInfo Property { get; }
  93. /// <summary>
  94. /// Target field
  95. /// </summary>
  96. FieldInfo Field { get; }
  97. /// <summary>
  98. /// Target constructor parameter
  99. /// </summary>
  100. ParameterInfo Parameter { get; }
  101. }
  102. static Link<Type, Action<IDbCommand, bool>> bindByNameCache;
  103. static Action<IDbCommand, bool> GetBindByName(Type commandType)
  104. {
  105. if (commandType == null) return null; // GIGO
  106. Action<IDbCommand, bool> action;
  107. if (Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action))
  108. {
  109. return action;
  110. }
  111. var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance);
  112. action = null;
  113. ParameterInfo[] indexers;
  114. MethodInfo setter;
  115. if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool)
  116. && ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0)
  117. && (setter = prop.GetSetMethod()) != null
  118. )
  119. {
  120. var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) });
  121. var il = method.GetILGenerator();
  122. il.Emit(OpCodes.Ldarg_0);
  123. il.Emit(OpCodes.Castclass, commandType);
  124. il.Emit(OpCodes.Ldarg_1);
  125. il.EmitCall(OpCodes.Callvirt, setter, null);
  126. il.Emit(OpCodes.Ret);
  127. action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>));
  128. }
  129. // cache it
  130. Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action);
  131. return action;
  132. }
  133. /// <summary>
  134. /// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example),
  135. /// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE**
  136. /// equality. The type is fully thread-safe.
  137. /// </summary>
  138. partial class Link<TKey, TValue> where TKey : class
  139. {
  140. public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
  141. {
  142. while (link != null)
  143. {
  144. if ((object)key == (object)link.Key)
  145. {
  146. value = link.Value;
  147. return true;
  148. }
  149. link = link.Tail;
  150. }
  151. value = default(TValue);
  152. return false;
  153. }
  154. public static bool TryAdd(ref Link<TKey, TValue> head, TKey key, ref TValue value)
  155. {
  156. bool tryAgain;
  157. do
  158. {
  159. var snapshot = Interlocked.CompareExchange(ref head, null, null);
  160. TValue found;
  161. if (TryGet(snapshot, key, out found))
  162. { // existing match; report the existing value instead
  163. value = found;
  164. return false;
  165. }
  166. var newNode = new Link<TKey, TValue>(key, value, snapshot);
  167. // did somebody move our cheese?
  168. tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot;
  169. } while (tryAgain);
  170. return true;
  171. }
  172. private Link(TKey key, TValue value, Link<TKey, TValue> tail)
  173. {
  174. Key = key;
  175. Value = value;
  176. Tail = tail;
  177. }
  178. public TKey Key { get; private set; }
  179. public TValue Value { get; private set; }
  180. public Link<TKey, TValue> Tail { get; private set; }
  181. }
  182. partial class CacheInfo
  183. {
  184. public DeserializerState Deserializer { get; set; }
  185. public Func<IDataReader, object>[] OtherDeserializers { get; set; }
  186. public Action<IDbCommand, object> ParamReader { get; set; }
  187. private int hitCount;
  188. public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); }
  189. public void RecordHit() { Interlocked.Increment(ref hitCount); }
  190. }
  191. static int GetColumnHash(IDataReader reader)
  192. {
  193. unchecked
  194. {
  195. int colCount = reader.FieldCount, hash = colCount;
  196. for (int i = 0; i < colCount; i++)
  197. {   // binding code is only interested in names - not types
  198. object tmp = reader.GetName(i);
  199. hash = (hash * 31) + (tmp == null ? 0 : tmp.GetHashCode());
  200. }
  201. return hash;
  202. }
  203. }
  204. struct DeserializerState
  205. {
  206. public readonly int Hash;
  207. public readonly Func<IDataReader, object> Func;
  208. public DeserializerState(int hash, Func<IDataReader, object> func)
  209. {
  210. Hash = hash;
  211. Func = func;
  212. }
  213. }
  214. /// <summary>
  215. /// Called if the query cache is purged via PurgeQueryCache
  216. /// </summary>
  217. public static event EventHandler QueryCachePurged;
  218. private static void OnQueryCachePurged()
  219. {
  220. var handler = QueryCachePurged;
  221. if (handler != null) handler(null, EventArgs.Empty);
  222. }
  223. #if CSHARP30
  224. private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>();
  225. // note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of
  226. // ReaderWriterLockSlim etc; a simple lock is faster
  227. private static void SetQueryCache(Identity key, CacheInfo value)
  228. {
  229. lock (_queryCache) { _queryCache[key] = value; }
  230. }
  231. private static bool TryGetQueryCache(Identity key, out CacheInfo value)
  232. {
  233. lock (_queryCache) { return _queryCache.TryGetValue(key, out value); }
  234. }
  235. private static void PurgeQueryCacheByType(Type type)
  236. {
  237. lock (_queryCache)
  238. {
  239. var toRemove = _queryCache.Keys.Where(id => id.type == type).ToArray();
  240. foreach (var key in toRemove)
  241. _queryCache.Remove(key);
  242. }
  243. }
  244. /// <summary>
  245. /// Purge the query cache
  246. /// </summary>
  247. public static void PurgeQueryCache()
  248. {
  249. lock (_queryCache)
  250. {
  251. _queryCache.Clear();
  252. }
  253. OnQueryCachePurged();
  254. }
  255. #else
  256. static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>();
  257. private static void SetQueryCache(Identity key, CacheInfo value)
  258. {
  259. if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS)
  260. {
  261. CollectCacheGarbage();
  262. }
  263. _queryCache[key] = value;
  264. }
  265. private static void CollectCacheGarbage()
  266. {
  267. try
  268. {
  269. foreach (var pair in _queryCache)
  270. {
  271. if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN)
  272. {
  273. CacheInfo cache;
  274. _queryCache.TryRemove(pair.Key, out cache);
  275. }
  276. }
  277. }
  278. finally
  279. {
  280. Interlocked.Exchange(ref collect, 0);
  281. }
  282. }
  283. private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
  284. private static int collect;
  285. private static bool TryGetQueryCache(Identity key, out CacheInfo value)
  286. {
  287. if (_queryCache.TryGetValue(key, out value))
  288. {
  289. value.RecordHit();
  290. return true;
  291. }
  292. value = null;
  293. return false;
  294. }
  295. /// <summary>
  296. /// Purge the query cache
  297. /// </summary>
  298. public static void PurgeQueryCache()
  299. {
  300. _queryCache.Clear();
  301. OnQueryCachePurged();
  302. }
  303. private static void PurgeQueryCacheByType(Type type)
  304. {
  305. foreach (var entry in _queryCache)
  306. {
  307. CacheInfo cache;
  308. if (entry.Key.type == type)
  309. _queryCache.TryRemove(entry.Key, out cache);
  310. }
  311. }
  312. /// <summary>
  313. /// Return a count of all the cached queries by dapper
  314. /// </summary>
  315. /// <returns></returns>
  316. public static int GetCachedSQLCount()
  317. {
  318. return _queryCache.Count;
  319. }
  320. /// <summary>
  321. /// Return a list of all the queries cached by dapper
  322. /// </summary>
  323. /// <param name="ignoreHitCountAbove"></param>
  324. /// <returns></returns>
  325. public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue)
  326. {
  327. var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount()));
  328. if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove);
  329. return data;
  330. }
  331. /// <summary>
  332. /// Deep diagnostics only: find any hash collisions in the cache
  333. /// </summary>
  334. /// <returns></returns>
  335. public static IEnumerable<Tuple<int, int>> GetHashCollissions()
  336. {
  337. var counts = new Dictionary<int, int>();
  338. foreach (var key in _queryCache.Keys)
  339. {
  340. int count;
  341. if (!counts.TryGetValue(key.hashCode, out count))
  342. {
  343. counts.Add(key.hashCode, 1);
  344. }
  345. else
  346. {
  347. counts[key.hashCode] = count + 1;
  348. }
  349. }
  350. return from pair in counts
  351. where pair.Value > 1
  352. select Tuple.Create(pair.Key, pair.Value);
  353. }
  354. #endif
  355. static readonly Dictionary<Type, DbType> typeMap;
  356. static SqlMapper()
  357. {
  358. typeMap = new Dictionary<Type, DbType>();
  359. typeMap[typeof(byte)] = DbType.Byte;
  360. typeMap[typeof(sbyte)] = DbType.SByte;
  361. typeMap[typeof(short)] = DbType.Int16;
  362. typeMap[typeof(ushort)] = DbType.UInt16;
  363. typeMap[typeof(int)] = DbType.Int32;
  364. typeMap[typeof(uint)] = DbType.UInt32;
  365. typeMap[typeof(long)] = DbType.Int64;
  366. typeMap[typeof(ulong)] = DbType.UInt64;
  367. typeMap[typeof(float)] = DbType.Single;
  368. typeMap[typeof(double)] = DbType.Double;
  369. typeMap[typeof(decimal)] = DbType.Decimal;
  370. typeMap[typeof(bool)] = DbType.Boolean;
  371. typeMap[typeof(string)] = DbType.String;
  372. typeMap[typeof(char)] = DbType.StringFixedLength;
  373. typeMap[typeof(Guid)] = DbType.Guid;
  374. typeMap[typeof(DateTime)] = DbType.DateTime;
  375. typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
  376. typeMap[typeof(TimeSpan)] = DbType.Time;
  377. typeMap[typeof(byte[])] = DbType.Binary;
  378. typeMap[typeof(byte?)] = DbType.Byte;
  379. typeMap[typeof(sbyte?)] = DbType.SByte;
  380. typeMap[typeof(short?)] = DbType.Int16;
  381. typeMap[typeof(ushort?)] = DbType.UInt16;
  382. typeMap[typeof(int?)] = DbType.Int32;
  383. typeMap[typeof(uint?)] = DbType.UInt32;
  384. typeMap[typeof(long?)] = DbType.Int64;
  385. typeMap[typeof(ulong?)] = DbType.UInt64;
  386. typeMap[typeof(float?)] = DbType.Single;
  387. typeMap[typeof(double?)] = DbType.Double;
  388. typeMap[typeof(decimal?)] = DbType.Decimal;
  389. typeMap[typeof(bool?)] = DbType.Boolean;
  390. typeMap[typeof(char?)] = DbType.StringFixedLength;
  391. typeMap[typeof(Guid?)] = DbType.Guid;
  392. typeMap[typeof(DateTime?)] = DbType.DateTime;
  393. typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
  394. typeMap[typeof(TimeSpan?)] = DbType.Time;
  395. typeMap[typeof(Object)] = DbType.Object;
  396. }
  397. /// <summary>
  398. /// Configire the specified type to be mapped to a given db-type
  399. /// </summary>
  400. public static void AddTypeMap(Type type, DbType dbType)
  401. {
  402. typeMap[type] = dbType;
  403. }
  404. internal const string LinqBinary = "System.Data.Linq.Binary";
  405. internal static DbType LookupDbType(Type type, string name)
  406. {
  407. DbType dbType;
  408. var nullUnderlyingType = Nullable.GetUnderlyingType(type);
  409. if (nullUnderlyingType != null) type = nullUnderlyingType;
  410. if (type.IsEnum && !typeMap.ContainsKey(type))
  411. {
  412. type = Enum.GetUnderlyingType(type);
  413. }
  414. if (typeMap.TryGetValue(type, out dbType))
  415. {
  416. return dbType;
  417. }
  418. if (type.FullName == LinqBinary)
  419. {
  420. return DbType.Binary;
  421. }
  422. if (typeof(IEnumerable).IsAssignableFrom(type))
  423. {
  424. return DynamicParameters.EnumerableMultiParameter;
  425. }
  426. throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type));
  427. }
  428. /// <summary>
  429. /// Identity of a cached query in Dapper, used for extensability
  430. /// </summary>
  431. public partial class Identity : IEquatable<Identity>
  432. {
  433. internal Identity ForGrid(Type primaryType, int gridIndex)
  434. {
  435. return new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex);
  436. }
  437. internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex)
  438. {
  439. return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex);
  440. }
  441. /// <summary>
  442. /// Create an identity for use with DynamicParameters, internal use only
  443. /// </summary>
  444. /// <param name="type"></param>
  445. /// <returns></returns>
  446. public Identity ForDynamicParameters(Type type)
  447. {
  448. return new Identity(sql, commandType, connectionString, this.type, type, null, -1);
  449. }
  450. internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes)
  451. : this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0)
  452. { }
  453. private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex)
  454. {
  455. this.sql = sql;
  456. this.commandType = commandType;
  457. this.connectionString = connectionString;
  458. this.type = type;
  459. this.parametersType = parametersType;
  460. this.gridIndex = gridIndex;
  461. unchecked
  462. {
  463. hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this
  464. hashCode = hashCode * 23 + commandType.GetHashCode();
  465. hashCode = hashCode * 23 + gridIndex.GetHashCode();
  466. hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode());
  467. hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode());
  468. if (otherTypes != null)
  469. {
  470. foreach (var t in otherTypes)
  471. {
  472. hashCode = hashCode * 23 + (t == null ? 0 : t.GetHashCode());
  473. }
  474. }
  475. hashCode = hashCode * 23 + (connectionString == null ? 0 : SqlMapper.connectionStringComparer.GetHashCode(connectionString));
  476. hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode());
  477. }
  478. }
  479. /// <summary>
  480. ///
  481. /// </summary>
  482. /// <param name="obj"></param>
  483. /// <returns></returns>
  484. public override bool Equals(object obj)
  485. {
  486. return Equals(obj as Identity);
  487. }
  488. /// <summary>
  489. /// The sql
  490. /// </summary>
  491. public readonly string sql;
  492. /// <summary>
  493. /// The command type
  494. /// </summary>
  495. public readonly CommandType? commandType;
  496. /// <summary>
  497. ///
  498. /// </summary>
  499. public readonly int hashCode, gridIndex;
  500. /// <summary>
  501. ///
  502. /// </summary>
  503. public readonly Type type;
  504. /// <summary>
  505. ///
  506. /// </summary>
  507. public readonly string connectionString;
  508. /// <summary>
  509. ///
  510. /// </summary>
  511. public readonly Type parametersType;
  512. /// <summary>
  513. ///
  514. /// </summary>
  515. /// <returns></returns>
  516. public override int GetHashCode()
  517. {
  518. return hashCode;
  519. }
  520. /// <summary>
  521. /// Compare 2 Identity objects
  522. /// </summary>
  523. /// <param name="other"></param>
  524. /// <returns></returns>
  525. public bool Equals(Identity other)
  526. {
  527. return
  528. other != null &&
  529. gridIndex == other.gridIndex &&
  530. type == other.type &&
  531. sql == other.sql &&
  532. commandType == other.commandType &&
  533. SqlMapper.connectionStringComparer.Equals(connectionString, other.connectionString) &&
  534. parametersType == other.parametersType;
  535. }
  536. }
  537. #if CSHARP30
  538. /// <summary>
  539. /// Execute parameterized SQL
  540. /// </summary>
  541. /// <returns>Number of rows affected</returns>
  542. public static int Execute(this IDbConnection cnn, string sql, object param)
  543. {
  544. return Execute(cnn, sql, param, null, null, null);
  545. }
  546. /// <summary>
  547. /// Execute parameterized SQL
  548. /// </summary>
  549. /// <returns>Number of rows affected</returns>
  550. public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
  551. {
  552. return Execute(cnn, sql, param, transaction, null, null);
  553. }
  554. /// <summary>
  555. /// Execute parameterized SQL
  556. /// </summary>
  557. /// <returns>Number of rows affected</returns>
  558. public static int Execute(this IDbConnection cnn, string sql, object param, CommandType commandType)
  559. {
  560. return Execute(cnn, sql, param, null, null, commandType);
  561. }
  562. /// <summary>
  563. /// Execute parameterized SQL
  564. /// </summary>
  565. /// <returns>Number of rows affected</returns>
  566. public static int Execute(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
  567. {
  568. return Execute(cnn, sql, param, transaction, null, commandType);
  569. }
  570. /// <summary>
  571. /// Executes a query, returning the data typed as per T
  572. /// </summary>
  573. /// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
  574. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  575. /// </returns>
  576. public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param)
  577. {
  578. return Query<T>(cnn, sql, param, null, true, null, null);
  579. }
  580. /// <summary>
  581. /// Executes a query, returning the data typed as per T
  582. /// </summary>
  583. /// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
  584. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  585. /// </returns>
  586. public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
  587. {
  588. return Query<T>(cnn, sql, param, transaction, true, null, null);
  589. }
  590. /// <summary>
  591. /// Executes a query, returning the data typed as per T
  592. /// </summary>
  593. /// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
  594. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  595. /// </returns>
  596. public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, CommandType commandType)
  597. {
  598. return Query<T>(cnn, sql, param, null, true, null, commandType);
  599. }
  600. /// <summary>
  601. /// Executes a query, returning the data typed as per T
  602. /// </summary>
  603. /// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
  604. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  605. /// </returns>
  606. public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
  607. {
  608. return Query<T>(cnn, sql, param, transaction, true, null, commandType);
  609. }
  610. /// <summary>
  611. /// Execute a command that returns multiple result sets, and access each in turn
  612. /// </summary>
  613. public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
  614. {
  615. return QueryMultiple(cnn, sql, param, transaction, null, null);
  616. }
  617. /// <summary>
  618. /// Execute a command that returns multiple result sets, and access each in turn
  619. /// </summary>
  620. public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, CommandType commandType)
  621. {
  622. return QueryMultiple(cnn, sql, param, null, null, commandType);
  623. }
  624. /// <summary>
  625. /// Execute a command that returns multiple result sets, and access each in turn
  626. /// </summary>
  627. public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType commandType)
  628. {
  629. return QueryMultiple(cnn, sql, param, transaction, null, commandType);
  630. }
  631. #endif
  632. private static int Execute(
  633. #if CSHARP30
  634. this IDbConnection cnn, string sql,Type type, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
  635. #else
  636. this IDbConnection cnn, string sql, Type type, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
  637. #endif
  638. )
  639. {
  640. IEnumerable multiExec = (object)param as IEnumerable;
  641. Identity identity;
  642. CacheInfo info = null;
  643. if (multiExec != null && !(multiExec is string))
  644. {
  645. bool isFirst = true;
  646. int total = 0;
  647. using (var cmd = SetupCommand(cnn, transaction, sql, null, null, commandTimeout, commandType))
  648. {
  649. string masterSql = null;
  650. foreach (var obj in multiExec)
  651. {
  652. if (isFirst)
  653. {
  654. masterSql = cmd.CommandText;
  655. isFirst = false;
  656. identity = new Identity(sql, cmd.CommandType, cnn, type, obj.GetType(), null);
  657. info = GetCacheInfo(identity);
  658. }
  659. else
  660. {
  661. cmd.CommandText = masterSql; // because we do magic replaces on "in" etc
  662. cmd.Parameters.Clear(); // current code is Add-tastic
  663. }
  664. info.ParamReader(cmd, obj);
  665. total += cmd.ExecuteNonQuery();
  666. }
  667. }
  668. return total;
  669. }
  670. // nice and simple
  671. if ((object)param != null)
  672. {
  673. identity = new Identity(sql, commandType, cnn, type, (object)param == null ? null : ((object)param).GetType(), null);
  674. info = GetCacheInfo(identity);
  675. }
  676. return ExecuteCommand(cnn, transaction, sql, (object)param == null ? null : info.ParamReader, (object)param, commandTimeout, commandType);
  677. }
  678. /// <summary>
  679. /// Execute parameterized SQL
  680. /// </summary>
  681. /// <returns>Number of rows affected</returns>
  682. public static int Execute<T>(
  683. #if CSHARP30
  684. this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
  685. #else
  686. this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
  687. #endif
  688. )
  689. where T : class
  690. {
  691. return Execute(cnn, sql, typeof(T), param, transaction, commandTimeout, commandType);
  692. }
  693. /// <summary>
  694. /// Execute parameterized SQL
  695. /// </summary>
  696. /// <returns>Number of rows affected</returns>
  697. public static int Execute(
  698. #if CSHARP30
  699. this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
  700. #else
  701. this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
  702. #endif
  703. )
  704. {
  705. return Execute(cnn, sql, null, param, transaction, commandTimeout, commandType);
  706. }
  707. #if !CSHARP30
  708. /// <summary>
  709. /// Return a list of dynamic objects, reader is closed after the call
  710. /// </summary>
  711. public static IEnumerable<dynamic> Query(this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null)
  712. {
  713. return Query<DapperRow>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
  714. }
  715. #else
  716. /// <summary>
  717. /// Return a list of dynamic objects, reader is closed after the call
  718. /// </summary>
  719. public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param)
  720. {
  721. return Query(cnn, sql, param, null, true, null, null);
  722. }
  723. /// <summary>
  724. /// Return a list of dynamic objects, reader is closed after the call
  725. /// </summary>
  726. public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction)
  727. {
  728. return Query(cnn, sql, param, transaction, true, null, null);
  729. }
  730. /// <summary>
  731. /// Return a list of dynamic objects, reader is closed after the call
  732. /// </summary>
  733. public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, CommandType? commandType)
  734. {
  735. return Query(cnn, sql, param, null, true, null, commandType);
  736. }
  737. /// <summary>
  738. /// Return a list of dynamic objects, reader is closed after the call
  739. /// </summary>
  740. public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, CommandType? commandType)
  741. {
  742. return Query(cnn, sql, param, transaction, true, null, commandType);
  743. }
  744. /// <summary>
  745. /// Return a list of dynamic objects, reader is closed after the call
  746. /// </summary>
  747. public static IEnumerable<IDictionary<string, object>> Query(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType)
  748. {
  749. return Query<IDictionary<string, object>>(cnn, sql, param, transaction, buffered, commandTimeout, commandType);
  750. }
  751. #endif
  752. /// <summary>
  753. /// Executes a query, returning the data typed as per T
  754. /// </summary>
  755. /// <remarks>the dynamic param may seem a bit odd, but this works around a major usability issue in vs, if it is Object vs completion gets annoying. Eg type new [space] get new object</remarks>
  756. /// <returns>A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is
  757. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  758. /// </returns>
  759. public static IEnumerable<T> Query<T>(
  760. #if CSHARP30
  761. this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
  762. #else
  763. this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
  764. #endif
  765. )
  766. {
  767. var data = QueryInternal<T>(cnn, sql, param as object, transaction, commandTimeout, commandType);
  768. return buffered ? data.ToList() : data;
  769. }
  770. /// <summary>
  771. /// Execute a command that returns multiple result sets, and access each in turn
  772. /// </summary>
  773. public static GridReader QueryMultiple(
  774. #if CSHARP30
  775. this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
  776. #else
  777. this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
  778. #endif
  779. )
  780. {
  781. Identity identity = new Identity(sql, commandType, cnn, typeof(GridReader), (object)param == null ? null : ((object)param).GetType(), null);
  782. CacheInfo info = GetCacheInfo(identity);
  783. IDbCommand cmd = null;
  784. IDataReader reader = null;
  785. bool wasClosed = cnn.State == ConnectionState.Closed;
  786. try
  787. {
  788. if (wasClosed) cnn.Open();
  789. cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType);
  790. reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
  791. var result = new GridReader(cmd, reader, identity);
  792. wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
  793. // with the CloseConnection flag, so the reader will deal with the connection; we
  794. // still need something in the "finally" to ensure that broken SQL still results
  795. // in the connection closing itself
  796. return result;
  797. }
  798. catch
  799. {
  800. if (reader != null)
  801. {
  802. if (!reader.IsClosed) try { cmd.Cancel(); }
  803. catch { /* don't spoil the existing exception */ }
  804. reader.Dispose();
  805. }
  806. if (cmd != null) cmd.Dispose();
  807. if (wasClosed) cnn.Close();
  808. throw;
  809. }
  810. }
  811. /// <summary>
  812. /// Return a typed list of objects, reader is closed after the call
  813. /// </summary>
  814. private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
  815. {
  816. var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
  817. var info = GetCacheInfo(identity);
  818. IDbCommand cmd = null;
  819. IDataReader reader = null;
  820. bool wasClosed = cnn.State == ConnectionState.Closed;
  821. try
  822. {
  823. cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType);
  824. if (wasClosed) cnn.Open();
  825. reader = cmd.ExecuteReader(wasClosed ? CommandBehavior.CloseConnection : CommandBehavior.Default);
  826. wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader
  827. // with the CloseConnection flag, so the reader will deal with the connection; we
  828. // still need something in the "finally" to ensure that broken SQL still results
  829. // in the connection closing itself
  830. var tuple = info.Deserializer;
  831. int hash = GetColumnHash(reader);
  832. if (tuple.Func == null || tuple.Hash != hash)
  833. {
  834. tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
  835. SetQueryCache(identity, info);
  836. }
  837. var func = tuple.Func;
  838. while (reader.Read())
  839. {
  840. yield return (T)func(reader);
  841. }
  842. // happy path; close the reader cleanly - no
  843. // need for "Cancel" etc
  844. reader.Dispose();
  845. reader = null;
  846. }
  847. finally
  848. {
  849. if (reader != null)
  850. {
  851. if (!reader.IsClosed) try { cmd.Cancel(); }
  852. catch { /* don't spoil the existing exception */ }
  853. reader.Dispose();
  854. }
  855. if (wasClosed) cnn.Close();
  856. if (cmd != null) cmd.Dispose();
  857. }
  858. }
  859. /// <summary>
  860. /// Maps a query to objects
  861. /// </summary>
  862. /// <typeparam name="TFirst">The first type in the recordset</typeparam>
  863. /// <typeparam name="TSecond">The second type in the recordset</typeparam>
  864. /// <typeparam name="TReturn">The return type</typeparam>
  865. /// <param name="cnn"></param>
  866. /// <param name="sql"></param>
  867. /// <param name="map"></param>
  868. /// <param name="param"></param>
  869. /// <param name="transaction"></param>
  870. /// <param name="buffered"></param>
  871. /// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
  872. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  873. /// <param name="commandType">Is it a stored proc or a batch?</param>
  874. /// <returns></returns>
  875. public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
  876. #if CSHARP30
  877. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  878. #else
  879. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
  880. #endif
  881. )
  882. {
  883. return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  884. }
  885. /// <summary>
  886. /// Maps a query to objects
  887. /// </summary>
  888. /// <typeparam name="TFirst"></typeparam>
  889. /// <typeparam name="TSecond"></typeparam>
  890. /// <typeparam name="TThird"></typeparam>
  891. /// <typeparam name="TReturn"></typeparam>
  892. /// <param name="cnn"></param>
  893. /// <param name="sql"></param>
  894. /// <param name="map"></param>
  895. /// <param name="param"></param>
  896. /// <param name="transaction"></param>
  897. /// <param name="buffered"></param>
  898. /// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
  899. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  900. /// <param name="commandType"></param>
  901. /// <returns></returns>
  902. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(
  903. #if CSHARP30
  904. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  905. #else
  906. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
  907. #endif
  908. )
  909. {
  910. return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  911. }
  912. /// <summary>
  913. /// Perform a multi mapping query with 4 input parameters
  914. /// </summary>
  915. /// <typeparam name="TFirst"></typeparam>
  916. /// <typeparam name="TSecond"></typeparam>
  917. /// <typeparam name="TThird"></typeparam>
  918. /// <typeparam name="TFourth"></typeparam>
  919. /// <typeparam name="TReturn"></typeparam>
  920. /// <param name="cnn"></param>
  921. /// <param name="sql"></param>
  922. /// <param name="map"></param>
  923. /// <param name="param"></param>
  924. /// <param name="transaction"></param>
  925. /// <param name="buffered"></param>
  926. /// <param name="splitOn"></param>
  927. /// <param name="commandTimeout"></param>
  928. /// <param name="commandType"></param>
  929. /// <returns></returns>
  930. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(
  931. #if CSHARP30
  932. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  933. #else
  934. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
  935. #endif
  936. )
  937. {
  938. return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  939. }
  940. #if !CSHARP30
  941. /// <summary>
  942. /// Perform a multi mapping query with 5 input parameters
  943. /// </summary>
  944. /// <typeparam name="TFirst"></typeparam>
  945. /// <typeparam name="TSecond"></typeparam>
  946. /// <typeparam name="TThird"></typeparam>
  947. /// <typeparam name="TFourth"></typeparam>
  948. /// <typeparam name="TFifth"></typeparam>
  949. /// <typeparam name="TReturn"></typeparam>
  950. /// <param name="cnn"></param>
  951. /// <param name="sql"></param>
  952. /// <param name="map"></param>
  953. /// <param name="param"></param>
  954. /// <param name="transaction"></param>
  955. /// <param name="buffered"></param>
  956. /// <param name="splitOn"></param>
  957. /// <param name="commandTimeout"></param>
  958. /// <param name="commandType"></param>
  959. /// <returns></returns>
  960. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(
  961. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
  962. )
  963. {
  964. return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  965. }
  966. /// <summary>
  967. /// Perform a multi mapping query with 6 input parameters
  968. /// </summary>
  969. /// <typeparam name="TFirst"></typeparam>
  970. /// <typeparam name="TSecond"></typeparam>
  971. /// <typeparam name="TThird"></typeparam>
  972. /// <typeparam name="TFourth"></typeparam>
  973. /// <typeparam name="TFifth"></typeparam>
  974. /// <typeparam name="TSixth"></typeparam>
  975. /// <typeparam name="TReturn"></typeparam>
  976. /// <param name="cnn"></param>
  977. /// <param name="sql"></param>
  978. /// <param name="map"></param>
  979. /// <param name="param"></param>
  980. /// <param name="transaction"></param>
  981. /// <param name="buffered"></param>
  982. /// <param name="splitOn"></param>
  983. /// <param name="commandTimeout"></param>
  984. /// <param name="commandType"></param>
  985. /// <returns></returns>
  986. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(
  987. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null
  988. )
  989. {
  990. return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  991. }
  992. /// <summary>
  993. /// Perform a multi mapping query with 7 input parameters
  994. /// </summary>
  995. /// <typeparam name="TFirst"></typeparam>
  996. /// <typeparam name="TSecond"></typeparam>
  997. /// <typeparam name="TThird"></typeparam>
  998. /// <typeparam name="TFourth"></typeparam>
  999. /// <typeparam name="TFifth"></typeparam>
  1000. /// <typeparam name="TSixth"></typeparam>
  1001. /// <typeparam name="TSeventh"></typeparam>
  1002. /// <typeparam name="TReturn"></typeparam>
  1003. /// <param name="cnn"></param>
  1004. /// <param name="sql"></param>
  1005. /// <param name="map"></param>
  1006. /// <param name="param"></param>
  1007. /// <param name="transaction"></param>
  1008. /// <param name="buffered"></param>
  1009. /// <param name="splitOn"></param>
  1010. /// <param name="commandTimeout"></param>
  1011. /// <param name="commandType"></param>
  1012. /// <returns></returns>
  1013. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
  1014. {
  1015. return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  1016. }
  1017. #endif
  1018. partial class DontMap { }
  1019. static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(
  1020. this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
  1021. {
  1022. var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null);
  1023. return buffered ? results.ToList() : results;
  1024. }
  1025. static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity)
  1026. {
  1027. identity = identity ?? new Identity(sql, commandType, cnn, typeof(TFirst), (object)param == null ? null : ((object)param).GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
  1028. CacheInfo cinfo = GetCacheInfo(identity);
  1029. IDbCommand ownedCommand = null;
  1030. IDataReader ownedReader = null;
  1031. bool wasClosed = cnn != null && cnn.State == ConnectionState.Closed;
  1032. try
  1033. {
  1034. if (reader == null)
  1035. {
  1036. ownedCommand = SetupCommand(cnn, transaction, sql, cinfo.ParamReader, (object)param, commandTimeout, commandType);
  1037. if (wasClosed) cnn.Open();
  1038. ownedReader = ownedCommand.ExecuteReader();
  1039. reader = ownedReader;
  1040. }
  1041. DeserializerState deserializer = default(DeserializerState);
  1042. Func<IDataReader, object>[] otherDeserializers = null;
  1043. int hash = GetColumnHash(reader);
  1044. if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash)
  1045. {
  1046. var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader);
  1047. deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]);
  1048. otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
  1049. SetQueryCache(identity, cinfo);
  1050. }
  1051. Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer.Func, otherDeserializers, map);
  1052. if (mapIt != null)
  1053. {
  1054. while (reader.Read())
  1055. {
  1056. yield return mapIt(reader);
  1057. }
  1058. }
  1059. }
  1060. finally
  1061. {
  1062. try
  1063. {
  1064. if (ownedReader != null)
  1065. {
  1066. ownedReader.Dispose();
  1067. }
  1068. }
  1069. finally
  1070. {
  1071. if (ownedCommand != null)
  1072. {
  1073. ownedCommand.Dispose();
  1074. }
  1075. if (wasClosed) cnn.Close();
  1076. }
  1077. }
  1078. }
  1079. private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map)
  1080. {
  1081. switch (otherDeserializers.Length)
  1082. {
  1083. case 1:
  1084. return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));
  1085. case 2:
  1086. return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));
  1087. case 3:
  1088. return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r));
  1089. #if !CSHARP30
  1090. case 4:
  1091. return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r));
  1092. case 5:
  1093. return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r));
  1094. case 6:
  1095. return r => ((Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r));
  1096. #endif
  1097. default:
  1098. throw new NotSupportedException();
  1099. }
  1100. }
  1101. private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
  1102. {
  1103. int current = 0;
  1104. var splits = splitOn.Split(',').ToArray();
  1105. var splitIndex = 0;
  1106. Func<Type, int> nextSplit = type =>
  1107. {
  1108. var currentSplit = splits[splitIndex].Trim();
  1109. if (splits.Length > splitIndex + 1)
  1110. {
  1111. splitIndex++;
  1112. }
  1113. bool skipFirst = false;
  1114. int startingPos = current + 1;
  1115. // if our current type has the split, skip the first time you see it.
  1116. if (type != typeof(Object))
  1117. {
  1118. var props = DefaultTypeMap.GetSettableProps(type);
  1119. var fields = DefaultTypeMap.GetSettableFields(type);
  1120. foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name)))
  1121. {
  1122. if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
  1123. {
  1124. skipFirst = true;
  1125. startingPos = current;
  1126. break;
  1127. }
  1128. }
  1129. }
  1130. int pos;
  1131. for (pos = startingPos; pos < reader.FieldCount; pos++)
  1132. {
  1133. // some people like ID some id ... assuming case insensitive splits for now
  1134. if (splitOn == "*")
  1135. {
  1136. break;
  1137. }
  1138. if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
  1139. {
  1140. if (skipFirst)
  1141. {
  1142. skipFirst = false;
  1143. }
  1144. else
  1145. {
  1146. break;
  1147. }
  1148. }
  1149. }
  1150. current = pos;
  1151. return pos;
  1152. };
  1153. var deserializers = new List<Func<IDataReader, object>>();
  1154. int split = 0;
  1155. bool first = true;
  1156. foreach (var type in types)
  1157. {
  1158. if (type != typeof(DontMap))
  1159. {
  1160. int next = nextSplit(type);
  1161. deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first));
  1162. first = false;
  1163. split = next;
  1164. }
  1165. }
  1166. return deserializers.ToArray();
  1167. }
  1168. private static CacheInfo GetCacheInfo(Identity identity)
  1169. {
  1170. CacheInfo info;
  1171. if (!TryGetQueryCache(identity, out info))
  1172. {
  1173. info = new CacheInfo();
  1174. if (identity.parametersType != null)
  1175. {
  1176. if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType))
  1177. {
  1178. info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd, identity); };
  1179. }
  1180. #if !CSHARP30
  1181. else if (typeof(IEnumerable<KeyValuePair<string, object>>).IsAssignableFrom(identity.parametersType) && typeof(System.Dynamic.IDynamicMetaObjectProvider).IsAssignableFrom(identity.parametersType))
  1182. {
  1183. info.ParamReader = (cmd, obj) =>
  1184. {
  1185. IDynamicParameters mapped = new DynamicParameters(obj);
  1186. mapped.AddParameters(cmd, identity);
  1187. };
  1188. }
  1189. #endif
  1190. else
  1191. {
  1192. info.ParamReader = CreateParamInfoGenerator(identity, false, true);
  1193. }
  1194. }
  1195. SetQueryCache(identity, info);
  1196. }
  1197. return info;
  1198. }
  1199. private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
  1200. {
  1201. #if !CSHARP30
  1202. // dynamic is passed in as Object ... by c# design
  1203. if (type == typeof(object)
  1204. || type == typeof(DapperRow))
  1205. {
  1206. return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing);
  1207. }
  1208. #else
  1209. if (type.IsAssignableFrom(typeof(Dictionary<string, object>)))
  1210. {
  1211. return GetDictionaryDeserializer(reader, startBound, length, returnNullIfFirstMissing);
  1212. }
  1213. #endif
  1214. Type underlyingType = null;
  1215. if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary ||
  1216. (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum)))
  1217. {
  1218. return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing);
  1219. }
  1220. return GetStructDeserializer(type, underlyingType ?? type, startBound);
  1221. }
  1222. #if !CSHARP30
  1223. private sealed partial class DapperTable
  1224. {
  1225. string[] fieldNames;
  1226. readonly Dictionary<string, int> fieldNameLookup;
  1227. internal string[] FieldNames { get { return fieldNames; } }
  1228. public DapperTable(string[] fieldNames)
  1229. {
  1230. if (fieldNames == null) throw new ArgumentNullException("fieldNames");
  1231. this.fieldNames = fieldNames;
  1232. fieldNameLookup = new Dictionary<string, int>(fieldNames.Length, StringComparer.Ordinal);
  1233. // if there are dups, we want the **first** key to be the "winner" - so iterate backwards
  1234. for (int i = fieldNames.Length - 1; i >= 0; i--)
  1235. {
  1236. string key = fieldNames[i];
  1237. if (key != null) fieldNameLookup[key] = i;
  1238. }
  1239. }
  1240. internal int IndexOfName(string name)
  1241. {
  1242. int result;
  1243. return (name != null && fieldNameLookup.TryGetValue(name, out result)) ? result : -1;
  1244. }
  1245. internal int AddField(string name)
  1246. {
  1247. if (name == null) throw new ArgumentNullException("name");
  1248. if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name);
  1249. int oldLen = fieldNames.Length;
  1250. Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case
  1251. fieldNames[oldLen] = name;
  1252. fieldNameLookup[name] = oldLen;
  1253. return oldLen;
  1254. }
  1255. internal bool FieldExists(string key)
  1256. {
  1257. return key != null && fieldNameLookup.ContainsKey(key);
  1258. }
  1259. public int FieldCount { get { return fieldNames.Length; } }
  1260. }
  1261. sealed partial class DapperRowMetaObject : System.Dynamic.DynamicMetaObject
  1262. {
  1263. static readonly MethodInfo getValueMethod = typeof(IDictionary<string, object>).GetProperty("Item").GetGetMethod();
  1264. static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) });
  1265. public DapperRowMetaObject(
  1266. System.Linq.Expressions.Expression expression,
  1267. System.Dynamic.BindingRestrictions restrictions
  1268. )
  1269. : base(expression, restrictions)
  1270. {
  1271. }
  1272. public DapperRowMetaObject(
  1273. System.Linq.Expressions.Expression expression,
  1274. System.Dynamic.BindingRestrictions restrictions,
  1275. object value
  1276. )
  1277. : base(expression, restrictions, value)
  1278. {
  1279. }
  1280. System.Dynamic.DynamicMetaObject CallMethod(
  1281. MethodInfo method,
  1282. System.Linq.Expressions.Expression[] parameters
  1283. )
  1284. {
  1285. var callMethod = new System.Dynamic.DynamicMetaObject(
  1286. System.Linq.Expressions.Expression.Call(
  1287. System.Linq.Expressions.Expression.Convert(Expression, LimitType),
  1288. method,
  1289. parameters),
  1290. System.Dynamic.BindingRestrictions.GetTypeRestriction(Expression, LimitType)
  1291. );
  1292. return callMethod;
  1293. }
  1294. public override System.Dynamic.DynamicMetaObject BindGetMember(System.Dynamic.GetMemberBinder binder)
  1295. {
  1296. var parameters = new System.Linq.Expressions.Expression[]
  1297. {
  1298. System.Linq.Expressions.Expression.Constant(binder.Name)
  1299. };
  1300. var callMethod = CallMethod(getValueMethod, parameters);
  1301. return callMethod;
  1302. }
  1303. // Needed for Visual basic dynamic support
  1304. public override System.Dynamic.DynamicMetaObject BindInvokeMember(System.Dynamic.InvokeMemberBinder binder, System.Dynamic.DynamicMetaObject[] args)
  1305. {
  1306. var parameters = new System.Linq.Expressions.Expression[]
  1307. {
  1308. System.Linq.Expressions.Expression.Constant(binder.Name)
  1309. };
  1310. var callMethod = CallMethod(getValueMethod, parameters);
  1311. return callMethod;
  1312. }
  1313. public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.SetMemberBinder binder, System.Dynamic.DynamicMetaObject value)
  1314. {
  1315. var parameters = new System.Linq.Expressions.Expression[]
  1316. {
  1317. System.Linq.Expressions.Expression.Constant(binder.Name),
  1318. value.Expression,
  1319. };
  1320. var callMethod = CallMethod(setValueMethod, parameters);
  1321. return callMethod;
  1322. }
  1323. }
  1324. private sealed partial class DapperRow
  1325. : System.Dynamic.IDynamicMetaObjectProvider
  1326. , IDictionary<string, object>
  1327. {
  1328. readonly DapperTable table;
  1329. object[] values;
  1330. public DapperRow(DapperTable table, object[] values)
  1331. {
  1332. if (table == null) throw new ArgumentNullException("table");
  1333. if (values == null) throw new ArgumentNullException("values");
  1334. this.table = table;
  1335. this.values = values;
  1336. }
  1337. private sealed class DeadValue
  1338. {
  1339. public static readonly DeadValue Default = new DeadValue();
  1340. private DeadValue() { }
  1341. }
  1342. int ICollection<KeyValuePair<string, object>>.Count
  1343. {
  1344. get
  1345. {
  1346. int count = 0;
  1347. for (int i = 0; i < values.Length; i++)
  1348. {
  1349. if (!(values[i] is DeadValue)) count++;
  1350. }
  1351. return count;
  1352. }
  1353. }
  1354. public bool TryGetValue(string name, out object value)
  1355. {
  1356. var index = table.IndexOfName(name);
  1357. if (index < 0)
  1358. { // doesn't exist
  1359. value = null;
  1360. return false;
  1361. }
  1362. // exists, **even if** we don't have a value; consider table rows heterogeneous
  1363. value = index < values.Length ? values[index] : null;
  1364. if (value is DeadValue)
  1365. { // pretend it isn't here
  1366. value = null;
  1367. return false;
  1368. }
  1369. return true;
  1370. }
  1371. public override string ToString()
  1372. {
  1373. var sb = new StringBuilder("{DapperRow");
  1374. foreach (var kv in this)
  1375. {
  1376. var value = kv.Value;
  1377. sb.Append(", ").Append(kv.Key);
  1378. if (value != null)
  1379. {
  1380. sb.Append(" = '").Append(kv.Value).Append('\'');
  1381. }
  1382. else
  1383. {
  1384. sb.Append(" = NULL");
  1385. }
  1386. }
  1387. return sb.Append('}').ToString();
  1388. }
  1389. System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject(
  1390. System.Linq.Expressions.Expression parameter)
  1391. {
  1392. return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this);
  1393. }
  1394. public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
  1395. {
  1396. var names = table.FieldNames;
  1397. for (var i = 0; i < names.Length; i++)
  1398. {
  1399. object value = i < values.Length ? values[i] : null;
  1400. if (!(value is DeadValue))
  1401. {
  1402. yield return new KeyValuePair<string, object>(names[i], value);
  1403. }
  1404. }
  1405. }
  1406. IEnumerator IEnumerable.GetEnumerator()
  1407. {
  1408. return GetEnumerator();
  1409. }
  1410. #region Implementation of ICollection<KeyValuePair<string,object>>
  1411. void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
  1412. {
  1413. IDictionary<string, object> dic = this;
  1414. dic.Add(item.Key, item.Value);
  1415. }
  1416. void ICollection<KeyValuePair<string, object>>.Clear()
  1417. { // removes values for **this row**, but doesn't change the fundamental table
  1418. for (int i = 0; i < values.Length; i++)
  1419. values[i] = DeadValue.Default;
  1420. }
  1421. bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
  1422. {
  1423. object value;
  1424. return TryGetValue(item.Key, out value) && Equals(value, item.Value);
  1425. }
  1426. void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
  1427. {
  1428. foreach (var kv in this)
  1429. {
  1430. array[arrayIndex++] = kv; // if they didn't leave enough space; not our fault
  1431. }
  1432. }
  1433. bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
  1434. {
  1435. IDictionary<string, object> dic = this;
  1436. return dic.Remove(item.Key);
  1437. }
  1438. bool ICollection<KeyValuePair<string, object>>.IsReadOnly
  1439. {
  1440. get { return false; }
  1441. }
  1442. #endregion
  1443. #region Implementation of IDictionary<string,object>
  1444. bool IDictionary<string, object>.ContainsKey(string key)
  1445. {
  1446. int index = table.IndexOfName(key);
  1447. if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
  1448. return true;
  1449. }
  1450. void IDictionary<string, object>.Add(string key, object value)
  1451. {
  1452. SetValue(key, value, true);
  1453. }
  1454. bool IDictionary<string, object>.Remove(string key)
  1455. {
  1456. int index = table.IndexOfName(key);
  1457. if (index < 0 || index >= values.Length || values[index] is DeadValue) return false;
  1458. values[index] = DeadValue.Default;
  1459. return true;
  1460. }
  1461. object IDictionary<string, object>.this[string key]
  1462. {
  1463. get { object val; TryGetValue(key, out val); return val; }
  1464. set { SetValue(key, value, false); }
  1465. }
  1466. public object SetValue(string key, object value)
  1467. {
  1468. return SetValue(key, value, false);
  1469. }
  1470. private object SetValue(string key, object value, bool isAdd)
  1471. {
  1472. if (key == null) throw new ArgumentNullException("key");
  1473. int index = table.IndexOfName(key);
  1474. if (index < 0)
  1475. {
  1476. index = table.AddField(key);
  1477. }
  1478. else if (isAdd && index < values.Length && !(values[index] is DeadValue))
  1479. {
  1480. // then semantically, this value already exists
  1481. throw new ArgumentException("An item with the same key has already been added", "key");
  1482. }
  1483. int oldLength = values.Length;
  1484. if (oldLength <= index)
  1485. {
  1486. // we'll assume they're doing lots of things, and
  1487. // grow it to the full width of the table
  1488. Array.Resize(ref values, table.FieldCount);
  1489. for (int i = oldLength; i < values.Length; i++)
  1490. {
  1491. values[i] = DeadValue.Default;
  1492. }
  1493. }
  1494. return values[index] = value;
  1495. }
  1496. ICollection<string> IDictionary<string, object>.Keys
  1497. {
  1498. get { return this.Select(kv => kv.Key).ToArray(); }
  1499. }
  1500. ICollection<object> IDictionary<string, object>.Values
  1501. {
  1502. get { return this.Select(kv => kv.Value).ToArray(); }
  1503. }
  1504. #endregion
  1505. }
  1506. #endif
  1507. private const string MultiMapSplitExceptionMessage = "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id";
  1508. #if !CSHARP30
  1509. internal static Func<IDataReader, object> GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
  1510. {
  1511. var fieldCount = reader.FieldCount;
  1512. if (length == -1)
  1513. {
  1514. length = fieldCount - startBound;
  1515. }
  1516. if (fieldCount <= startBound)
  1517. {
  1518. throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
  1519. }
  1520. var effectiveFieldCount = Math.Min(fieldCount - startBound, length);
  1521. DapperTable table = null;
  1522. return
  1523. r =>
  1524. {
  1525. if (table == null)
  1526. {
  1527. string[] names = new string[effectiveFieldCount];
  1528. for (int i = 0; i < effectiveFieldCount; i++)
  1529. {
  1530. names[i] = r.GetName(i + startBound);
  1531. }
  1532. table = new DapperTable(names);
  1533. }
  1534. var values = new object[effectiveFieldCount];
  1535. if (returnNullIfFirstMissing)
  1536. {
  1537. values[0] = r.GetValue(startBound);
  1538. if (values[0] is DBNull)
  1539. {
  1540. return null;
  1541. }
  1542. }
  1543. if (startBound == 0)
  1544. {
  1545. r.GetValues(values);
  1546. for (int i = 0; i < values.Length; i++)
  1547. if (values[i] is DBNull) values[i] = null;
  1548. }
  1549. else
  1550. {
  1551. var begin = returnNullIfFirstMissing ? 1 : 0;
  1552. for (var iter = begin; iter < effectiveFieldCount; ++iter)
  1553. {
  1554. object obj = r.GetValue(iter + startBound);
  1555. values[iter] = obj is DBNull ? null : obj;
  1556. }
  1557. }
  1558. return new DapperRow(table, values);
  1559. };
  1560. }
  1561. #else
  1562. internal static Func<IDataReader, object> GetDictionaryDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
  1563. {
  1564. var fieldCount = reader.FieldCount;
  1565. if (length == -1)
  1566. {
  1567. length = fieldCount - startBound;
  1568. }
  1569. if (fieldCount <= startBound)
  1570. {
  1571. throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
  1572. }
  1573. return
  1574. r =>
  1575. {
  1576. IDictionary<string, object> row = new Dictionary<string, object>(length);
  1577. for (var i = startBound; i < startBound + length; i++)
  1578. {
  1579. var tmp = r.GetValue(i);
  1580. tmp = tmp == DBNull.Value ? null : tmp;
  1581. row[r.GetName(i)] = tmp;
  1582. if (returnNullIfFirstMissing && i == startBound && tmp == null)
  1583. {
  1584. return null;
  1585. }
  1586. }
  1587. return row;
  1588. };
  1589. }
  1590. #endif
  1591. /// <summary>
  1592. /// Internal use only
  1593. /// </summary>
  1594. /// <param name="value"></param>
  1595. /// <returns></returns>
  1596. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
  1597. [Obsolete("This method is for internal usage only", false)]
  1598. public static char ReadChar(object value)
  1599. {
  1600. if (value == null || value is DBNull) throw new ArgumentNullException("value");
  1601. string s = value as string;
  1602. if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
  1603. return s[0];
  1604. }
  1605. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
  1606. [Obsolete("This method is for internal usage only", false)]
  1607. public static bool ReadBool(object value)
  1608. {
  1609. if (value.GetType() != typeof(bool))
  1610. {
  1611. return Convert.ToBoolean(value);
  1612. }
  1613. else
  1614. return (bool)value;
  1615. }
  1616. /// <summary>
  1617. /// Internal use only
  1618. /// </summary>
  1619. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
  1620. [Obsolete("This method is for internal usage only", false)]
  1621. public static char? ReadNullableChar(object value)
  1622. {
  1623. if (value == null || value is DBNull) return null;
  1624. string s = value as string;
  1625. if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value");
  1626. return s[0];
  1627. }
  1628. /// <summary>
  1629. /// Internal use only
  1630. /// </summary>
  1631. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
  1632. [Obsolete("This method is for internal usage only", true)]
  1633. public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name)
  1634. {
  1635. IDbDataParameter result;
  1636. if (parameters.Contains(name))
  1637. {
  1638. result = (IDbDataParameter)parameters[name];
  1639. }
  1640. else
  1641. {
  1642. result = command.CreateParameter();
  1643. result.ParameterName = name;
  1644. parameters.Add(result);
  1645. }
  1646. return result;
  1647. }
  1648. /// <summary>
  1649. /// Internal use only
  1650. /// </summary>
  1651. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
  1652. [Obsolete("This method is for internal usage only", false)]
  1653. public static void PackListParameters(IDbCommand command, string namePrefix, object value)
  1654. {
  1655. // initially we tried TVP, however it performs quite poorly.
  1656. // keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare
  1657. var list = value as IEnumerable;
  1658. var count = 0;
  1659. if (list != null)
  1660. {
  1661. if (FeatureSupport.Get(command.Connection).Arrays)
  1662. {
  1663. var arrayParm = command.CreateParameter();
  1664. arrayParm.Value = list;
  1665. arrayParm.ParameterName = namePrefix;
  1666. command.Parameters.Add(arrayParm);
  1667. }
  1668. else
  1669. {
  1670. bool isString = value is IEnumerable<string>;
  1671. bool isDbString = value is IEnumerable<DbString>;
  1672. foreach (var item in list)
  1673. {
  1674. count++;
  1675. var listParam = command.CreateParameter();
  1676. listParam.ParameterName = namePrefix + count;
  1677. listParam.Value = item ?? DBNull.Value;
  1678. if (isString)
  1679. {
  1680. listParam.Size = 4000;
  1681. if (item != null && ((string)item).Length > 4000)
  1682. {
  1683. listParam.Size = -1;
  1684. }
  1685. }
  1686. if (isDbString && item as DbString != null)
  1687. {
  1688. var str = item as DbString;
  1689. str.AddParameter(command, listParam.ParameterName);
  1690. }
  1691. else
  1692. {
  1693. command.Parameters.Add(listParam);
  1694. }
  1695. }
  1696. if (count == 0)
  1697. {
  1698. command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), "(SELECT NULL WHERE 1 = 0)");
  1699. }
  1700. else
  1701. {
  1702. command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), match =>
  1703. {
  1704. var grp = match.Value;
  1705. var sb = new StringBuilder("(").Append(grp).Append(1);
  1706. for (int i = 2; i <= count; i++)
  1707. {
  1708. sb.Append(',').Append(grp).Append(i);
  1709. }
  1710. return sb.Append(')').ToString();
  1711. });
  1712. }
  1713. }
  1714. }
  1715. }
  1716. private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql)
  1717. {
  1718. return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline));
  1719. }
  1720. // look for ? / @ / : *by itself*
  1721. static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-zA-Z0-9_])[?@:](?![a-zA-Z0-9_])", RegexOptions.Compiled);
  1722. /// <summary>
  1723. /// Internal use only
  1724. /// </summary>
  1725. public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused)
  1726. {
  1727. Type type = identity.parametersType;
  1728. bool filterParams = false;
  1729. if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text)
  1730. {
  1731. filterParams = !smellsLikeOleDb.IsMatch(identity.sql);
  1732. }
  1733. var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
  1734. var il = dm.GetILGenerator();
  1735. il.DeclareLocal(type); // 0
  1736. bool haveInt32Arg1 = false;
  1737. il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
  1738. il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param]
  1739. il.Emit(OpCodes.Stloc_0);// stack is now empty
  1740. il.Emit(OpCodes.Ldarg_0); // stack is now [command]
  1741. il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters]
  1742. var propsArr = type.GetProperties().Where(p => p.GetIndexParameters().Length == 0).ToArray();
  1743. var ctors = type.GetConstructors();
  1744. ParameterInfo[] ctorParams;
  1745. IEnumerable<PropertyInfo> props = null;
  1746. // try to detect tuple patterns, e.g. anon-types, and use that to choose the order
  1747. // otherwise: alphabetical
  1748. if (ctors.Length == 1 && propsArr.Length == (ctorParams = ctors[0].GetParameters()).Length)
  1749. {
  1750. // check if reflection was kind enough to put everything in the right order for us
  1751. bool ok = true;
  1752. for (int i = 0; i < propsArr.Length; i++)
  1753. {
  1754. if (!string.Equals(propsArr[i].Name, ctorParams[i].Name, StringComparison.InvariantCultureIgnoreCase))
  1755. {
  1756. ok = false;
  1757. break;
  1758. }
  1759. }
  1760. if(ok)
  1761. {
  1762. // pre-sorted; the reflection gods have smiled upon us
  1763. props = propsArr;
  1764. }
  1765. else { // might still all be accounted for; check the hard way
  1766. var positionByName = new Dictionary<string,int>(StringComparer.InvariantCultureIgnoreCase);
  1767. foreach(var param in ctorParams)
  1768. {
  1769. positionByName[param.Name] = param.Position;
  1770. }
  1771. if (positionByName.Count == propsArr.Length)
  1772. {
  1773. int[] positions = new int[propsArr.Length];
  1774. ok = true;
  1775. for (int i = 0; i < propsArr.Length; i++)
  1776. {
  1777. int pos;
  1778. if (!positionByName.TryGetValue(propsArr[i].Name, out pos))
  1779. {
  1780. ok = false;
  1781. break;
  1782. }
  1783. positions[i] = pos;
  1784. }
  1785. if (ok)
  1786. {
  1787. Array.Sort(positions, propsArr);
  1788. props = propsArr;
  1789. }
  1790. }
  1791. }
  1792. }
  1793. if(props == null) props = propsArr.OrderBy(x => x.Name);
  1794. if (filterParams)
  1795. {
  1796. props = FilterParameters(props, identity.sql);
  1797. }
  1798. DapperPocoInfo dpi = null;
  1799. if (identity.type != null)
  1800. {
  1801. dpi = identity.type.GetPocoInfo();
  1802. }
  1803. foreach (var prop in props)
  1804. {
  1805. if (filterParams)
  1806. {
  1807. if (identity.sql.IndexOf("@" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0
  1808. && identity.sql.IndexOf(":" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0
  1809. && identity.sql.IndexOf("?" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0)
  1810. { // can't see the parameter in the text (even in a comment, etc) - burn it with fire
  1811. continue;
  1812. }
  1813. }
  1814. if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType))
  1815. {
  1816. il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
  1817. il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring]
  1818. il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command]
  1819. il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name]
  1820. il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("AddParameter"), null); // stack is now [parameters]
  1821. continue;
  1822. }
  1823. DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
  1824. KeyValuePair<DbType, int>? kvp = null;
  1825. if (dbType == DbType.String && dpi != null)//默认所有字符串在Dapper中被param成 DbType.String
  1826. {
  1827. kvp = dpi.GetStringColumnMap(prop.Name);
  1828. }
  1829. if (dbType == DynamicParameters.EnumerableMultiParameter)
  1830. {
  1831. // this actually represents special handling for list types;
  1832. il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
  1833. il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name]
  1834. il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
  1835. il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
  1836. if (prop.PropertyType.IsValueType)
  1837. {
  1838. il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
  1839. }
  1840. il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters]
  1841. continue;
  1842. }
  1843. il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters]
  1844. il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command]
  1845. if (checkForDuplicates)
  1846. {
  1847. // need to be a little careful about adding; use a utility method
  1848. il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [command] [name]
  1849. il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("FindOrAddParameter"), null); // stack is [parameters] [parameter]
  1850. }
  1851. else
  1852. {
  1853. // no risk of duplicates; just blindly add
  1854. il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter]
  1855. il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
  1856. il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
  1857. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
  1858. }
  1859. 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
  1860. {
  1861. //string parameter extensions  对于字符串参数化的扩展
  1862. int dbTypeValue = (int)dbType;
  1863. if (kvp.HasValue)
  1864. {
  1865. dbTypeValue = (int)kvp.Value.Key;
  1866. }
  1867. il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
  1868. EmitInt32(il, dbTypeValue);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type]
  1869. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
  1870. }
  1871. il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
  1872. EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [[parameters]] [parameter] [parameter] [dir]
  1873. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
  1874. il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
  1875. il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param]
  1876. il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value]
  1877. bool checkForNull = true;
  1878. if (prop.PropertyType.IsValueType)
  1879. {
  1880. il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value]
  1881. if (Nullable.GetUnderlyingType(prop.PropertyType) == null)
  1882. {   // struct but not Nullable<T>; boxed value cannot be null
  1883. checkForNull = false;
  1884. }
  1885. }
  1886. if (checkForNull)
  1887. {
  1888. if (dbType == DbType.String && !haveInt32Arg1)
  1889. {
  1890. il.DeclareLocal(typeof(int));
  1891. haveInt32Arg1 = true;
  1892. }
  1893. // relative stack: [boxed value]
  1894. il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
  1895. Label notNull = il.DefineLabel();
  1896. Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null;
  1897. il.Emit(OpCodes.Brtrue_S, notNull);
  1898. // relative stack [boxed value = null]
  1899. il.Emit(OpCodes.Pop); // relative stack empty
  1900. il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull]
  1901. if (dbType == DbType.String)
  1902. {
  1903. EmitInt32(il, 0);
  1904. il.Emit(OpCodes.Stloc_1);
  1905. }
  1906. if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value);
  1907. il.MarkLabel(notNull);
  1908. //if (prop.PropertyType == typeof(string))
  1909. //{
  1910. //    il.Emit(OpCodes.Dup); // [string] [string]
  1911. //    il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length]
  1912. //    EmitInt32(il, 4000); // [string] [length] [4000]
  1913. //    il.Emit(OpCodes.Clt); // [string] [0 or 1]
  1914. //    Label isLong = il.DefineLabel(), lenDone = il.DefineLabel();
  1915. //    il.Emit(OpCodes.Brtrue_S, isLong);
  1916. //    EmitInt32(il, 4000); // [string] [4000]
  1917. //    il.Emit(OpCodes.Br_S, lenDone);
  1918. //    il.MarkLabel(isLong);
  1919. //    EmitInt32(il, -1); // [string] [-1]
  1920. //    il.MarkLabel(lenDone);
  1921. //    il.Emit(OpCodes.Stloc_1); // [string]
  1922. //}
  1923. if (prop.PropertyType.FullName == LinqBinary)
  1924. {
  1925. il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null);
  1926. }
  1927. if (allDone != null) il.MarkLabel(allDone.Value);
  1928. // relative stack [boxed value or DBNull]
  1929. }
  1930. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter]
  1931. if (prop.PropertyType == typeof(string))
  1932. {
  1933. //var endOfSize = il.DefineLabel();
  1934. //// don't set if 0
  1935. //il.Emit(OpCodes.Ldloc_1); // [parameters] [[parameters]] [parameter] [size]
  1936. //il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter]
  1937. //il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter]
  1938. //il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size]
  1939. //il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter]
  1940. //il.MarkLabel(endOfSize);
  1941. if (kvp.HasValue)
  1942. {
  1943. il.Emit(OpCodes.Dup);
  1944. EmitInt32(il, kvp.Value.Value);
  1945. il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter]
  1946. }
  1947. }
  1948. if (checkForDuplicates)
  1949. {
  1950. // stack is now [parameters] [parameter]
  1951. il.Emit(OpCodes.Pop); // don't need parameter any more
  1952. }
  1953. else
  1954. {
  1955. // stack is now [parameters] [parameters] [parameter]
  1956. // blindly add
  1957. il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters]
  1958. il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
  1959. }
  1960. }
  1961. // stack is currently [parameters]
  1962. il.Emit(OpCodes.Pop); // stack is now empty
  1963. il.Emit(OpCodes.Ret);
  1964. return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
  1965. }
  1966. private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
  1967. {
  1968. var cmd = cnn.CreateCommand();
  1969. var bindByName = GetBindByName(cmd.GetType());
  1970. if (bindByName != null) bindByName(cmd, true);
  1971. if (transaction != null)
  1972. cmd.Transaction = transaction;
  1973. cmd.CommandText = sql;
  1974. if (commandTimeout.HasValue)
  1975. cmd.CommandTimeout = commandTimeout.Value;
  1976. if (commandType.HasValue)
  1977. cmd.CommandType = commandType.Value;
  1978. if (paramReader != null)
  1979. {
  1980. paramReader(cmd, obj);
  1981. }
  1982. return cmd;
  1983. }
  1984. private static int ExecuteCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
  1985. {
  1986. IDbCommand cmd = null;
  1987. bool wasClosed = cnn.State == ConnectionState.Closed;
  1988. try
  1989. {
  1990. cmd = SetupCommand(cnn, transaction, sql, paramReader, obj, commandTimeout, commandType);
  1991. if (wasClosed) cnn.Open();
  1992. return cmd.ExecuteNonQuery();
  1993. }
  1994. finally
  1995. {
  1996. if (wasClosed) cnn.Close();
  1997. if (cmd != null) cmd.Dispose();
  1998. }
  1999. }
  2000. private static Func<IDataReader, object> GetStructDeserializer(Type type, Type effectiveType, int index)
  2001. {
  2002. // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!)
  2003. #pragma warning disable 618
  2004. if (type == typeof(char))
  2005. { // this *does* need special handling, though
  2006. return r => SqlMapper.ReadChar(r.GetValue(index));
  2007. }
  2008. if (type == typeof(char?))
  2009. {
  2010. return r => SqlMapper.ReadNullableChar(r.GetValue(index));
  2011. }
  2012. if (type.FullName == LinqBinary)
  2013. {
  2014. return r => Activator.CreateInstance(type, r.GetValue(index));
  2015. }
  2016. #pragma warning restore 618
  2017. if (effectiveType.IsEnum)
  2018. {   // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum
  2019. return r =>
  2020. {
  2021. var val = r.GetValue(index);
  2022. return val is DBNull ? null : Enum.ToObject(effectiveType, val);
  2023. };
  2024. }
  2025. return r =>
  2026. {
  2027. var val = r.GetValue(index);
  2028. return val is DBNull ? null : val;
  2029. };
  2030. }
  2031. static readonly MethodInfo
  2032. enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
  2033. getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
  2034. .Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int))
  2035. .Select(p => p.GetGetMethod()).First();
  2036. /// <summary>
  2037. /// Gets type-map for the given type
  2038. /// </summary>
  2039. /// <returns>Type map implementation, DefaultTypeMap instance if no override present</returns>
  2040. public static ITypeMap GetTypeMap(Type type)
  2041. {
  2042. if (type == null) throw new ArgumentNullException("type");
  2043. var map = (ITypeMap)_typeMaps[type];
  2044. if (map == null)
  2045. {
  2046. lock (_typeMaps)
  2047. {   // double-checked; store this to avoid reflection next time we see this type
  2048. // since multiple queries commonly use the same domain-entity/DTO/view-model type
  2049. map = (ITypeMap)_typeMaps[type];
  2050. if (map == null)
  2051. {
  2052. map = new DefaultTypeMap(type);
  2053. _typeMaps[type] = map;
  2054. }
  2055. }
  2056. }
  2057. return map;
  2058. }
  2059. // use Hashtable to get free lockless reading
  2060. private static readonly Hashtable _typeMaps = new Hashtable();
  2061. /// <summary>
  2062. /// Set custom mapping for type deserializers
  2063. /// </summary>
  2064. /// <param name="type">Entity type to override</param>
  2065. /// <param name="map">Mapping rules impementation, null to remove custom map</param>
  2066. public static void SetTypeMap(Type type, ITypeMap map)
  2067. {
  2068. if (type == null)
  2069. throw new ArgumentNullException("type");
  2070. if (map == null || map is DefaultTypeMap)
  2071. {
  2072. lock (_typeMaps)
  2073. {
  2074. _typeMaps.Remove(type);
  2075. }
  2076. }
  2077. else
  2078. {
  2079. lock (_typeMaps)
  2080. {
  2081. _typeMaps[type] = map;
  2082. }
  2083. }
  2084. PurgeQueryCacheByType(type);
  2085. }
  2086. /// <summary>
  2087. /// Internal use only
  2088. /// </summary>
  2089. /// <param name="type"></param>
  2090. /// <param name="reader"></param>
  2091. /// <param name="startBound"></param>
  2092. /// <param name="length"></param>
  2093. /// <param name="returnNullIfFirstMissing"></param>
  2094. /// <returns></returns>
  2095. public static Func<IDataReader, object> GetTypeDeserializer(
  2096. #if CSHARP30
  2097. Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
  2098. #else
  2099. Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
  2100. #endif
  2101. )
  2102. {
  2103. var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), typeof(object), new[] { typeof(IDataReader) }, true);
  2104. var il = dm.GetILGenerator();
  2105. il.DeclareLocal(typeof(int));
  2106. il.DeclareLocal(type);
  2107. il.Emit(OpCodes.Ldc_I4_0);
  2108. il.Emit(OpCodes.Stloc_0);
  2109. if (length == -1)
  2110. {
  2111. length = reader.FieldCount - startBound;
  2112. }
  2113. if (reader.FieldCount <= startBound)
  2114. {
  2115. throw new ArgumentException(MultiMapSplitExceptionMessage, "splitOn");
  2116. }
  2117. var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray();
  2118. ITypeMap typeMap = GetTypeMap(type);
  2119. int index = startBound;
  2120. ConstructorInfo specializedConstructor = null;
  2121. if (type.IsValueType)
  2122. {
  2123. il.Emit(OpCodes.Ldloca_S, (byte)1);
  2124. il.Emit(OpCodes.Initobj, type);
  2125. }
  2126. else
  2127. {
  2128. var types = new Type[length];
  2129. for (int i = startBound; i < startBound + length; i++)
  2130. {
  2131. types[i - startBound] = reader.GetFieldType(i);
  2132. }
  2133. if (type.IsValueType)
  2134. {
  2135. il.Emit(OpCodes.Ldloca_S, (byte)1);
  2136. il.Emit(OpCodes.Initobj, type);
  2137. }
  2138. else
  2139. {
  2140. var ctor = typeMap.FindConstructor(names, types);
  2141. if (ctor == null)
  2142. {
  2143. string proposedTypes = "(" + String.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")";
  2144. throw new InvalidOperationException(String.Format("A parameterless default constructor or one matching signature {0} is required for {1} materialization", proposedTypes, type.FullName));
  2145. }
  2146. if (ctor.GetParameters().Length == 0)
  2147. {
  2148. il.Emit(OpCodes.Newobj, ctor);
  2149. il.Emit(OpCodes.Stloc_1);
  2150. }
  2151. else
  2152. specializedConstructor = ctor;
  2153. }
  2154. }
  2155. il.BeginExceptionBlock();
  2156. if (type.IsValueType)
  2157. {
  2158. il.Emit(OpCodes.Ldloca_S, (byte)1);// [target]
  2159. }
  2160. else if (specializedConstructor == null)
  2161. {
  2162. il.Emit(OpCodes.Ldloc_1);// [target]
  2163. }
  2164. var members = (specializedConstructor != null
  2165. ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n))
  2166. : names.Select(n => typeMap.GetMember(n))).ToList();
  2167. // stack is now [target]
  2168. bool first = true;
  2169. var allDone = il.DefineLabel();
  2170. int enumDeclareLocal = -1;
  2171. foreach (var item in members)
  2172. {
  2173. if (item != null)
  2174. {
  2175. if (specializedConstructor == null)
  2176. il.Emit(OpCodes.Dup); // stack is now [target][target]
  2177. Label isDbNullLabel = il.DefineLabel();
  2178. Label finishLabel = il.DefineLabel();
  2179. il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
  2180. EmitInt32(il, index); // stack is now [target][target][reader][index]
  2181. il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
  2182. il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
  2183. il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
  2184. Type memberType = item.MemberType;
  2185. if (memberType == typeof(char) || memberType == typeof(char?))
  2186. {
  2187. il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
  2188. memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
  2189. }
  2190. else
  2191. {
  2192. il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
  2193. il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
  2194. il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object]
  2195. // unbox nullable enums as the primitive, i.e. byte etc
  2196. var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
  2197. var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType;
  2198. if (unboxType.IsEnum)
  2199. {
  2200. if (enumDeclareLocal == -1)
  2201. {
  2202. enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex;
  2203. }
  2204. Label isNotString = il.DefineLabel();
  2205. il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
  2206. il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
  2207. il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
  2208. StoreLocal(il, enumDeclareLocal); // stack is now [target][target][value-as-object][string or null]
  2209. il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
  2210. il.Emit(OpCodes.Pop); // stack is now [target][target]
  2211. il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
  2212. il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
  2213. il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
  2214. il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
  2215. il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
  2216. il.MarkLabel(isNotString);
  2217. il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
  2218. if (nullUnderlyingType != null)
  2219. {
  2220. il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][enum-value]
  2221. }
  2222. }
  2223. else if (memberType.FullName == LinqBinary)
  2224. {
  2225. il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
  2226. il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
  2227. }
  2228. else
  2229. {
  2230. Type dataType = reader.GetFieldType(index);
  2231. TypeCode dataTypeCode = Type.GetTypeCode(dataType), unboxTypeCode = Type.GetTypeCode(unboxType);
  2232. if (dataType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType))
  2233. {
  2234. il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
  2235. }
  2236. else
  2237. {
  2238. // not a direct match; need to tweak the unbox
  2239. MethodInfo op;
  2240. if ((op = GetOperator(dataType, nullUnderlyingType ?? unboxType)) != null)
  2241. { // this is handy for things like decimal <===> double
  2242. il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
  2243. il.Emit(OpCodes.Call, op); // stack is now [target][target][typed-value]
  2244. }
  2245. else
  2246. {
  2247. bool handled = true;
  2248. OpCode opCode = default(OpCode);
  2249. if (dataTypeCode == TypeCode.Decimal || unboxTypeCode == TypeCode.Decimal)
  2250. {   // no IL level conversions to/from decimal; I guess we could use the static operators, but
  2251. // this feels an edge-case
  2252. handled = false;
  2253. }
  2254. else
  2255. {
  2256. switch (unboxTypeCode)
  2257. {
  2258. case TypeCode.Byte:
  2259. opCode = OpCodes.Conv_Ovf_I1_Un; break;
  2260. case TypeCode.SByte:
  2261. opCode = OpCodes.Conv_Ovf_I1; break;
  2262. case TypeCode.UInt16:
  2263. opCode = OpCodes.Conv_Ovf_I2_Un; break;
  2264. case TypeCode.Int16:
  2265. opCode = OpCodes.Conv_Ovf_I2; break;
  2266. case TypeCode.UInt32:
  2267. opCode = OpCodes.Conv_Ovf_I4_Un; break;
  2268. case TypeCode.Boolean: // boolean is basically an int, at least at this level
  2269. case TypeCode.Int32:
  2270. opCode = OpCodes.Conv_Ovf_I4; break;
  2271. case TypeCode.UInt64:
  2272. opCode = OpCodes.Conv_Ovf_I8_Un; break;
  2273. case TypeCode.Int64:
  2274. opCode = OpCodes.Conv_Ovf_I8; break;
  2275. case TypeCode.Single:
  2276. opCode = OpCodes.Conv_R4; break;
  2277. case TypeCode.Double:
  2278. opCode = OpCodes.Conv_R8; break;
  2279. default:
  2280. handled = false;
  2281. break;
  2282. }
  2283. }
  2284. if (handled)
  2285. { // unbox as the data-type, then use IL-level convert
  2286. il.Emit(OpCodes.Unbox_Any, dataType); // stack is now [target][target][data-typed-value]
  2287. il.Emit(opCode); // stack is now [target][target][typed-value]
  2288. if (unboxTypeCode == TypeCode.Boolean)
  2289. { // compare to zero; I checked "csc" - this is the trick it uses; nice
  2290. il.Emit(OpCodes.Ldc_I4_0);
  2291. il.Emit(OpCodes.Ceq);
  2292. il.Emit(OpCodes.Ldc_I4_0);
  2293. il.Emit(OpCodes.Ceq);
  2294. }
  2295. }
  2296. else
  2297. { // use flexible conversion
  2298. il.Emit(OpCodes.Ldtoken, nullUnderlyingType ?? unboxType); // stack is now [target][target][value][member-type-token]
  2299. il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null); // stack is now [target][target][value][member-type]
  2300. il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod("ChangeType", new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value]
  2301. il.Emit(OpCodes.Unbox_Any, nullUnderlyingType ?? unboxType); // stack is now [target][target][typed-value]
  2302. }
  2303. }
  2304. if (nullUnderlyingType != null)
  2305. {
  2306. il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value]
  2307. }
  2308. }
  2309. }
  2310. }
  2311. if (specializedConstructor == null)
  2312. {
  2313. // Store the value in the property/field
  2314. if (item.Property != null)
  2315. {
  2316. if (type.IsValueType)
  2317. {
  2318. il.Emit(OpCodes.Call, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
  2319. }
  2320. else
  2321. {
  2322. il.Emit(OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target]
  2323. }
  2324. }
  2325. else
  2326. {
  2327. il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
  2328. }
  2329. }
  2330. il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
  2331. il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
  2332. if (specializedConstructor != null)
  2333. {
  2334. il.Emit(OpCodes.Pop);
  2335. if (item.MemberType.IsValueType)
  2336. {
  2337. int localIndex = il.DeclareLocal(item.MemberType).LocalIndex;
  2338. LoadLocalAddress(il, localIndex);
  2339. il.Emit(OpCodes.Initobj, item.MemberType);
  2340. LoadLocal(il, localIndex);
  2341. }
  2342. else
  2343. {
  2344. il.Emit(OpCodes.Ldnull);
  2345. }
  2346. }
  2347. else
  2348. {
  2349. il.Emit(OpCodes.Pop); // stack is now [target][target]
  2350. il.Emit(OpCodes.Pop); // stack is now [target]
  2351. }
  2352. if (first && returnNullIfFirstMissing)
  2353. {
  2354. il.Emit(OpCodes.Pop);
  2355. il.Emit(OpCodes.Ldnull); // stack is now [null]
  2356. il.Emit(OpCodes.Stloc_1);
  2357. il.Emit(OpCodes.Br, allDone);
  2358. }
  2359. il.MarkLabel(finishLabel);
  2360. }
  2361. first = false;
  2362. index += 1;
  2363. }
  2364. if (type.IsValueType)
  2365. {
  2366. il.Emit(OpCodes.Pop);
  2367. }
  2368. else
  2369. {
  2370. if (specializedConstructor != null)
  2371. {
  2372. il.Emit(OpCodes.Newobj, specializedConstructor);
  2373. }
  2374. il.Emit(OpCodes.Stloc_1); // stack is empty
  2375. }
  2376. il.MarkLabel(allDone);
  2377. il.BeginCatchBlock(typeof(Exception)); // stack is Exception
  2378. il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
  2379. il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
  2380. il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
  2381. il.EndExceptionBlock();
  2382. il.Emit(OpCodes.Ldloc_1); // stack is [rval]
  2383. if (type.IsValueType)
  2384. {
  2385. il.Emit(OpCodes.Box, type);
  2386. }
  2387. il.Emit(OpCodes.Ret);
  2388. return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
  2389. }
  2390. static MethodInfo GetOperator(Type from, Type to)
  2391. {
  2392. if (to == null) return null;
  2393. MethodInfo[] fromMethods, toMethods;
  2394. return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
  2395. ?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit")
  2396. ?? ResolveOperator(fromMethods, from, to, "op_Explicit")
  2397. ?? ResolveOperator(toMethods, from, to, "op_Explicit");
  2398. }
  2399. static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name)
  2400. {
  2401. for (int i = 0; i < methods.Length; i++)
  2402. {
  2403. if (methods[i].Name != name || methods[i].ReturnType != to) continue;
  2404. var args = methods[i].GetParameters();
  2405. if (args.Length != 1 || args[0].ParameterType != from) continue;
  2406. return methods[i];
  2407. }
  2408. return null;
  2409. }
  2410. private static void LoadLocal(ILGenerator il, int index)
  2411. {
  2412. if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
  2413. switch (index)
  2414. {
  2415. case 0: il.Emit(OpCodes.Ldloc_0); break;
  2416. case 1: il.Emit(OpCodes.Ldloc_1); break;
  2417. case 2: il.Emit(OpCodes.Ldloc_2); break;
  2418. case 3: il.Emit(OpCodes.Ldloc_3); break;
  2419. default:
  2420. if (index <= 255)
  2421. {
  2422. il.Emit(OpCodes.Ldloc_S, (byte)index);
  2423. }
  2424. else
  2425. {
  2426. il.Emit(OpCodes.Ldloc, (short)index);
  2427. }
  2428. break;
  2429. }
  2430. }
  2431. private static void StoreLocal(ILGenerator il, int index)
  2432. {
  2433. if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
  2434. switch (index)
  2435. {
  2436. case 0: il.Emit(OpCodes.Stloc_0); break;
  2437. case 1: il.Emit(OpCodes.Stloc_1); break;
  2438. case 2: il.Emit(OpCodes.Stloc_2); break;
  2439. case 3: il.Emit(OpCodes.Stloc_3); break;
  2440. default:
  2441. if (index <= 255)
  2442. {
  2443. il.Emit(OpCodes.Stloc_S, (byte)index);
  2444. }
  2445. else
  2446. {
  2447. il.Emit(OpCodes.Stloc, (short)index);
  2448. }
  2449. break;
  2450. }
  2451. }
  2452. private static void LoadLocalAddress(ILGenerator il, int index)
  2453. {
  2454. if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException("index");
  2455. if (index <= 255)
  2456. {
  2457. il.Emit(OpCodes.Ldloca_S, (byte)index);
  2458. }
  2459. else
  2460. {
  2461. il.Emit(OpCodes.Ldloca, (short)index);
  2462. }
  2463. }
  2464. /// <summary>
  2465. /// Throws a data exception, only used internally
  2466. /// </summary>
  2467. /// <param name="ex"></param>
  2468. /// <param name="index"></param>
  2469. /// <param name="reader"></param>
  2470. public static void ThrowDataException(Exception ex, int index, IDataReader reader)
  2471. {
  2472. Exception toThrow;
  2473. try
  2474. {
  2475. string name = "(n/a)", value = "(n/a)";
  2476. if (reader != null && index >= 0 && index < reader.FieldCount)
  2477. {
  2478. name = reader.GetName(index);
  2479. object val = reader.GetValue(index);
  2480. if (val == null || val is DBNull)
  2481. {
  2482. value = "<null>";
  2483. }
  2484. else
  2485. {
  2486. value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType());
  2487. }
  2488. }
  2489. toThrow = new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex);
  2490. }
  2491. catch
  2492. { // throw the **original** exception, wrapped as DataException
  2493. toThrow = new DataException(ex.Message, ex);
  2494. }
  2495. throw toThrow;
  2496. }
  2497. private static void EmitInt32(ILGenerator il, int value)
  2498. {
  2499. switch (value)
  2500. {
  2501. case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
  2502. case 0: il.Emit(OpCodes.Ldc_I4_0); break;
  2503. case 1: il.Emit(OpCodes.Ldc_I4_1); break;
  2504. case 2: il.Emit(OpCodes.Ldc_I4_2); break;
  2505. case 3: il.Emit(OpCodes.Ldc_I4_3); break;
  2506. case 4: il.Emit(OpCodes.Ldc_I4_4); break;
  2507. case 5: il.Emit(OpCodes.Ldc_I4_5); break;
  2508. case 6: il.Emit(OpCodes.Ldc_I4_6); break;
  2509. case 7: il.Emit(OpCodes.Ldc_I4_7); break;
  2510. case 8: il.Emit(OpCodes.Ldc_I4_8); break;
  2511. default:
  2512. if (value >= -128 && value <= 127)
  2513. {
  2514. il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
  2515. }
  2516. else
  2517. {
  2518. il.Emit(OpCodes.Ldc_I4, value);
  2519. }
  2520. break;
  2521. }
  2522. }
  2523. /// <summary>
  2524. /// How should connection strings be compared for equivalence? Defaults to StringComparer.Ordinal.
  2525. /// Providing a custom implementation can be useful for allowing multi-tenancy databases with identical
  2526. /// schema to share startegies. Note that usual equivalence rules apply: any equivalent connection strings
  2527. /// <b>MUST</b> yield the same hash-code.
  2528. /// </summary>
  2529. public static IEqualityComparer<string> ConnectionStringComparer
  2530. {
  2531. get { return connectionStringComparer; }
  2532. set { connectionStringComparer = value ?? StringComparer.Ordinal; }
  2533. }
  2534. private static IEqualityComparer<string> connectionStringComparer = StringComparer.Ordinal;
  2535. /// <summary>
  2536. /// The grid reader provides interfaces for reading multiple result sets from a Dapper query
  2537. /// </summary>
  2538. public partial class GridReader : IDisposable
  2539. {
  2540. private IDataReader reader;
  2541. private IDbCommand command;
  2542. private Identity identity;
  2543. internal GridReader(IDbCommand command, IDataReader reader, Identity identity)
  2544. {
  2545. this.command = command;
  2546. this.reader = reader;
  2547. this.identity = identity;
  2548. }
  2549. #if !CSHARP30
  2550. /// <summary>
  2551. /// Read the next grid of results, returned as a dynamic object
  2552. /// </summary>
  2553. public IEnumerable<dynamic> Read(bool buffered = true)
  2554. {
  2555. return Read<DapperRow>(buffered);
  2556. }
  2557. #endif
  2558. #if CSHARP30
  2559. /// <summary>
  2560. /// Read the next grid of results
  2561. /// </summary>
  2562. public IEnumerable<T> Read<T>()
  2563. {
  2564. return Read<T>(true);
  2565. }
  2566. #endif
  2567. /// <summary>
  2568. /// Read the next grid of results
  2569. /// </summary>
  2570. #if CSHARP30
  2571. public IEnumerable<T> Read<T>(bool buffered)
  2572. #else
  2573. public IEnumerable<T> Read<T>(bool buffered = true)
  2574. #endif
  2575. {
  2576. if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
  2577. if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
  2578. var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
  2579. CacheInfo cache = GetCacheInfo(typedIdentity);
  2580. var deserializer = cache.Deserializer;
  2581. int hash = GetColumnHash(reader);
  2582. if (deserializer.Func == null || deserializer.Hash != hash)
  2583. {
  2584. deserializer = new DeserializerState(hash, GetDeserializer(typeof(T), reader, 0, -1, false));
  2585. cache.Deserializer = deserializer;
  2586. }
  2587. consumed = true;
  2588. var result = ReadDeferred<T>(gridIndex, deserializer.Func, typedIdentity);
  2589. return buffered ? result.ToList() : result;
  2590. }
  2591. private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(object func, string splitOn)
  2592. {
  2593. var identity = this.identity.ForGrid(typeof(TReturn), new Type[] {
  2594. typeof(TFirst),
  2595. typeof(TSecond),
  2596. typeof(TThird),
  2597. typeof(TFourth),
  2598. typeof(TFifth),
  2599. typeof(TSixth),
  2600. typeof(TSeventh)
  2601. }, gridIndex);
  2602. try
  2603. {
  2604. foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(null, null, func, null, null, splitOn, null, null, reader, identity))
  2605. {
  2606. yield return r;
  2607. }
  2608. }
  2609. finally
  2610. {
  2611. NextResult();
  2612. }
  2613. }
  2614. #if CSHARP30
  2615. /// <summary>
  2616. /// Read multiple objects from a single recordset on the grid
  2617. /// </summary>
  2618. public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)
  2619. {
  2620. return Read<TFirst, TSecond, TReturn>(func, splitOn, true);
  2621. }
  2622. #endif
  2623. /// <summary>
  2624. /// Read multiple objects from a single recordset on the grid
  2625. /// </summary>
  2626. #if CSHARP30
  2627. public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn, bool buffered)
  2628. #else
  2629. public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id", bool buffered = true)
  2630. #endif
  2631. {
  2632. var result = MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
  2633. return buffered ? result.ToList() : result;
  2634. }
  2635. #if CSHARP30
  2636. /// <summary>
  2637. /// Read multiple objects from a single recordset on the grid
  2638. /// </summary>
  2639. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn)
  2640. {
  2641. return Read<TFirst, TSecond, TThird, TReturn>(func, splitOn, true);
  2642. }
  2643. #endif
  2644. /// <summary>
  2645. /// Read multiple objects from a single recordset on the grid
  2646. /// </summary>
  2647. #if CSHARP30
  2648. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn, bool buffered)
  2649. #else
  2650. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id", bool buffered = true)
  2651. #endif
  2652. {
  2653. var result = MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
  2654. return buffered ? result.ToList() : result;
  2655. }
  2656. #if CSHARP30
  2657. /// <summary>
  2658. /// Read multiple objects from a single record set on the grid
  2659. /// </summary>
  2660. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn)
  2661. {
  2662. return Read<TFirst, TSecond, TThird, TFourth, TReturn>(func, splitOn, true);
  2663. }
  2664. #endif
  2665. /// <summary>
  2666. /// Read multiple objects from a single record set on the grid
  2667. /// </summary>
  2668. #if CSHARP30
  2669. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn, bool buffered)
  2670. #else
  2671. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id", bool buffered = true)
  2672. #endif
  2673. {
  2674. var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(func, splitOn);
  2675. return buffered ? result.ToList() : result;
  2676. }
  2677. #if !CSHARP30
  2678. /// <summary>
  2679. /// Read multiple objects from a single record set on the grid
  2680. /// </summary>
  2681. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id", bool buffered = true)
  2682. {
  2683. var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(func, splitOn);
  2684. return buffered ? result.ToList() : result;
  2685. }
  2686. /// <summary>
  2687. /// Read multiple objects from a single record set on the grid
  2688. /// </summary>
  2689. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn> func, string splitOn = "id", bool buffered = true)
  2690. {
  2691. var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(func, splitOn);
  2692. return buffered ? result.ToList() : result;
  2693. }
  2694. /// <summary>
  2695. /// Read multiple objects from a single record set on the grid
  2696. /// </summary>
  2697. public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn> func, string splitOn = "id", bool buffered = true)
  2698. {
  2699. var result = MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(func, splitOn);
  2700. return buffered ? result.ToList() : result;
  2701. }
  2702. #endif
  2703. private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity)
  2704. {
  2705. try
  2706. {
  2707. while (index == gridIndex && reader.Read())
  2708. {
  2709. yield return (T)deserializer(reader);
  2710. }
  2711. }
  2712. finally // finally so that First etc progresses things even when multiple rows
  2713. {
  2714. if (index == gridIndex)
  2715. {
  2716. NextResult();
  2717. }
  2718. }
  2719. }
  2720. private int gridIndex, readCount;
  2721. private bool consumed;
  2722. private void NextResult()
  2723. {
  2724. if (reader.NextResult())
  2725. {
  2726. readCount++;
  2727. gridIndex++;
  2728. consumed = false;
  2729. }
  2730. else
  2731. {
  2732. // happy path; close the reader cleanly - no
  2733. // need for "Cancel" etc
  2734. reader.Dispose();
  2735. reader = null;
  2736. Dispose();
  2737. }
  2738. }
  2739. /// <summary>
  2740. /// Dispose the grid, closing and disposing both the underlying reader and command.
  2741. /// </summary>
  2742. public void Dispose()
  2743. {
  2744. if (reader != null)
  2745. {
  2746. if (!reader.IsClosed && command != null) command.Cancel();
  2747. reader.Dispose();
  2748. reader = null;
  2749. }
  2750. if (command != null)
  2751. {
  2752. command.Dispose();
  2753. command = null;
  2754. }
  2755. }
  2756. }
  2757. }
  2758. /// <summary>
  2759. /// A bag of parameters that can be passed to the Dapper Query and Execute methods
  2760. /// </summary>
  2761. partial class DynamicParameters : SqlMapper.IDynamicParameters
  2762. {
  2763. internal const DbType EnumerableMultiParameter = (DbType)(-1);
  2764. static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
  2765. Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();
  2766. List<object> templates;
  2767. partial class ParamInfo
  2768. {
  2769. public string Name { get; set; }
  2770. public object Value { get; set; }
  2771. public ParameterDirection ParameterDirection { get; set; }
  2772. public DbType? DbType { get; set; }
  2773. public int? Size { get; set; }
  2774. public IDbDataParameter AttachedParam { get; set; }
  2775. }
  2776. /// <summary>
  2777. /// construct a dynamic parameter bag
  2778. /// </summary>
  2779. public DynamicParameters()
  2780. {
  2781. RemoveUnused = true;
  2782. }
  2783. /// <summary>
  2784. /// construct a dynamic parameter bag
  2785. /// </summary>
  2786. /// <param name="template">can be an anonymous type or a DynamicParameters bag</param>
  2787. public DynamicParameters(object template)
  2788. {
  2789. RemoveUnused = true;
  2790. AddDynamicParams(template);
  2791. }
  2792. /// <summary>
  2793. /// Append a whole object full of params to the dynamic
  2794. /// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
  2795. /// </summary>
  2796. /// <param name="param"></param>
  2797. public void AddDynamicParams(
  2798. #if CSHARP30
  2799. object param
  2800. #else
  2801. dynamic param
  2802. #endif
  2803. )
  2804. {
  2805. var obj = param as object;
  2806. if (obj != null)
  2807. {
  2808. var subDynamic = obj as DynamicParameters;
  2809. if (subDynamic == null)
  2810. {
  2811. var dictionary = obj as IEnumerable<KeyValuePair<string, object>>;
  2812. if (dictionary == null)
  2813. {
  2814. templates = templates ?? new List<object>();
  2815. templates.Add(obj);
  2816. }
  2817. else
  2818. {
  2819. foreach (var kvp in dictionary)
  2820. {
  2821. #if CSHARP30
  2822. Add(kvp.Key, kvp.Value, null, null, null);
  2823. #else
  2824. Add(kvp.Key, kvp.Value);
  2825. #endif
  2826. }
  2827. }
  2828. }
  2829. else
  2830. {
  2831. if (subDynamic.parameters != null)
  2832. {
  2833. foreach (var kvp in subDynamic.parameters)
  2834. {
  2835. parameters.Add(kvp.Key, kvp.Value);
  2836. }
  2837. }
  2838. if (subDynamic.templates != null)
  2839. {
  2840. templates = templates ?? new List<object>();
  2841. foreach (var t in subDynamic.templates)
  2842. {
  2843. templates.Add(t);
  2844. }
  2845. }
  2846. }
  2847. }
  2848. }
  2849. /// <summary>
  2850. /// Add a parameter to this dynamic parameter list
  2851. /// </summary>
  2852. /// <param name="name"></param>
  2853. /// <param name="value"></param>
  2854. /// <param name="dbType"></param>
  2855. /// <param name="direction"></param>
  2856. /// <param name="size"></param>
  2857. public void Add(
  2858. #if CSHARP30
  2859. string name, object value, DbType? dbType, ParameterDirection? direction, int? size
  2860. #else
  2861. string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null
  2862. #endif
  2863. )
  2864. {
  2865. parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size };
  2866. }
  2867. static string Clean(string name)
  2868. {
  2869. if (!string.IsNullOrEmpty(name))
  2870. {
  2871. switch (name[0])
  2872. {
  2873. case '@':
  2874. case ':':
  2875. case '?':
  2876. return name.Substring(1);
  2877. }
  2878. }
  2879. return name;
  2880. }
  2881. void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
  2882. {
  2883. AddParameters(command, identity);
  2884. }
  2885. /// <summary>
  2886. /// If true, the command-text is inspected and only values that are clearly used are included on the connection
  2887. /// </summary>
  2888. public bool RemoveUnused { get; set; }
  2889. /// <summary>
  2890. /// Add all the parameters needed to the command just before it executes
  2891. /// </summary>
  2892. /// <param name="command">The raw command prior to execution</param>
  2893. /// <param name="identity">Information about the query</param>
  2894. protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
  2895. {
  2896. if (templates != null)
  2897. {
  2898. foreach (var template in templates)
  2899. {
  2900. var newIdent = identity.ForDynamicParameters(template.GetType());
  2901. Action<IDbCommand, object> appender;
  2902. lock (paramReaderCache)
  2903. {
  2904. if (!paramReaderCache.TryGetValue(newIdent, out appender))
  2905. {
  2906. appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused);
  2907. paramReaderCache[newIdent] = appender;
  2908. }
  2909. }
  2910. appender(command, template);
  2911. }
  2912. }
  2913. foreach (var param in parameters.Values)
  2914. {
  2915. var dbType = param.DbType;
  2916. var val = param.Value;
  2917. string name = Clean(param.Name);
  2918. if (dbType == null && val != null) dbType = SqlMapper.LookupDbType(val.GetType(), name);
  2919. if (dbType == DynamicParameters.EnumerableMultiParameter)
  2920. {
  2921. #pragma warning disable 612, 618
  2922. SqlMapper.PackListParameters(command, name, val);
  2923. #pragma warning restore 612, 618
  2924. }
  2925. else
  2926. {
  2927. bool add = !command.Parameters.Contains(name);
  2928. IDbDataParameter p;
  2929. if (add)
  2930. {
  2931. p = command.CreateParameter();
  2932. p.ParameterName = name;
  2933. }
  2934. else
  2935. {
  2936. p = (IDbDataParameter)command.Parameters[name];
  2937. }
  2938. p.Value = val ?? DBNull.Value;
  2939. p.Direction = param.ParameterDirection;
  2940. var s = val as string;
  2941. if (s != null)
  2942. {
  2943. if (s.Length <= 4000)
  2944. {
  2945. p.Size = 4000;
  2946. }
  2947. }
  2948. if (param.Size != null)
  2949. {
  2950. p.Size = param.Size.Value;
  2951. }
  2952. if (dbType != null)
  2953. {
  2954. p.DbType = dbType.Value;
  2955. }
  2956. if (add)
  2957. {
  2958. command.Parameters.Add(p);
  2959. }
  2960. param.AttachedParam = p;
  2961. }
  2962. }
  2963. }
  2964. /// <summary>
  2965. /// All the names of the param in the bag, use Get to yank them out
  2966. /// </summary>
  2967. public IEnumerable<string> ParameterNames
  2968. {
  2969. get
  2970. {
  2971. return parameters.Select(p => p.Key);
  2972. }
  2973. }
  2974. /// <summary>
  2975. /// Get the value of a parameter
  2976. /// </summary>
  2977. /// <typeparam name="T"></typeparam>
  2978. /// <param name="name"></param>
  2979. /// <returns>The value, note DBNull.Value is not returned, instead the value is returned as null</returns>
  2980. public T Get<T>(string name)
  2981. {
  2982. var val = parameters[Clean(name)].AttachedParam.Value;
  2983. if (val == DBNull.Value)
  2984. {
  2985. if (default(T) != null)
  2986. {
  2987. throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
  2988. }
  2989. return default(T);
  2990. }
  2991. return (T)val;
  2992. }
  2993. }
  2994. /// <summary>
  2995. /// This class represents a SQL string, it can be used if you need to denote your parameter is a Char vs VarChar vs nVarChar vs nChar
  2996. /// </summary>
  2997. sealed partial class DbString : Dapper.SqlMapper.ICustomQueryParameter
  2998. {
  2999. /// <summary>
  3000. /// Create a new DbString
  3001. /// </summary>
  3002. public DbString() { Length = -1; }
  3003. /// <summary>
  3004. /// Ansi vs Unicode
  3005. /// </summary>
  3006. public bool IsAnsi { get; set; }
  3007. /// <summary>
  3008. /// Fixed length
  3009. /// </summary>
  3010. public bool IsFixedLength { get; set; }
  3011. /// <summary>
  3012. /// Length of the string -1 for max
  3013. /// </summary>
  3014. public int Length { get; set; }
  3015. /// <summary>
  3016. /// The value of the string
  3017. /// </summary>
  3018. public string Value { get; set; }
  3019. /// <summary>
  3020. /// Add the parameter to the command... internal use only
  3021. /// </summary>
  3022. /// <param name="command"></param>
  3023. /// <param name="name"></param>
  3024. public void AddParameter(IDbCommand command, string name)
  3025. {
  3026. if (IsFixedLength && Length == -1)
  3027. {
  3028. throw new InvalidOperationException("If specifying IsFixedLength,  a Length must also be specified");
  3029. }
  3030. var param = command.CreateParameter();
  3031. param.ParameterName = name;
  3032. param.Value = (object)Value ?? DBNull.Value;
  3033. if (Length == -1 && Value != null && Value.Length <= 4000)
  3034. {
  3035. param.Size = 4000;
  3036. }
  3037. else
  3038. {
  3039. param.Size = Length;
  3040. }
  3041. param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String);
  3042. command.Parameters.Add(param);
  3043. }
  3044. }
  3045. /// <summary>
  3046. /// Handles variances in features per DBMS
  3047. /// </summary>
  3048. partial class FeatureSupport
  3049. {
  3050. /// <summary>
  3051. /// Dictionary of supported features index by connection type name
  3052. /// </summary>
  3053. private static readonly Dictionary<string, FeatureSupport> FeatureList = new Dictionary<string, FeatureSupport>(StringComparer.InvariantCultureIgnoreCase) {
  3054. {"sqlserverconnection", new FeatureSupport { Arrays = false}},
  3055. {"npgsqlconnection", new FeatureSupport {Arrays = true}}
  3056. };
  3057. /// <summary>
  3058. /// Gets the featureset based on the passed connection
  3059. /// </summary>
  3060. public static FeatureSupport Get(IDbConnection connection)
  3061. {
  3062. string name = connection.GetType().Name;
  3063. FeatureSupport features;
  3064. return FeatureList.TryGetValue(name, out features) ? features : FeatureList.Values.First();
  3065. }
  3066. /// <summary>
  3067. /// True if the db supports array columns e.g. Postgresql
  3068. /// </summary>
  3069. public bool Arrays { get; set; }
  3070. }
  3071. /// <summary>
  3072. /// Represents simple memeber map for one of target parameter or property or field to source DataReader column
  3073. /// </summary>
  3074. sealed partial class SimpleMemberMap : SqlMapper.IMemberMap
  3075. {
  3076. private readonly string _columnName;
  3077. private readonly PropertyInfo _property;
  3078. private readonly FieldInfo _field;
  3079. private readonly ParameterInfo _parameter;
  3080. /// <summary>
  3081. /// Creates instance for simple property mapping
  3082. /// </summary>
  3083. /// <param name="columnName">DataReader column name</param>
  3084. /// <param name="property">Target property</param>
  3085. public SimpleMemberMap(string columnName, PropertyInfo property)
  3086. {
  3087. if (columnName == null)
  3088. throw new ArgumentNullException("columnName");
  3089. if (property == null)
  3090. throw new ArgumentNullException("property");
  3091. _columnName = columnName;
  3092. _property = property;
  3093. }
  3094. /// <summary>
  3095. /// Creates instance for simple field mapping
  3096. /// </summary>
  3097. /// <param name="columnName">DataReader column name</param>
  3098. /// <param name="field">Target property</param>
  3099. public SimpleMemberMap(string columnName, FieldInfo field)
  3100. {
  3101. if (columnName == null)
  3102. throw new ArgumentNullException("columnName");
  3103. if (field == null)
  3104. throw new ArgumentNullException("field");
  3105. _columnName = columnName;
  3106. _field = field;
  3107. }
  3108. /// <summary>
  3109. /// Creates instance for simple constructor parameter mapping
  3110. /// </summary>
  3111. /// <param name="columnName">DataReader column name</param>
  3112. /// <param name="parameter">Target constructor parameter</param>
  3113. public SimpleMemberMap(string columnName, ParameterInfo parameter)
  3114. {
  3115. if (columnName == null)
  3116. throw new ArgumentNullException("columnName");
  3117. if (parameter == null)
  3118. throw new ArgumentNullException("parameter");
  3119. _columnName = columnName;
  3120. _parameter = parameter;
  3121. }
  3122. /// <summary>
  3123. /// DataReader column name
  3124. /// </summary>
  3125. public string ColumnName
  3126. {
  3127. get { return _columnName; }
  3128. }
  3129. /// <summary>
  3130. /// Target member type
  3131. /// </summary>
  3132. public Type MemberType
  3133. {
  3134. get
  3135. {
  3136. if (_field != null)
  3137. return _field.FieldType;
  3138. if (_property != null)
  3139. return _property.PropertyType;
  3140. if (_parameter != null)
  3141. return _parameter.ParameterType;
  3142. return null;
  3143. }
  3144. }
  3145. /// <summary>
  3146. /// Target property
  3147. /// </summary>
  3148. public PropertyInfo Property
  3149. {
  3150. get { return _property; }
  3151. }
  3152. /// <summary>
  3153. /// Target field
  3154. /// </summary>
  3155. public FieldInfo Field
  3156. {
  3157. get { return _field; }
  3158. }
  3159. /// <summary>
  3160. /// Target constructor parameter
  3161. /// </summary>
  3162. public ParameterInfo Parameter
  3163. {
  3164. get { return _parameter; }
  3165. }
  3166. }
  3167. /// <summary>
  3168. /// Represents default type mapping strategy used by Dapper
  3169. /// </summary>
  3170. sealed partial class DefaultTypeMap : SqlMapper.ITypeMap
  3171. {
  3172. private readonly List<FieldInfo> _fields;
  3173. private readonly List<PropertyInfo> _properties;
  3174. private readonly Type _type;
  3175. /// <summary>
  3176. /// Creates default type map
  3177. /// </summary>
  3178. /// <param name="type">Entity type</param>
  3179. public DefaultTypeMap(Type type)
  3180. {
  3181. if (type == null)
  3182. throw new ArgumentNullException("type");
  3183. _fields = GetSettableFields(type);
  3184. _properties = GetSettableProps(type);
  3185. _type = type;
  3186. }
  3187. internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type)
  3188. {
  3189. return propertyInfo.DeclaringType == type ?
  3190. propertyInfo.GetSetMethod(true) :
  3191. propertyInfo.DeclaringType.GetProperty(propertyInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).GetSetMethod(true);
  3192. }
  3193. internal static List<PropertyInfo> GetSettableProps(Type t)
  3194. {
  3195. return t
  3196. .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  3197. .Where(p => GetPropertySetter(p, t) != null)
  3198. .ToList();
  3199. }
  3200. internal static List<FieldInfo> GetSettableFields(Type t)
  3201. {
  3202. return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
  3203. }
  3204. /// <summary>
  3205. /// Finds best constructor
  3206. /// </summary>
  3207. /// <param name="names">DataReader column names</param>
  3208. /// <param name="types">DataReader column types</param>
  3209. /// <returns>Matching constructor or default one</returns>
  3210. public ConstructorInfo FindConstructor(string[] names, Type[] types)
  3211. {
  3212. var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
  3213. foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length))
  3214. {
  3215. ParameterInfo[] ctorParameters = ctor.GetParameters();
  3216. if (ctorParameters.Length == 0)
  3217. return ctor;
  3218. if (ctorParameters.Length != types.Length)
  3219. continue;
  3220. int i = 0;
  3221. for (; i < ctorParameters.Length; i++)
  3222. {
  3223. if (!String.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase))
  3224. break;
  3225. if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary)
  3226. continue;
  3227. var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType;
  3228. if (unboxedType != types[i]
  3229. && !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i])
  3230. && !(unboxedType == typeof(char) && types[i] == typeof(string)))
  3231. break;
  3232. }
  3233. if (i == ctorParameters.Length)
  3234. return ctor;
  3235. }
  3236. return null;
  3237. }
  3238. /// <summary>
  3239. /// Gets mapping for constructor parameter
  3240. /// </summary>
  3241. /// <param name="constructor">Constructor to resolve</param>
  3242. /// <param name="columnName">DataReader column name</param>
  3243. /// <returns>Mapping implementation</returns>
  3244. public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
  3245. {
  3246. var parameters = constructor.GetParameters();
  3247. return new SimpleMemberMap(columnName, parameters.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)));
  3248. }
  3249. /// <summary>
  3250. /// Gets member mapping for column
  3251. /// </summary>
  3252. /// <param name="columnName">DataReader column name</param>
  3253. /// <returns>Mapping implementation</returns>
  3254. public SqlMapper.IMemberMap GetMember(string columnName)
  3255. {
  3256. var property = _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
  3257. ?? _properties.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
  3258. if (property != null)
  3259. return new SimpleMemberMap(columnName, property);
  3260. var field = _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.Ordinal))
  3261. ?? _fields.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase));
  3262. if (field != null)
  3263. return new SimpleMemberMap(columnName, field);
  3264. return null;
  3265. }
  3266. }
  3267. /// <summary>
  3268. /// Implements custom property mapping by user provided criteria (usually presence of some custom attribute with column to member mapping)
  3269. /// </summary>
  3270. sealed partial class CustomPropertyTypeMap : SqlMapper.ITypeMap
  3271. {
  3272. private readonly Type _type;
  3273. private readonly Func<Type, string, PropertyInfo> _propertySelector;
  3274. /// <summary>
  3275. /// Creates custom property mapping
  3276. /// </summary>
  3277. /// <param name="type">Target entity type</param>
  3278. /// <param name="propertySelector">Property selector based on target type and DataReader column name</param>
  3279. public CustomPropertyTypeMap(Type type, Func<Type, string, PropertyInfo> propertySelector)
  3280. {
  3281. if (type == null)
  3282. throw new ArgumentNullException("type");
  3283. if (propertySelector == null)
  3284. throw new ArgumentNullException("propertySelector");
  3285. _type = type;
  3286. _propertySelector = propertySelector;
  3287. }
  3288. /// <summary>
  3289. /// Always returns default constructor
  3290. /// </summary>
  3291. /// <param name="names">DataReader column names</param>
  3292. /// <param name="types">DataReader column types</param>
  3293. /// <returns>Default constructor</returns>
  3294. public ConstructorInfo FindConstructor(string[] names, Type[] types)
  3295. {
  3296. return _type.GetConstructor(new Type[0]);
  3297. }
  3298. /// <summary>
  3299. /// Not impelmeneted as far as default constructor used for all cases
  3300. /// </summary>
  3301. /// <param name="constructor"></param>
  3302. /// <param name="columnName"></param>
  3303. /// <returns></returns>
  3304. public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName)
  3305. {
  3306. throw new NotSupportedException();
  3307. }
  3308. /// <summary>
  3309. /// Returns property based on selector strategy
  3310. /// </summary>
  3311. /// <param name="columnName">DataReader column name</param>
  3312. /// <returns>Poperty member map</returns>
  3313. public SqlMapper.IMemberMap GetMember(string columnName)
  3314. {
  3315. var prop = _propertySelector(_type, columnName);
  3316. return prop != null ? new SimpleMemberMap(columnName, prop) : null;
  3317. }
  3318. }
  3319. // Define DAPPER_MAKE_PRIVATE if you reference Dapper by source
  3320. // and you like to make the Dapper types private (in order to avoid
  3321. // conflicts with other projects that also reference Dapper by source)
  3322. #if !DAPPER_MAKE_PRIVATE
  3323. public partial class SqlMapper
  3324. {
  3325. }
  3326. public partial class DynamicParameters
  3327. {
  3328. }
  3329. public partial class DbString
  3330. {
  3331. }
  3332. public partial class SimpleMemberMap
  3333. {
  3334. }
  3335. public partial class DefaultTypeMap
  3336. {
  3337. }
  3338. public partial class CustomPropertyTypeMap
  3339. {
  3340. }
  3341. public partial class FeatureSupport
  3342. {
  3343. }
  3344. #endif
  3345. }

Nunits测试代码

1、对数据操作部分的测试

[csharp] view plaincopy
  1. using System;
  2. using System.Linq;
  3. using NUnit.Framework;
  4. using System.Data.SqlClient;
  5. using Dapper;
  6. using System.Collections;
  7. using System.Collections.Generic;
  8. namespace DapperTest.DapperExtensions
  9. {
  10. [Serializable]
  11. public class AutoCreate
  12. {
  13. public int ID { get; set; }
  14. public string Name { get; set; }
  15. }
  16. [TestFixture]
  17. public class DataOperateTest
  18. {
  19. private static string connectionStr = @"Server=W-PC\DEMO; Database=ByteartRetail; Integrated Security=True; MultipleActiveResultSets=True;";
  20. SqlConnection conn;
  21. [TestFixtureSetUp]
  22. public void InitSqlConnection()
  23. {
  24. DapperPocoInfo dpi = typeof(AutoCreate).GetPocoInfo();
  25. dpi.AddStringColumnMap("Name");
  26. dpi.TableName = "Tbl_AutoCreate";
  27. conn = new SqlConnection(connectionStr);
  28. conn.Open();
  29. }
  30. [TestFixtureTearDown]
  31. public void CloseConnection()
  32. {
  33. conn.Close();
  34. }
  35. [Test]
  36. public void IUQDTypedTest()
  37. {
  38. AutoCreate poco = new AutoCreate();
  39. poco.Name = DateTime.Now.ToString("yyMMddHHmmssfff");
  40. bool success = conn.Insert<AutoCreate>(poco);
  41. Assert.AreEqual(success, true);
  42. Assert.AreNotEqual(0, poco.ID);
  43. poco.Name = "UU" + DateTime.Now.ToString("yyMMddHHmmssfff");
  44. success = conn.Update<AutoCreate>(poco);
  45. Assert.AreEqual(success, true);
  46. AutoCreate pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = poco.ID });
  47. Assert.AreEqual(poco.Name, pocoQuery.Name);
  48. success = conn.DeleteByKey<AutoCreate>(new { ID = poco.ID });
  49. Assert.AreEqual(true, success);
  50. pocoQuery = conn.QueryByKey<AutoCreate>(poco);
  51. Assert.IsNull(pocoQuery);
  52. }
  53. [Test]
  54. public void IUQDDynamicTest()
  55. {
  56. //AutoCreate tmp = new AutoCreate();
  57. string name = DateTime.Now.ToString("yyMMddHHmmssfff");
  58. var poco = new { ID = 0, Name = name };
  59. MsSqlServerAdapter adapter = (MsSqlServerAdapter)conn.GetSqlAdapter();
  60. bool success = adapter.Insert<AutoCreate>(conn, poco);
  61. Assert.AreEqual(success, true);
  62. //Assert.AreNotEqual(0, poco.ID);
  63. int id;
  64. //id = adapter.GetLastInsertID<AutoCreate>(conn);
  65. id = (int)conn.GetLastInsertIndentityID<AutoCreate>();
  66. Assert.Greater(id, 0);
  67. AutoCreate pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = id });
  68. Assert.AreEqual(poco.Name, pocoQuery.Name);//测试插入的数据是否正确
  69. name = "UU" + DateTime.Now.ToString("yyMMddHHmmssfff");
  70. //success = adapter.UpdateByKey<AutoCreate>(conn, new { ID = id, Name = name });
  71. success = conn.Update<AutoCreate>(new { ID = id, Name = name });
  72. Assert.AreEqual(success, true);
  73. pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = id });
  74. Assert.AreEqual(name, pocoQuery.Name);//测试插入的数据是否正确
  75. success = conn.DeleteByKey<AutoCreate>(new { ID = id });
  76. Assert.AreEqual(true, success);
  77. pocoQuery = conn.QueryByKey<AutoCreate>(new { ID = id });
  78. Assert.IsNull(pocoQuery);
  79. }
  80. }
  81. }

2、对POCO扩展部分的测试

[csharp] view plaincopy
  1. using System;
  2. namespace DapperTest
  3. {
  4. [Serializable]
  5. public class TestEndWithIDModel
  6. {
  7. public byte ID { get; set; }
  8. public string Name { get; set; }
  9. }
  10. [Serializable]
  11. public class TestNotEndWithIDModel
  12. {
  13. public int OrderNum { get; set; }
  14. public string OrderName { get; set; }
  15. }
  16. }

对ID结尾的主键测试

[csharp] view plaincopy
  1. using System.Linq;
  2. using NUnit.Framework;
  3. using Dapper;
  4. namespace DapperTest.PocoInfo
  5. {
  6. [TestFixture]
  7. public class PocoInfoEndWithIDKeyTest
  8. {
  9. DapperPocoInfo dpi;
  10. [SetUp]
  11. public void GetDapperPocoInfo()
  12. {
  13. dpi = typeof(TestEndWithIDModel).GetPocoInfo();
  14. //dpi.AddKeyMap("ID");//此行代码注销以下测试也是正确的
  15. }
  16. [Test]
  17. public void TestModelKeyOnlyOneTest()
  18. {
  19. Assert.AreEqual(1, dpi.KeyProperties.Count());
  20. Assert.AreEqual("ID", dpi.KeyProperties.First().Name);
  21. Assert.AreEqual(true, dpi.IsUnWriteKey());
  22. }
  23. }
  24. }

非ID结尾的主键测试

[csharp] view plaincopy
  1. using System.Linq;
  2. using NUnit.Framework;
  3. using Dapper;
  4. namespace DapperTest.PocoInfo
  5. {
  6. [TestFixture]
  7. public class PocoInfoNotEndWithIDKeyTest
  8. {
  9. DapperPocoInfo dpi;
  10. [SetUp]
  11. public void GetDapperPocoInfo()
  12. {
  13. dpi = typeof(TestNotEndWithIDModel).GetPocoInfo();
  14. dpi.AddKeyMap("OrderNum");
  15. }
  16. [Test]
  17. public void TestModelKeyOnlyOneTest()
  18. {
  19. Assert.AreEqual(1, dpi.KeyProperties.Count());
  20. Assert.AreEqual("OrderNum", dpi.KeyProperties.First().Name);
  21. Assert.AreEqual(true, dpi.IsUnWriteKey());
  22. }
  23. }
  24. }

字符串映射测试

[csharp] view plaincopy
    1. using System.Collections.Generic;
    2. using NUnit.Framework;
    3. using Dapper;
    4. using System.Data;
    5. namespace DapperTest.PocoInfo
    6. {
    7. [TestFixture]
    8. public class PocoInfoStringColumnMapTest
    9. {
    10. KeyValuePair<DbType, int>? kvp;
    11. [SetUp]
    12. public void GetBasicInfo()
    13. {
    14. DapperPocoInfo dpi = typeof(TestEndWithIDModel).GetPocoInfo();
    15. dpi.AddStringColumnMap("Name");
    16. kvp = dpi.GetStringColumnMap("Name");
    17. }
    18. [Test]
    19. public void TestModelMappedStringColumnMapTest()
    20. {
    21. Assert.NotNull(kvp);
    22. Assert.AreEqual(DbType.AnsiString, kvp.Value.Key);
    23. Assert.AreEqual(50, kvp.Value.Value);
    24. }
    25. }
    26. }

Dapper的完整扩展(转)的更多相关文章

  1. 为EasySharding.EFCore提供Dapper相关查询扩展

    承接上一篇博文中的中间件基本都是写入性的操作,但对于查询操作实际上是比较鸡肋的,如果单纯的查询,没有分表的情况下基本还能适应,这里为了Dapper提供了扩展 Dapper的扩展查询是需要写表名称的,所 ...

  2. 开源Dapper的Lambda扩展-Sikiro.Dapper.Extension V2.0

    前言 去年我在业余时间,自己整了一套dapper的lambda表达式的封装,原本是作为了一个个人的娱乐项目,当时也只支持了Sql Server数据库.随之开源后,有不少朋友也对此做了试用,也对我这个项 ...

  3. Dapper链接查询扩展

    一对多映射关系 /// <summary> /// 一对多连接查询 /// </summary> /// <typeparam name="FirstT&quo ...

  4. 编写自己的dapper lambda扩展-使用篇

    前言 这是针对dapper的一个扩展,支持lambda表达式的写法,链式风格让开发者使用起来更加优雅.直观.现在暂时只有MsSql的扩展,也没有实现事务的写法,将会在后续的版本补充. 这是个人业余的开 ...

  5. 【转】.NET(C#):浅谈程序集清单资源和RESX资源 关于单元测试的思考--Asp.Net Core单元测试最佳实践 封装自己的dapper lambda扩展-设计篇 编写自己的dapper lambda扩展-使用篇 正确理解CAP定理 Quartz.NET的使用(附源码) 整理自己的.net工具库 GC的前世与今生 Visual Studio Package 插件开发之自动生

    [转].NET(C#):浅谈程序集清单资源和RESX资源   目录 程序集清单资源 RESX资源文件 使用ResourceReader和ResourceSet解析二进制资源文件 使用ResourceM ...

  6. 基于Dapper的开源Lambda扩展LnskyDB 2.0已支持多表查询

    LnskyDB LnskyDB是基于Dapper的Lambda扩展,支持按时间分库分表,也可以自定义分库分表方法.而且可以T4生成实体类免去手写实体类的烦恼. 文档地址: https://lining ...

  7. 基于Dapper的开源Lambda扩展,且支持分库分表自动生成实体之基础介绍

    LnskyDB LnskyDB是基于Dapper的Lambda扩展,支持按时间分库分表,也可以自定义分库分表方法.而且可以T4生成实体类免去手写实体类的烦恼. 文档地址: https://lining ...

  8. 基于Dapper的开源LINQ扩展,且支持分库分表自动生成实体二

    LnskyDB LnskyDB是基于Dapper的Lambda扩展,支持按时间分库分表,也可以自定义分库分表方法.而且可以T4生成实体类免去手写实体类的烦恼. 文档地址: https://lining ...

  9. 基于Dapper的开源Lambda扩展LnskyDB 3.0已支持Mysql数据库

    LnskyDB LnskyDB是基于Dapper的Lambda扩展,支持按时间分库分表,也可以自定义分库分表方法.而且可以T4生成实体类免去手写实体类的烦恼.,现在已经支持MySql和Sql serv ...

随机推荐

  1. QTableView带可编辑进度条

    main文件与上一个例子完全一致,也使用QStandardItemModel,关键是有这句:QStandardItem.setEditable(false);  继承QAbstractItemDele ...

  2. MyBatis学习总结_01_MyBatis快速入门

    一.Mybatis介绍 MyBatis是一个支持普通SQL查询,存储过程和高级映射的优秀持久层框架.MyBatis消除了几乎所有的JDBC代码和参数的手工设置以及对结果集的检索封装.MyBatis可以 ...

  3. 转 RMI、RPC、SOAP通信技术介绍及比对

    http://www.open-open.com/home/space.php?uid=37924&do=blog&id=8974 1.RMI 使用java的程序员,对于RMI(Rem ...

  4. C语言的几个有趣问题

    问题1. 不能使用分号,编写一个“Hello World”程序. 问题2. 如何用C语言打印“ I am print %”? 问题3. 不能使用">.<.>=.<=“ ...

  5. Android之开发杂记(三)

    一.popup 弹出框 在onCreate中创建时异常 Unable to add window -- token null is not valid; is your activity runnin ...

  6. dojo 九 effects dojo/_base/fx 和 dojo/fx

    官方教程:Dojo Effects这里讲学习一下dojo如何实现淡入.淡出.滑动等效果.实现这些特殊的效果有两个包 dojo/_base/fx 和 dojo/fx.dojo/_base/fx 中提供了 ...

  7. 如何实现上下左右键盘控制焦点使之落在相邻文本框或下拉框中-Web开发/JavaScript

    我用jquery只实现了文本框的移动(暂时上下移动等同于左右移动) $(function () { var cols = 1;//按一下跳几个控件 var obj = $("input[id ...

  8. django 外键 ,django __

    data sqlite> select * from author; id name age 1 jim 12 2 tom 11 sqlite> select * from book; i ...

  9. mvp(2)一个简单示例,加深理解

    参考: http://www.cnblogs.com/liuling/p/mvp-pattern-android.html 架构图: 1.View层 public interface NewsView ...

  10. android:descendantFocusability的作用:viewgroup与其上面view的焦点控制,如何让子view失去焦点等。

    ViewGroup的下面这个属性可以控制. 原文: android:descendantFocusability Defines the relationship between the ViewGr ...