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

搬家到博客园:https://www.cnblogs.com/likeli/p/4875341.html

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 类,如下图:

Dapper完美兼容Oracle,执行存储过程,并返回结果集。_第1张图片

可以看到,这个类,是实现了一个接口的。说明,原作者给我们预留了接口去自己实现其他内容。

继续看看接口:

Dapper完美兼容Oracle,执行存储过程,并返回结果集。_第2张图片

接口的内容很简单,就是一个AddParameters方法。

那么,可以确定,上面的猜测是对的。

我们直接扩展实现这个接口就可以了。如图:

Dapper完美兼容Oracle,执行存储过程,并返回结果集。_第3张图片

自己去创建一个实现了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> bindByNameCache;
  33         static Action GetBindByName(Type commandType)
  34         {
  35             if (commandType == null) return null; // GIGO
  36             Action action;
  37             if (Link>.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)method.CreateDelegate(typeof(Action));
  58             }
  59             // cache it            
  60             Link>.TryAdd(ref bindByNameCache, commandType, ref action);
  61             return action;
  62         }
  63         /// 
  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         /// 
  68         class Link where TKey : class
  69         {
  70             public static bool TryGet(Link 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 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(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 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 Tail { get; private set; }
 111         }
 112         class CacheInfo
 113         {
 114             public Func Deserializer { get; set; }
 115             public Func[] OtherDeserializers { get; set; }
 116             public Action 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 _queryCache = new Dictionary();
 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 _queryCache = new System.Collections.Concurrent.ConcurrentDictionary();
 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> 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> GetHashCollissions()
 212         {
 213             var counts = new Dictionary();
 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 typeMap;
 235 
 236         static SqlMapper()
 237         {
 238             typeMap = new Dictionary();
 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
 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         /// 
 374         /// Execute parameterized SQL  
 375         /// 
 376         /// Number of rows affected
 377         public static int Execute(this IDbConnection cnn, string sql, object param)
 378         {
 379             return Execute(cnn, sql, param, null, null, null);
 380         }
 381         /// 
 382         /// Executes a query, returning the data typed as per T
 383         /// 
 384         /// 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  get new object
 385         /// 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         /// 
 388         public static IEnumerable Query(this IDbConnection cnn, string sql, object param)
 389         {
 390             return Query(cnn, sql, param, null, true, null, null);
 391         }
 392 
 393 #endif
 394         /// 
 395         /// Execute parameterized SQL  
 396         /// 
 397         /// Number of rows affected
 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         /// 
 445         /// Return a list of dynamic objects, reader is closed after the call
 446         /// 
 447         public static IEnumerable 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(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType);
 450         }
 451 #endif
 452 
 453         /// 
 454         /// Executes a query, returning the data typed as per T
 455         /// 
 456         /// 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  get new object
 457         /// 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         /// 
 460         public static IEnumerable Query(
 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(cnn, sql, param as object, transaction, commandTimeout, commandType);
 469             return buffered ? data.ToList() : data;
 470         }
 471 
 472         /// 
 473         /// Execute a command that returns multiple result sets, and access each in turn
 474         /// 
 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         /// 
 503         /// Return a typed list of objects, reader is closed after the call
 504         /// 
 505         private static IEnumerable QueryInternal(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> 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         /// 
 549         /// Maps a query to objects
 550         /// 
 551         /// The return type
 552         /// 
 553         /// 
 554         /// 
 555         /// 
 556         /// 
 557         /// 
 558         /// 
 559         /// The Field we should split and read the second object from (default: id)
 560         /// Number of seconds before command execution timeout
 561         /// 
 562         public static IEnumerable Query(
 563 #if CSHARP30  
 564             this IDbConnection cnn, string sql, Func map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
 565 #else
 566 this IDbConnection cnn, string sql, Func 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(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
 571         }
 572 
 573         public static IEnumerable Query(
 574 #if CSHARP30
 575             this IDbConnection cnn, string sql, Func map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
 576 #else
 577 this IDbConnection cnn, string sql, Func 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(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
 582         }
 583 
 584         public static IEnumerable Query(
 585 #if CSHARP30
 586             this IDbConnection cnn, string sql, Func map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType
 587 #else
 588 this IDbConnection cnn, string sql, Func 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(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
 593         }
 594 #if !CSHARP30
 595         public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, dynamic param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null)
 596         {
 597             return MultiMap(cnn, sql, map, param as object, transaction, buffered, splitOn, commandTimeout, commandType);
 598         }
 599 #endif
 600         class DontMap { }
 601         static IEnumerable MultiMap(
 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(cnn, sql, map, param, transaction, splitOn, commandTimeout, commandType, null, null);
 605             return buffered ? results.ToList() : results;
 606         }
 607 
 608 
 609         static IEnumerable MultiMapImpl(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 deserializer = null;
 626                 Func[] 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 mapIt = GenerateMapper(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(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 GenerateMapper(Func deserializer, Func[] otherDeserializers, object map)
 682         {
 683             switch (otherDeserializers.Length)
 684             {
 685                 case 1:
 686                     return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r));
 687                 case 2:
 688                     return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r));
 689                 case 3:
 690                     return r => ((Func)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)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[] 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 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>();
 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 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
 817         {
 818             IDictionary data;
 819 
 820             public static FastExpando Attach(IDictionary 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 Members
 837 
 838             void IDictionary.Add(string key, object value)
 839             {
 840                 throw new NotImplementedException();
 841             }
 842 
 843             bool IDictionary.ContainsKey(string key)
 844             {
 845                 return data.ContainsKey(key);
 846             }
 847 
 848             ICollection IDictionary.Keys
 849             {
 850                 get { return data.Keys; }
 851             }
 852 
 853             bool IDictionary.Remove(string key)
 854             {
 855                 throw new NotImplementedException();
 856             }
 857 
 858             bool IDictionary.TryGetValue(string key, out object value)
 859             {
 860                 return data.TryGetValue(key, out value);
 861             }
 862 
 863             ICollection IDictionary.Values
 864             {
 865                 get { return data.Values; }
 866             }
 867 
 868             object IDictionary.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> Members
 883 
 884             void ICollection>.Add(KeyValuePair item)
 885             {
 886                 throw new NotImplementedException();
 887             }
 888 
 889             void ICollection>.Clear()
 890             {
 891                 throw new NotImplementedException();
 892             }
 893 
 894             bool ICollection>.Contains(KeyValuePair item)
 895             {
 896                 return data.Contains(item);
 897             }
 898 
 899             void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex)
 900             {
 901                 data.CopyTo(array, arrayIndex);
 902             }
 903 
 904             int ICollection>.Count
 905             {
 906                 get { return data.Count; }
 907             }
 908 
 909             bool ICollection>.IsReadOnly
 910             {
 911                 get { return true; }
 912             }
 913 
 914             bool ICollection>.Remove(KeyValuePair item)
 915             {
 916                 throw new NotImplementedException();
 917             }
 918 
 919             #endregion
 920 
 921             #region IEnumerable> Members
 922 
 923             IEnumerator> IEnumerable>.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 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 row = new Dictionary(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;
1004                 foreach (var item in list)
1005                 {
1006                     count++;
1007                     var listParam = command.CreateParameter();
1008                     listParam.ParameterName = namePrefix + count;
1009                     listParam.Value = item ?? DBNull.Value;
1010                     if (isString)
1011                     {
1012                         listParam.Size = 4000;
1013                         if (item != null && ((string)item).Length > 4000)
1014                         {
1015                             listParam.Size = -1;
1016                         }
1017                     }
1018                     command.Parameters.Add(listParam);
1019                 }
1020 
1021                 if (count == 0)
1022                 {
1023                     command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), "(SELECT NULL WHERE 1 = 0)");
1024                 }
1025                 else
1026                 {
1027                     command.CommandText = Regex.Replace(command.CommandText, @"[?@:]" + Regex.Escape(namePrefix), match =>
1028                     {
1029                         var grp = match.Value;
1030                         var sb = new StringBuilder("(").Append(grp).Append(1);
1031                         for (int i = 2; i <= count; i++)
1032                         {
1033                             sb.Append(',').Append(grp).Append(i);
1034                         }
1035                         return sb.Append(')').ToString();
1036                     });
1037                 }
1038             }
1039 
1040         }
1041 
1042         private static IEnumerable FilterParameters(IEnumerable 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 CreateParamInfoGenerator(Identity identity)
1048         {
1049             Type type = identity.parametersType;
1050             bool filterParams = identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text;
1051 
1052             var dm = new DynamicMethod(string.Format("ParamInfo{0}", Guid.NewGuid()), null, new[] { typeof(IDbCommand), typeof(object) }, type, true);
1053 
1054             var il = dm.GetILGenerator();
1055 
1056             il.DeclareLocal(type); // 0
1057             bool haveInt32Arg1 = false;
1058             il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param]
1059             il.Emit(OpCodes.Unbox_Any, type); // stack is now [typed-param]
1060             il.Emit(OpCodes.Stloc_0);// stack is now empty
1061 
1062             il.Emit(OpCodes.Ldarg_0); // stack is now [command]
1063             il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty("Parameters").GetGetMethod(), null); // stack is now [parameters]
1064 
1065             IEnumerable props = type.GetProperties().OrderBy(p => p.Name);
1066             if (filterParams)
1067             {
1068                 props = FilterParameters(props, identity.sql);
1069             }
1070             foreach (var prop in props)
1071             {
1072                 if (filterParams)
1073                 {
1074                     if (identity.sql.IndexOf("@" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0
1075                         && identity.sql.IndexOf(":" + prop.Name, StringComparison.InvariantCultureIgnoreCase) < 0)
1076                     { // can't see the parameter in the text (even in a comment, etc) - burn it with fire
1077                         continue;
1078                     }
1079                 }
1080                 if (prop.PropertyType == typeof(DbString))
1081                 {
1082                     il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param]
1083                     il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [dbstring]
1084                     il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [dbstring] [command]
1085                     il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [dbstring] [command] [name]
1086                     il.EmitCall(OpCodes.Callvirt, typeof(DbString).GetMethod("AddParameter"), null); // stack is now [parameters]
1087                     continue;
1088                 }
1089                 DbType dbType = LookupDbType(prop.PropertyType, prop.Name);
1090                 if (dbType == DbType.Xml)
1091                 {
1092                     // this actually represents special handling for list types;
1093                     il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command]
1094                     il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name]
1095                     il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param]
1096                     il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value]
1097                     if (prop.PropertyType.IsValueType)
1098                     {
1099                         il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value]
1100                     }
1101                     il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("PackListParameters"), null); // stack is [parameters]
1102                     continue;
1103                 }
1104                 il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters]
1105 
1106                 il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [parameters] [command]
1107                 il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod("CreateParameter"), null);// stack is now [parameters] [parameters] [parameter]
1108 
1109                 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
1110                 il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name]
1111                 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("ParameterName").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
1112 
1113                 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
1114                 EmitInt32(il, (int)dbType);// stack is now [parameters] [parameters] [parameter] [parameter] [db-type]
1115 
1116                 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("DbType").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
1117 
1118                 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
1119                 EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [parameters] [parameter] [parameter] [dir]
1120                 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Direction").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
1121 
1122                 il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
1123                 il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [parameters] [parameter] [parameter] [typed-param]
1124                 il.Emit(OpCodes.Callvirt, prop.GetGetMethod()); // stack is [parameters] [parameters] [parameter] [parameter] [typed-value]
1125                 bool checkForNull = true;
1126                 if (prop.PropertyType.IsValueType)
1127                 {
1128                     il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [parameters] [parameter] [parameter] [boxed-value]
1129                     if (Nullable.GetUnderlyingType(prop.PropertyType) == null)
1130                     {   // struct but not Nullable; boxed value cannot be null
1131                         checkForNull = false;
1132                     }
1133                 }
1134                 if (checkForNull)
1135                 {
1136                     if (dbType == DbType.String && !haveInt32Arg1)
1137                     {
1138                         il.DeclareLocal(typeof(int));
1139                         haveInt32Arg1 = true;
1140                     }
1141                     // relative stack: [boxed value]
1142                     il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value]
1143                     Label notNull = il.DefineLabel();
1144                     Label? allDone = dbType == DbType.String ? il.DefineLabel() : (Label?)null;
1145                     il.Emit(OpCodes.Brtrue_S, notNull);
1146                     // relative stack [boxed value = null]
1147                     il.Emit(OpCodes.Pop); // relative stack empty
1148                     il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField("Value")); // relative stack [DBNull]
1149                     if (dbType == DbType.String)
1150                     {
1151                         EmitInt32(il, 0);
1152                         il.Emit(OpCodes.Stloc_1);
1153                     }
1154                     if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value);
1155                     il.MarkLabel(notNull);
1156                     if (prop.PropertyType == typeof(string))
1157                     {
1158                         il.Emit(OpCodes.Dup); // [string] [string]
1159                         il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty("Length").GetGetMethod(), null); // [string] [length]
1160                         EmitInt32(il, 4000); // [string] [length] [4000]
1161                         il.Emit(OpCodes.Cgt); // [string] [0 or 1]
1162                         Label isLong = il.DefineLabel(), lenDone = il.DefineLabel();
1163                         il.Emit(OpCodes.Brtrue_S, isLong);
1164                         EmitInt32(il, 4000); // [string] [4000]
1165                         il.Emit(OpCodes.Br_S, lenDone);
1166                         il.MarkLabel(isLong);
1167                         EmitInt32(il, -1); // [string] [-1]
1168                         il.MarkLabel(lenDone);
1169                         il.Emit(OpCodes.Stloc_1); // [string] 
1170                     }
1171                     if (prop.PropertyType == typeof(System.Data.Linq.Binary))
1172                     {
1173                         il.EmitCall(OpCodes.Callvirt, typeof(System.Data.Linq.Binary).GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null);
1174                     }
1175                     if (allDone != null) il.MarkLabel(allDone.Value);
1176                     // relative stack [boxed value or DBNull]
1177                 }
1178                 il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty("Value").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
1179 
1180                 if (prop.PropertyType == typeof(string))
1181                 {
1182                     var endOfSize = il.DefineLabel();
1183                     // don't set if 0
1184                     il.Emit(OpCodes.Ldloc_1); // [parameters] [parameters] [parameter] [size]
1185                     il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [parameters] [parameter]
1186 
1187                     il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter]
1188                     il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [parameters] [parameter] [parameter] [size]
1189                     il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty("Size").GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter]
1190 
1191                     il.MarkLabel(endOfSize);
1192                 }
1193 
1194                 il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod("Add"), null); // stack is now [parameters]
1195                 il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
1196             }
1197             // stack is currently [command]
1198             il.Emit(OpCodes.Pop); // stack is now empty
1199             il.Emit(OpCodes.Ret);
1200             return (Action)dm.CreateDelegate(typeof(Action));
1201         }
1202 
1203         private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action 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 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 GetStructDeserializer(Type type, int index)
1231         {
1232             // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!)
1233 #pragma warning disable 618
1234             if (type == typeof(char))
1235             { // this *does* need special handling, though
1236                 return r => SqlMapper.ReadChar(r.GetValue(index));
1237             }
1238             if (type == typeof(char?))
1239             {
1240                 return r => SqlMapper.ReadNullableChar(r.GetValue(index));
1241             }
1242             if (type == typeof(System.Data.Linq.Binary))
1243             {
1244                 return r => new System.Data.Linq.Binary((byte[])r.GetValue(index));
1245             }
1246 #pragma warning restore 618
1247             return r =>
1248             {
1249                 var val = r.GetValue(index);
1250                 return val is DBNull ? null : Convert.ChangeType(val, type);
1251             };
1252         }
1253 
1254         static readonly MethodInfo
1255                     enumParse = typeof(Enum).GetMethod("Parse", new Type[] { typeof(Type), typeof(string), typeof(bool) }),
1256                     getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public)
1257                         .Where(p => p.GetIndexParameters().Any() && p.GetIndexParameters()[0].ParameterType == typeof(int))
1258                         .Select(p => p.GetGetMethod()).First();
1259 
1260         class PropInfo
1261         {
1262             public string Name { get; set; }
1263             public MethodInfo Setter { get; set; }
1264             public Type Type { get; set; }
1265         }
1266 
1267         static List GetSettableProps(Type t)
1268         {
1269             return t
1270                   .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
1271                   .Select(p => new PropInfo
1272                   {
1273                       Name = p.Name,
1274                       Setter = p.DeclaringType == t ? p.GetSetMethod(true) : p.DeclaringType.GetProperty(p.Name).GetSetMethod(true),
1275                       Type = p.PropertyType
1276                   })
1277                   .Where(info => info.Setter != null)
1278                   .ToList();
1279         }
1280 
1281         static List GetSettableFields(Type t)
1282         {
1283             return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList();
1284         }
1285 
1286         public static Func GetClassDeserializer(
1287 #if CSHARP30
1288             Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing
1289 #else
1290 Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false
1291 #endif
1292 )
1293         {
1294             var dm = new DynamicMethod(string.Format("Deserialize{0}", Guid.NewGuid()), type, new[] { typeof(IDataReader) }, true);
1295 
1296             var il = dm.GetILGenerator();
1297             il.DeclareLocal(typeof(int));
1298             il.DeclareLocal(type);
1299             bool haveEnumLocal = false;
1300             il.Emit(OpCodes.Ldc_I4_0);
1301             il.Emit(OpCodes.Stloc_0);
1302             var properties = GetSettableProps(type);
1303             var fields = GetSettableFields(type);
1304             if (length == -1)
1305             {
1306                 length = reader.FieldCount - startBound;
1307             }
1308 
1309             if (reader.FieldCount <= startBound)
1310             {
1311                 throw new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn");
1312             }
1313 
1314             var names = new List();
1315 
1316             for (int i = startBound; i < startBound + length; i++)
1317             {
1318                 names.Add(reader.GetName(i));
1319             }
1320 
1321             var setters = (
1322                             from n in names
1323                             let prop = properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // property case sensitive first
1324                                   ?? properties.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase)) // property case insensitive second
1325                             let field = prop != null ? null : (fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.Ordinal)) // field case sensitive third
1326                                 ?? fields.FirstOrDefault(p => string.Equals(p.Name, n, StringComparison.OrdinalIgnoreCase))) // field case insensitive fourth
1327                             select new { Name = n, Property = prop, Field = field }
1328                           ).ToList();
1329 
1330             int index = startBound;
1331 
1332             il.BeginExceptionBlock();
1333             // stack is empty
1334             il.Emit(OpCodes.Newobj, type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, Type.EmptyTypes, null)); // stack is now [target]
1335             bool first = true;
1336             var allDone = il.DefineLabel();
1337             foreach (var item in setters)
1338             {
1339                 if (item.Property != null || item.Field != null)
1340                 {
1341                     il.Emit(OpCodes.Dup); // stack is now [target][target]
1342                     Label isDbNullLabel = il.DefineLabel();
1343                     Label finishLabel = il.DefineLabel();
1344 
1345                     il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader]
1346                     EmitInt32(il, index); // stack is now [target][target][reader][index]
1347                     il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index]
1348                     il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index]
1349                     il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object]
1350 
1351 
1352                     Type memberType = item.Property != null ? item.Property.Type : item.Field.FieldType;
1353 
1354                     if (memberType == typeof(char) || memberType == typeof(char?))
1355                     {
1356                         il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(
1357                             memberType == typeof(char) ? "ReadChar" : "ReadNullableChar", BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value]
1358                     }
1359                     else
1360                     {
1361                         il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
1362                         il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null]
1363                         il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object]
1364 
1365                         // unbox nullable enums as the primitive, i.e. byte etc
1366 
1367                         var nullUnderlyingType = Nullable.GetUnderlyingType(memberType);
1368                         var unboxType = nullUnderlyingType != null && nullUnderlyingType.IsEnum ? nullUnderlyingType : memberType;
1369 
1370                         if (unboxType.IsEnum)
1371                         {
1372                             if (!haveEnumLocal)
1373                             {
1374                                 il.DeclareLocal(typeof(string));
1375                                 haveEnumLocal = true;
1376                             }
1377 
1378                             Label isNotString = il.DefineLabel();
1379                             il.Emit(OpCodes.Dup); // stack is now [target][target][value][value]
1380                             il.Emit(OpCodes.Isinst, typeof(string)); // stack is now [target][target][value-as-object][string or null]
1381                             il.Emit(OpCodes.Dup);// stack is now [target][target][value-as-object][string or null][string or null]
1382                             il.Emit(OpCodes.Stloc_2); // stack is now [target][target][value-as-object][string or null]
1383                             il.Emit(OpCodes.Brfalse_S, isNotString); // stack is now [target][target][value-as-object]
1384 
1385                             il.Emit(OpCodes.Pop); // stack is now [target][target]
1386 
1387 
1388                             il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token]
1389                             il.EmitCall(OpCodes.Call, typeof(Type).GetMethod("GetTypeFromHandle"), null);// stack is now [target][target][enum-type]
1390                             il.Emit(OpCodes.Ldloc_2); // stack is now [target][target][enum-type][string]
1391                             il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true]
1392                             il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object]
1393 
1394                             il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
1395 
1396                             if (nullUnderlyingType != null)
1397                             {
1398                                 il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType }));
1399                             }
1400                             if (item.Property != null)
1401                             {
1402                                 il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target]
1403                             }
1404                             else
1405                             {
1406                                 il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
1407                             }
1408                             il.Emit(OpCodes.Br_S, finishLabel);
1409 
1410 
1411                             il.MarkLabel(isNotString);
1412                         }
1413                         if (memberType == typeof(System.Data.Linq.Binary))
1414                         {
1415                             il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array]
1416                             il.Emit(OpCodes.Newobj, typeof(System.Data.Linq.Binary).GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary]
1417                         }
1418                         else
1419                         {
1420                             il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value]
1421                         }
1422                         if (nullUnderlyingType != null && nullUnderlyingType.IsEnum)
1423                         {
1424                             il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType }));
1425                         }
1426                     }
1427                     if (item.Property != null)
1428                     {
1429                         il.Emit(OpCodes.Callvirt, item.Property.Setter); // stack is now [target]
1430                     }
1431                     else
1432                     {
1433                         il.Emit(OpCodes.Stfld, item.Field); // stack is now [target]
1434                     }
1435 
1436                     il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target]
1437 
1438                     il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value]
1439 
1440                     il.Emit(OpCodes.Pop); // stack is now [target][target]
1441                     il.Emit(OpCodes.Pop); // stack is now [target]
1442 
1443                     if (first && returnNullIfFirstMissing)
1444                     {
1445                         il.Emit(OpCodes.Pop);
1446                         il.Emit(OpCodes.Ldnull); // stack is now [null]
1447                         il.Emit(OpCodes.Stloc_1);
1448                         il.Emit(OpCodes.Br, allDone);
1449                     }
1450 
1451                     il.MarkLabel(finishLabel);
1452                 }
1453                 first = false;
1454                 index += 1;
1455             }
1456             il.Emit(OpCodes.Stloc_1); // stack is empty
1457             il.MarkLabel(allDone);
1458             il.BeginCatchBlock(typeof(Exception)); // stack is Exception
1459             il.Emit(OpCodes.Ldloc_0); // stack is Exception, index
1460             il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader
1461             il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod("ThrowDataException"), null);
1462             il.Emit(OpCodes.Ldnull);
1463             il.Emit(OpCodes.Stloc_1); // to make it verifiable
1464             il.EndExceptionBlock();
1465 
1466             il.Emit(OpCodes.Ldloc_1); // stack is empty
1467             il.Emit(OpCodes.Ret);
1468 
1469             return (Func)dm.CreateDelegate(typeof(Func));
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 = "";
1481                 }
1482                 else
1483                 {
1484                     value = Convert.ToString(val) + " - " + Type.GetTypeCode(val.GetType());
1485                 }
1486             }
1487             throw new DataException(string.Format("Error parsing column {0} ({1}={2})", index, name, value), ex);
1488         }
1489         private static void EmitInt32(ILGenerator il, int value)
1490         {
1491             switch (value)
1492             {
1493                 case -1: il.Emit(OpCodes.Ldc_I4_M1); break;
1494                 case 0: il.Emit(OpCodes.Ldc_I4_0); break;
1495                 case 1: il.Emit(OpCodes.Ldc_I4_1); break;
1496                 case 2: il.Emit(OpCodes.Ldc_I4_2); break;
1497                 case 3: il.Emit(OpCodes.Ldc_I4_3); break;
1498                 case 4: il.Emit(OpCodes.Ldc_I4_4); break;
1499                 case 5: il.Emit(OpCodes.Ldc_I4_5); break;
1500                 case 6: il.Emit(OpCodes.Ldc_I4_6); break;
1501                 case 7: il.Emit(OpCodes.Ldc_I4_7); break;
1502                 case 8: il.Emit(OpCodes.Ldc_I4_8); break;
1503                 default:
1504                     if (value >= -128 && value <= 127)
1505                     {
1506                         il.Emit(OpCodes.Ldc_I4_S, (sbyte)value);
1507                     }
1508                     else
1509                     {
1510                         il.Emit(OpCodes.Ldc_I4, value);
1511                     }
1512                     break;
1513             }
1514         }
1515 
1516         public class GridReader : IDisposable
1517         {
1518             private IDataReader reader;
1519             private IDbCommand command;
1520             private Identity identity;
1521 
1522             internal GridReader(IDbCommand command, IDataReader reader, Identity identity)
1523             {
1524                 this.command = command;
1525                 this.reader = reader;
1526                 this.identity = identity;
1527             }
1528             /// 
1529             /// Read the next grid of results
1530             /// 
1531             public IEnumerable Read()
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> 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(gridIndex, deserializer, typedIdentity, deserializerGenerator);
1552             }
1553 
1554             private IEnumerable MultiReadInternal(object func, string splitOn)
1555             {
1556 
1557                 var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { 
1558                     typeof(TFirst), 
1559                     typeof(TSecond),
1560                     typeof(TThird),
1561                     typeof(TFourth),
1562                     typeof(TFifth)
1563                 }, gridIndex);
1564                 try
1565                 {
1566                     foreach (var r in SqlMapper.MultiMapImpl(null, null, func, null, null, splitOn, null, null, reader, identity))
1567                     {
1568                         yield return r;
1569                     }
1570                 }
1571                 finally
1572                 {
1573                     NextResult();
1574                 }
1575             }
1576 
1577 #if CSHARP30  
1578             public IEnumerable Read(Func func, string splitOn)
1579 #else
1580             public IEnumerable Read(Func func, string splitOn = "id")
1581 #endif
1582             {
1583                 return MultiReadInternal(func, splitOn);
1584             }
1585 
1586 #if CSHARP30  
1587             public IEnumerable Read(Func func, string splitOn)
1588 #else
1589             public IEnumerable Read(Func func, string splitOn = "id")
1590 #endif
1591             {
1592                 return MultiReadInternal(func, splitOn);
1593             }
1594 
1595 #if CSHARP30  
1596             public IEnumerable Read(Func func, string splitOn)
1597 #else
1598             public IEnumerable Read(Func func, string splitOn = "id")
1599 #endif
1600             {
1601                 return MultiReadInternal(func, splitOn);
1602             }
1603 
1604 #if !CSHARP30
1605             public IEnumerable Read(Func func, string splitOn = "id")
1606             {
1607                 return MultiReadInternal(func, splitOn);
1608             }
1609 #endif
1610 
1611             private IEnumerable ReadDeferred(int index, Func deserializer, Identity typedIdentity, Func> deserializerGenerator)
1612             {
1613                 try
1614                 {
1615                     while (index == gridIndex && reader.Read())
1616                     {
1617                         object next;
1618                         try
1619                         {
1620                             next = deserializer(reader);
1621                         }
1622                         catch (DataException)
1623                         {
1624                             deserializer = deserializerGenerator();
1625                             next = deserializer(reader);
1626                         }
1627                         yield return (T)next;
1628                     }
1629                 }
1630                 finally // finally so that First etc progresses things even when multiple rows
1631                 {
1632                     if (index == gridIndex)
1633                     {
1634                         NextResult();
1635                     }
1636                 }
1637             }
1638             private int gridIndex;
1639             private bool consumed;
1640             private void NextResult()
1641             {
1642                 if (reader.NextResult())
1643                 {
1644                     gridIndex++;
1645                     consumed = false;
1646                 }
1647                 else
1648                 {
1649                     Dispose();
1650                 }
1651 
1652             }
1653             public void Dispose()
1654             {
1655                 if (reader != null)
1656                 {
1657                     reader.Dispose();
1658                     reader = null;
1659                 }
1660                 if (command != null)
1661                 {
1662                     command.Dispose();
1663                     command = null;
1664                 }
1665             }
1666         }
1667     }
1668 
1669     public class DynamicParameters : SqlMapper.IDynamicParameters
1670     {
1671         static Dictionary> paramReaderCache = new Dictionary>();
1672 
1673         Dictionary parameters = new Dictionary();
1674         List templates;
1675 
1676         class ParamInfo
1677         {
1678             public string Name { get; set; }
1679             public object Value { get; set; }
1680             public ParameterDirection ParameterDirection { get; set; }
1681             public DbType? DbType { get; set; }
1682             public int? Size { get; set; }
1683             public IDbDataParameter AttachedParam { get; set; }
1684         }
1685 
1686         public DynamicParameters() { }
1687         public DynamicParameters(object template)
1688         {
1689             if (template != null)
1690             {
1691                 AddDynamicParams(template);
1692             }
1693         }
1694 
1695         /// 
1696         /// Append a whole object full of params to the dynamic
1697         /// EG: AddParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
1698         /// 
1699         /// 
1700         public void AddDynamicParams(
1701 #if CSHARP30
1702             object param
1703 #else
1704 dynamic param
1705 #endif
1706 )
1707         {
1708             object obj = param as object;
1709 
1710             if (obj != null)
1711             {
1712                 templates = templates ?? new List();
1713                 templates.Add(obj);
1714             }
1715         }
1716 
1717 
1718         public void Add(
1719 #if CSHARP30
1720             string name, object value, DbType? dbType, ParameterDirection? direction, int? size
1721 #else
1722 string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null
1723 #endif
1724 )
1725         {
1726             parameters[Clean(name)] = new ParamInfo() { Name = name, Value = value, ParameterDirection = direction ?? ParameterDirection.Input, DbType = dbType, Size = size };
1727         }
1728 
1729         static string Clean(string name)
1730         {
1731             if (!string.IsNullOrEmpty(name))
1732             {
1733                 switch (name[0])
1734                 {
1735                     case '@':
1736                     case ':':
1737                     case '?':
1738                         return name.Substring(1);
1739                 }
1740             }
1741             return name;
1742         }
1743 
1744         void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
1745         {
1746             if (templates != null)
1747             {
1748                 foreach (var template in templates)
1749                 {
1750                     var newIdent = identity.ForDynamicParameters(template.GetType());
1751                     Action 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(string name)
1795         {
1796             var val = parameters[Clean(name)].AttachedParam.Value;
1797             if (val == DBNull.Value)
1798             {
1799                 if (default(T) != null)
1800                 {
1801                     throw new ApplicationException("Attempting to cast a DBNull to a non nullable type!");
1802                 }
1803                 return default(T);
1804             }
1805             return (T)val;
1806         }
1807     }
1808 
1809     public class OracleDynamicParameters : SqlMapper.IDynamicParameters
1810     {
1811         private readonly DynamicParameters dynamicParameters = new DynamicParameters();
1812 
1813         private readonly List oracleParameters = new List();
1814 
1815         public void Add(string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null)
1816         {
1817             dynamicParameters.Add(name, value, dbType, direction, size);
1818         }
1819 
1820         public void Add(string name, OracleDbType oracleDbType, ParameterDirection direction)
1821         {
1822             var oracleParameter = new OracleParameter(name, oracleDbType, direction);
1823             oracleParameters.Add(oracleParameter);
1824         }
1825 
1826         public void AddParameters(IDbCommand command, SqlMapper.Identity identity)
1827         {
1828             ((SqlMapper.IDynamicParameters)dynamicParameters).AddParameters(command, identity);
1829 
1830             var oracleCommand = command as OracleCommand;
1831 
1832             if (oracleCommand != null)
1833             {
1834                 oracleCommand.Parameters.AddRange(oracleParameters.ToArray());
1835             }
1836         }
1837     }
1838 
1839     public sealed class DbString
1840     {
1841         public DbString() { Length = -1; }
1842         public bool IsAnsi { get; set; }
1843         public bool IsFixedLength { get; set; }
1844         public int Length { get; set; }
1845         public string Value { get; set; }
1846         public void AddParameter(IDbCommand command, string name)
1847         {
1848             if (IsFixedLength && Length == -1)
1849             {
1850                 throw new InvalidOperationException("If specifying IsFixedLength,  a Length must also be specified");
1851             }
1852             var param = command.CreateParameter();
1853             param.ParameterName = name;
1854             param.Value = (object)Value ?? DBNull.Value;
1855             if (Length == -1 && Value != null && Value.Length <= 4000)
1856             {
1857                 param.Size = 4000;
1858             }
1859             else
1860             {
1861                 param.Size = Length;
1862             }
1863             param.DbType = IsAnsi ? (IsFixedLength ? DbType.AnsiStringFixedLength : DbType.AnsiString) : (IsFixedLength ? DbType.StringFixedLength : DbType.String);
1864             command.Parameters.Add(param);
1865         }
1866     }
1867 } 
  

View Code

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

 1         /// 
 2         /// 执行带参数存储过程,并返回结果
 3         /// 
 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

你可能感兴趣的:(Dapper完美兼容Oracle,执行存储过程,并返回结果集。)