From d6022ba94f511677eb382baff7880c70b2eff343 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 2 Jun 2011 21:34:47 +0100 Subject: [PATCH] enable BindByName when available --- Dapper/SqlMapper.cs | 85 +++++++++++++++++++++++++++++++++++++++++++-- Tests/Tests.cs | 2 +- 2 files changed, 84 insertions(+), 3 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index a90937c..4b856e9 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -14,6 +14,7 @@ using System.Reflection; using System.Reflection.Emit; using System.Text; +using System.Threading; namespace Dapper { @@ -23,7 +24,86 @@ public interface IDynamicParameters { void AddParameter(IDbCommand command); } - + static Link> bindByNameCache; + static Action GetBindByName(Type commandType) + { + if(commandType == null) return null; // GIGO + Action action; + if (Link>.TryGet(bindByNameCache, commandType, out action)) + { + return action; + } + var prop = commandType.GetProperty("BindByName", BindingFlags.Public | BindingFlags.Instance); + action = null; + ParameterInfo[] indexers; + MethodInfo setter; + if (prop != null && prop.CanWrite && prop.PropertyType == typeof(bool) + && ((indexers = prop.GetIndexParameters()) == null || indexers.Length == 0) + && (setter = prop.GetSetMethod()) != null + ) + { + var method = new DynamicMethod(commandType.Name + "_BindByName", null, new Type[] { typeof(IDbCommand), typeof(bool) }); + var il = method.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, commandType); + il.Emit(OpCodes.Ldarg_1); + il.EmitCall(OpCodes.Callvirt, setter, null); + il.Emit(OpCodes.Ret); + action = (Action)method.CreateDelegate(typeof(Action)); + } + // cache it + Link>.TryAdd(ref bindByNameCache, commandType, ref action); + return action; + } + /// + /// This is a micro-cache; suitable when the number of terms is controllable (a few hundred, for example), + /// and strictly append-only; you cannot change existing values. All key matches are on **REFERENCE** + /// equality. The type is fully thread-safe. + /// + class Link where TKey : class + { + public static bool TryGet(Link link, TKey key, out TValue value) + { + while (link != null) + { + if ((object)key==(object)link.Key) + { + value = link.Value; + return true; + } + link = link.Tail; + } + value = default(TValue); + return false; + } + public static bool TryAdd(ref Link head, TKey key, ref TValue value) + { + bool tryAgain; + do + { + var snapshot = Interlocked.CompareExchange(ref head, null, null); + TValue found; + if (TryGet(snapshot, key, out found)) + { // existing match; report the existing value instead + value = found; + return false; + } + var newNode = new Link(key, value, snapshot); + // did somebody move our cheese? + tryAgain = Interlocked.CompareExchange(ref head, newNode, snapshot) != snapshot; + } while (tryAgain); + return true; + } + private Link(TKey key, TValue value, Link tail) + { + Key = key; + Value = value; + Tail = tail; + } + public TKey Key { get; private set; } + public TValue Value { get; private set; } + public Link Tail { get; private set; } + } class CacheInfo { public object Deserializer { get; set; } @@ -855,7 +935,8 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj private static IDbCommand SetupCommand(IDbConnection cnn, IDbTransaction transaction, string sql, Action paramReader, object obj, int? commandTimeout, CommandType? commandType) { var cmd = cnn.CreateCommand(); - + var bindByName = GetBindByName(cmd.GetType()); + if (bindByName != null) bindByName(cmd, true); cmd.Transaction = transaction; cmd.CommandText = sql; if (commandTimeout.HasValue) diff --git a/Tests/Tests.cs b/Tests/Tests.cs index 46d7c41..25e42f2 100644 --- a/Tests/Tests.cs +++ b/Tests/Tests.cs @@ -686,7 +686,7 @@ public void TestDapperSetsPrivates() { connection.Query("select 'one' ShadowInDB").First().Shadow.IsEqualTo(1); } - + /* TODO: * public void TestMagicParam() -- GitLab