Dapper完美兼容Oracle,執行存儲過程,並返回結果集。
這個問題,困擾了我整整兩天。
剛剛用到Dapper的時候,感覺非常牛掰。特別是配合.net 4.0新特性dynamic,讓我生成泛型集合,再轉json一氣呵成。
不過,各種ORM總有讓人吐槽的地方。。。
比如,我之前在SqlServer上寫測試,搞封裝,沒有任何問題。CURD、批量操作、存儲過程、事物等。
可是以轉到Oracle上,就出問題了【喂~不是說好的支持Oracle的麼】
在寫Dapper+Oracle單元測試的前期,是沒有問題的,也就是說普通的Sql操作是沒有任何問題的。
然後,我寫到存儲過程的單元測試的時候,就蛋疼了。
因為原版采用的DbType數據類型枚舉。Sqlserver返回結果集並沒有輸出游標。
但是Oracle輸出結果集,就需要用游標了。那麼,這裡問題就來了。給OracleParameter設置參數類型,DbType並沒有Cursor游標類型。
關於Dapper的文檔也是不多,而且大部分都集中在SqlServer上,可能應為服務於.Net平台,比較側重於微軟的配套數據庫。
好吧,問題來了,那就解決。反正是開源的。源代碼都有。
先根據問題來搜索【我不喜歡用百度,因為百度搜出來一大堆不相關的東西,銅臭味太重。google在國內有無法訪問,我就選擇了Bing,結果效果還不錯。】
經過網上搜集,發現Dapper確實是支持Oracle的,但是對於調用Oracle存儲過程的內容卻沒有。
好吧,沒有的話,先自己分析分析。
既然是參數類型不支持,那麼換成支持的不就成了?
原版的是這樣的:
1 DynamicParameters dp = new DynamicParameters(); 2 dp.Add("RoleId", "1"); 3 dp.Add("RoleName", "", DbType.String, ParameterDirection.Output);
這是Dapper原版中,聲明parameter的部分,上面代碼紅色部分,就是指定參數類型。
在system.data.oracleclient 中,有OracleType這個枚舉有Cursor類型。
然後,去查看 DynamicParameters 類,如下圖:
可以看到,這個類,是實現了一個接口的。說明,原作者給我們預留了接口去自己實現其他內容。
繼續看看接口:
接口的內容很簡單,就是一個AddParameters方法。
那麼,可以確定,上面的猜測是對的。
我們直接擴展實現這個接口就可以了。如圖:
自己去創建一個實現了IDynamicParameters的類OracleDynamicParameters。
然後參照原作者提供的DynamicParameters類來實現這個接口。
最終修改版如下(代碼多,展開了直接復制代碼貼到你的文件裡面):
1 /* 2 License: http://www.apache.org/licenses/LICENSE-2.0 3 Home page: http://code.google.com/p/dapper-dot-net/ 4 5 Note: to build on C# 3.0 + .NET 3.5, include the CSHARP30 compiler symbol (and yes, 6 I know the difference between language and runtime versions; this is a compromise). 7 * 8 * 增加Oracle存儲過程支持 9 * 李科笠 2015年10月13日 17:43:54 10 */ 11 using System; 12 using System.Collections; 13 using System.Collections.Generic; 14 using System.ComponentModel; 15 using System.Data; 16 using System.Linq; 17 using System.Reflection; 18 using System.Reflection.Emit; 19 using System.Text; 20 using System.Threading; 21 using System.Text.RegularExpressions; 22 using Oracle.DataAccess.Client; 23 24 namespace Dapper 25 { 26 public static partial class SqlMapper 27 { 28 public interface IDynamicParameters 29 { 30 void AddParameters(IDbCommand command, Identity identity); 31 } 32 static Link<Type, Action<IDbCommand, bool>> bindByNameCache; 33 static Action<IDbCommand, bool> GetBindByName(Type commandType) 34 { 35 if (commandType == null) return null; // GIGO 36 Action<IDbCommand, bool> action; 37 if (Link<Type, Action<IDbCommand, bool>>.TryGet(bindByNameCache, commandType, out action)) 38 { 39 return action; 40 } 41 var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance); 42 action = null; 43 ParameterInfo[] indexers; 44 MethodInfo setter; 45 if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool) 46 && ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0) 47 && (setter = prop.GetSetMethod()) != null 48 ) 49 { 50 var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) }); 51 var il = method.GetILGenerator(); 52 il.Emit(OpCodes.Ldarg_0); 53 il.Emit(OpCodes.Castclass, commandType); 54 il.Emit(OpCodes.Ldarg_1); 55 il.EmitCall(OpCodes.Callvirt, setter, null); 56 il.Emit(OpCodes.Ret); 57 action = (Action<IDbCommand, bool>)method.CreateDelegate(typeof(Action<IDbCommand, bool>)); 58 } 59 // cache it 60 Link<Type, Action<IDbCommand, bool>>.TryAdd(ref bindByNameCache, commandType, ref action); 61 return action; 62 } 63 /// <summary> 64 /// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), 65 /// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** 66 /// equality. The type is fully thread-safe. 67 /// </summary> 68 class Link<TKey, TValue> where TKey : class 69 { 70 public static bool TryGet(Link<TKey, TValue> link, TKey key, out TValue value) 71 { 72 while (link != null) 73 { 74 if ((object)key == (object)link.Key) 75 { 76 value = link.Value; 77 return true; 78 } 79 link = link.Tail; 80 } 81 value = default(TValue); 82 return false; 83 } 84 public static bool TryAdd(ref Link<TKey, TValue> head, TKey key, ref TValue value) 85 { 86 bool tryAgain; 87 do 88 { 89 var snapshot = Interlocked.CompareExchange(ref head, null, null); 90 TValue found; 91 if (TryGet(snapshot, key, out found)) 92 { // existing match; report the existing value instead 93 value = found; 94 return false; 95 } 96 var newNode = new Link<TKey, TValue>(key, value, snapshot); 97 // did somebody move our cheese? 98 tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot; 99 } while (tryAgain); 100 return true; 101 } 102 private Link(TKey key, TValue value, Link<TKey, TValue> tail) 103 { 104 Key = key; 105 Value = value; 106 Tail = tail; 107 } 108 public TKey Key { get; private set; } 109 public TValue Value { get; private set; } 110 public Link<TKey, TValue> Tail { get; private set; } 111 } 112 class CacheInfo 113 { 114 public Func<IDataReader, object> Deserializer { get; set; } 115 public Func<IDataReader, object>[] OtherDeserializers { get; set; } 116 public Action<IDbCommand, object> ParamReader { get; set; } 117 private int hitCount; 118 public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); } 119 public void RecordHit() { Interlocked.Increment(ref hitCount); } 120 } 121 122 public static event EventHandler QueryCachePurged; 123 private static void OnQueryCachePurged() 124 { 125 var handler = QueryCachePurged; 126 if (handler != null) handler(null, EventArgs.Empty); 127 } 128 #if CSHARP30 129 private static readonly Dictionary<Identity, CacheInfo> _queryCache = new Dictionary<Identity, CacheInfo>(); 130 // note: conflicts between readers and writers are so short-lived that it isn't worth the overhead of 131 // ReaderWriterLockSlim etc; a simple lock is faster 132 private static void SetQueryCache(Identity key, CacheInfo value) 133 { 134 lock (_queryCache) { _queryCache[key] = value; } 135 } 136 private static bool TryGetQueryCache(Identity key, out CacheInfo value) 137 { 138 lock (_queryCache) { return _queryCache.TryGetValue(key, out value); } 139 } 140 public static void PurgeQueryCache() 141 { 142 lock (_queryCache) 143 { 144 _queryCache.Clear(); 145 } 146 OnQueryCachePurged(); 147 } 148 #else 149 static readonly System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo> _queryCache = new System.Collections.Concurrent.ConcurrentDictionary<Identity, CacheInfo>(); 150 private static void SetQueryCache(Identity key, CacheInfo value) 151 { 152 if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS) 153 { 154 CollectCacheGarbage(); 155 } 156 _queryCache[key] = value; 157 } 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 173 finally 174 { 175 Interlocked.Exchange(ref collect, 0); 176 } 177 } 178 179 private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0; 180 private static int collect; 181 private static bool TryGetQueryCache(Identity key, out CacheInfo value) 182 { 183 if (_queryCache.TryGetValue(key, out value)) 184 { 185 value.RecordHit(); 186 return true; 187 } 188 value = null; 189 return false; 190 } 191 192 public static void PurgeQueryCache() 193 { 194 _queryCache.Clear(); 195 OnQueryCachePurged(); 196 } 197 198 public static int GetCachedSQLCount() 199 { 200 return _queryCache.Count; 201 } 202 203 204 public static IEnumerable<Tuple<string, string, int>> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue) 205 { 206 var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount())); 207 if (ignoreHitCountAbove < int.MaxValue) data = data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove); 208 return data; 209 } 210 211 public static IEnumerable<Tuple<int, int>> GetHashCollissions() 212 { 213 var counts = new Dictionary<int, int>(); 214 foreach (var key in _queryCache.Keys) 215 { 216 int count; 217 if (!counts.TryGetValue(key.hashCode, out count)) 218 { 219 counts.Add(key.hashCode, 1); 220 } 221 else 222 { 223 counts[key.hashCode] = count + 1; 224 } 225 } 226 return from pair in counts 227 where pair.Value > 1 228 select Tuple.Create(pair.Key, pair.Value); 229 230 } 231 #endif 232 233 234 static readonly Dictionary<Type, DbType> typeMap; 235 236 static SqlMapper() 237 { 238 typeMap = new Dictionary<Type, DbType>(); 239 typeMap[typeof(byte)] = DbType.Byte; 240 typeMap[typeof(sbyte)] = DbType.SByte; 241 typeMap[typeof(short)] = DbType.Int16; 242 typeMap[typeof(ushort)] = DbType.UInt16; 243 typeMap[typeof(int)] = DbType.Int32; 244 typeMap[typeof(uint)] = DbType.UInt32; 245 typeMap[typeof(long)] = DbType.Int64; 246 typeMap[typeof(ulong)] = DbType.UInt64; 247 typeMap[typeof(float)] = DbType.Single; 248 typeMap[typeof(double)] = DbType.Double; 249 typeMap[typeof(decimal)] = DbType.Decimal; 250 typeMap[typeof(bool)] = DbType.Boolean; 251 typeMap[typeof(string)] = DbType.String; 252 typeMap[typeof(char)] = DbType.StringFixedLength; 253 typeMap[typeof(Guid)] = DbType.Guid; 254 typeMap[typeof(DateTime)] = DbType.DateTime; 255 typeMap[typeof(DateTimeOffset)] = DbType.DateTimeOffset; 256 typeMap[typeof(byte[])] = DbType.Binary; 257 typeMap[typeof(byte?)] = DbType.Byte; 258 typeMap[typeof(sbyte?)] = DbType.SByte; 259 typeMap[typeof(short?)] = DbType.Int16; 260 typeMap[typeof(ushort?)] = DbType.UInt16; 261 typeMap[typeof(int?)] = DbType.Int32; 262 typeMap[typeof(uint?)] = DbType.UInt32; 263 typeMap[typeof(long?)] = DbType.Int64; 264 typeMap[typeof(ulong?)] = DbType.UInt64; 265 typeMap[typeof(float?)] = DbType.Single; 266 typeMap[typeof(double?)] = DbType.Double; 267 typeMap[typeof(decimal?)] = DbType.Decimal; 268 typeMap[typeof(bool?)] = DbType.Boolean; 269 typeMap[typeof(char?)] = DbType.StringFixedLength; 270 typeMap[typeof(Guid?)] = DbType.Guid; 271 typeMap[typeof(DateTime?)] = DbType.DateTime; 272 typeMap[typeof(DateTimeOffset?)] = DbType.DateTimeOffset; 273 typeMap[typeof(System.Data.Linq.Binary)] = DbType.Binary; 274 } 275 276 private static DbType LookupDbType(Type type, string name) 277 { 278 DbType dbType; 279 var nullUnderlyingType = Nullable.GetUnderlyingType(type); 280 if (nullUnderlyingType != null) type = nullUnderlyingType; 281 if (type.IsEnum) 282 { 283 type = Enum.GetUnderlyingType(type); 284 } 285 if (typeMap.TryGetValue(type, out dbType)) 286 { 287 return dbType; 288 } 289 if (typeof(IEnumerable).IsAssignableFrom(type)) 290 { 291 // use xml to denote its a list, hacky but will work on any DB 292 return DbType.Xml; 293 } 294 295 296 throw new NotSupportedException(string.Format("The member {0} of type {1} cannot be used as a parameter value", name, type)); 297 } 298 299 public class Identity : IEquatable<Identity> 300 { 301 internal Identity ForGrid(Type primaryType, int gridIndex) 302 { 303 return new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex); 304 } 305 306 internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) 307 { 308 return new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); 309 } 310 311 public Identity ForDynamicParameters(Type type) 312 { 313 return new Identity(sql, commandType, connectionString, this.type, type, null, -1); 314 } 315 316 internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes) 317 : this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0) 318 { } 319 private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex) 320 { 321 this.sql = sql; 322 this.commandType = commandType; 323 this.connectionString = connectionString; 324 this.type = type; 325 this.parametersType = parametersType; 326 this.gridIndex = gridIndex; 327 unchecked 328 { 329 hashCode = 17; // we *know* we are using this in a dictionary, so pre-compute this 330 hashCode = hashCode * 23 + commandType.GetHashCode(); 331 hashCode = hashCode * 23 + gridIndex.GetHashCode(); 332 hashCode = hashCode * 23 + (sql == null ? 0 : sql.GetHashCode()); 333 hashCode = hashCode * 23 + (type == null ? 0 : type.GetHashCode()); 334 if (otherTypes != null) 335 { 336 foreach (var t in otherTypes) 337 { 338 hashCode = hashCode * 23 + (t == null ? 0 : t.GetHashCode()); 339 } 340 } 341 hashCode = hashCode * 23 + (connectionString == null ? 0 : connectionString.GetHashCode()); 342 hashCode = hashCode * 23 + (parametersType == null ? 0 : parametersType.GetHashCode()); 343 } 344 } 345 public override bool Equals(object obj) 346 { 347 return Equals(obj as Identity); 348 } 349 public readonly string sql; 350 public readonly CommandType? commandType; 351 public readonly int hashCode, gridIndex; 352 private readonly Type type; 353 public readonly string connectionString; 354 public readonly Type parametersType; 355 public override int GetHashCode() 356 { 357 return hashCode; 358 } 359 public bool Equals(Identity other) 360 { 361 return 362 other != null && 363 gridIndex == other.gridIndex && 364 type == other.type && 365 sql == other.sql && 366 commandType == other.commandType && 367 connectionString == other.connectionString && 368 parametersType == other.parametersType; 369 } 370 } 371 372 #if CSHARP30 373 /// <summary> 374 /// Execute parameterized SQL 375 /// </summary> 376 /// <returns>Number of rows affected</returns> 377 public static int Execute(this IDbConnection cnn, string sql, object param) 378 { 379 return Execute(cnn, sql, param, null, null, null); 380 } 381 /// <summary> 382 /// Executes a query, returning the data typed as per T 383 /// </summary> 384 /// <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> 385 /// <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 386 /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). 387 /// </returns> 388 public static IEnumerable<T> Query<T>(this IDbConnection cnn, string sql, object param) 389 { 390 return Query<T>(cnn, sql, param, null, true, null, null); 391 } 392 393 #endif 394 /// <summary> 395 /// Execute parameterized SQL 396 /// </summary> 397 /// <returns>Number of rows affected</returns> 398 public static int Execute( 399 #if CSHARP30 400 this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType 401 #else 402 this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null 403 #endif 404 ) 405 { 406 IEnumerable multiExec = (object)param as IEnumerable; 407 Identity identity; 408 CacheInfo info = null; 409 if (multiExec != null && !(multiExec is string)) 410 { 411 bool isFirst = true; 412 int total = 0; 413 using (var cmd = SetupCommand(cnn, transaction, sql, null, null, commandTimeout, commandType)) 414 { 415 416 string masterSql = null; 417 foreach (var obj in multiExec) 418 { 419 if (isFirst) 420 { 421 masterSql = cmd.CommandText; 422 isFirst = false; 423 identity = new Identity(sql, cmd.CommandType, cnn, null, obj.GetType(), null); 424 info = GetCacheInfo(identity); 425 } 426 else 427 { 428 cmd.CommandText = masterSql; // because we do magic replaces on "in" etc 429 cmd.Parameters.Clear(); // current code is Add-tastic 430 } 431 info.ParamReader(cmd, obj); 432 total += cmd.ExecuteNonQuery(); 433 } 434 } 435 return total; 436 } 437 438 // nice and simple 439 identity = new Identity(sql, commandType, cnn, null, (object)param == null ? null : ((object)param).GetType(), null); 440 info = GetCacheInfo(identity); 441 return ExecuteCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType); 442 } 443 #if !CSHARP30 444 /// <summary> 445 /// Return a list of dynamic objects, reader is closed after the call 446 /// </summary> 447 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) 448 { 449 return Query<FastExpando>(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType); 450 } 451 #endif 452 453 /// <summary> 454 /// Executes a query, returning the data typed as per T 455 /// </summary> 456 /// <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> 457 /// <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 458 /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). 459 /// </returns> 460 public static IEnumerable<T> Query<T>( 461 #if CSHARP30 462 this IDbConnection cnn, string sql, object param, IDbTransaction transaction, bool buffered, int? commandTimeout, CommandType? commandType 463 #else 464 this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null 465 #endif 466 ) 467 { 468 var data = QueryInternal<T>(cnn, sql, param as object, transaction, commandTimeout, commandType); 469 return buffered ? data.ToList() : data; 470 } 471 472 /// <summary> 473 /// Execute a command that returns multiple result sets, and access each in turn 474 /// </summary> 475 public static GridReader QueryMultiple( 476 #if CSHARP30 477 this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType 478 #else 479 this IDbConnection cnn, string sql, dynamic param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null 480 #endif 481 ) 482 { 483 Identity identity = new Identity(sql, commandType, cnn, typeof(GridReader), (object)param == null ? null : ((object)param).GetType(), null); 484 CacheInfo info = GetCacheInfo(identity); 485 486 IDbCommand cmd = null; 487 IDataReader reader = null; 488 try 489 { 490 cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, (object)param, commandTimeout, commandType); 491 reader = cmd.ExecuteReader(); 492 return new GridReader(cmd, reader, identity); 493 } 494 catch 495 { 496 if (reader != null) reader.Dispose(); 497 if (cmd != null) cmd.Dispose(); 498 throw; 499 } 500 } 501 502 /// <summary> 503 /// Return a typed list of objects, reader is closed after the call 504 /// </summary> 505 private static IEnumerable<T> QueryInternal<T>(this IDbConnection cnn, string sql, object param, IDbTransaction transaction, int? commandTimeout, CommandType? commandType) 506 { 507 var identity = new Identity(sql, commandType, cnn, typeof(T), param == null ? null : param.GetType(), null); 508 var info = GetCacheInfo(identity); 509 510 using (var cmd = SetupCommand(cnn, transaction, sql, info.ParamReader, param, commandTimeout, commandType)) 511 { 512 using (var reader = cmd.ExecuteReader()) 513 { 514 Func<Func<IDataReader, object>> cacheDeserializer = () => 515 { 516 info.Deserializer = GetDeserializer(typeof(T), reader, 0, -1, false); 517 SetQueryCache(identity, info); 518 return info.Deserializer; 519 }; 520 521 if (info.Deserializer == null) 522 { 523 cacheDeserializer(); 524 } 525 526 var deserializer = info.Deserializer; 527 528 while (reader.Read()) 529 { 530 object next; 531 try 532 { 533 next = deserializer(reader); 534 } 535 catch (DataException) 536 { 537 // give it another shot, in case the underlying schema changed 538 deserializer = cacheDeserializer(); 539 next = deserializer(reader); 540 } 541 yield return (T)next; 542 } 543 544 } 545 } 546 } 547 548 /// <summary> 549 /// Maps a query to objects 550 /// </summary> 551 /// <typeparam name="T">The return type</typeparam> 552 /// <typeparam name="U"></typeparam> 553 /// <param name="cnn"></param> 554 /// <param name="sql"></param> 555 /// <param name="map"></param> 556 /// <param name="param"></param> 557 /// <param name="transaction"></param> 558 /// <param name="buffered"></param> 559 /// <param name="splitOn">The Field we should split and read the second object from (default: id)</param> 560 /// <param name="commandTimeout">Number of seconds before command execution timeout</param> 561 /// <returns></returns> 562 public static IEnumerable<TReturn> Query<TFirst, TSecond, TReturn>( 563 #if CSHARP30 564 this IDbConnection cnn, string sql, Func<TFirst, TSecond, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType 565 #else 566 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 567 #endif 568 ) 569 { 570 return MultiMap<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); 571 } 572 573 public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TReturn>( 574 #if CSHARP30 575 this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType 576 #else 577 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 578 #endif 579 ) 580 { 581 return MultiMap<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); 582 } 583 584 public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TReturn>( 585 #if CSHARP30 586 this IDbConnection cnn, string sql, Func<TFirst, TSecond, TThird, TFourth, TReturn> map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType 587 #else 588 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 589 #endif 590 ) 591 { 592 return MultiMap<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); 593 } 594 #if !CSHARP30 595 public static IEnumerable<TReturn> Query<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(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) 596 { 597 return MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType); 598 } 599 #endif 600 class DontMap { } 601 static IEnumerable<TReturn> MultiMap<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>( 602 this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) 603 { 604 var results = MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null); 605 return buffered ? results.ToList() : results; 606 } 607 608 609 static IEnumerable<TReturn> MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(this IDbConnection cnn, string sql, object map, object param, IDbTransaction transaction, string splitOn, int? commandTimeout, CommandType? commandType, IDataReader reader, Identity identity) 610 { 611 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) }); 612 CacheInfo cinfo = GetCacheInfo(identity); 613 614 IDbCommand ownedCommand = null; 615 IDataReader ownedReader = null; 616 617 try 618 { 619 if (reader == null) 620 { 621 ownedCommand = SetupCommand(cnn, transaction, sql, cinfo.ParamReader, (object)param, commandTimeout, commandType); 622 ownedReader = ownedCommand.ExecuteReader(); 623 reader = ownedReader; 624 } 625 Func<IDataReader, object> deserializer = null; 626 Func<IDataReader, object>[] otherDeserializers = null; 627 628 Action cacheDeserializers = () => 629 { 630 var deserializers = GenerateDeserializers(new Type[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth) }, splitOn, reader); 631 deserializer = cinfo.Deserializer = deserializers[0]; 632 otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); 633 SetQueryCache(identity, cinfo); 634 }; 635 636 if ((deserializer = cinfo.Deserializer) == null || (otherDeserializers = cinfo.OtherDeserializers) == null) 637 { 638 cacheDeserializers(); 639 } 640 641 Func<IDataReader, TReturn> mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer, otherDeserializers, map); 642 643 if (mapIt != null) 644 { 645 while (reader.Read()) 646 { 647 TReturn next; 648 try 649 { 650 next = mapIt(reader); 651 } 652 catch (DataException) 653 { 654 cacheDeserializers(); 655 mapIt = GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(deserializer, otherDeserializers, map); 656 next = mapIt(reader); 657 } 658 yield return next; 659 } 660 } 661 } 662 finally 663 { 664 try 665 { 666 if (ownedReader != null) 667 { 668 ownedReader.Dispose(); 669 } 670 } 671 finally 672 { 673 if (ownedCommand != null) 674 { 675 ownedCommand.Dispose(); 676 } 677 } 678 } 679 } 680 681 private static Func<IDataReader, TReturn> GenerateMapper<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<IDataReader, object> deserializer, Func<IDataReader, object>[] otherDeserializers, object map) 682 { 683 switch (otherDeserializers.Length) 684 { 685 case 1: 686 return r => ((Func<TFirst, TSecond, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r)); 687 case 2: 688 return r => ((Func<TFirst, TSecond, TThird, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r)); 689 case 3: 690 return r => ((Func<TFirst, TSecond, TThird, TFourth, TReturn>)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r)); 691 #if !CSHARP30 692 case 4: 693 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)); 694 #endif 695 default: 696 throw new NotSupportedException(); 697 } 698 } 699 700 private static Func<IDataReader, object>[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader) 701 { 702 int current = 0; 703 var splits = splitOn.Split(',').ToArray(); 704 var splitIndex = 0; 705 706 Func<Type, int> nextSplit = type => 707 { 708 var currentSplit = splits[splitIndex]; 709 if (splits.Length > splitIndex + 1) 710 { 711 splitIndex++; 712 } 713 714 bool skipFirst = false; 715 int startingPos = current + 1; 716 // if our current type has the split, skip the first time you see it. 717 if (type != typeof(Object)) 718 { 719 var props = GetSettableProps(type); 720 var fields = GetSettableFields(type); 721 722 foreach (var name in props.Select(p => p.Name).Concat(fields.Select(f => f.Name))) 723 { 724 if (string.Equals(name, currentSplit, StringComparison.OrdinalIgnoreCase)) 725 { 726 skipFirst = true; 727 startingPos = current; 728 break; 729 } 730 } 731 732 } 733 734 int pos; 735 for (pos = startingPos; pos < reader.FieldCount; pos++) 736 { 737 // some people like ID some id ... assuming case insensitive splits for now 738 if (splitOn == "*") 739 { 740 break; 741 } 742 if (string.Equals(reader.GetName(pos), currentSplit, StringComparison.OrdinalIgnoreCase)) 743 { 744 if (skipFirst) 745 { 746 skipFirst = false; 747 } 748 else 749 { 750 break; 751 } 752 } 753 } 754 current = pos; 755 return pos; 756 }; 757 758 var deserializers = new List<Func<IDataReader, object>>(); 759 int split = 0; 760 bool first = true; 761 foreach (var type in types) 762 { 763 if (type != typeof(DontMap)) 764 { 765 int next = nextSplit(type); 766 deserializers.Add(GetDeserializer(type, reader, split, next - split, /* returnNullIfFirstMissing: */ !first)); 767 first = false; 768 split = next; 769 } 770 } 771 772 return deserializers.ToArray(); 773 } 774 775 private static CacheInfo GetCacheInfo(Identity identity) 776 { 777 CacheInfo info; 778 if (!TryGetQueryCache(identity, out info)) 779 { 780 info = new CacheInfo(); 781 if (identity.parametersType != null) 782 { 783 if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType)) 784 { 785 info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd, identity); }; 786 } 787 else 788 { 789 info.ParamReader = CreateParamInfoGenerator(identity); 790 } 791 } 792 SetQueryCache(identity, info); 793 } 794 return info; 795 } 796 797 private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) 798 { 799 #if !CSHARP30 800 // dynamic is passed in as Object ... by c# design 801 if (type == typeof(object) 802 || type == typeof(FastExpando)) 803 { 804 return GetDynamicDeserializer(reader, startBound, length, returnNullIfFirstMissing); 805 } 806 #endif 807 808 if (type.IsClass && type != typeof(string) && type != typeof(byte[]) && type != typeof(System.Data.Linq.Binary)) 809 { 810 return GetClassDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); 811 } 812 return GetStructDeserializer(type, startBound); 813 814 } 815 #if !CSHARP30 816 private class FastExpando : System.Dynamic.DynamicObject, IDictionary<string, object> 817 { 818 IDictionary<string, object> data; 819 820 public static FastExpando Attach(IDictionary<string, object> data) 821 { 822 return new FastExpando { data = data }; 823 } 824 825 public override bool TrySetMember(System.Dynamic.SetMemberBinder binder, object value) 826 { 827 data[binder.Name] = value; 828 return true; 829 } 830 831 public override bool TryGetMember(System.Dynamic.GetMemberBinder binder, out object result) 832 { 833 return data.TryGetValue(binder.Name, out result); 834 } 835 836 #region IDictionary<string,object> Members 837 838 void IDictionary<string, object>.Add(string key, object value) 839 { 840 throw new NotImplementedException(); 841 } 842 843 bool IDictionary<string, object>.ContainsKey(string key) 844 { 845 return data.ContainsKey(key); 846 } 847 848 ICollection<string> IDictionary<string, object>.Keys 849 { 850 get { return data.Keys; } 851 } 852 853 bool IDictionary<string, object>.Remove(string key) 854 { 855 throw new NotImplementedException(); 856 } 857 858 bool IDictionary<string, object>.TryGetValue(string key, out object value) 859 { 860 return data.TryGetValue(key, out value); 861 } 862 863 ICollection<object> IDictionary<string, object>.Values 864 { 865 get { return data.Values; } 866 } 867 868 object IDictionary<string, object>.this[string key] 869 { 870 get 871 { 872 return data[key]; 873 } 874 set 875 { 876 throw new NotImplementedException(); 877 } 878 } 879 880 #endregion 881 882 #region ICollection<KeyValuePair<string,object>> Members 883 884 void ICollection<KeyValuePair<string, object>>.Add(KeyValuePair<string, object> item) 885 { 886 throw new NotImplementedException(); 887 } 888 889 void ICollection<KeyValuePair<string, object>>.Clear() 890 { 891 throw new NotImplementedException(); 892 } 893 894 bool ICollection<KeyValuePair<string, object>>.Contains(KeyValuePair<string, object> item) 895 { 896 return data.Contains(item); 897 } 898 899 void ICollection<KeyValuePair<string, object>>.CopyTo(KeyValuePair<string, object>[] array, int arrayIndex) 900 { 901 data.CopyTo(array, arrayIndex); 902 } 903 904 int ICollection<KeyValuePair<string, object>>.Count 905 { 906 get { return data.Count; } 907 } 908 909 bool ICollection<KeyValuePair<string, object>>.IsReadOnly 910 { 911 get { return true; } 912 } 913 914 bool ICollection<KeyValuePair<string, object>>.Remove(KeyValuePair<string, object> item) 915 { 916 throw new NotImplementedException(); 917 } 918 919 #endregion 920 921 #region IEnumerable<KeyValuePair<string,object>> Members 922 923 IEnumerator<KeyValuePair<string, object>> IEnumerable<KeyValuePair<string, object>>.GetEnumerator() 924 { 925 return data.GetEnumerator(); 926 } 927 928 #endregion 929 930 #region IEnumerable Members 931 932 IEnumerator IEnumerable.GetEnumerator() 933 { 934 return data.GetEnumerator(); 935 } 936 937 #endregion 938 } 939 940 941 private static Func<IDataReader, object> GetDynamicDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) 942 { 943 var fieldCount = reader.FieldCount; 944 if (length == -1) 945 { 946 length = fieldCount - startBound; 947 } 948 949 if (fieldCount <= startBound) 950 { 951 throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); 952 } 953 954 return 955 r => 956 { 957 IDictionary<string, object> row = new Dictionary<string, object>(length); 958 for (var i = startBound; i < startBound + length; i++) 959 { 960 var tmp = r.GetValue(i); 961 tmp = tmp == DBNull.Value ? null : tmp; 962 row[r.GetName(i)] = tmp; 963 if (returnNullIfFirstMissing && i == startBound && tmp == null) 964 { 965 return null; 966 } 967 } 968 //we know this is an object so it will not box 969 return FastExpando.Attach(row); 970 }; 971 } 972 #endif 973 [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] 974 [Obsolete("This method is for internal usage only", false)] 975 public static char ReadChar(object value) 976 { 977 if (value == null || value is DBNull) throw new ArgumentNullException("value"); 978 string s = value as string; 979 if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value"); 980 return s[0]; 981 } 982 [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] 983 [Obsolete("This method is for internal usage only", false)] 984 public static char? ReadNullableChar(object value) 985 { 986 if (value == null || value is DBNull) return null; 987 string s = value as string; 988 if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", "value"); 989 return s[0]; 990 } 991 [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] 992 [Obsolete("This method is for internal usage only", true)] 993 public static void PackListParameters(IDbCommand command, string namePrefix, object value) 994 { 995 // initially we tried TVP, however it performs quite poorly. 996 // keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare 997 998 var list = value as IEnumerable; 999 var count = 0; 1000 1001 if (list != null) 1002 { 1003 bool isString = value is IEnumerable<string>; 1004 foreach (var item in list) 1005 { 1006 count++; 1007 var listParam = command.CreateParameter(); 1008 listParam.ParameterName = namePrefix + count; 1009 listParam.Value = item ?? DBNull.Value; 1010 if (isString) 1011 { 1012 listParam.Size = 4000; 1013 if (item != null && ((string)item).Length > 4000) 1014 { 1015 listParam.Size = -1; 1016 } 1017 } 1018 command.Parameters.Add(listParam); 1019 } 1020 1021 if (count == 0) 1022 { 1023 command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), "(SELECT NULL WHERE 1 = 0)"); 1024 } 1025 else 1026 { 1027 command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), match => 1028 { 1029 var grp = match.Value; 1030 var sb = new StringBuilder("(").Append(grp).Append(1); 1031 for (int i = 2; i <= count; i++) 1032 { 1033 sb.Append(',').Append(grp).Append(i); 1034 } 1035 return sb.Append(')').ToString(); 1036 }); 1037 } 1038 } 1039 1040 } 1041 1042 private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql) 1043 { 1044 return parameters.Where(p => Regex.IsMatch(sql, "[@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline)); 1045 } 1046 1047 public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity) 1048 { 1049 Type type = identity.parametersType; 1050 bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text; 1051 1052 var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); 1053 1054 var il = dm.GetILGenerator(); 1055 1056 il.DeclareLocal(type); // 0 1057 bool haveInt32Arg1 = false; 1058 il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] 1059 il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param] 1060 il.Emit(OpCodes.Stloc_0);// stack is now empty 1061 1062 il.Emit(OpCodes.Ldarg_0); // stack is now [command] 1063 il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters] 1064 1065 IEnumerable<PropertyInfo> props = type.GetProperties().OrderBy(p => p.Name); 1066 if (filterParams) 1067 { 1068 props = FilterParameters(props, identity.sql); 1069 } 1070 foreach (var prop in props) 1071 { 1072 if (filterParams) 1073 { 1074 if (identity.sql.IndexOf("@" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0 1075 && identity.sql.IndexOf(":" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0) 1076 { // can't see the parameter in the text (even in a comment, etc) - burn it with fire 1077 continue; 1078 } 1079 } 1080 if (prop.PropertyType == typeof(DbString)) 1081 { 1082 il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param] 1083 il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring] 1084 il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command] 1085 il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name] 1086 il.EmitCall(OpCodes.Callvirt, typeof(DbString).GetMethod("AddParameter"), null); // stack is now [parameters] 1087 continue; 1088 } 1089 DbType dbType = LookupDbType(prop.PropertyType, prop.Name); 1090 if (dbType == DbType.Xml) 1091 { 1092 // this actually represents special handling for list types; 1093 il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command] 1094 il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] 1095 il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param] 1096 il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value] 1097 if (prop.PropertyType.IsValueType) 1098 { 1099 il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value] 1100 } 1101 il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters] 1102 continue; 1103 } 1104 il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters] 1105 1106 il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command] 1107 il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter] 1108 1109 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] 1110 il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name] 1111 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] 1112 1113 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] 1114 EmitInt32(il, (int)dbType);// stack is now [parameters] [parameters] [parameter] [parameter] [db-type] 1115 1116 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] 1117 1118 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] 1119 EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [parameters] [parameter] [parameter] [dir] 1120 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] 1121 1122 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] 1123 il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [parameters] [parameter] [parameter] [typed-param] 1124 il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [parameters] [parameter] [parameter] [typed-value] 1125 bool checkForNull = true; 1126 if (prop.PropertyType.IsValueType) 1127 { 1128 il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [parameters] [parameter] [parameter] [boxed-value] 1129 if (Nullable.GetUnderlyingType(prop.PropertyType) == null) 1130 { // struct but not Nullable<T>; boxed value cannot be null 1131 checkForNull = false; 1132 } 1133 } 1134 if (checkForNull) 1135 { 1136 if (dbType == DbType.String && !haveInt32Arg1) 1137 { 1138 il.DeclareLocal(typeof(int)); 1139 haveInt32Arg1 = true; 1140 } 1141 // relative stack: [boxed value] 1142 il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value] 1143 Label notNull = il.DefineLabel(); 1144 Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null; 1145 il.Emit(OpCodes.Brtrue_S, notNull); 1146 // relative stack [boxed value = null] 1147 il.Emit(OpCodes.Pop); // relative stack empty 1148 il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull] 1149 if (dbType == DbType.String) 1150 { 1151 EmitInt32(il, 0); 1152 il.Emit(OpCodes.Stloc_1); 1153 } 1154 if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value); 1155 il.MarkLabel(notNull); 1156 if (prop.PropertyType == typeof(string)) 1157 { 1158 il.Emit(OpCodes.Dup); // [string] [string] 1159 il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length] 1160 EmitInt32(il, 4000); // [string] [length] [4000] 1161 il.Emit(OpCodes.Cgt); // [string] [0 or 1] 1162 Label isLong = il.DefineLabel(), lenDone = il.DefineLabel(); 1163 il.Emit(OpCodes.Brtrue_S, isLong); 1164 EmitInt32(il, 4000); // [string] [4000] 1165 il.Emit(OpCodes.Br_S, lenDone); 1166 il.MarkLabel(isLong); 1167 EmitInt32(il, -1); // [string] [-1] 1168 il.MarkLabel(lenDone); 1169 il.Emit(OpCodes.Stloc_1); // [string] 1170 } 1171 if (prop.PropertyType == typeof(System.Data.Linq.Binary)) 1172 { 1173 il.EmitCall(OpCodes.Callvirt, typeof(System.Data.Linq.Binary).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null); 1174 } 1175 if (allDone != null) il.MarkLabel(allDone.Value); 1176 // relative stack [boxed value or DBNull] 1177 } 1178 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] 1179 1180 if (prop.PropertyType == typeof(string)) 1181 { 1182 var endOfSize = il.DefineLabel(); 1183 // don't set if 0 1184 il.Emit(OpCodes.Ldloc_1); // [parameters] [parameters] [parameter] [size] 1185 il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [parameters] [parameter] 1186 1187 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] 1188 il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [parameters] [parameter] [parameter] [size] 1189 il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] 1190 1191 il.MarkLabel(endOfSize); 1192 } 1193 1194 il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters] 1195 il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care 1196 } 1197 // stack is currently [command] 1198 il.Emit(OpCodes.Pop); // stack is now empty 1199 il.Emit(OpCodes.Ret); 1200 return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>)); 1201 } 1202 1203 private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType) 1204 { 1205 var cmd = cnn.CreateCommand(); 1206 var bindByName = GetBindByName(cmd.GetType()); 1207 if (bindByName != null) bindByName(cmd, true); 1208 cmd.Transaction = transaction; 1209 cmd.CommandText = sql; 1210 if (commandTimeout.HasValue) 1211 cmd.CommandTimeout = commandTimeout.Value; 1212 if (commandType.HasValue) 1213 cmd.CommandType = commandType.Value; 1214 if (paramReader != null) 1215 { 1216 paramReader(cmd, obj); 1217 } 1218 return cmd; 1219 } 1220 1221 1222 private static int ExecuteCommand(IDbConnection cnn, IDbTransaction tranaction, string sql, Action<IDbCommand, object> paramReader, object obj, int? commandTimeout, CommandType? commandType) 1223 { 1224 using (var cmd = SetupCommand(cnn, tranaction, sql, paramReader, obj, commandTimeout, commandType)) 1225 { 1226 return cmd.ExecuteNonQuery(); 1227 } 1228 } 1229 1230 private static Func<IDataReader, object> GetStructDeserializer(Type type, int index) 1231 { 1232 // 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!) 1233 #pragma warning disable 618 1234 if (type == typeof(char)) 1235 { // this *does* need special handling, though 1236 return r => SqlMapper.ReadChar(r.GetValue(index)); 1237 } 1238 if (type == typeof(char?)) 1239 { 1240 return r => SqlMapper.ReadNullableChar(r.GetValue(index)); 1241 } 1242 if (type == typeof(System.Data.Linq.Binary)) 1243 { 1244 return r => new System.Data.Linq.Binary((byte[])r.GetValue(index)); 1245 } 1246 #pragma warning restore 618 1247 return r => 1248 { 1249 var val = r.GetValue(index); 1250 return val is DBNull ? null : Convert.ChangeType(val, type); 1251 }; 1252 } 1253 1254 static readonly MethodInfo 1255 enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }), 1256 getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public) 1257 .Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int)) 1258 .Select(p => p.GetGetMethod()).First(); 1259 1260 class PropInfo 1261 { 1262 public string Name { get; set; } 1263 public MethodInfo Setter { get; set; } 1264 public Type Type { get; set; } 1265 } 1266 1267 static List<PropInfo> GetSettableProps(Type t) 1268 { 1269 return t 1270 .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) 1271 .Select(p => new PropInfo 1272 { 1273 Name = p.Name, 1274 Setter = p.DeclaringType == t ? p.GetSetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetSetMethod(true), 1275 Type = p.PropertyType 1276 }) 1277 .Where(info => info.Setter != null) 1278 .ToList(); 1279 } 1280 1281 static List<FieldInfo> GetSettableFields(Type t) 1282 { 1283 return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList(); 1284 } 1285 1286 public static Func<IDataReader, object> GetClassDeserializer( 1287 #if CSHARP30 1288 Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing 1289 #else 1290 Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false 1291 #endif 1292 ) 1293 { 1294 var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true); 1295 1296 var il = dm.GetILGenerator(); 1297 il.DeclareLocal(typeof(int)); 1298 il.DeclareLocal(type); 1299 bool haveEnumLocal = false; 1300 il.Emit(OpCodes.Ldc_I4_0); 1301 il.Emit(OpCodes.Stloc_0); 1302 var properties = GetSettableProps(type); 1303 var fields = GetSettableFields(type); 1304 if (length == -1) 1305 { 1306 length = reader.FieldCount - startBound; 1307 } 1308 1309 if (reader.FieldCount <= startBound) 1310 { 1311 throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); 1312 } 1313 1314 var names = new List<string>(); 1315 1316 for (int i = startBound; i < startBound + length; i++) 1317 { 1318 names.Add(reader.GetName(i)); 1319 } 1320 1321 var setters = ( 1322 from n in names 1323 let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // property case sensitive first 1324 ?? properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase)) // property case insensitive second 1325 let field = prop != null ? null : (fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // field case sensitive third 1326 ?? fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase))) // field case insensitive fourth 1327 select new { Name = n, Property = prop, Field = field } 1328 ).ToList(); 1329 1330 int index = startBound; 1331 1332 il.BeginExceptionBlock(); 1333 // stack is empty 1334 il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)); // stack is now [target] 1335 bool first = true; 1336 var allDone = il.DefineLabel(); 1337 foreach (var item in setters) 1338 { 1339 if (item.Property != null || item.Field != null) 1340 { 1341 il.Emit(OpCodes.Dup); // stack is now [target][target] 1342 Label isDbNullLabel = il.DefineLabel(); 1343 Label finishLabel = il.DefineLabel(); 1344 1345 il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader] 1346 EmitInt32(il, index); // stack is now [target][target][reader][index] 1347 il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index] 1348 il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] 1349 il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] 1350 1351 1352 Type memberType = item.Property != null ? item.Property.Type : item.Field.FieldType; 1353 1354 if (memberType == typeof(char) || memberType == typeof(char?)) 1355 { 1356 il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( 1357 memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value] 1358 } 1359 else 1360 { 1361 il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] 1362 il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null] 1363 il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] 1364 1365 // unbox nullable enums as the primitive, i.e. byte etc 1366 1367 var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); 1368 var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType; 1369 1370 if (unboxType.IsEnum) 1371 { 1372 if (!haveEnumLocal) 1373 { 1374 il.DeclareLocal(typeof(string)); 1375 haveEnumLocal = true; 1376 } 1377 1378 Label isNotString = il.DefineLabel(); 1379 il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] 1380 il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null] 1381 il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null] 1382 il.Emit(OpCodes.Stloc_2); // stack is now [target][target][value-as-object][string or null] 1383 il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object] 1384 1385 il.Emit(OpCodes.Pop); // stack is now [target][target] 1386 1387 1388 il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token] 1389 il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type] 1390 il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string] 1391 il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true] 1392 il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object] 1393 1394 il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] 1395 1396 if (nullUnderlyingType != null) 1397 { 1398 il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); 1399 } 1400 if (item.Property != null) 1401 { 1402 il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target] 1403 } 1404 else 1405 { 1406 il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] 1407 } 1408 il.Emit(OpCodes.Br_S, finishLabel); 1409 1410 1411 il.MarkLabel(isNotString); 1412 } 1413 if (memberType == typeof(System.Data.Linq.Binary)) 1414 { 1415 il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array] 1416 il.Emit(OpCodes.Newobj, typeof(System.Data.Linq.Binary).GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary] 1417 } 1418 else 1419 { 1420 il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] 1421 } 1422 if (nullUnderlyingType != null && nullUnderlyingType.IsEnum) 1423 { 1424 il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); 1425 } 1426 } 1427 if (item.Property != null) 1428 { 1429 il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target] 1430 } 1431 else 1432 { 1433 il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] 1434 } 1435 1436 il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] 1437 1438 il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value] 1439 1440 il.Emit(OpCodes.Pop); // stack is now [target][target] 1441 il.Emit(OpCodes.Pop); // stack is now [target] 1442 1443 if (first && returnNullIfFirstMissing) 1444 { 1445 il.Emit(OpCodes.Pop); 1446 il.Emit(OpCodes.Ldnull); // stack is now [null] 1447 il.Emit(OpCodes.Stloc_1); 1448 il.Emit(OpCodes.Br, allDone); 1449 } 1450 1451 il.MarkLabel(finishLabel); 1452 } 1453 first = false; 1454 index += 1; 1455 } 1456 il.Emit(OpCodes.Stloc_1); // stack is empty 1457 il.MarkLabel(allDone); 1458 il.BeginCatchBlock(typeof(Exception)); // stack is Exception 1459 il.Emit(OpCodes.Ldloc_0); // stack is Exception, index 1460 il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader 1461 il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null); 1462 il.Emit(OpCodes.Ldnull); 1463 il.Emit(OpCodes.Stloc_1); // to make it verifiable 1464 il.EndExceptionBlock(); 1465 1466 il.Emit(OpCodes.Ldloc_1); // stack is empty 1467 il.Emit(OpCodes.Ret); 1468 1469 return (Func<IDataReader, object>)dm.CreateDelegate(typeof(Func<IDataReader, object>)); 1470 } 1471 public static void ThrowDataException(Exception ex, int index, IDataReader reader) 1472 { 1473 string name = "(n/a)", value = "(n/a)"; 1474 if (reader != null && index >= 0 && index < reader.FieldCount) 1475 { 1476 name = reader.GetName(index); 1477 object val = reader.GetValue(index); 1478 if (val == null || val is DBNull) 1479 { 1480 value = "<null>"; 1481 } 1482 else 1483 { 1484 value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType()); 1485 } 1486 } 1487 throw new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex); 1488 } 1489 private static void EmitInt32(ILGenerator il, int value) 1490 { 1491 switch (value) 1492 { 1493 case -1: il.Emit(OpCodes.Ldc_I4_M1); break; 1494 case 0: il.Emit(OpCodes.Ldc_I4_0); break; 1495 case 1: il.Emit(OpCodes.Ldc_I4_1); break; 1496 case 2: il.Emit(OpCodes.Ldc_I4_2); break; 1497 case 3: il.Emit(OpCodes.Ldc_I4_3); break; 1498 case 4: il.Emit(OpCodes.Ldc_I4_4); break; 1499 case 5: il.Emit(OpCodes.Ldc_I4_5); break; 1500 case 6: il.Emit(OpCodes.Ldc_I4_6); break; 1501 case 7: il.Emit(OpCodes.Ldc_I4_7); break; 1502 case 8: il.Emit(OpCodes.Ldc_I4_8); break; 1503 default: 1504 if (value >= -128 && value <= 127) 1505 { 1506 il.Emit(OpCodes.Ldc_I4_S, (sbyte)value); 1507 } 1508 else 1509 { 1510 il.Emit(OpCodes.Ldc_I4, value); 1511 } 1512 break; 1513 } 1514 } 1515 1516 public class GridReader : IDisposable 1517 { 1518 private IDataReader reader; 1519 private IDbCommand command; 1520 private Identity identity; 1521 1522 internal GridReader(IDbCommand command, IDataReader reader, Identity identity) 1523 { 1524 this.command = command; 1525 this.reader = reader; 1526 this.identity = identity; 1527 } 1528 /// <summary> 1529 /// Read the next grid of results 1530 /// </summary> 1531 public IEnumerable<T> Read<T>() 1532 { 1533 if (reader == null) throw new ObjectDisposedException(GetType().Name); 1534 if (consumed) throw new InvalidOperationException("Each grid can only be iterated once"); 1535 var typedIdentity = identity.ForGrid(typeof(T), gridIndex); 1536 CacheInfo cache = GetCacheInfo(typedIdentity); 1537 var deserializer = cache.Deserializer; 1538 1539 Func<Func<IDataReader, object>> deserializerGenerator = () => 1540 { 1541 deserializer = GetDeserializer(typeof(T), reader, 0, -1, false); 1542 cache.Deserializer = deserializer; 1543 return deserializer; 1544 }; 1545 1546 if (deserializer == null) 1547 { 1548 deserializer = deserializerGenerator(); 1549 } 1550 consumed = true; 1551 return ReadDeferred<T>(gridIndex, deserializer, typedIdentity, deserializerGenerator); 1552 } 1553 1554 private IEnumerable<TReturn> MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(object func, string splitOn) 1555 { 1556 1557 var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { 1558 typeof(TFirst), 1559 typeof(TSecond), 1560 typeof(TThird), 1561 typeof(TFourth), 1562 typeof(TFifth) 1563 }, gridIndex); 1564 try 1565 { 1566 foreach (var r in SqlMapper.MultiMapImpl<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(null, null, func, null, null, splitOn, null, null, reader, identity)) 1567 { 1568 yield return r; 1569 } 1570 } 1571 finally 1572 { 1573 NextResult(); 1574 } 1575 } 1576 1577 #if CSHARP30 1578 public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn) 1579 #else 1580 public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id") 1581 #endif 1582 { 1583 return MultiReadInternal<TFirst, TSecond, DontMap, DontMap, DontMap, TReturn>(func, splitOn); 1584 } 1585 1586 #if CSHARP30 1587 public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn) 1588 #else 1589 public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id") 1590 #endif 1591 { 1592 return MultiReadInternal<TFirst, TSecond, TThird, DontMap, DontMap, TReturn>(func, splitOn); 1593 } 1594 1595 #if CSHARP30 1596 public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn) 1597 #else 1598 public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id") 1599 #endif 1600 { 1601 return MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(func, splitOn); 1602 } 1603 1604 #if !CSHARP30 1605 public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TFifth, TReturn> func, string splitOn = "id") 1606 { 1607 return MultiReadInternal<TFirst, TSecond, TThird, TFourth, TFifth, TReturn>(func, splitOn); 1608 } 1609 #endif 1610 1611 private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity, Func<Func<IDataReader, object>> deserializerGenerator) 1612 { 1613 try 1614 { 1615 while (index == gridIndex && reader.Read()) 1616 { 1617 object next; 1618 try 1619 { 1620 next = deserializer(reader); 1621 } 1622 catch (DataException) 1623 { 1624 deserializer = deserializerGenerator(); 1625 next = deserializer(reader); 1626 } 1627 yield return (T)next; 1628 } 1629 } 1630 finally // finally so that First etc progresses things even when multiple rows 1631 { 1632 if (index == gridIndex) 1633 { 1634 NextResult(); 1635 } 1636 } 1637 } 1638 private int gridIndex; 1639 private bool consumed; 1640 private void NextResult() 1641 { 1642 if (reader.NextResult()) 1643 { 1644 gridIndex++; 1645 consumed = false; 1646 } 1647 else 1648 { 1649 Dispose(); 1650 } 1651 1652 } 1653 public void Dispose() 1654 { 1655 if (reader != null) 1656 { 1657 reader.Dispose(); 1658 reader = null; 1659 } 1660 if (command != null) 1661 { 1662 command.Dispose(); 1663 command = null; 1664 } 1665 } 1666 } 1667 } 1668 1669 public class DynamicParameters : SqlMapper.IDynamicParameters 1670 { 1671 static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>(); 1672 1673 Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>(); 1674 List<object> templates; 1675 1676 class ParamInfo 1677 { 1678 public string Name { get; set; } 1679 public object Value { get; set; } 1680 public ParameterDirection ParameterDirection { get; set; } 1681 public DbType? DbType { get; set; } 1682 public int? Size { get; set; } 1683 public IDbDataParameter AttachedParam { get; set; } 1684 } 1685 1686 public DynamicParameters() { } 1687 public DynamicParameters(object template) 1688 { 1689 if (template != null) 1690 { 1691 AddDynamicParams(template); 1692 } 1693 } 1694 1695 /// <summary> 1696 /// Append a whole object full of params to the dynamic 1697 /// EG: AddParams(new {A = 1, B = 2}) // will add property A and B to the dynamic 1698 /// </summary> 1699 /// <param name="param"></param> 1700 public void AddDynamicParams( 1701 #if CSHARP30 1702 object param 1703 #else 1704 dynamic param 1705 #endif 1706 ) 1707 { 1708 object obj = param as object; 1709 1710 if (obj != null) 1711 { 1712 templates = templates ?? new List<object>(); 1713 templates.Add(obj); 1714 } 1715 } 1716 1717 1718 public void Add( 1719 #if CSHARP30 1720 string name, object value, DbType? dbType, ParameterDirection? direction, int? size 1721 #else 1722 string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null 1723 #endif 1724 ) 1725 { 1726 parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size }; 1727 } 1728 1729 static string Clean(string name) 1730 { 1731 if (!string.IsNullOrEmpty(name)) 1732 { 1733 switch (name[0]) 1734 { 1735 case '@': 1736 case ':': 1737 case '?': 1738 return name.Substring(1); 1739 } 1740 } 1741 return name; 1742 } 1743 1744 void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity) 1745 { 1746 if (templates != null) 1747 { 1748 foreach (var template in templates) 1749 { 1750 var newIdent = identity.ForDynamicParameters(template.GetType()); 1751 Action<IDbCommand, object> appender; 1752 1753 lock (paramReaderCache) 1754 { 1755 if (!paramReaderCache.TryGetValue(newIdent, out appender)) 1756 { 1757 appender = SqlMapper.CreateParamInfoGenerator(newIdent); 1758 paramReaderCache[newIdent] = appender; 1759 } 1760 } 1761 1762 appender(command, template); 1763 } 1764 } 1765 1766 foreach (var param in parameters.Values) 1767 { 1768 var p = command.CreateParameter(); 1769 var val = param.Value; 1770 p.ParameterName = param.Name; 1771 p.Value = val ?? DBNull.Value; 1772 p.Direction = param.ParameterDirection; 1773 var s = val as string; 1774 if (s != null) 1775 { 1776 if (s.Length <= 4000) 1777 { 1778 p.Size = 4000; 1779 } 1780 } 1781 if (param.Size != null) 1782 { 1783 p.Size = param.Size.Value; 1784 } 1785 if (param.DbType != null) 1786 { 1787 p.DbType = param.DbType.Value; 1788 } 1789 command.Parameters.Add(p); 1790 param.AttachedParam = p; 1791 } 1792 } 1793 1794 public T Get<T>(string name) 1795 { 1796 var val = parameters[Clean(name)].AttachedParam.Value; 1797 if (val == DBNull.Value) 1798 { 1799 if (default(T) != null) 1800 { 1801 throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!"); 1802 } 1803 return default(T); 1804 } 1805 return (T)val; 1806 } 1807 } 1808 1809 public class OracleDynamicParameters : SqlMapper.IDynamicParameters 1810 { 1811 private readonly DynamicParameters dynamicParameters = new DynamicParameters(); 1812 1813 private readonly List<OracleParameter> oracleParameters = new List<OracleParameter>(); 1814 1815 public void Add(string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null) 1816 { 1817 dynamicParameters.Add(name, value, dbType, direction, size); 1818 } 1819 1820 public void Add(string name, OracleDbType oracleDbType, ParameterDirection direction) 1821 { 1822 var oracleParameter = new OracleParameter(name, oracleDbType, direction); 1823 oracleParameters.Add(oracleParameter); 1824 } 1825 1826 public void AddParameters(IDbCommand command, SqlMapper.Identity identity) 1827 { 1828 ((SqlMapper.IDynamicParameters)dynamicParameters).AddParameters(command, identity); 1829 1830 var oracleCommand = command as OracleCommand; 1831 1832 if (oracleCommand != null) 1833 { 1834 oracleCommand.Parameters.AddRange(oracleParameters.ToArray()); 1835 } 1836 } 1837 } 1838 1839 public sealed class DbString 1840 { 1841 public DbString() { Length = -1; } 1842 public bool IsAnsi { get; set; } 1843 public bool IsFixedLength { get; set; } 1844 public int Length { get; set; } 1845 public string Value { get; set; } 1846 public void AddParameter(IDbCommand command, string name) 1847 { 1848 if (IsFixedLength && Length == -1) 1849 { 1850 throw new InvalidOperationException("If specifying IsFixedLength, a Length must also be specified"); 1851 } 1852 var param = command.CreateParameter(); 1853 param.ParameterName = name; 1854 param.Value = (object)Value ?? DBNull.Value; 1855 if (Length == -1 && Value != null && Value.Length <= 4000) 1856 { 1857 param.Size = 4000; 1858 } 1859 else 1860 { 1861 param.Size = Length; 1862 } 1863 param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String); 1864 command.Parameters.Add(param); 1865 } 1866 } 1867 } View Codeok,擴展寫完了,來一個單元測試,試一試:
1 /// <summary> 2 /// 執行帶參數存儲過程,並返回結果 3 /// </summary> 4 public static void ExectPro() 5 { 6 var p = new OracleDynamicParameters(); 7 p.Add("beginTime", 201501); 8 p.Add("endTime", 201512); 9 p.Add("targetColumn", "tax"); 10 p.Add("vCur", OracleDbType.RefCursor, ParameterDirection.Output); 11 using (IDbConnection conn = new OracleConnection(SqlConnOdp)) 12 { 13 conn.Open(); 14 var aa = conn.Query("p_123c", param: p, commandType: CommandType.StoredProcedure).ToList(); 15 aa.ForEach(m => Console.WriteLine(m.C_NAME)); 16 } 17 Console.ReadLine(); 18 }
結果執行通過,並打印了首列的所有值。
那麼,Dapper的簡單擴展就完成了。
寫在後面
補充說明: 我用的Oracle驅動是ODP.NET,.net是4.0
這個ODP.NET的Oracle.DataAccess.dll推薦從你的目標服務器,復制回來,不要用本地的,反正我用本地的,就提示外部程序錯誤。猜測是版本問題或者是位數問題。
相關參考文章
http://stackoverflow.com/questions/6212992/using-dapper-with-oracle
https://stackoverflow.com/questions/15943389/using-dapper-with-oracle-user-defined-types
http://stackoverflow.com/questions/7390015/using-dapper-with-oracle-stored-procedures-which-return-cursors