电子药箱通讯服务端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SqlMapper.cs 70KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542
  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.ComponentModel;
  5. using System.Data;
  6. using System.Linq;
  7. using System.Reflection;
  8. using System.Reflection.Emit;
  9. using System.Text;
  10. using System.Text.RegularExpressions;
  11. using System.Threading;
  12. using System.Threading.Tasks;
  13. namespace RDH.Data
  14. {
  15. public static partial class SqlMapper
  16. {
  17. private static Dictionary<Type, TypeMapperBase> customTypeMaps = new Dictionary<Type, TypeMapperBase>();
  18. /// <summary>
  19. ///
  20. /// </summary>
  21. /// <typeparam name="T">mapToType</typeparam>
  22. /// <param name="fromType"></param>
  23. /// <param name="mapDeMapHander"> </param>
  24. public static void RegisterMapperProvider<T>(DbType fromType, Func<object, T> mapDeMapHander)
  25. {
  26. var mapTo = typeof(T);
  27. if (typeMap.ContainsKey(mapTo))
  28. {
  29. typeMap[mapTo] = fromType;
  30. }
  31. else
  32. {
  33. typeMap.Add(mapTo, fromType);
  34. }
  35. customTypeMaps.Add(mapTo, new TypeMapper<T>
  36. {
  37. DbType = fromType,
  38. MethodName = mapDeMapHander.Method.Name,
  39. DeMapHanderR = mapDeMapHander,
  40. ProviderType = mapDeMapHander.Method.ReflectedType
  41. });
  42. }
  43. static Link<Type, Action<IDbCommand, bool>> bindByNameCache;
  44. static Action<IDbCommand, bool> GetBindByName(Type commandType)
  45. {
  46. if (commandType == null) return null; // GIGO
  47. Action<IDbCommand, bool> action;
  48. if (Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action))
  49. {
  50. return action;
  51. }
  52. var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance);
  53. action = null;
  54. ParameterInfo[] indexers;
  55. MethodInfo setter;
  56. if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool)
  57. && ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0)
  58. && (setter = prop.GetSetMethod()) != null
  59. )
  60. {
  61. var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) });
  62. var il = method.GetILGenerator();
  63. il.Emit(OpCodes.Ldarg_0);
  64. il.Emit(OpCodes.Castclass, commandType);
  65. il.Emit(OpCodes.Ldarg_1);
  66. il.EmitCall(OpCodes.Callvirt, setter, null);
  67. il.Emit(OpCodes.Ret);
  68. action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>));
  69. }
  70. // cache it
  71. Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action);
  72. return action;
  73. }
  74. /// <summary>
  75. /// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example),
  76. /// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE**
  77. /// equality. The type is fully thread-safe.
  78. /// </summary>
  79. class Link<TKey, TValue> where TKey : class
  80. {
  81. public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value)
  82. {
  83. while (link != null)
  84. {
  85. if ((object)key == (object)link.Key)
  86. {
  87. value = link.Value;
  88. return true;
  89. }
  90. link = link.Tail;
  91. }
  92. value = default(TValue);
  93. return false;
  94. }
  95. public static bool TryAdd(ref Link<TKey, TValue> head, TKey key, ref TValue value)
  96. {
  97. bool tryAgain;
  98. do
  99. {
  100. var snapshot = Interlocked.CompareExchange(ref head, null, null);
  101. TValue found;
  102. if (TryGet(snapshot, key, out found))
  103. { // existing match; report the existing value instead
  104. value = found;
  105. return false;
  106. }
  107. var newNode = new Link<TKey, TValue>(key, value, snapshot);
  108. // did somebody move our cheese?
  109. tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot;
  110. } while (tryAgain);
  111. return true;
  112. }
  113. private Link(TKey key, TValue value, Link<TKey, TValue> tail)
  114. {
  115. Key = key;
  116. Value = value;
  117. Tail = tail;
  118. }
  119. public TKey Key { get; private set; }
  120. public TValue Value { get; private set; }
  121. public Link<TKey, TValue> Tail { get; private set; }
  122. }
  123. public static event EventHandler QueryCachePurged;
  124. private static void OnQueryCachePurged()
  125. {
  126. var handler = QueryCachePurged;
  127. if (handler != null) handler(null, EventArgs.Empty);
  128. }
  129. #if CSHARP30
  130. private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>();
  131. // note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of
  132. // ReaderWriterLockSlim etc; a simple lock is faster
  133. private static void SetQueryCache(Identity key, CacheInfo value)
  134. {
  135. lock (_queryCache) { _queryCache[key] = value; }
  136. }
  137. private static bool TryGetQueryCache(Identity key, out CacheInfo value)
  138. {
  139. lock (_queryCache) { return _queryCache.TryGetValue(key, out value); }
  140. }
  141. public static void PurgeQueryCache()
  142. {
  143. lock (_queryCache)
  144. {
  145. _queryCache.Clear();
  146. }
  147. OnQueryCachePurged();
  148. }
  149. #else
  150. static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>();
  151. private static void SetQueryCache(Identity key, CacheInfo value)
  152. {
  153. if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS)
  154. {
  155. CollectCacheGarbage();
  156. }
  157. _queryCache[key] = value;
  158. }
  159. private static void CollectCacheGarbage()
  160. {
  161. try
  162. {
  163. foreach (var pair in _queryCache)
  164. {
  165. if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN)
  166. {
  167. CacheInfo cache;
  168. _queryCache.TryRemove(pair.Key, out cache);
  169. }
  170. }
  171. }
  172. finally
  173. {
  174. Interlocked.Exchange(ref collect, 0);
  175. }
  176. }
  177. private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0;
  178. private static int collect;
  179. private static bool TryGetQueryCache(Identity key, out CacheInfo value)
  180. {
  181. if (_queryCache.TryGetValue(key, out value))
  182. {
  183. value.RecordHit();
  184. return true;
  185. }
  186. value = null;
  187. return false;
  188. }
  189. public static void PurgeQueryCache()
  190. {
  191. _queryCache.Clear();
  192. OnQueryCachePurged();
  193. }
  194. public static int GetCachedSQLCount()
  195. {
  196. return _queryCache.Count;
  197. }
  198. public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue)
  199. {
  200. var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount()));
  201. if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove);
  202. return data;
  203. }
  204. public static IEnumerable<Tuple<int, int>> GetHashCollissions()
  205. {
  206. var counts = new Dictionary<int, int>();
  207. foreach (var key in _queryCache.Keys)
  208. {
  209. int count;
  210. if (!counts.TryGetValue(key.hashCode, out count))
  211. {
  212. counts.Add(key.hashCode, 1);
  213. }
  214. else
  215. {
  216. counts[key.hashCode] = count + 1;
  217. }
  218. }
  219. return from pair in counts
  220. where pair.Value > 1
  221. select Tuple.Create(pair.Key, pair.Value);
  222. }
  223. #endif
  224. static readonly Dictionary<Type, DbType> typeMap;
  225. static SqlMapper()
  226. {
  227. typeMap = new Dictionary<Type, DbType>();
  228. typeMap[typeof(byte)] = DbType.Byte;
  229. typeMap[typeof(sbyte)] = DbType.SByte;
  230. typeMap[typeof(short)] = DbType.Int16;
  231. typeMap[typeof(ushort)] = DbType.UInt16;
  232. typeMap[typeof(int)] = DbType.Int32;
  233. typeMap[typeof(uint)] = DbType.UInt32;
  234. typeMap[typeof(long)] = DbType.Int64;
  235. typeMap[typeof(ulong)] = DbType.UInt64;
  236. typeMap[typeof(float)] = DbType.Single;
  237. typeMap[typeof(double)] = DbType.Double;
  238. typeMap[typeof(decimal)] = DbType.Decimal;
  239. typeMap[typeof(bool)] = DbType.Boolean;
  240. typeMap[typeof(string)] = DbType.String;
  241. typeMap[typeof(char)] = DbType.StringFixedLength;
  242. typeMap[typeof(Guid)] = DbType.String;
  243. typeMap[typeof(DateTime)] = DbType.DateTime;
  244. typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset;
  245. typeMap[typeof(byte[])] = DbType.Binary;
  246. typeMap[typeof(byte?)] = DbType.Byte;
  247. typeMap[typeof(sbyte?)] = DbType.SByte;
  248. typeMap[typeof(short?)] = DbType.Int16;
  249. typeMap[typeof(ushort?)] = DbType.UInt16;
  250. typeMap[typeof(int?)] = DbType.Int32;
  251. typeMap[typeof(uint?)] = DbType.UInt32;
  252. typeMap[typeof(long?)] = DbType.Int64;
  253. typeMap[typeof(ulong?)] = DbType.UInt64;
  254. typeMap[typeof(float?)] = DbType.Single;
  255. typeMap[typeof(double?)] = DbType.Double;
  256. typeMap[typeof(decimal?)] = DbType.Decimal;
  257. typeMap[typeof(bool?)] = DbType.Boolean;
  258. typeMap[typeof(char?)] = DbType.StringFixedLength;
  259. typeMap[typeof(Guid?)] = DbType.Guid;
  260. typeMap[typeof(DateTime?)] = DbType.DateTime;
  261. typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset;
  262. //typeMap[typeof(System.Data.Linq.Binary)] = DbType.Binary;
  263. //by zxb reg map types types
  264. SqlMapper.RegisterMapperProvider<bool>(System.Data.DbType.AnsiStringFixedLength, TypeMapProvider.ReadBoolean);
  265. SqlMapper.RegisterMapperProvider<bool?>(System.Data.DbType.AnsiStringFixedLength, TypeMapProvider.ReadNullableBoolean);
  266. SqlMapper.RegisterMapperProvider<char>(System.Data.DbType.StringFixedLength, TypeMapProvider.ReadChar);
  267. SqlMapper.RegisterMapperProvider<char?>(System.Data.DbType.StringFixedLength, TypeMapProvider.ReadNullableChar);
  268. SqlMapper.RegisterMapperProvider<Guid>(System.Data.DbType.String, TypeMapProvider.ReadGuid);
  269. SqlMapper.RegisterMapperProvider<Guid?>(System.Data.DbType.String, TypeMapProvider.ReadNullableGuid);
  270. SqlMapper.RegisterMapperProvider<decimal>(System.Data.DbType.Decimal, TypeMapProvider.ReadDecimal);
  271. SqlMapper.RegisterMapperProvider<decimal?>(System.Data.DbType.Decimal, TypeMapProvider.ReadNullableDecimal);
  272. SqlMapper.RegisterMapperProvider<int>(System.Data.DbType.Decimal, TypeMapProvider.ReadInt);
  273. SqlMapper.RegisterMapperProvider<int?>(System.Data.DbType.Decimal, TypeMapProvider.ReadNullableInt);
  274. SqlMapper.RegisterMapperProvider<double>(System.Data.DbType.Double, TypeMapProvider.ReadDouble);
  275. SqlMapper.RegisterMapperProvider<double?>(System.Data.DbType.Double, TypeMapProvider.ReadNullableDouble);
  276. }
  277. private static DbType LookupDbType(Type type, string name)
  278. {
  279. DbType dbType;
  280. var nullUnderlyingType = Nullable.GetUnderlyingType(type);
  281. if (nullUnderlyingType != null) type = nullUnderlyingType;
  282. if (type.IsEnum)
  283. {
  284. type = Enum.GetUnderlyingType(type);
  285. }
  286. if (typeMap.TryGetValue(type, out dbType))
  287. {
  288. return dbType;
  289. }
  290. if (typeof(IEnumerable).IsAssignableFrom(type))
  291. {
  292. // use xml to denote its a list, hacky but will work on any DB
  293. return DbType.Xml;
  294. }
  295. throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type));
  296. }
  297. #if CSHARP30
  298. /// <summary>
  299. /// Execute parameterized SQL
  300. /// </summary>
  301. /// <returns>Number of rows affected</returns>
  302. public static int Execute(this IDbConnection cnn, string sql, object param)
  303. {
  304. return Execute(cnn, sql, param, null, null, null);
  305. }
  306. /// <summary>
  307. /// Executes a query, returning the data typed as per T
  308. /// </summary>
  309. /// <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>
  310. /// <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
  311. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  312. /// </returns>
  313. public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param)
  314. {
  315. return Query<T>(cnn, sql, param, null, true, null, null);
  316. }
  317. #endif
  318. /// <summary>
  319. /// Execute parameterized SQL
  320. /// </summary>
  321. /// <returns>Number of rows affected</returns>
  322. public static int Execute(
  323. #if CSHARP30
  324. this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
  325. #else
  326. this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
  327. #endif
  328. )
  329. {
  330. IEnumerable multiExec = (object)param as IEnumerable;
  331. Identity identity;
  332. CacheInfo info = null;
  333. if (multiExec != null && !(multiExec is string))
  334. {
  335. bool isFirst = true;
  336. int total = 0;
  337. using (var cmd = SetupCommand(cnn, transaction, sql, null, null, commandTimeout, commandType))
  338. {
  339. string masterSql = null;
  340. foreach (var obj in multiExec)
  341. {
  342. if (isFirst)
  343. {
  344. masterSql = cmd.CommandText;
  345. isFirst = false;
  346. identity = new Identity(sql, cmd.CommandType, cnn, null, obj.GetType(), null);
  347. info = GetCacheInfo(identity);
  348. }
  349. else
  350. {
  351. cmd.CommandText = masterSql; // because we do magic replaces on "in" etc
  352. cmd.Parameters.Clear(); // current code is Add-tastic
  353. }
  354. info.ParamReader(cmd, obj);
  355. total += cmd.ExecuteNonQuery();
  356. }
  357. }
  358. return total;
  359. }
  360. // nice and simple
  361. identity = new Identity(sql, commandType, cnn, null, (object)param == null ? null : ((object)param).GetType(), null);
  362. info = GetCacheInfo(identity);
  363. Int32 i = ExecuteCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType);
  364. return i;
  365. }
  366. #if !CSHARP30
  367. /// <summary>
  368. /// Return a list of dynamic objects, reader is closed after the call
  369. /// </summary>
  370. 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)
  371. {
  372. return Query<FastExpando>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
  373. }
  374. #endif
  375. /// <summary>
  376. /// Executes a query, returning the data typed as per T
  377. /// </summary>
  378. /// <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>
  379. /// <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
  380. /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive).
  381. /// </returns>
  382. public static IEnumerable<T> Query<T>(
  383. #if CSHARP30
  384. this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType
  385. #else
  386. this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null
  387. #endif
  388. )
  389. {
  390. var data = QueryInternal<T>(cnn, sql, param as object, transaction, commandTimeout, commandType);
  391. return buffered ? data.ToList() : data;
  392. }
  393. /// <summary>
  394. /// Execute a command that returns multiple result sets, and access each in turn
  395. /// </summary>
  396. public static GridReader QueryMultiple(
  397. #if CSHARP30
  398. this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType
  399. #else
  400. this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null
  401. #endif
  402. )
  403. {
  404. Identity identity = new Identity(sql, commandType, cnn, typeof(GridReader), (object)param == null ? null : ((object)param).GetType(), null);
  405. CacheInfo info = GetCacheInfo(identity);
  406. IDbCommand cmd = null;
  407. IDataReader reader = null;
  408. try
  409. {
  410. cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType);
  411. reader = cmd.ExecuteReader();
  412. return new GridReader(cmd, reader, identity);
  413. }
  414. catch
  415. {
  416. if (reader != null) reader.Dispose();
  417. if (cmd != null) cmd.Dispose();
  418. throw;
  419. }
  420. }
  421. /// <summary>
  422. /// Return a typed list of objects, reader is closed after the call
  423. /// </summary>
  424. private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType)
  425. {
  426. var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
  427. var info = GetCacheInfo(identity);
  428. if (cnn.State != ConnectionState.Open) { cnn.Open(); }
  429. using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType))
  430. {
  431. using (var reader = cmd.ExecuteReader())
  432. {
  433. Func<Func<IDataReader, object>> cacheDeserializer = () =>
  434. {
  435. info.Deserializer = GetDeserializer(typeof(T), reader, 0, -1, false);
  436. SetQueryCache(identity, info);
  437. return info.Deserializer;
  438. };
  439. if (info.Deserializer == null)
  440. {
  441. cacheDeserializer();
  442. }
  443. var deserializer = info.Deserializer;
  444. while (reader.Read())
  445. {
  446. object next;
  447. try
  448. {
  449. next = deserializer(reader);
  450. }
  451. catch (DataException)
  452. {
  453. // give it another shot, in case the underlying schema changed
  454. deserializer = cacheDeserializer();
  455. next = deserializer(reader);
  456. }
  457. yield return (T)next;
  458. }
  459. }
  460. }
  461. }
  462. /// <summary>
  463. /// Maps a query to objects
  464. /// </summary>
  465. /// <typeparam name="T">The return type</typeparam>
  466. /// <typeparam name="U"></typeparam>
  467. /// <param name="cnn"></param>
  468. /// <param name="sql"></param>
  469. /// <param name="map"></param>
  470. /// <param name="param"></param>
  471. /// <param name="transaction"></param>
  472. /// <param name="buffered"></param>
  473. /// <param name="splitOn">The Field we should split and read the second object from (default: id)</param>
  474. /// <param name="commandTimeout">Number of seconds before command execution timeout</param>
  475. /// <returns></returns>
  476. public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>(
  477. #if CSHARP30
  478. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  479. #else
  480. 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
  481. #endif
  482. )
  483. {
  484. return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  485. }
  486. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>(
  487. #if CSHARP30
  488. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  489. #else
  490. 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
  491. #endif
  492. )
  493. {
  494. return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  495. }
  496. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>(
  497. #if CSHARP30
  498. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  499. #else
  500. 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
  501. #endif
  502. )
  503. {
  504. return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  505. }
  506. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(
  507. #if CSHARP30
  508. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  509. #else
  510. 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
  511. #endif
  512. )
  513. {
  514. return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  515. }
  516. public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TReturn>(
  517. #if CSHARP30
  518. this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
  519. #else
  520. 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
  521. #endif
  522. )
  523. {
  524. return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  525. }
  526. #if !CSHARP30
  527. 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)
  528. {
  529. return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
  530. }
  531. #endif
  532. static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(
  533. this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType)
  534. {
  535. var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null);
  536. return buffered ? results.ToList() : results;
  537. }
  538. internal 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)
  539. {
  540. 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) });
  541. CacheInfo cinfo = GetCacheInfo(identity);
  542. IDbCommand ownedCommand = null;
  543. IDataReader ownedReader = null;
  544. try
  545. {
  546. if (reader == null)
  547. {
  548. ownedCommand = SetupCommand(cnn, transaction, sql, cinfo.ParamReader, (object)param, commandTimeout, commandType);
  549. ownedReader = ownedCommand.ExecuteReader();
  550. reader = ownedReader;
  551. }
  552. Func<IDataReader, object> deserializer = null;
  553. Func<IDataReader, object>[] otherDeserializers = null;
  554. Action cacheDeserializers = () =>
  555. {
  556. var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }, splitOn, reader);
  557. deserializer = cinfo.Deserializer = deserializers[0];
  558. otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray();
  559. SetQueryCache(identity, cinfo);
  560. };
  561. if ((deserializer = cinfo.Deserializer) == null || (otherDeserializers = cinfo.OtherDeserializers) == null)
  562. {
  563. cacheDeserializers();
  564. }
  565. Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer, otherDeserializers, map);
  566. if (mapIt != null)
  567. {
  568. while (reader.Read())
  569. {
  570. TReturn next;
  571. try
  572. {
  573. next = mapIt(reader);
  574. }
  575. catch (DataException)
  576. {
  577. cacheDeserializers();
  578. mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(deserializer, otherDeserializers, map);
  579. next = mapIt(reader);
  580. }
  581. yield return next;
  582. }
  583. }
  584. }
  585. finally
  586. {
  587. try
  588. {
  589. if (ownedReader != null)
  590. {
  591. ownedReader.Dispose();
  592. }
  593. }
  594. finally
  595. {
  596. if (ownedCommand != null)
  597. {
  598. ownedCommand.Dispose();
  599. }
  600. }
  601. }
  602. }
  603. private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TSixth, TSeventh, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map)
  604. {
  605. switch (otherDeserializers.Length)
  606. {
  607. case 1:
  608. return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));
  609. case 2:
  610. return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));
  611. case 3:
  612. return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r));
  613. #if !CSHARP30
  614. case 4:
  615. 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));
  616. #endif
  617. case 5:
  618. 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));
  619. case 6:
  620. 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));
  621. default:
  622. throw new NotSupportedException();
  623. }
  624. }
  625. private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader)
  626. {
  627. int current = 0;
  628. var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray();
  629. var splitIndex = 0;
  630. Func<Type, int> nextSplit = type =>
  631. {
  632. var currentSplit = splits[splitIndex];
  633. if (splits.Length > splitIndex + 1)
  634. {
  635. splitIndex++;
  636. }
  637. bool skipFirst = false;
  638. int startingPos = current + 1;
  639. // if our current type has the split, skip the first time you see it.
  640. if (type != typeof(Object))
  641. {
  642. var props = GetSettableProps(type);
  643. var fields = GetSettableFields(type);
  644. foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name)))
  645. {
  646. if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase))
  647. {
  648. skipFirst = true;
  649. startingPos = current;
  650. break;
  651. }
  652. }
  653. }
  654. int pos;
  655. for (pos = startingPos; pos < reader.FieldCount; pos++)
  656. {
  657. // some people like ID some id ... assuming case insensitive splits for now
  658. if (splitOn == "*")
  659. {
  660. break;
  661. }
  662. if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase))
  663. {
  664. if (skipFirst)
  665. {
  666. skipFirst = false;
  667. }
  668. else
  669. {
  670. break;
  671. }
  672. }
  673. }
  674. current = pos;
  675. return pos;
  676. };
  677. var deserializers = new List<Func<IDataReader, object>>();
  678. int split = 0;
  679. bool first = true;
  680. foreach (var type in types)
  681. {
  682. if (type != typeof(DontMap))
  683. {
  684. int next = nextSplit(type);
  685. deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first));
  686. first = false;
  687. split = next;
  688. }
  689. }
  690. return deserializers.ToArray();
  691. }
  692. internal static CacheInfo GetCacheInfo(Identity identity)
  693. {
  694. CacheInfo info;
  695. if (!TryGetQueryCache(identity, out info))
  696. {
  697. info = new CacheInfo();
  698. if (identity.parametersType != null)
  699. {
  700. if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType))
  701. {
  702. info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd, identity); };
  703. }
  704. else
  705. {
  706. info.ParamReader = CreateParamInfoGenerator(identity);
  707. }
  708. }
  709. SetQueryCache(identity, info);
  710. }
  711. return info;
  712. }
  713. internal static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
  714. {
  715. #if !CSHARP30
  716. // dynamic is passed in as Object ... by c# design
  717. if (type == typeof(object)
  718. || type == typeof(FastExpando))
  719. {
  720. return GetDynamicDeserializer(reader, startBound, length, returnNullIfFirstMissing);
  721. }
  722. #endif
  723. if (type.IsClass && type != typeof(string)
  724. && type != typeof(byte[]))
  725. {
  726. return GetClassDeserializer2(type, reader, startBound, length, returnNullIfFirstMissing);
  727. }
  728. return GetStructDeserializer(type, startBound);
  729. }
  730. #if !CSHARP30
  731. private class FastExpando : System.Dynamic.DynamicObject, IDictionary<string, object>
  732. {
  733. IDictionary<string, object> data;
  734. public static FastExpando Attach(IDictionary<string, object> data)
  735. {
  736. return new FastExpando { data = data };
  737. }
  738. public override bool TrySetMember(System.Dynamic.SetMemberBinder binder, object value)
  739. {
  740. data[binder.Name] = value;
  741. return true;
  742. }
  743. public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result)
  744. {
  745. return data.TryGetValue(binder.Name, out result);
  746. }
  747. #region IDictionary<string,object> Members
  748. void IDictionary<string, object>.Add(string key, object value)
  749. {
  750. throw new NotImplementedException();
  751. }
  752. bool IDictionary<string, object>.ContainsKey(string key)
  753. {
  754. return data.ContainsKey(key);
  755. }
  756. ICollection<string> IDictionary<string, object>.Keys
  757. {
  758. get { return data.Keys; }
  759. }
  760. bool IDictionary<string, object>.Remove(string key)
  761. {
  762. throw new NotImplementedException();
  763. }
  764. bool IDictionary<string, object>.TryGetValue(string key, out object value)
  765. {
  766. return data.TryGetValue(key, out value);
  767. }
  768. ICollection<object> IDictionary<string, object>.Values
  769. {
  770. get { return data.Values; }
  771. }
  772. object IDictionary<string, object>.this[string key]
  773. {
  774. get
  775. {
  776. return data[key];
  777. }
  778. set
  779. {
  780. throw new NotImplementedException();
  781. }
  782. }
  783. #endregion
  784. #region ICollection<KeyValuePair<string,object>> Members
  785. void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item)
  786. {
  787. throw new NotImplementedException();
  788. }
  789. void ICollection<KeyValuePair<string, object>>.Clear()
  790. {
  791. throw new NotImplementedException();
  792. }
  793. bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item)
  794. {
  795. return data.Contains(item);
  796. }
  797. void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
  798. {
  799. data.CopyTo(array, arrayIndex);
  800. }
  801. int ICollection<KeyValuePair<string, object>>.Count
  802. {
  803. get { return data.Count; }
  804. }
  805. bool ICollection<KeyValuePair<string, object>>.IsReadOnly
  806. {
  807. get { return true; }
  808. }
  809. bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item)
  810. {
  811. throw new NotImplementedException();
  812. }
  813. #endregion
  814. #region IEnumerable<KeyValuePair<string,object>> Members
  815. IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator()
  816. {
  817. return data.GetEnumerator();
  818. }
  819. #endregion
  820. #region IEnumerable Members
  821. IEnumerator IEnumerable.GetEnumerator()
  822. {
  823. return data.GetEnumerator();
  824. }
  825. #endregion
  826. }
  827. private static Func<IDataReader, object> GetDynamicDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing)
  828. {
  829. var fieldCount = reader.FieldCount;
  830. if (length == -1)
  831. {
  832. length = fieldCount - startBound;
  833. }
  834. if (fieldCount <= startBound)
  835. {
  836. throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
  837. }
  838. return
  839. r =>
  840. {
  841. IDictionary<string, object> row = new Dictionary<string, object>(length);
  842. for (var i = startBound; i < startBound + length; i++)
  843. {
  844. var tmp = r.GetValue(i);
  845. tmp = tmp == DBNull.Value ? null : tmp;
  846. row[r.GetName(i)] = tmp;
  847. if (returnNullIfFirstMissing && i == startBound && tmp == null)
  848. {
  849. return null;
  850. }
  851. }
  852. //we know this is an object so it will not box
  853. return FastExpando.Attach(row);
  854. };
  855. }
  856. #endif
  857. [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)]
  858. [Obsolete("This method is for internal usage only", true)]
  859. public static void PackListParameters(IDbCommand command, string namePrefix, object value)
  860. {
  861. // initially we tried TVP, however it performs quite poorly.
  862. // keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare
  863. var list = value as IEnumerable;
  864. var count = 0;
  865. if (list != null)
  866. {
  867. bool isString = value is IEnumerable<string>;
  868. foreach (var item in list)
  869. {
  870. count++;
  871. var listParam = command.CreateParameter();
  872. listParam.ParameterName = namePrefix + count;
  873. listParam.Value = item ?? DBNull.Value;
  874. if (isString)
  875. {
  876. listParam.Size = 4000;
  877. if (item != null && ((string)item).Length > 4000)
  878. {
  879. listParam.Size = -1;
  880. }
  881. }
  882. command.Parameters.Add(listParam);
  883. }
  884. if (count == 0)
  885. {
  886. command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), "(SELECT NULL WHERE 1 = 0)");
  887. }
  888. else
  889. {
  890. command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), match =>
  891. {
  892. var grp = match.Value;
  893. var sb = new StringBuilder("(").Append(grp).Append(1);
  894. for (int i = 2; i <= count; i++)
  895. {
  896. sb.Append(',').Append(grp).Append(i);
  897. }
  898. return sb.Append(')').ToString();
  899. });
  900. }
  901. }
  902. }
  903. private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql)
  904. {
  905. return parameters.Where(p => Regex.IsMatch(sql, "[@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline));
  906. }
  907. public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity)
  908. {
  909. Type type = identity.parametersType;
  910. bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text;
  911. var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
  912. var il = dm.GetILGenerator();
  913. il.DeclareLocal(type); // 0
  914. bool haveInt32Arg1 = false;
  915. il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
  916. il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param]
  917. il.Emit(OpCodes.Stloc_0);// stack is now empty
  918. il.Emit(OpCodes.Ldarg_0); // stack is now [command]
  919. il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters]
  920. IEnumerable<PropertyInfo> props = type.GetProperties().OrderBy(p => p.Name);
  921. if (filterParams)
  922. {
  923. props = FilterParameters(props, identity.sql);
  924. }
  925. foreach (var prop in props)
  926. {
  927. if (filterParams)
  928. {
  929. if (identity.sql.IndexOf("@" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0
  930. && identity.sql.IndexOf(":" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0)
  931. { // can't see the parameter in the text (even in a comment, etc) - burn it with fire
  932. continue;
  933. }
  934. }
  935. if (prop.PropertyType == typeof(DbString))
  936. {
  937. il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
  938. il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring]
  939. il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command]
  940. il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name]
  941. il.EmitCall(OpCodes.Callvirt, typeof(DbString).GetMethod("AddParameter"), null); // stack is now [parameters]
  942. continue;
  943. }
  944. DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
  945. if (dbType == DbType.Xml)
  946. {
  947. // this actually represents special handling for list types;
  948. il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
  949. il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name]
  950. il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
  951. il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
  952. if (prop.PropertyType.IsValueType)
  953. {
  954. il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
  955. }
  956. il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters]
  957. continue;
  958. }
  959. il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters]
  960. il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command]
  961. il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter]
  962. il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
  963. il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
  964. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
  965. il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
  966. EmitInt32(il, (int)dbType);// stack is now [parameters] [parameters] [parameter] [parameter] [db-type]
  967. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
  968. il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
  969. EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [parameters] [parameter] [parameter] [dir]
  970. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
  971. il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
  972. il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [parameters] [parameter] [parameter] [typed-param]
  973. il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [parameters] [parameter] [parameter] [typed-value]
  974. bool checkForNull = true;
  975. if (prop.PropertyType.IsValueType)
  976. {
  977. il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [parameters] [parameter] [parameter] [boxed-value]
  978. if (Nullable.GetUnderlyingType(prop.PropertyType) == null)
  979. { // struct but not Nullable<T>; boxed value cannot be null
  980. checkForNull = false;
  981. }
  982. }
  983. if (checkForNull)
  984. {
  985. if (dbType == DbType.String && !haveInt32Arg1)
  986. {
  987. il.DeclareLocal(typeof(int));
  988. haveInt32Arg1 = true;
  989. }
  990. // relative stack: [boxed value]
  991. il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
  992. Label notNull = il.DefineLabel();
  993. Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null;
  994. il.Emit(OpCodes.Brtrue_S, notNull);
  995. // relative stack [boxed value = null]
  996. il.Emit(OpCodes.Pop); // relative stack empty
  997. il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull]
  998. if (dbType == DbType.String)
  999. {
  1000. EmitInt32(il, 0);
  1001. il.Emit(OpCodes.Stloc_1);
  1002. }
  1003. if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value);
  1004. il.MarkLabel(notNull);
  1005. if (prop.PropertyType == typeof(string))
  1006. {
  1007. il.Emit(OpCodes.Dup); // [string] [string]
  1008. il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length]
  1009. EmitInt32(il, 4000); // [string] [length] [4000]
  1010. il.Emit(OpCodes.Cgt); // [string] [0 or 1]
  1011. Label isLong = il.DefineLabel(), lenDone = il.DefineLabel();
  1012. il.Emit(OpCodes.Brtrue_S, isLong);
  1013. EmitInt32(il, 4000); // [string] [4000]
  1014. il.Emit(OpCodes.Br_S, lenDone);
  1015. il.MarkLabel(isLong);
  1016. EmitInt32(il, -1); // [string] [-1]
  1017. il.MarkLabel(lenDone);
  1018. il.Emit(OpCodes.Stloc_1); // [string]
  1019. }
  1020. //if (prop.PropertyType == typeof(System.Data.Linq.Binary))
  1021. //{
  1022. // il.EmitCall(OpCodes.Callvirt, typeof(System.Data.Linq.Binary).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null);
  1023. //}
  1024. if (allDone != null) il.MarkLabel(allDone.Value);
  1025. // relative stack [boxed value or DBNull]
  1026. }
  1027. il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
  1028. if (prop.PropertyType == typeof(string))
  1029. {
  1030. var endOfSize = il.DefineLabel();
  1031. // don't set if 0
  1032. il.Emit(OpCodes.Ldloc_1); // [parameters] [parameters] [parameter] [size]
  1033. il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [parameters] [parameter]
  1034. il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
  1035. il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [parameters] [parameter] [parameter] [size]
  1036. il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
  1037. il.MarkLabel(endOfSize);
  1038. }
  1039. il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters]
  1040. il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
  1041. }
  1042. // stack is currently [command]
  1043. il.Emit(OpCodes.Pop); // stack is now empty
  1044. il.Emit(OpCodes.Ret);
  1045. return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
  1046. }
  1047. private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
  1048. {
  1049. var cmd = cnn.CreateCommand();
  1050. var bindByName = GetBindByName(cmd.GetType());
  1051. if (bindByName != null) bindByName(cmd, true);
  1052. cmd.Transaction = transaction;
  1053. cmd.CommandText = sql;
  1054. if (commandTimeout.HasValue)
  1055. cmd.CommandTimeout = commandTimeout.Value;
  1056. if (commandType.HasValue)
  1057. cmd.CommandType = commandType.Value;
  1058. if (paramReader != null)
  1059. {
  1060. paramReader(cmd, obj);
  1061. }
  1062. return cmd;
  1063. }
  1064. private static int ExecuteCommand(IDbConnection cnn, IDbTransaction tranaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType)
  1065. {
  1066. using (var cmd = SetupCommand(cnn, tranaction, sql, paramReader, obj, commandTimeout, commandType))
  1067. {
  1068. return cmd.ExecuteNonQuery();
  1069. }
  1070. }
  1071. private static Func<IDataReader, object> GetStructDeserializer(Type type, int index)
  1072. {
  1073. // 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!)
  1074. #pragma warning disable 618
  1075. if (customTypeMaps.ContainsKey(type))
  1076. {
  1077. return r => customTypeMaps[type].DeMapHander(r.GetValue(index));
  1078. }
  1079. #pragma warning restore 618
  1080. return r =>
  1081. {
  1082. var val = r.GetValue(index);
  1083. return val is DBNull ? null : val;
  1084. };
  1085. }
  1086. static readonly MethodInfo
  1087. enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
  1088. getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
  1089. .Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int))
  1090. .Select(p => p.GetGetMethod()).First();
  1091. class PropInfo
  1092. {
  1093. public string Name { get; set; }
  1094. public MethodInfo Setter { get; set; }
  1095. public Type Type { get; set; }
  1096. }
  1097. static List<PropInfo> GetSettableProps(Type t)
  1098. {
  1099. return t
  1100. .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
  1101. .Select(p => new PropInfo
  1102. {
  1103. Name = p.Name,
  1104. Setter = p.DeclaringType == t ? p.GetSetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetSetMethod(true),
  1105. Type = p.PropertyType
  1106. })
  1107. .Where(info => info.Setter != null)
  1108. .ToList();
  1109. }
  1110. static List<FieldInfo> GetSettableFields(Type t)
  1111. {
  1112. return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
  1113. }
  1114. private static Func<IDataReader, object> GetClassDeserializer2(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
  1115. {
  1116. var properties = type.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);
  1117. if (length == -1)
  1118. {
  1119. length = reader.FieldCount - startBound;
  1120. }
  1121. if (reader.FieldCount <= startBound)
  1122. {
  1123. throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
  1124. }
  1125. var names = new Dictionary<string, int>();
  1126. for (int i = startBound; i < startBound + length; i++)
  1127. {
  1128. names.Add(reader.GetName(i), i);
  1129. }
  1130. var setters = (
  1131. from n in names
  1132. let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n.Key, StringComparison.Ordinal)) // property case sensitive first
  1133. ?? properties.FirstOrDefault(p => string.Equals(p.Name, n.Key, StringComparison.OrdinalIgnoreCase)) // property case insensitive second
  1134. select new { Name = n, Property = prop }
  1135. ).ToList();
  1136. return (r) =>
  1137. {
  1138. var instance = Activator.CreateInstance(type);
  1139. bool create = false;
  1140. foreach (var setter in setters)
  1141. {
  1142. if (r.IsDBNull(setter.Name.Value) || setter.Property == null)
  1143. { continue; }
  1144. create = true;
  1145. var value = r.GetValue(setter.Name.Value);
  1146. object converted = value;
  1147. if (!setter.Property.PropertyType.IsAssignableFrom(value.GetType()))
  1148. {
  1149. if (customTypeMaps.ContainsKey(setter.Property.PropertyType))
  1150. {
  1151. converted = customTypeMaps[setter.Property.PropertyType].DeMapHander(value);
  1152. }
  1153. }
  1154. setter.Property.SetValue(instance, converted);
  1155. }
  1156. return create ? instance : null;
  1157. };
  1158. }
  1159. public static Func<IDataReader, object> GetClassDeserializer(
  1160. #if CSHARP30
  1161. Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
  1162. #else
  1163. Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
  1164. #endif
  1165. )
  1166. {
  1167. var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true);
  1168. var il = dm.GetILGenerator();
  1169. il.DeclareLocal(typeof(int));
  1170. il.DeclareLocal(type);
  1171. bool haveEnumLocal = false;
  1172. il.Emit(OpCodes.Ldc_I4_0);
  1173. il.Emit(OpCodes.Stloc_0);
  1174. var properties = GetSettableProps(type);
  1175. var fields = GetSettableFields(type);
  1176. if (length == -1)
  1177. {
  1178. length = reader.FieldCount - startBound;
  1179. }
  1180. if (reader.FieldCount <= startBound)
  1181. {
  1182. throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
  1183. }
  1184. var names = new List<string>();
  1185. for (int i = startBound; i < startBound + length; i++)
  1186. {
  1187. names.Add(reader.GetName(i));
  1188. }
  1189. var setters = (
  1190. from n in names
  1191. let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // property case sensitive first
  1192. ?? properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase)) // property case insensitive second
  1193. let field = prop != null ? null : (fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // field case sensitive third
  1194. ?? fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase))) // field case insensitive fourth
  1195. select new { Name = n, Property = prop, Field = field }
  1196. ).ToList();
  1197. int index = startBound;
  1198. il.BeginExceptionBlock();
  1199. // stack is empty
  1200. il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)); // stack is now [target]
  1201. bool first = true;
  1202. var allDone = il.DefineLabel();
  1203. foreach (var item in setters)
  1204. {
  1205. if (item.Property != null || item.Field != null)
  1206. {
  1207. il.Emit(OpCodes.Dup); // stack is now [target][target]
  1208. Label isDbNullLabel = il.DefineLabel();
  1209. Label finishLabel = il.DefineLabel();
  1210. il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
  1211. EmitInt32(il, index); // stack is now [target][target][reader][index]
  1212. il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
  1213. il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
  1214. il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
  1215. Type memberType = item.Property != null ? item.Property.Type : item.Field.FieldType;
  1216. if (customTypeMaps.ContainsKey(memberType))
  1217. {
  1218. il.EmitCall(OpCodes.Call, customTypeMaps[memberType].ProviderType.GetMethod(
  1219. customTypeMaps[memberType].MethodName, BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
  1220. }
  1221. else
  1222. {
  1223. il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
  1224. il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
  1225. il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object]
  1226. // unbox nullable enums as the primitive, i.e. byte etc
  1227. var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
  1228. var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType;
  1229. if (unboxType.IsEnum)
  1230. {
  1231. if (!haveEnumLocal)
  1232. {
  1233. il.DeclareLocal(typeof(string));
  1234. haveEnumLocal = true;
  1235. }
  1236. Label isNotString = il.DefineLabel();
  1237. il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
  1238. il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
  1239. il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
  1240. il.Emit(OpCodes.Stloc_2); // stack is now [target][target][value-as-object][string or null]
  1241. il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
  1242. il.Emit(OpCodes.Pop); // stack is now [target][target]
  1243. il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
  1244. il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
  1245. il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
  1246. il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
  1247. il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
  1248. il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
  1249. if (nullUnderlyingType != null)
  1250. {
  1251. il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType }));
  1252. }
  1253. if (item.Property != null)
  1254. {
  1255. il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target]
  1256. }
  1257. else
  1258. {
  1259. il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
  1260. }
  1261. il.Emit(OpCodes.Br_S, finishLabel);
  1262. il.MarkLabel(isNotString);
  1263. }
  1264. //if (memberType == typeof(System.Data.Linq.Binary))
  1265. //{
  1266. // il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
  1267. // il.Emit(OpCodes.Newobj, typeof(System.Data.Linq.Binary).GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
  1268. //}
  1269. //else
  1270. {
  1271. il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
  1272. }
  1273. if (nullUnderlyingType != null && nullUnderlyingType.IsEnum)
  1274. {
  1275. il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType }));
  1276. }
  1277. }
  1278. if (item.Property != null)
  1279. {
  1280. il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target]
  1281. }
  1282. else
  1283. {
  1284. il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
  1285. }
  1286. il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
  1287. il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
  1288. il.Emit(OpCodes.Pop); // stack is now [target][target]
  1289. il.Emit(OpCodes.Pop); // stack is now [target]
  1290. if (first && returnNullIfFirstMissing)
  1291. {
  1292. il.Emit(OpCodes.Pop);
  1293. il.Emit(OpCodes.Ldnull); // stack is now [null]
  1294. il.Emit(OpCodes.Stloc_1);
  1295. il.Emit(OpCodes.Br, allDone);
  1296. }
  1297. il.MarkLabel(finishLabel);
  1298. }
  1299. first = false;
  1300. index += 1;
  1301. }
  1302. il.Emit(OpCodes.Stloc_1); // stack is empty
  1303. il.MarkLabel(allDone);
  1304. il.BeginCatchBlock(typeof(Exception)); // stack is Exception
  1305. il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
  1306. il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
  1307. il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
  1308. il.Emit(OpCodes.Ldnull);
  1309. il.Emit(OpCodes.Stloc_1); // to make it verifiable
  1310. il.EndExceptionBlock();
  1311. il.Emit(OpCodes.Ldloc_1); // stack is empty
  1312. il.Emit(OpCodes.Ret);
  1313. return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>));
  1314. }
  1315. public static void ThrowDataException(Exception ex, int index, IDataReader reader)
  1316. {
  1317. string name = "(n/a)", value = "(n/a)";
  1318. if (reader != null && index >= 0 && index < reader.FieldCount)
  1319. {
  1320. name = reader.GetName(index);
  1321. object val = reader.GetValue(index);
  1322. if (val == null || val is DBNull)
  1323. {
  1324. value = "<null>";
  1325. }
  1326. else
  1327. {
  1328. value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType());
  1329. }
  1330. }
  1331. throw new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex);
  1332. }
  1333. private static void EmitInt32(ILGenerator il, int value)
  1334. {
  1335. switch (value)
  1336. {
  1337. case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
  1338. case 0: il.Emit(OpCodes.Ldc_I4_0); break;
  1339. case 1: il.Emit(OpCodes.Ldc_I4_1); break;
  1340. case 2: il.Emit(OpCodes.Ldc_I4_2); break;
  1341. case 3: il.Emit(OpCodes.Ldc_I4_3); break;
  1342. case 4: il.Emit(OpCodes.Ldc_I4_4); break;
  1343. case 5: il.Emit(OpCodes.Ldc_I4_5); break;
  1344. case 6: il.Emit(OpCodes.Ldc_I4_6); break;
  1345. case 7: il.Emit(OpCodes.Ldc_I4_7); break;
  1346. case 8: il.Emit(OpCodes.Ldc_I4_8); break;
  1347. default:
  1348. if (value >= -128 && value <= 127)
  1349. {
  1350. il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
  1351. }
  1352. else
  1353. {
  1354. il.Emit(OpCodes.Ldc_I4, value);
  1355. }
  1356. break;
  1357. }
  1358. }
  1359. }
  1360. }