当前位置: 代码迷 >> Oracle开发 >> Dapper完善兼容Oracle,执行存储过程,并返回结果集
  详细解决方案

Dapper完善兼容Oracle,执行存储过程,并返回结果集

热度:171   发布时间:2016-04-24 06:24:31.0
Dapper完美兼容Oracle,执行存储过程,并返回结果集。

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                 else1026                 {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); // 01057             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 empty1061 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) < 01075                         && 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 fire1077                         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 null1131                         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 empty1148                     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 01184                     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 care1196             }1197             // stack is currently [command]1198             il.Emit(OpCodes.Pop); // stack is now empty1199             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 6181234             if (type == typeof(char))1235             { // this *does* need special handling, though1236                 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 6181247             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 MethodInfo1255                     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 PropInfo1261         {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 t1270                   .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)1271                   .Select(p => new PropInfo1272                   {1273                       Name = p.Name,1274                       Setter = p.DeclaringType == t ? p.GetSetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetSetMethod(true),1275                       Type = p.PropertyType1276                   })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 CSHARP301288             Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing1289 #else1290 Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false1291 #endif1292 )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 names1323                             let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // property case sensitive first1324                                   ?? properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase)) // property case insensitive second1325                             let field = prop != null ? null : (fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // field case sensitive third1326                                 ?? fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase))) // field case insensitive fourth1327                             select new { Name = n, Property = prop, Field = field }1328                           ).ToList();1329 1330             int index = startBound;1331 1332             il.BeginExceptionBlock();1333             // stack is empty1334             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                     else1360                     {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 etc1366 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                             else1405                             {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                         else1419                         {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                     else1432                     {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 empty1457             il.MarkLabel(allDone);1458             il.BeginCatchBlock(typeof(Exception)); // stack is Exception1459             il.Emit(OpCodes.Ldloc_0); // stack is Exception, index1460             il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader1461             il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);1462             il.Emit(OpCodes.Ldnull);1463             il.Emit(OpCodes.Stloc_1); // to make it verifiable1464             il.EndExceptionBlock();1465 1466             il.Emit(OpCodes.Ldloc_1); // stack is empty1467             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                 else1483                 {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                     else1509                     {1510                         il.Emit(OpCodes.Ldc_I4, value);1511                     }1512                     break;1513             }1514         }1515 1516         public class GridReader : IDisposable1517         {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 results1530             /// </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                 try1565                 {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                 finally1572                 {1573                     NextResult();1574                 }1575             }1576 1577 #if CSHARP30  1578             public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn)1579 #else1580             public IEnumerable<TReturn> Read<TFirst, TSecond, TReturn>(Func<TFirst, TSecond, TReturn> func, string splitOn = "id")1581 #endif1582             {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 #else1589             public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TReturn>(Func<TFirst, TSecond, TThird, TReturn> func, string splitOn = "id")1590 #endif1591             {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 #else1598             public IEnumerable<TReturn> Read<TFirst, TSecond, TThird, TFourth, TReturn>(Func<TFirst, TSecond, TThird, TFourth, TReturn> func, string splitOn = "id")1599 #endif1600             {1601                 return MultiReadInternal<TFirst, TSecond, TThird, TFourth, DontMap, TReturn>(func, splitOn);1602             }1603 1604 #if !CSHARP301605             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 #endif1610 1611             private IEnumerable<T> ReadDeferred<T>(int index, Func<IDataReader, object> deserializer, Identity typedIdentity, Func<Func<IDataReader, object>> deserializerGenerator)1612             {1613                 try1614                 {1615                     while (index == gridIndex && reader.Read())1616                     {1617                         object next;1618                         try1619                         {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 rows1631                 {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                 else1648                 {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.IDynamicParameters1670     {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 ParamInfo1677         {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 dynamic1697         /// EG: AddParams(new {A = 1, B = 2}) // will add property A and B to the dynamic1698         /// </summary>1699         /// <param name="param"></param>1700         public void AddDynamicParams(1701 #if CSHARP301702             object param1703 #else1704 dynamic param1705 #endif1706 )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 CSHARP301720             string name, object value, DbType? dbType, ParameterDirection? direction, int? size1721 #else1722 string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null1723 #endif1724 )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.IDynamicParameters1810     {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 DbString1840     {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             else1860             {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 Code

ok,扩展写完了,来一个单元测试,试一试:

 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

  相关解决方案