提交 6416adf9 编写于 作者: M Marc Gravell

Support named positional parameters

上级 d2381158
......@@ -47,7 +47,7 @@ public enum CommandFlags
/// <summary>
/// Should the plan cache be bypassed?
/// </summary>
NoCache = 4
NoCache = 4,
}
/// <summary>
/// Represents the key aspects of a sql operation
......@@ -2044,18 +2044,14 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter
info = new CacheInfo();
if (identity.parametersType != null)
{
Action<IDbCommand, object> reader;
if (exampleParameters is IDynamicParameters)
{
info.ParamReader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); };
reader = (cmd, obj) => { ((IDynamicParameters)obj).AddParameters(cmd, identity); };
}
#if CSHARP30
else if (exampleParameters is IEnumerable<KeyValuePair<string, object>>)
#else
// special-case dictionary and `dynamic`
else if (exampleParameters is IEnumerable<KeyValuePair<string, object>> || exampleParameters is System.Dynamic.IDynamicMetaObjectProvider)
#endif
{
info.ParamReader = (cmd, obj) =>
reader = (cmd, obj) =>
{
IDynamicParameters mapped = new DynamicParameters(obj);
mapped.AddParameters(cmd, identity);
......@@ -2064,14 +2060,67 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter
else
{
var literals = GetLiteralTokens(identity.sql);
info.ParamReader = CreateParamInfoGenerator(identity, false, true, literals);
reader = CreateParamInfoGenerator(identity, false, true, literals);
}
if((identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql))
{
var tail = reader;
var sql = identity.sql;
reader = (cmd, obj) =>
{
tail(cmd, obj);
PassByPosition(cmd);
};
}
info.ParamReader = reader;
}
if(addToCache) SetQueryCache(identity, info);
}
return info;
}
private static bool ShouldPassByPosition(string sql)
{
return sql != null && sql.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql);
}
private static void PassByPosition(IDbCommand cmd)
{
if (cmd.Parameters.Count == 0) return;
Dictionary<string, IDbDataParameter> parameters = new Dictionary<string, IDbDataParameter>(StringComparer.InvariantCulture);
foreach(IDbDataParameter param in cmd.Parameters)
{
if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param;
}
HashSet<string> consumed = new HashSet<string>(StringComparer.InvariantCulture);
cmd.Parameters.Clear();
cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match =>
{
string key = match.Groups[1].Value;
IDbDataParameter param;
if (!consumed.Add(key))
{
throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once");
}
else if (parameters.TryGetValue(key, out param))
{
// if found, return the anonymous token "?"
cmd.Parameters.Add(param);
parameters.Remove(key);
consumed.Add(key);
return "?";
}
else
{
// otherwise, leave alone for simple debugging
return match.Value;
}
});
}
private static Func<IDataReader, object> GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing)
{
#if !CSHARP30
......@@ -2682,14 +2731,14 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj
private static IEnumerable<PropertyInfo> FilterParameters(IEnumerable<PropertyInfo> parameters, string sql)
{
return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-zA-Z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant));
return parameters.Where(p => Regex.IsMatch(sql, @"[?@:]" + p.Name + "([^a-z0-9_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant));
}
// look for ? / @ / : *by itself*
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);
static readonly Regex smellsLikeOleDb = new Regex(@"(?<![a-z0-9@_])[?@:](?![a-zA-Z0-9@_])", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
literalTokens = new Regex(@"(?<![a-z0-9_])\{=([a-z0-9_]+)\}", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled),
pseudoPositional = new Regex(@"\?([a-z_][a-z0-9_]*)\?", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
/// <summary>
/// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql
/// </summary>
......@@ -4280,13 +4329,7 @@ public DynamicParameters(object template)
/// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic
/// </summary>
/// <param name="param"></param>
public void AddDynamicParams(
#if CSHARP30
object param
#else
dynamic param
#endif
)
public void AddDynamicParams(object param)
{
var obj = param as object;
if (obj != null)
......@@ -4304,11 +4347,7 @@ dynamic param
{
foreach (var kvp in dictionary)
{
#if CSHARP30
Add(kvp.Key, kvp.Value, null, null, null);
#else
Add(kvp.Key, kvp.Value);
#endif
}
}
}
......
......@@ -24,6 +24,10 @@ public static void IsSequenceEqualTo<T>(this IEnumerable<T> obj, IEnumerable<T>
}
}
public static void Fail()
{
throw new ApplicationException("Expectation failed");
}
public static void IsFalse(this bool b)
{
if (b)
......
......@@ -47,6 +47,9 @@
<Reference Include="EntityFramework.SqlServer">
<HintPath>..\packages\EntityFramework.6.1.0\lib\net45\EntityFramework.SqlServer.dll</HintPath>
</Reference>
<Reference Include="FirebirdSql.Data.FirebirdClient">
<HintPath>..\packages\FirebirdSql.Data.FirebirdClient.4.5.1.0\lib\net45\FirebirdSql.Data.FirebirdClient.dll</HintPath>
</Reference>
<Reference Include="FSharp.Core, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>..\packages\FSharp.Core.4.0.0\lib\FSharp.Core.dll</HintPath>
......@@ -264,6 +267,7 @@
<ItemGroup>
<Folder Include="bltoolkit\" />
<Folder Include="Simple.Data\" />
<Folder Include="SqlMapper\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Dapper NET40\Dapper NET40.csproj">
......
......@@ -18,6 +18,7 @@
using System.Data.Entity.Spatial;
using Microsoft.SqlServer.Types;
using System.Data.SqlTypes;
using FirebirdSql.Data.FirebirdClient;
#if POSTGRESQL
using Npgsql;
#endif
......@@ -498,7 +499,7 @@ public void TestExtraFields()
// see http://stackoverflow.com/q/18847510/23354
public void TestOleDbParameters()
{
using (var conn = new System.Data.OleDb.OleDbConnection(Program.OleDbConnectionString))
using (var conn = ConnectViaOledb())
{
var row = conn.Query("select Id = ?, Age = ?",
new { foo = 12, bar = 23 } // these names DO NOT MATTER!!!
......@@ -510,6 +511,13 @@ public void TestOleDbParameters()
}
}
System.Data.OleDb.OleDbConnection ConnectViaOledb()
{
var conn = new System.Data.OleDb.OleDbConnection(Program.OleDbConnectionString);
conn.Open();
return conn;
}
public void TestStrongType()
{
var guid = Guid.NewGuid();
......@@ -3851,6 +3859,131 @@ public void AllowIDictionaryParameters()
connection.Query("SELECT @param1", parameters);
}
public void Issue178_SqlServer()
{
const string sql = @"select count(*) from Issue178";
try { connection.Execute("drop table Issue178"); } catch { }
try { connection.Execute("create table Issue178(id int not null)"); } catch { }
// raw ADO.net
var sqlCmd = new SqlCommand(sql, connection);
using (IDataReader reader1 = sqlCmd.ExecuteReader())
{
Assert.IsTrue(reader1.Read());
reader1.GetInt32(0).IsEqualTo(0);
Assert.IsFalse(reader1.Read());
Assert.IsFalse(reader1.NextResult());
}
// dapper
using (var reader2 = connection.ExecuteReader(sql))
{
Assert.IsTrue(reader2.Read());
reader2.GetInt32(0).IsEqualTo(0);
Assert.IsFalse(reader2.Read());
Assert.IsFalse(reader2.NextResult());
}
}
public void Issue178_Firebird() // we expect this to fail due to a bug in Firebird; a PR to fix it has been submitted
{
var cs = @"initial catalog=localhost:database;user id=SYSDBA;password=masterkey";
using (var connection = new FbConnection(cs))
{
connection.Open();
const string sql = @"select count(*) from Issue178";
try { connection.Execute("drop table Issue178"); } catch { }
try { connection.Execute("create table Issue178(id int not null)"); } catch { }
// raw ADO.net
var sqlCmd = new FbCommand(sql, connection);
using (IDataReader reader1 = sqlCmd.ExecuteReader())
{
Assert.IsTrue(reader1.Read());
reader1.GetInt32(0).IsEqualTo(0);
Assert.IsFalse(reader1.Read());
Assert.IsFalse(reader1.NextResult());
}
// dapper
using (var reader2 = connection.ExecuteReader(sql))
{
Assert.IsTrue(reader2.Read());
reader2.GetInt32(0).IsEqualTo(0);
Assert.IsFalse(reader2.Read());
Assert.IsFalse(reader2.NextResult());
}
}
}
public void PseudoPositionalParameters_Simple()
{
using (var connection = ConnectViaOledb())
{
int value = connection.Query<int>("select ?x? + ?y_2? + ?z?", new { x = 1, y_2 = 3, z = 5, z2 = 24 }).Single();
value.IsEqualTo(9);
}
}
public void PseudoPositionalParameters_Dynamic()
{
using (var connection = ConnectViaOledb())
{
var args = new DynamicParameters();
args.Add("x", 1);
args.Add("y_2", 3);
args.Add("z", 5);
args.Add("z2", 24);
int value = connection.Query<int>("select ?x? + ?y_2? + ?z?", args).Single();
value.IsEqualTo(9);
}
}
public void PseudoPositionalParameters_ReusedParameter()
{
using (var connection = ConnectViaOledb())
{
try
{
int value = connection.Query<int>("select ?x? + ?y_2? + ?x?", new { x = 1, y_2 = 3 }).Single();
Assert.Fail();
}
catch (InvalidOperationException ex)
{
ex.Message.IsEqualTo("When passing parameters by position, each parameter can only be referenced once");
}
}
}
public void PseudoPositionalParameters_ExecSingle()
{
using (var connection = ConnectViaOledb())
{
var data = new { x = 6 };
connection.Execute("create table #named_single(val int not null)");
int count = connection.Execute("insert #named_single (val) values (?x?)", data);
int sum = (int)connection.ExecuteScalar("select sum(val) from #named_single");
count.IsEqualTo(1);
sum.IsEqualTo(6);
}
}
public void PseudoPositionalParameters_ExecMulti()
{
using (var connection = ConnectViaOledb())
{
var data = new[]
{
new { x = 1, y = 1 },
new { x = 3, y = 1 },
new { x = 6, y = 1 },
};
connection.Execute("create table #named_multi(val int not null)");
int count = connection.Execute("insert #named_multi (val) values (?x?)", data);
int sum = (int)connection.ExecuteScalar("select sum(val) from #named_multi");
count.IsEqualTo(3);
sum.IsEqualTo(10);
}
}
#if POSTGRESQL
class Cat
......
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="EntityFramework" version="6.1.0" targetFramework="net45" />
<package id="FirebirdSql.Data.FirebirdClient" version="4.5.1.0" targetFramework="net45" />
<package id="FSharp.Core" version="4.0.0" targetFramework="net45" />
<package id="FSPowerPack.Community" version="2.0.0.0" />
<package id="Microsoft.SqlServer.Types" version="11.0.1" targetFramework="net45" />
......
README - FirebirdClient ADO.NET 2.0 Data provider for Firebird
==============================================================
This project is supported by:
-----------------------------
Sean Leyne (Broadview Software)
SMS-Timing
Developement list
-----------------
You can subscribe to the developement list at:
http://lists.sourceforge.net/lists/listinfo/firebird-net-provider
You can access to the lastest developement sources through SVN, see:
https://sourceforge.net/p/firebird/NETProvider/
Reporting Bugs
--------------
Yo can report bugs using two ways:
1. Sending it to the developement list.
2. Thru the Firebird Project tracker (category DNET):
http://tracker.firebirdsql.org/DNET
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册