Commit 9afb2fca authored by Marc Gravell's avatar Marc Gravell

Introducing: literal value replacements, i.e. `where row.Id={=id}` - use...

Introducing: literal value replacements, i.e. `where row.Id={=id}` - use rarely, but to avoid query-plan poisoning
parent 056b22a5
...@@ -18,7 +18,7 @@ ...@@ -18,7 +18,7 @@
using System.Threading; using System.Threading;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
namespace Dapper namespace Dapper
{ {
...@@ -167,6 +167,17 @@ public partial interface IDynamicParameters ...@@ -167,6 +167,17 @@ public partial interface IDynamicParameters
void AddParameters(IDbCommand command, Identity identity); void AddParameters(IDbCommand command, Identity identity);
} }
/// <summary>
/// Extends IDynamicParameters providing by-name lookup of parameter values
/// </summary>
public interface IParameterLookup : IDynamicParameters
{
/// <summary>
/// Get the value of the specified parameter (return null if not found)
/// </summary>
object this[string name] { get; }
}
/// <summary> /// <summary>
/// Implement this interface to pass an arbitrary db specific parameter to Dapper /// Implement this interface to pass an arbitrary db specific parameter to Dapper
/// </summary> /// </summary>
...@@ -855,7 +866,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com ...@@ -855,7 +866,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
masterSql = cmd.CommandText; masterSql = cmd.CommandText;
isFirst = false; isFirst = false;
identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null);
info = GetCacheInfo(identity); info = GetCacheInfo(identity, obj);
} }
else else
{ {
...@@ -873,7 +884,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com ...@@ -873,7 +884,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com
if (param != null) if (param != null)
{ {
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity); info = GetCacheInfo(identity, param);
} }
return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader); return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader);
} }
...@@ -1032,7 +1043,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD ...@@ -1032,7 +1043,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD
{ {
object param = command.Parameters; object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null); Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity); CacheInfo info = GetCacheInfo(identity, param);
IDbCommand cmd = null; IDbCommand cmd = null;
IDataReader reader = null; IDataReader reader = null;
...@@ -1068,7 +1079,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini ...@@ -1068,7 +1079,7 @@ private static IEnumerable<T> QueryImpl<T>(this IDbConnection cnn, CommandDefini
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null); var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity, param);
IDbCommand cmd = null; IDbCommand cmd = null;
IDataReader reader = null; IDataReader reader = null;
...@@ -1295,7 +1306,7 @@ partial class DontMap { } ...@@ -1295,7 +1306,7 @@ partial class DontMap { }
{ {
object param = command.Parameters; object param = command.Parameters;
identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
CacheInfo cinfo = GetCacheInfo(identity); CacheInfo cinfo = GetCacheInfo(identity, param);
IDbCommand ownedCommand = null; IDbCommand ownedCommand = null;
IDataReader ownedReader = null; IDataReader ownedReader = null;
...@@ -1450,7 +1461,7 @@ partial class DontMap { } ...@@ -1450,7 +1461,7 @@ partial class DontMap { }
return deserializers.ToArray(); return deserializers.ToArray();
} }
private static CacheInfo GetCacheInfo(Identity identity) private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters)
{ {
CacheInfo info; CacheInfo info;
if (!TryGetQueryCache(identity, out info)) if (!TryGetQueryCache(identity, out info))
...@@ -1458,12 +1469,13 @@ private static CacheInfo GetCacheInfo(Identity identity) ...@@ -1458,12 +1469,13 @@ private static CacheInfo GetCacheInfo(Identity identity)
info = new CacheInfo(); info = new CacheInfo();
if (identity.parametersType != null) if (identity.parametersType != null)
{ {
if (typeof(IDynamicParameters).IsAssignableFrom(identity.parametersType)) if (exampleParameters is IDynamicParameters)
{ {
info.ParamReader = (cmd, obj) => { (obj as IDynamicParameters).AddParameters(cmd, identity); }; info.ParamReader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); };
} }
#if !CSHARP30 #if !CSHARP30
else if (typeof(IEnumerable<KeyValuePair<string, object>>).IsAssignableFrom(identity.parametersType) && typeof(System.Dynamic.IDynamicMetaObjectProvider).IsAssignableFrom(identity.parametersType)) // special-case dictionary && `dynamic`
else if (exampleParameters is IEnumerable<KeyValuePair<string, object>> && exampleParameters is System.Dynamic.IDynamicMetaObjectProvider)
{ {
info.ParamReader = (cmd, obj) => info.ParamReader = (cmd, obj) =>
{ {
...@@ -1474,7 +1486,8 @@ private static CacheInfo GetCacheInfo(Identity identity) ...@@ -1474,7 +1486,8 @@ private static CacheInfo GetCacheInfo(Identity identity)
#endif #endif
else else
{ {
info.ParamReader = CreateParamInfoGenerator(identity, false, true); var literals = GetLiteralTokens(identity.sql);
info.ParamReader = CreateParamInfoGenerator(identity, false, true, literals);
} }
} }
SetQueryCache(identity, info); SetQueryCache(identity, info);
...@@ -2060,12 +2073,93 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn ...@@ -2060,12 +2073,93 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn
// look for ? / @ / : *by itself* // look for ? / @ / : *by itself*
static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-zA-Z0-9_])[?@:](?![a-zA-Z0-9_])", RegexOptions.Compiled); static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-zA-Z0-9_])[?@:](?![a-zA-Z0-9_])", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
literalTokens = new Regex(@"\{=([a-zA-Z0-9_]+)\}", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled);
/// <summary>
/// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql
/// </summary>
internal struct LiteralToken
{
private readonly string token, member;
/// <summary>
/// The text in the original command that should be replaced
/// </summary>
public string Token { get { return token; } }
/// <summary>
/// The name of the member referred to by the token
/// </summary>
public string Member { get { return member; } }
internal LiteralToken(string token, string member)
{
this.token = token;
this.member = member;
}
internal static readonly IList<LiteralToken> None = new LiteralToken[0];
}
/// <summary>
/// Replace all literal tokens with their text form
/// </summary>
public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand command)
{
var tokens = GetLiteralTokens(command.CommandText);
if (tokens.Count != 0) ReplaceLiterals(parameters, command, tokens);
}
internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand command, IList<LiteralToken> tokens)
{
var sql = command.CommandText;
foreach (var token in tokens)
{
object value = parameters[token.Member];
string text;
if (value == null)
{
text = "";
}
else if (value is string)
{
text = (string)value;
}
else
{
text = Convert.ToString(value, CultureInfo.InvariantCulture);
}
sql = sql.Replace(token.Token, text);
}
command.CommandText = sql;
}
internal static IList<LiteralToken> GetLiteralTokens(string sql)
{
if (string.IsNullOrEmpty(sql)) return LiteralToken.None;
if (!literalTokens.IsMatch(sql)) return LiteralToken.None;
var matches = literalTokens.Matches(sql);
var found = new HashSet<string>(StringComparer.InvariantCulture);
List<LiteralToken> list = new List<LiteralToken>(matches.Count);
foreach(Match match in matches)
{
string token = match.Value;
if(found.Add(match.Value))
{
list.Add(new LiteralToken(token, match.Groups[1].Value));
}
}
return list.Count == 0 ? LiteralToken.None : list;
}
/// <summary> /// <summary>
/// Internal use only /// Internal use only
/// </summary> /// </summary>
public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) public static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused)
{
return CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql));
}
internal static Action<IDbCommand, object> CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList<LiteralToken> literals)
{ {
Type type = identity.parametersType; Type type = identity.parametersType;
...@@ -2285,11 +2379,117 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn ...@@ -2285,11 +2379,117 @@ private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyIn
il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care
} }
} }
// stack is currently [parameters] // stack is currently [parameters]
il.Emit(OpCodes.Pop); // stack is now empty il.Emit(OpCodes.Pop); // stack is now empty
if(literals.Count != 0 && propsArr != null)
{
il.Emit(OpCodes.Ldarg_0); // command
il.Emit(OpCodes.Ldarg_0); // command, command
var cmdText = typeof(IDbCommand).GetProperty("CommandText");
il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod(), null); // command, sql
Dictionary<Type, LocalBuilder> locals = null;
LocalBuilder local = null;
foreach (var literal in literals)
{
// find the best member, preferring case-sensitive
PropertyInfo exact = null, fallback = null;
string huntName = literal.Member;
for(int i = 0; i < propsArr.Length;i++)
{
string thisName = propsArr[i].Name;
if(string.Equals(thisName, huntName, StringComparison.InvariantCultureIgnoreCase))
{
fallback = propsArr[i];
if(string.Equals(thisName, huntName, StringComparison.InvariantCulture))
{
exact = fallback;
break;
}
}
}
var prop = exact ?? fallback;
if(prop != null)
{
il.Emit(OpCodes.Ldstr, literal.Token);
il.Emit(OpCodes.Ldloc_0); // command, sql, typed parameter
il.EmitCall(OpCodes.Callvirt, prop.GetGetMethod(), null); // command, sql, typed value
Type propType = prop.PropertyType;
if (propType == typeof(string))
{
// do nothing
}
else
{
var convert = GetToString(propType);
if (convert == null || convert.ReturnType != typeof(string)) throw new InvalidOperationException("No suitable ToString method for literal replacement of: " + literal.Token);
if (propType.IsValueType)
{
// neeed to stloc, ldloca, call
// re-use existing locals (both the last known, and via a dictionary)
if (local == null || local.LocalType != propType)
{
if (locals == null)
{
locals = new Dictionary<Type, LocalBuilder>();
local = null;
}
else
{
if (!locals.TryGetValue(propType, out local)) local = null;
}
if (local == null)
{
local = il.DeclareLocal(propType);
locals.Add(propType, local);
}
}
il.Emit(OpCodes.Stloc, local); // command, sql
il.Emit(OpCodes.Ldloca, local); // command, sql, ref-to-value
il.EmitCall(OpCodes.Call, InvariantCulture, null); // command, sql, ref-to-value, culture
il.EmitCall(OpCodes.Call, convert, null); // command, sql, string value
}
else
{
il.EmitCall(OpCodes.Call, InvariantCulture, null); // command, sql, typed value, culture
il.EmitCall(OpCodes.Callvirt, convert, null); // command, sql, string value
}
}
il.EmitCall(OpCodes.Callvirt, StringReplace, null);
}
}
il.EmitCall(OpCodes.Callvirt, cmdText.GetSetMethod(), null); // empty
}
il.Emit(OpCodes.Ret); il.Emit(OpCodes.Ret);
return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>)); return (Action<IDbCommand, object>)dm.CreateDelegate(typeof(Action<IDbCommand, object>));
} }
static readonly Hashtable toStrings = new Hashtable();
static readonly object NullSentinel = new object();
static MethodInfo GetToString(Type type)
{
if (type == null) return null;
var obj = toStrings[type];
if(obj == null)
{
lock(toStrings)
{
obj = toStrings[type];
if(obj == null)
{
obj = type.GetMethod("ToString", BindingFlags.Public | BindingFlags.Instance, null, new[] { typeof(IFormatProvider) }, null);
toStrings[type] = obj ?? NullSentinel;
}
}
}
return obj as MethodInfo;
}
static readonly MethodInfo StringReplace = typeof(string).GetMethod("Replace", BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(string), typeof(string) }, null),
InvariantCulture = typeof(CultureInfo).GetProperty("InvariantCulture", BindingFlags.Public | BindingFlags.Static).GetGetMethod();
private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action<IDbCommand, object> paramReader) private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action<IDbCommand, object> paramReader)
{ {
...@@ -2344,7 +2544,7 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin ...@@ -2344,7 +2544,7 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin
if (param != null) if (param != null)
{ {
identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null);
info = GetCacheInfo(identity); info = GetCacheInfo(identity, param);
} }
var paramReader = info == null ? null : info.ParamReader; var paramReader = info == null ? null : info.ParamReader;
return paramReader; return paramReader;
...@@ -2980,7 +3180,7 @@ public IEnumerable<T> Read<T>(bool buffered = true) ...@@ -2980,7 +3180,7 @@ public IEnumerable<T> Read<T>(bool buffered = true)
if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed");
if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); if (consumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once");
var typedIdentity = identity.ForGrid(typeof(T), gridIndex); var typedIdentity = identity.ForGrid(typeof(T), gridIndex);
CacheInfo cache = GetCacheInfo(typedIdentity); CacheInfo cache = GetCacheInfo(typedIdentity, null);
var deserializer = cache.Deserializer; var deserializer = cache.Deserializer;
int hash = GetColumnHash(reader); int hash = GetColumnHash(reader);
...@@ -3175,7 +3375,7 @@ public void Dispose() ...@@ -3175,7 +3375,7 @@ public void Dispose()
/// <summary> /// <summary>
/// A bag of parameters that can be passed to the Dapper Query and Execute methods /// A bag of parameters that can be passed to the Dapper Query and Execute methods
/// </summary> /// </summary>
partial class DynamicParameters : SqlMapper.IDynamicParameters partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper.IParameterLookup
{ {
internal const DbType EnumerableMultiParameter = (DbType)(-1); internal const DbType EnumerableMultiParameter = (DbType)(-1);
static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>(); static Dictionary<SqlMapper.Identity, Action<IDbCommand, object>> paramReaderCache = new Dictionary<SqlMapper.Identity, Action<IDbCommand, object>>();
...@@ -3183,6 +3383,15 @@ partial class DynamicParameters : SqlMapper.IDynamicParameters ...@@ -3183,6 +3383,15 @@ partial class DynamicParameters : SqlMapper.IDynamicParameters
Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>(); Dictionary<string, ParamInfo> parameters = new Dictionary<string, ParamInfo>();
List<object> templates; List<object> templates;
object SqlMapper.IParameterLookup.this[string member]
{
get
{
ParamInfo param;
return parameters.TryGetValue(member, out param) ? param.Value : null;
}
}
partial class ParamInfo partial class ParamInfo
{ {
public string Name { get; set; } public string Name { get; set; }
...@@ -3321,6 +3530,7 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id ...@@ -3321,6 +3530,7 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id
/// <param name="identity">Information about the query</param> /// <param name="identity">Information about the query</param>
protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{ {
var literals = SqlMapper.GetLiteralTokens(identity.sql);
if (templates != null) if (templates != null)
{ {
foreach (var template in templates) foreach (var template in templates)
...@@ -3332,7 +3542,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) ...@@ -3332,7 +3542,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{ {
if (!paramReaderCache.TryGetValue(newIdent, out appender)) if (!paramReaderCache.TryGetValue(newIdent, out appender))
{ {
appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused); appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused, literals);
paramReaderCache[newIdent] = appender; paramReaderCache[newIdent] = appender;
} }
} }
...@@ -3399,8 +3609,9 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) ...@@ -3399,8 +3609,9 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity)
} }
param.AttachedParam = p; param.AttachedParam = p;
} }
} }
// note: most non-priveleged implementations would use: this.ReplaceLiterals(command);
if(literals.Count != 0) SqlMapper.ReplaceLiterals(this, command, literals);
} }
/// <summary> /// <summary>
......
...@@ -27,7 +27,7 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, C ...@@ -27,7 +27,7 @@ public static async Task<IEnumerable<T>> QueryAsync<T>(this IDbConnection cnn, C
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null); var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(T), param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity, param);
bool wasClosed = cnn.State == ConnectionState.Closed; bool wasClosed = cnn.State == ConnectionState.Closed;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{ {
...@@ -61,7 +61,7 @@ public static async Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefini ...@@ -61,7 +61,7 @@ public static async Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefini
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param == null ? null : param.GetType(), null); var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param == null ? null : param.GetType(), null);
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity, param);
bool wasClosed = cnn.State == ConnectionState.Closed; bool wasClosed = cnn.State == ConnectionState.Closed;
using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader))
{ {
...@@ -252,7 +252,7 @@ public static async Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefini ...@@ -252,7 +252,7 @@ public static async Task<int> ExecuteAsync(this IDbConnection cnn, CommandDefini
{ {
object param = command.Parameters; object param = command.Parameters;
var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param == null ? null : param.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) });
var info = GetCacheInfo(identity); var info = GetCacheInfo(identity, param);
bool wasClosed = cnn.State == ConnectionState.Closed; bool wasClosed = cnn.State == ConnectionState.Closed;
try try
{ {
...@@ -308,7 +308,7 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn, ...@@ -308,7 +308,7 @@ public static async Task<GridReader> QueryMultipleAsync(this IDbConnection cnn,
{ {
object param = command.Parameters; object param = command.Parameters;
Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null); Identity identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param == null ? null : param.GetType(), null);
CacheInfo info = GetCacheInfo(identity); CacheInfo info = GetCacheInfo(identity, param);
DbCommand cmd = null; DbCommand cmd = null;
IDataReader reader = null; IDataReader reader = null;
......
...@@ -1696,6 +1696,7 @@ public void Add(IDbDataParameter value) ...@@ -1696,6 +1696,7 @@ public void Add(IDbDataParameter value)
{ {
foreach (IDbDataParameter parameter in parameters) foreach (IDbDataParameter parameter in parameters)
command.Parameters.Add(parameter); command.Parameters.Add(parameter);
} }
} }
public void TestCustomParameters() public void TestCustomParameters()
...@@ -2667,6 +2668,25 @@ public void TestParameterInclusionNotSensitiveToCurrentCulture() ...@@ -2667,6 +2668,25 @@ public void TestParameterInclusionNotSensitiveToCurrentCulture()
Thread.CurrentThread.CurrentCulture = current; Thread.CurrentThread.CurrentCulture = current;
} }
} }
public void LiteralReplacement()
{
connection.Execute("create table #literal1 (id int not null)");
connection.Execute("insert #literal1 (id) values ({=id})", new { id = 123 });
var count = connection.Query<int>("select count(1) from #literal1 where id={=foo}", new { foo = 123 }).Single();
count.IsEqualTo(1);
}
public void LiteralReplacementDynamic()
{
var args = new DynamicParameters();
args.Add("id", 123);
connection.Execute("create table #literal2 (id int not null)");
connection.Execute("insert #literal2 (id) values ({=id})", args);
args = new DynamicParameters();
args.Add("foo", 123);
var count = connection.Query<int>("select count(1) from #literal2 where id={=foo}", args).Single();
count.IsEqualTo(1);
}
public void TestProcedureWithTimeParameter() public void TestProcedureWithTimeParameter()
{ {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment