Files
Dapper/tests/Dapper.Tests/ParameterTests.cs
Giorgi Dalakishvili 19193b52bb Add a setting to turn off Ole Db "smell check" (#1974)
* Add a setting to turn off Ole Db "smell check"

* Rename setting, fix logic

* Test for SupportLegacyParameterTokens

* Fix exception type

* Add release note
2023-10-11 13:10:00 +01:00

1746 lines
67 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Data.Common;
using System.Data.SqlTypes;
using System.Diagnostics;
using System.Dynamic;
using System.Globalization;
using System.Linq;
using System.Text.RegularExpressions;
using Xunit;
#if ENTITY_FRAMEWORK
using System.Data.Entity.Spatial;
using Microsoft.SqlServer.Types;
#endif
namespace Dapper.Tests
{
[Collection(NonParallelDefinition.Name)] // because it creates SQL types that compete between the two providers
public sealed class SystemSqlClientParameterTests : ParameterTests<SystemSqlClientProvider> { }
#if MSSQLCLIENT
[Collection(NonParallelDefinition.Name)] // because it creates SQL types that compete between the two providers
public sealed class MicrosoftSqlClientParameterTests : ParameterTests<MicrosoftSqlClientProvider> { }
#endif
public abstract class ParameterTests<TProvider> : TestBase<TProvider> where TProvider : DatabaseProvider
{
public class DbDynamicParams : SqlMapper.IDynamicParameters, IEnumerable<IDbDataParameter>
{
private readonly List<IDbDataParameter> parameters = new List<IDbDataParameter>();
public IEnumerator<IDbDataParameter> GetEnumerator() { return parameters.GetEnumerator(); }
IEnumerator IEnumerable.GetEnumerator() { return GetEnumerator(); }
public void Add(IDbDataParameter value)
{
parameters.Add(value);
}
void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
foreach (IDbDataParameter parameter in parameters)
command.Parameters.Add(parameter);
}
}
public class DbCustomParam : SqlMapper.ICustomQueryParameter
{
private readonly IDbDataParameter _sqlParameter;
public DbCustomParam(IDbDataParameter sqlParameter)
{
_sqlParameter = sqlParameter;
}
public void AddParameter(IDbCommand command, string name)
{
command.Parameters.Add(_sqlParameter);
}
}
private static IEnumerable<IDataRecord> CreateSqlDataRecordList(IDbCommand command, IEnumerable<int> numbers)
{
if (command is System.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_SD(numbers);
if (command is Microsoft.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_MD(numbers);
throw new ArgumentException(nameof(command));
}
private static IEnumerable<IDataRecord> CreateSqlDataRecordList(IDbConnection connection, IEnumerable<int> numbers)
{
if (connection is System.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_SD(numbers);
if (connection is Microsoft.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_MD(numbers);
throw new ArgumentException(nameof(connection));
}
private static List<Microsoft.SqlServer.Server.SqlDataRecord> CreateSqlDataRecordList_SD(IEnumerable<int> numbers)
{
var number_list = new List<Microsoft.SqlServer.Server.SqlDataRecord>();
// Create an SqlMetaData object that describes our table type.
Microsoft.SqlServer.Server.SqlMetaData[] tvp_definition = { new Microsoft.SqlServer.Server.SqlMetaData("n", SqlDbType.Int) };
foreach (int n in numbers)
{
// Create a new record, using the metadata array above.
var rec = new Microsoft.SqlServer.Server.SqlDataRecord(tvp_definition);
rec.SetInt32(0, n); // Set the value.
number_list.Add(rec); // Add it to the list.
}
return number_list;
}
private static List<Microsoft.Data.SqlClient.Server.SqlDataRecord> CreateSqlDataRecordList_MD(IEnumerable<int> numbers)
{
var number_list = new List<Microsoft.Data.SqlClient.Server.SqlDataRecord>();
// Create an SqlMetaData object that describes our table type.
Microsoft.Data.SqlClient.Server.SqlMetaData[] tvp_definition = { new Microsoft.Data.SqlClient.Server.SqlMetaData("n", SqlDbType.Int) };
foreach (int n in numbers)
{
// Create a new record, using the metadata array above.
var rec = new Microsoft.Data.SqlClient.Server.SqlDataRecord(tvp_definition);
rec.SetInt32(0, n); // Set the value.
number_list.Add(rec); // Add it to the list.
}
return number_list;
}
private class IntDynamicParam : SqlMapper.IDynamicParameters
{
private readonly IEnumerable<int> numbers;
public IntDynamicParam(IEnumerable<int> numbers)
{
this.numbers = numbers;
}
public void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
command.CommandType = CommandType.StoredProcedure;
var number_list = CreateSqlDataRecordList(command, numbers);
AddStructured(command, number_list);
}
}
private class IntCustomParam : SqlMapper.ICustomQueryParameter
{
private readonly IEnumerable<int> numbers;
public IntCustomParam(IEnumerable<int> numbers)
{
this.numbers = numbers;
}
public void AddParameter(IDbCommand command, string name)
{
command.CommandType = CommandType.StoredProcedure;
var number_list = CreateSqlDataRecordList(command, numbers);
// Add the table parameter.
AddStructured(command, number_list);
}
}
private static IDbDataParameter AddStructured(IDbCommand command, object value)
{
if (command is System.Data.SqlClient.SqlCommand sdcmd)
{
var p = sdcmd.Parameters.Add("integers", SqlDbType.Structured);
p.Direction = ParameterDirection.Input;
p.TypeName = "int_list_type";
p.Value = value;
return p;
}
else if (command is Microsoft.Data.SqlClient.SqlCommand mdcmd)
{
var p = mdcmd.Parameters.Add("integers", SqlDbType.Structured);
p.Direction = ParameterDirection.Input;
p.TypeName = "int_list_type";
p.Value = value;
return p;
}
else
throw new ArgumentException(nameof(command));
}
/* TODO:
*
public void TestMagicParam()
{
// magic params allow you to pass in single params without using an anon class
// this test fails for now, but I would like to support a single param by parsing the sql with regex and remapping.
var first = connection.Query("select @a as a", 1).First();
Assert.Equal(first.a, 1);
}
* */
[Fact]
public void TestDoubleParam()
{
Assert.Equal(0.1d, connection.Query<double>("select @d", new { d = 0.1d }).First());
}
[Fact]
public void TestBoolParam()
{
Assert.False(connection.Query<bool>("select @b", new { b = false }).First());
}
// http://code.google.com/p/dapper-dot-net/issues/detail?id=70
// https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time
[Fact]
public void TestTimeSpanParam()
{
Assert.Equal(connection.Query<TimeSpan>("select @ts", new { ts = TimeSpan.FromMinutes(42) }).First(), TimeSpan.FromMinutes(42));
}
[Fact]
public void PassInIntArray()
{
Assert.Equal(
new[] { 1, 2, 3 },
connection.Query<int>("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = new int[] { 1, 2, 3 }.AsEnumerable() })
);
}
[Fact]
public void PassInEmptyIntArray()
{
Assert.Equal(
Array.Empty<int>(),
connection.Query<int>("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = Array.Empty<int>() })
);
}
[Fact]
public void TestExecuteCommandWithHybridParameters()
{
var p = new DynamicParameters(new { a = 1, b = 2 });
p.Add("c", dbType: DbType.Int32, direction: ParameterDirection.Output);
connection.Execute("set @c = @a + @b", p);
Assert.Equal(3, p.Get<int>("@c"));
}
[Fact]
public void GuidIn_SO_24177902()
{
// invent and populate
Guid a = Guid.NewGuid(), b = Guid.NewGuid(), c = Guid.NewGuid(), d = Guid.NewGuid();
connection.Execute("create table #foo (i int, g uniqueidentifier)");
connection.Execute("insert #foo(i,g) values(@i,@g)",
new[] { new { i = 1, g = a }, new { i = 2, g = b },
new { i = 3, g = c },new { i = 4, g = d }});
// check that rows 2&3 yield guids b&c
var guids = connection.Query<Guid>("select g from #foo where i in (2,3)").ToArray();
Assert.Equal(2, guids.Length);
Assert.DoesNotContain(a, guids);
Assert.Contains(b, guids);
Assert.Contains(c, guids);
Assert.DoesNotContain(d, guids);
// in query on the guids
var rows = connection.Query("select * from #foo where g in @guids order by i", new { guids })
.Select(row => new { i = (int)row.i, g = (Guid)row.g }).ToArray();
Assert.Equal(2, rows.Length);
Assert.Equal(2, rows[0].i);
Assert.Equal(b, rows[0].g);
Assert.Equal(3, rows[1].i);
Assert.Equal(c, rows[1].g);
}
[FactUnlessCaseSensitiveDatabase]
public void TestParameterInclusionNotSensitiveToCurrentCulture()
{
// note this might fail if your database server is case-sensitive
CultureInfo current = ActiveCulture;
try
{
ActiveCulture = new CultureInfo("tr-TR");
connection.Query<int>("select @pid", new { PId = 1 }).Single();
}
finally
{
ActiveCulture = current;
}
}
[Fact]
public void TestMassiveStrings()
{
var str = new string('X', 20000);
Assert.Equal(connection.Query<string>("select @a", new { a = str }).First(), str);
}
[Fact]
public void TestTVPWithAnonymousObject()
{
try
{
connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers");
var nums = connection.Query<int>("get_ints", new { integers = new IntCustomParam(new int[] { 1, 2, 3 }) }, commandType: CommandType.StoredProcedure).ToList();
Assert.Equal(1, nums[0]);
Assert.Equal(2, nums[1]);
Assert.Equal(3, nums[2]);
Assert.Equal(3, nums.Count);
}
finally
{
try
{
connection.Execute("DROP PROC get_ints");
}
finally
{
connection.Execute("DROP TYPE int_list_type");
}
}
}
[Fact]
public void TestTVPWithAnonymousEmptyObject()
{
try
{
connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers");
var nums = connection.Query<int>("get_ints", new { integers = new IntCustomParam(new int[] { }) }, commandType: CommandType.StoredProcedure).ToList();
Assert.Equal(1, nums[0]);
Assert.Equal(2, nums[1]);
Assert.Equal(3, nums[2]);
Assert.Equal(3, nums.Count);
}
catch (ArgumentException ex)
{
Assert.True(string.Compare(ex.Message, "There are no records in the SqlDataRecord enumeration. To send a table-valued parameter with no rows, use a null reference for the value instead.") == 0);
}
finally
{
try
{
connection.Execute("DROP PROC get_ints");
}
finally
{
connection.Execute("DROP TYPE int_list_type");
}
}
}
// SQL Server specific test to demonstrate TVP
[Fact]
public void TestTVP()
{
try
{
connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers");
var nums = connection.Query<int>("get_ints", new IntDynamicParam(new int[] { 1, 2, 3 })).ToList();
Assert.Equal(1, nums[0]);
Assert.Equal(2, nums[1]);
Assert.Equal(3, nums[2]);
Assert.Equal(3, nums.Count);
}
finally
{
try
{
try { connection.Execute("DROP PROC get_ints"); } catch { }
}
finally
{
try { connection.Execute("DROP TYPE int_list_type"); } catch { }
}
}
}
private class DynamicParameterWithIntTVP : DynamicParameters, SqlMapper.IDynamicParameters
{
private readonly IEnumerable<int> numbers;
public DynamicParameterWithIntTVP(IEnumerable<int> numbers)
{
this.numbers = numbers;
}
public new void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
base.AddParameters(command, identity);
command.CommandType = CommandType.StoredProcedure;
var number_list = CreateSqlDataRecordList(command, numbers);
// Add the table parameter.
AddStructured(command, number_list);
}
}
[Fact]
public void TestTVPWithAdditionalParams()
{
try
{
connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
connection.Execute("CREATE PROC get_values @integers int_list_type READONLY, @stringParam varchar(20), @dateParam datetime AS select i.*, @stringParam as stringParam, @dateParam as dateParam from @integers i");
var dynamicParameters = new DynamicParameterWithIntTVP(new int[] { 1, 2, 3 });
dynamicParameters.AddDynamicParams(new { stringParam = "stringParam", dateParam = new DateTime(2012, 1, 1) });
var results = connection.Query("get_values", dynamicParameters, commandType: CommandType.StoredProcedure).ToList();
Assert.Equal(3, results.Count);
for (int i = 0; i < results.Count; i++)
{
var result = results[i];
Assert.Equal(i + 1, result.n);
Assert.Equal("stringParam", result.stringParam);
Assert.Equal(new DateTime(2012, 1, 1), result.dateParam);
}
}
finally
{
try
{
connection.Execute("DROP PROC get_values");
}
finally
{
connection.Execute("DROP TYPE int_list_type");
}
}
}
[Fact]
public void TestSqlDataRecordListParametersWithAsTableValuedParameter()
{
try
{
connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers");
var records = CreateSqlDataRecordList(connection, new int[] { 1, 2, 3 });
var nums = connection.Query<int>("get_ints", new { integers = records.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList();
Assert.Equal(new int[] { 1, 2, 3 }, nums);
nums = connection.Query<int>("select * from @integers", new { integers = records.AsTableValuedParameter("int_list_type") }).ToList();
Assert.Equal(new int[] { 1, 2, 3 }, nums);
try
{
connection.Query<int>("select * from @integers", new { integers = records.AsTableValuedParameter() }).First();
throw new InvalidOperationException();
}
catch (Exception ex)
{
ex.Message.Equals("The table type parameter 'ids' must have a valid type name.");
}
}
finally
{
try
{
connection.Execute("DROP PROC get_ints");
}
finally
{
connection.Execute("DROP TYPE int_list_type");
}
}
}
[Fact]
public void TestEmptySqlDataRecordListParametersWithAsTableValuedParameter()
{
try
{
connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers");
var emptyRecord = CreateSqlDataRecordList(connection, Enumerable.Empty<int>());
var nums = connection.Query<int>("get_ints", new { integers = emptyRecord.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList();
Assert.True(nums.Count == 0);
}
finally
{
try
{
connection.Execute("DROP PROC get_ints");
}
finally
{
connection.Execute("DROP TYPE int_list_type");
}
}
}
[Fact]
public void TestSqlDataRecordListParametersWithTypeHandlers()
{
try
{
connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)");
connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers");
// Variable type has to be IEnumerable<SqlDataRecord> for TypeHandler to kick in.
object args;
if (connection is System.Data.SqlClient.SqlConnection)
{
IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> records = CreateSqlDataRecordList_SD(new int[] { 1, 2, 3 });
args = new { integers = records };
}
else if (connection is Microsoft.Data.SqlClient.SqlConnection)
{
IEnumerable<Microsoft.Data.SqlClient.Server.SqlDataRecord> records = CreateSqlDataRecordList_MD(new int[] { 1, 2, 3 });
args = new { integers = records };
}
else
{
throw new ArgumentException(nameof(connection));
}
var nums = connection.Query<int>("get_ints", args, commandType: CommandType.StoredProcedure).ToList();
Assert.Equal(new int[] { 1, 2, 3 }, nums);
try
{
connection.Query<int>("select * from @integers", args).First();
throw new InvalidOperationException();
}
catch (Exception ex)
{
ex.Message.Equals("The table type parameter 'ids' must have a valid type name.");
}
}
finally
{
try
{
connection.Execute("DROP PROC get_ints");
}
finally
{
connection.Execute("DROP TYPE int_list_type");
}
}
}
[Fact]
public void DataTableParameters()
{
try { connection.Execute("drop proc #DataTableParameters"); }
catch { /* don't care */ }
try { connection.Execute("drop table #DataTableParameters"); }
catch { /* don't care */ }
try { connection.Execute("drop type MyTVPType"); }
catch { /* don't care */ }
connection.Execute("create type MyTVPType as table (id int)");
connection.Execute("create proc #DataTableParameters @ids MyTVPType readonly as select count(1) from @ids");
var table = new DataTable { Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 2 }, { 3 } } };
int count = connection.Query<int>("#DataTableParameters", new { ids = table.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).First();
Assert.Equal(3, count);
count = connection.Query<int>("select count(1) from @ids", new { ids = table.AsTableValuedParameter("MyTVPType") }).First();
Assert.Equal(3, count);
try
{
connection.Query<int>("select count(1) from @ids", new { ids = table.AsTableValuedParameter() }).First();
throw new InvalidOperationException();
}
catch (Exception ex)
{
ex.Message.Equals("The table type parameter 'ids' must have a valid type name.");
}
}
[Fact]
public void SO29533765_DataTableParametersViaDynamicParameters()
{
try { connection.Execute("drop proc #DataTableParameters"); } catch { /* don't care */ }
try { connection.Execute("drop table #DataTableParameters"); } catch { /* don't care */ }
try { connection.Execute("drop type MyTVPType"); } catch { /* don't care */ }
connection.Execute("create type MyTVPType as table (id int)");
connection.Execute("create proc #DataTableParameters @ids MyTVPType readonly as select count(1) from @ids");
var table = new DataTable { TableName = "MyTVPType", Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 2 }, { 3 } } };
table.SetTypeName(table.TableName); // per SO29533765
IDictionary<string, object> args = new Dictionary<string, object>(1)
{
["ids"] = table
};
int count = connection.Query<int>("#DataTableParameters", args, commandType: CommandType.StoredProcedure).First();
Assert.Equal(3, count);
count = connection.Query<int>("select count(1) from @ids", args).First();
Assert.Equal(3, count);
}
[Fact]
public void SO26468710_InWithTVPs()
{
// this is just to make it re-runnable; normally you only do this once
try { connection.Execute("drop type MyIdList"); }
catch { /* don't care */ }
connection.Execute("create type MyIdList as table(id int);");
var ids = new DataTable
{
Columns = { { "id", typeof(int) } },
Rows = { { 1 }, { 3 }, { 5 } }
};
ids.SetTypeName("MyIdList");
int sum = connection.Query<int>(@"
declare @tmp table(id int not null);
insert @tmp (id) values(1), (2), (3), (4), (5), (6), (7);
select * from @tmp t inner join @ids i on i.id = t.id", new { ids }).Sum();
Assert.Equal(9, sum);
}
[Fact]
public void DataTableParametersWithExtendedProperty()
{
try { connection.Execute("drop proc #DataTableParameters"); }
catch { /* don't care */ }
try { connection.Execute("drop table #DataTableParameters"); }
catch { /* don't care */ }
try { connection.Execute("drop type MyTVPType"); }
catch { /* don't care */ }
connection.Execute("create type MyTVPType as table (id int)");
connection.Execute("create proc #DataTableParameters @ids MyTVPType readonly as select count(1) from @ids");
var table = new DataTable { Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 2 }, { 3 } } };
table.SetTypeName("MyTVPType"); // <== extended metadata
int count = connection.Query<int>("#DataTableParameters", new { ids = table }, commandType: CommandType.StoredProcedure).First();
Assert.Equal(3, count);
count = connection.Query<int>("select count(1) from @ids", new { ids = table }).First();
Assert.Equal(3, count);
try
{
connection.Query<int>("select count(1) from @ids", new { ids = table }).First();
throw new InvalidOperationException();
}
catch (Exception ex)
{
ex.Message.Equals("The table type parameter 'ids' must have a valid type name.");
}
}
[Fact]
public void SupportInit()
{
var obj = connection.Query<WithInit>("select 'abc' as Value").Single();
Assert.Equal("abc", obj.Value);
Assert.Equal(31, obj.Flags);
}
public class WithInit : ISupportInitialize
{
public string? Value { get; set; }
public int Flags { get; set; }
void ISupportInitialize.BeginInit() => Flags++;
void ISupportInitialize.EndInit() => Flags += 30;
}
[Fact]
public void SO29596645_TvpProperty()
{
try { connection.Execute("CREATE TYPE SO29596645_ReminderRuleType AS TABLE (id int NOT NULL)"); }
catch { /* don't care */ }
connection.Execute(@"create proc #SO29596645_Proc (@Id int, @Rules SO29596645_ReminderRuleType READONLY)
as begin select @Id + ISNULL((select sum(id) from @Rules), 0); end");
var obj = new SO29596645_OrganisationDTO();
int val = connection.Query<int>("#SO29596645_Proc", obj.Rules, commandType: CommandType.StoredProcedure).Single();
// 4 + 9 + 7 = 20
Assert.Equal(20, val);
}
private class SO29596645_RuleTableValuedParameters : SqlMapper.IDynamicParameters
{
private readonly string parameterName;
public SO29596645_RuleTableValuedParameters(string parameterName)
{
this.parameterName = parameterName;
}
public void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
Debug.WriteLine("> AddParameters");
var p = command.CreateParameter();
p.ParameterName = "Id";
p.Value = 7;
command.Parameters.Add(p);
var table = new DataTable
{
Columns = { { "Id", typeof(int) } },
Rows = { { 4 }, { 9 } }
};
p = command.CreateParameter();
p.ParameterName = "Rules";
p.Value = table;
command.Parameters.Add(p);
Debug.WriteLine("< AddParameters");
}
}
private class SO29596645_OrganisationDTO
{
public SO29596645_RuleTableValuedParameters Rules { get; }
public SO29596645_OrganisationDTO()
{
Rules = new SO29596645_RuleTableValuedParameters("@Rules");
}
}
#if ENTITY_FRAMEWORK
private class HazGeo
{
public int Id { get; set; }
public DbGeography? Geo { get; set; }
public DbGeometry? Geometry { get; set; }
}
private class HazSqlGeo
{
public int Id { get; set; }
public SqlGeography? Geo { get; set; }
public SqlGeometry? Geometry { get; set; }
}
[Fact]
public void DBGeography_SO24405645_SO24402424()
{
SkipIfMsDataClient();
EntityFramework.Handlers.Register();
connection.Execute("create table #Geo (id int, geo geography, geometry geometry)");
var obj = new HazGeo
{
Id = 1,
Geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326),
Geometry = DbGeometry.LineFromText("LINESTRING (100 100, 20 180, 180 180)", 0)
};
connection.Execute("insert #Geo(id, geo, geometry) values (@Id, @Geo, @Geometry)", obj);
var row = connection.Query<HazGeo>("select * from #Geo where id=1").SingleOrDefault();
Assert.NotNull(row);
Assert.Equal(1, row.Id);
Assert.NotNull(row.Geo);
Assert.NotNull(row.Geometry);
}
[Fact]
public void SqlGeography_SO25538154()
{
SkipIfMsDataClient();
SqlMapper.ResetTypeHandlers();
connection.Execute("create table #SqlGeo (id int, geo geography, geometry geometry)");
var obj = new HazSqlGeo
{
Id = 1,
Geo = SqlGeography.STLineFromText(new SqlChars(new SqlString("LINESTRING(-122.360 47.656, -122.343 47.656 )")), 4326),
Geometry = SqlGeometry.STLineFromText(new SqlChars(new SqlString("LINESTRING (100 100, 20 180, 180 180)")), 0)
};
connection.Execute("insert #SqlGeo(id, geo, geometry) values (@Id, @Geo, @Geometry)", obj);
var row = connection.Query<HazSqlGeo>("select * from #SqlGeo where id=1").SingleOrDefault();
Assert.NotNull(row);
Assert.Equal(1, row.Id);
Assert.NotNull(row.Geo);
Assert.NotNull(row.Geometry);
}
[Fact]
public void NullableSqlGeometry()
{
SqlMapper.ResetTypeHandlers();
connection.Execute("create table #SqlNullableGeo (id int, geometry geometry null)");
var obj = new HazSqlGeo
{
Id = 1,
Geometry = null
};
connection.Execute("insert #SqlNullableGeo(id, geometry) values (@Id, @Geometry)", obj);
var row = connection.Query<HazSqlGeo>("select * from #SqlNullableGeo where id=1").SingleOrDefault();
Assert.NotNull(row);
Assert.Equal(1, row.Id);
Assert.Null(row.Geometry);
}
[Fact]
public void SqlHierarchyId_SO18888911()
{
SkipIfMsDataClient();
SqlMapper.ResetTypeHandlers();
var row = connection.Query<HazSqlHierarchy>("select 3 as [Id], hierarchyid::Parse('/1/2/3/') as [Path]").Single();
Assert.Equal(3, row.Id);
Assert.NotEqual(default(SqlHierarchyId), row.Path);
var val = connection.Query<SqlHierarchyId>("select @Path", row).Single();
Assert.NotEqual(default(SqlHierarchyId), val);
}
public class HazSqlHierarchy
{
public int Id { get; set; }
public SqlHierarchyId Path { get; set; }
}
#endif
[Fact]
public void TestDynamicParameters()
{
var args = new DbDynamicParams {
Provider.CreateRawParameter("foo", 123),
Provider.CreateRawParameter("bar", "abc")
};
var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single();
int foo = result.Foo;
string bar = result.Bar;
Assert.Equal(123, foo);
Assert.Equal("abc", bar);
}
[Fact]
public void TestDynamicParametersReuse()
{
var args = new DbDynamicParams {
Provider.CreateRawParameter("foo", 123),
Provider.CreateRawParameter("bar", "abc")
};
var result1 = connection.Query("select Foo=@foo, Bar=@bar", args).Single();
var result2 = connection.Query("select Foo=@foo, Bar=@bar", args).Single();
Assert.Equal(123, result1.Foo);
Assert.Equal("abc", result1.Bar);
Assert.Equal(123, result2.Foo);
Assert.Equal("abc", result2.Bar);
}
[Fact]
public void TestCustomParameter()
{
var args = new {
foo = new DbCustomParam(Provider.CreateRawParameter("foo", 123)),
bar = "abc"
};
var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single();
int foo = result.Foo;
string bar = result.Bar;
Assert.Equal(123, foo);
Assert.Equal("abc", bar);
}
[Fact]
public void TestCustomParameterReuse()
{
var args = new {
foo = new DbCustomParam(Provider.CreateRawParameter("foo", 123)),
bar = "abc"
};
var result1 = connection.Query("select Foo=@foo, Bar=@bar", args).Single();
var result2 = connection.Query("select Foo=@foo, Bar=@bar", args).Single();
Assert.Equal(123, result1.Foo);
Assert.Equal("abc", result1.Bar);
Assert.Equal(123, result2.Foo);
Assert.Equal("abc", result2.Bar);
}
[Fact]
public void TestDynamicParamNullSupport()
{
var p = new DynamicParameters();
p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output);
connection.Execute("select @b = null", p);
Assert.Null(p.Get<int?>("@b"));
}
[Fact]
public void TestAppendingAnonClasses()
{
var p = new DynamicParameters();
p.AddDynamicParams(new { A = 1, B = 2 });
p.AddDynamicParams(new { C = 3, D = 4 });
var result = connection.Query("select @A a,@B b,@C c,@D d", p).Single();
Assert.Equal(1, (int)result.a);
Assert.Equal(2, (int)result.b);
Assert.Equal(3, (int)result.c);
Assert.Equal(4, (int)result.d);
}
[Fact]
public void TestAppendingADictionary()
{
var dictionary = new Dictionary<string, object>(2)
{
["A"] = 1,
["B"] = "two"
};
var p = new DynamicParameters();
p.AddDynamicParams(dictionary);
var result = connection.Query("select @A a, @B b", p).Single();
Assert.Equal(1, (int)result.a);
Assert.Equal("two", (string)result.b);
}
[Fact]
public void TestAppendingAnExpandoObject()
{
dynamic expando = new ExpandoObject();
expando.A = 1;
expando.B = "two";
var p = new DynamicParameters();
p.AddDynamicParams(expando);
var result = connection.Query("select @A a, @B b", p).Single();
Assert.Equal(1, (int)result.a);
Assert.Equal("two", (string)result.b);
}
[Fact]
public void TestAppendingAList()
{
var p = new DynamicParameters();
var list = new int[] { 1, 2, 3 };
p.AddDynamicParams(new { list });
var result = connection.Query<int>("select * from (select 1 A union all select 2 union all select 3) X where A in @list", p).ToList();
Assert.Equal(1, result[0]);
Assert.Equal(2, result[1]);
Assert.Equal(3, result[2]);
}
[Fact]
public void TestAppendingAListAsDictionary()
{
var p = new DynamicParameters();
var list = new int[] { 1, 2, 3 };
var args = new Dictionary<string, object>(1) { ["ids"] = list };
p.AddDynamicParams(args);
var result = connection.Query<int>("select * from (select 1 A union all select 2 union all select 3) X where A in @ids", p).ToList();
Assert.Equal(1, result[0]);
Assert.Equal(2, result[1]);
Assert.Equal(3, result[2]);
}
[Fact]
public void TestAppendingAListByName()
{
DynamicParameters p = new DynamicParameters();
var list = new int[] { 1, 2, 3 };
p.Add("ids", list);
var result = connection.Query<int>("select * from (select 1 A union all select 2 union all select 3) X where A in @ids", p).ToList();
Assert.Equal(1, result[0]);
Assert.Equal(2, result[1]);
Assert.Equal(3, result[2]);
}
[Fact]
public void ParameterizedInWithOptimizeHint()
{
const string sql = @"
select count(1)
from(
select 1 as x
union all select 2
union all select 5) y
where y.x in @vals
option (optimize for (@vals unKnoWn))";
int count = connection.Query<int>(sql, new { vals = new[] { 1, 2, 3, 4 } }).Single();
Assert.Equal(2, count);
count = connection.Query<int>(sql, new { vals = new[] { 1 } }).Single();
Assert.Equal(1, count);
count = connection.Query<int>(sql, new { vals = new int[0] }).Single();
Assert.Equal(0, count);
}
[Fact]
public void TestProcedureWithTimeParameter()
{
var p = new DynamicParameters();
p.Add("a", TimeSpan.FromHours(10), dbType: DbType.Time);
connection.Execute(@"CREATE PROCEDURE #TestProcWithTimeParameter
@a TIME
AS
BEGIN
SELECT @a
END");
Assert.Equal(connection.Query<TimeSpan>("#TestProcWithTimeParameter", p, commandType: CommandType.StoredProcedure).First(), new TimeSpan(10, 0, 0));
}
[Fact]
public void TestUniqueIdentifier()
{
var guid = Guid.NewGuid();
var result = connection.Query<Guid>("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single();
Assert.Equal(guid, result);
}
[Fact]
public void TestNullableUniqueIdentifierNonNull()
{
Guid? guid = Guid.NewGuid();
var result = connection.Query<Guid?>("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single();
Assert.Equal(guid, result);
}
[Fact]
public void TestNullableUniqueIdentifierNull()
{
Guid? guid = null;
var result = connection.Query<Guid?>("declare @foo uniqueidentifier set @foo = @guid select @foo", new { guid }).Single();
Assert.Equal(guid, result);
}
[Fact]
public void TestSupportForDynamicParameters()
{
var p = new DynamicParameters();
p.Add("name", "bob");
p.Add("age", dbType: DbType.Int32, direction: ParameterDirection.Output);
Assert.Equal("bob", connection.Query<string>("set @age = 11 select @name", p).First());
Assert.Equal(11, p.Get<int>("age"));
}
[Fact]
public void TestSupportForDynamicParametersOutputExpressions()
{
var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } };
var p = new DynamicParameters(bob);
p.Output(bob, b => b.PersonId);
p.Output(bob, b => b.Occupation);
p.Output(bob, b => b.NumberOfLegs);
p.Output(bob, b => b.Address!.Name);
p.Output(bob, b => b.Address!.PersonId);
connection.Execute(@"
SET @Occupation = 'grillmaster'
SET @PersonId = @PersonId + 1
SET @NumberOfLegs = @NumberOfLegs - 1
SET @AddressName = 'bobs burgers'
SET @AddressPersonId = @PersonId", p);
Assert.Equal("grillmaster", bob.Occupation);
Assert.Equal(2, bob.PersonId);
Assert.Equal(1, bob.NumberOfLegs);
Assert.Equal("bobs burgers", bob.Address.Name);
Assert.Equal(2, bob.Address.PersonId);
}
[Fact]
public void TestSupportForDynamicParametersOutputExpressions_Scalar()
{
using (var connection = GetOpenConnection())
{
var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } };
var p = new DynamicParameters(bob);
p.Output(bob, b => b.PersonId);
p.Output(bob, b => b.Occupation);
p.Output(bob, b => b.NumberOfLegs);
p.Output(bob, b => b.Address!.Name);
p.Output(bob, b => b.Address!.PersonId);
var result = (int)connection.ExecuteScalar(@"
SET @Occupation = 'grillmaster'
SET @PersonId = @PersonId + 1
SET @NumberOfLegs = @NumberOfLegs - 1
SET @AddressName = 'bobs burgers'
SET @AddressPersonId = @PersonId
select 42", p)!;
Assert.Equal("grillmaster", bob.Occupation);
Assert.Equal(2, bob.PersonId);
Assert.Equal(1, bob.NumberOfLegs);
Assert.Equal("bobs burgers", bob.Address.Name);
Assert.Equal(2, bob.Address.PersonId);
Assert.Equal(42, result);
}
}
[Fact]
public void TestSupportForDynamicParametersOutputExpressions_Query_Buffered()
{
using (var connection = GetOpenConnection())
{
var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } };
var p = new DynamicParameters(bob);
p.Output(bob, b => b.PersonId);
p.Output(bob, b => b.Occupation);
p.Output(bob, b => b.NumberOfLegs);
p.Output(bob, b => b.Address!.Name);
p.Output(bob, b => b.Address!.PersonId);
var result = connection.Query<int>(@"
SET @Occupation = 'grillmaster'
SET @PersonId = @PersonId + 1
SET @NumberOfLegs = @NumberOfLegs - 1
SET @AddressName = 'bobs burgers'
SET @AddressPersonId = @PersonId
select 42", p, buffered: true).Single();
Assert.Equal("grillmaster", bob.Occupation);
Assert.Equal(2, bob.PersonId);
Assert.Equal(1, bob.NumberOfLegs);
Assert.Equal("bobs burgers", bob.Address.Name);
Assert.Equal(2, bob.Address.PersonId);
Assert.Equal(42, result);
}
}
[Fact]
public void TestSupportForDynamicParametersOutputExpressions_Query_NonBuffered()
{
using (var connection = GetOpenConnection())
{
var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } };
var p = new DynamicParameters(bob);
p.Output(bob, b => b.PersonId);
p.Output(bob, b => b.Occupation);
p.Output(bob, b => b.NumberOfLegs);
p.Output(bob, b => b.Address!.Name);
p.Output(bob, b => b.Address!.PersonId);
var result = connection.Query<int>(@"
SET @Occupation = 'grillmaster'
SET @PersonId = @PersonId + 1
SET @NumberOfLegs = @NumberOfLegs - 1
SET @AddressName = 'bobs burgers'
SET @AddressPersonId = @PersonId
select 42", p, buffered: false).Single();
Assert.Equal("grillmaster", bob.Occupation);
Assert.Equal(2, bob.PersonId);
Assert.Equal(1, bob.NumberOfLegs);
Assert.Equal("bobs burgers", bob.Address.Name);
Assert.Equal(2, bob.Address.PersonId);
Assert.Equal(42, result);
}
}
[Fact]
public void TestSupportForDynamicParametersOutputExpressions_QueryMultiple()
{
using (var connection = GetOpenConnection())
{
var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } };
var p = new DynamicParameters(bob);
p.Output(bob, b => b.PersonId);
p.Output(bob, b => b.Occupation);
p.Output(bob, b => b.NumberOfLegs);
p.Output(bob, b => b.Address!.Name);
p.Output(bob, b => b.Address!.PersonId);
int x, y;
using (var multi = connection.QueryMultiple(@"
SET @Occupation = 'grillmaster'
SET @PersonId = @PersonId + 1
SET @NumberOfLegs = @NumberOfLegs - 1
SET @AddressName = 'bobs burgers'
select 42
select 17
SET @AddressPersonId = @PersonId", p))
{
x = multi.Read<int>().Single();
y = multi.Read<int>().Single();
}
Assert.Equal("grillmaster", bob.Occupation);
Assert.Equal(2, bob.PersonId);
Assert.Equal(1, bob.NumberOfLegs);
Assert.Equal("bobs burgers", bob.Address.Name);
Assert.Equal(2, bob.Address.PersonId);
Assert.Equal(42, x);
Assert.Equal(17, y);
}
}
[Fact]
public void TestSupportForExpandoObjectParameters()
{
dynamic p = new ExpandoObject();
p.name = "bob";
object parameters = p;
string result = connection.Query<string>("select @name", parameters).First();
Assert.Equal("bob", result);
}
[Fact]
public void SO25069578_DynamicParams_Procs()
{
var parameters = new DynamicParameters();
parameters.Add("foo", "bar");
// parameters = new DynamicParameters(parameters);
try { connection.Execute("drop proc SO25069578"); }
catch { /* don't care */ }
connection.Execute("create proc SO25069578 @foo nvarchar(max) as select @foo as [X]");
var tran = connection.BeginTransaction(); // gist used transaction; behaves the same either way, though
var row = connection.Query<HazX>("SO25069578", parameters,
commandType: CommandType.StoredProcedure, transaction: tran).Single();
tran.Rollback();
Assert.Equal("bar", row.X);
}
public class HazX
{
public string? X { get; set; }
}
[Fact]
public void SO25297173_DynamicIn()
{
const string query = @"
declare @table table(value int not null);
insert @table values(1);
insert @table values(2);
insert @table values(3);
insert @table values(4);
insert @table values(5);
insert @table values(6);
insert @table values(7);
SELECT value FROM @table WHERE value IN @myIds";
var queryParams = new Dictionary<string, object>(1)
{
["myIds"] = new[] { 5, 6 }
};
var dynamicParams = new DynamicParameters(queryParams);
List<int> result = connection.Query<int>(query, dynamicParams).ToList();
Assert.Equal(2, result.Count);
Assert.Contains(5, result);
Assert.Contains(6, result);
}
[Fact]
public void Test_AddDynamicParametersRepeatedShouldWork()
{
var args = new DynamicParameters();
args.AddDynamicParams(new { Foo = 123 });
args.AddDynamicParams(new { Foo = 123 });
int i = connection.Query<int>("select @Foo", args).Single();
Assert.Equal(123, i);
}
[Fact]
public void Test_AddDynamicParametersRepeatedIfParamTypeIsDbStiringShouldWork()
{
var foo = new DbString() { Value = "123" };
var args = new DynamicParameters();
args.AddDynamicParams(new { Foo = foo });
args.AddDynamicParams(new { Foo = foo });
int i = connection.Query<int>("select @Foo", args).Single();
Assert.Equal(123, i);
}
[Fact]
public void AllowIDictionaryParameters()
{
var parameters = new Dictionary<string, object>(1)
{
["param1"] = 0
};
connection.Query("SELECT @param1", parameters);
}
[Fact]
public void TestParameterWithIndexer()
{
connection.Execute(@"create proc #TestProcWithIndexer
@A int
as
begin
select @A
end");
var item = connection.Query<int>("#TestProcWithIndexer", new ParameterWithIndexer(), commandType: CommandType.StoredProcedure).Single();
}
public class ParameterWithIndexer
{
public int A { get; set; }
public virtual string? this[string columnName]
{
get { return null; }
set { }
}
}
[Fact]
public void TestMultipleParametersWithIndexer()
{
var order = connection.Query<MultipleParametersWithIndexer>("select 1 A,2 B").First();
Assert.Equal(1, order.A);
Assert.Equal(2, order.B);
}
public class MultipleParametersWithIndexer : MultipleParametersWithIndexerDeclaringType
{
public int A { get; set; }
}
public class MultipleParametersWithIndexerDeclaringType
{
public object? this[object field]
{
get { return null; }
set { }
}
public object? this[object field, int index]
{
get { return null; }
set { }
}
public int B { get; set; }
}
[Fact]
public void Issue182_BindDynamicObjectParametersAndColumns()
{
connection.Execute("create table #Dyno ([Id] uniqueidentifier primary key, [Name] nvarchar(50) not null, [Foo] bigint not null);");
var guid = Guid.NewGuid();
var orig = new Dyno { Name = "T Rex", Id = guid, Foo = 123L };
var result = connection.Execute("insert into #Dyno ([Id], [Name], [Foo]) values (@Id, @Name, @Foo);", orig);
var fromDb = connection.Query<Dyno>("select * from #Dyno where Id=@Id", orig).Single();
Assert.Equal((Guid)fromDb.Id, guid);
Assert.Equal("T Rex", fromDb.Name);
Assert.NotNull(fromDb.Foo);
Assert.Equal(123L, (long)fromDb.Foo);
}
public class Dyno
{
public dynamic? Id { get; set; }
public string? Name { get; set; }
public object? Foo { get; set; }
}
[Fact]
public void Issue151_ExpandoObjectArgsQuery()
{
dynamic args = new ExpandoObject();
args.Id = 123;
args.Name = "abc";
var row = connection.Query("select @Id as [Id], @Name as [Name]", (object)args).Single();
Assert.Equal(123, (int)row.Id);
Assert.Equal("abc", (string)row.Name);
}
[Fact]
public void Issue151_ExpandoObjectArgsExec()
{
dynamic args = new ExpandoObject();
args.Id = 123;
args.Name = "abc";
connection.Execute("create table #issue151 (Id int not null, Name nvarchar(20) not null)");
Assert.Equal(1, connection.Execute("insert #issue151 values(@Id, @Name)", (object)args));
var row = connection.Query("select Id, Name from #issue151").Single();
Assert.Equal(123, (int)row.Id);
Assert.Equal("abc", (string)row.Name);
}
[Fact]
public void Issue192_InParameterWorksWithSimilarNames()
{
var rows = connection.Query(@"
declare @Issue192 table (
Field INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Field_1 INT NOT NULL);
insert @Issue192(Field_1) values (1), (2), (3);
SELECT * FROM @Issue192 WHERE Field IN @Field AND Field_1 IN @Field_1",
new { Field = new[] { 1, 2 }, Field_1 = new[] { 2, 3 } }).Single();
Assert.Equal(2, (int)rows.Field);
Assert.Equal(2, (int)rows.Field_1);
}
[Fact]
public void Issue192_InParameterWorksWithSimilarNamesWithUnicode()
{
var rows = connection.Query(@"
declare @Issue192 table (
Field INT NOT NULL PRIMARY KEY IDENTITY(1,1),
Field_1 INT NOT NULL);
insert @Issue192(Field_1) values (1), (2), (3);
SELECT * FROM @Issue192 WHERE Field IN @µ AND Field_1 IN @µµ",
new { µ = new[] { 1, 2 }, µµ = new[] { 2, 3 } }).Single();
Assert.Equal(2, (int)rows.Field);
Assert.Equal(2, (int)rows.Field_1);
}
[FactUnlessCaseSensitiveDatabase]
public void Issue220_InParameterCanBeSpecifiedInAnyCase()
{
// note this might fail if your database server is case-sensitive
Assert.Equal(
new[] { 1 },
connection.Query<int>("select * from (select 1 as Id) as X where Id in @ids", new { Ids = new[] { 1 } })
);
}
[Fact]
public void SO30156367_DynamicParamsWithoutExec()
{
var dbParams = new DynamicParameters();
dbParams.Add("Field1", 1);
var value = dbParams.Get<int>("Field1");
Assert.Equal(1, value);
}
[Fact]
public void RunAllStringSplitTestsDisabled()
{
RunAllStringSplitTests(-1, 1500);
}
[FactRequiredCompatibilityLevel(FactRequiredCompatibilityLevelAttribute.SqlServer2016)]
public void RunAllStringSplitTestsEnabled()
{
RunAllStringSplitTests(10, 4500);
}
private void RunAllStringSplitTests(int stringSplit, int max = 150)
{
int oldVal = SqlMapper.Settings.InListStringSplitCount;
try
{
SqlMapper.Settings.InListStringSplitCount = stringSplit;
try { connection.Execute("drop table #splits"); } catch { /* don't care */ }
int count = connection.QuerySingle<int>("create table #splits (i int not null);"
+ string.Concat(Enumerable.Range(-max, max * 3).Select(i => $"insert #splits (i) values ({i});"))
+ "select count(1) from #splits");
Assert.Equal(count, 3 * max);
for (int i = 0; i < max; Incr(ref i))
{
try
{
var vals = Enumerable.Range(1, i);
var list = connection.Query<int>("select i from #splits where i in @vals", new { vals }).AsList();
Assert.Equal(list.Count, i);
Assert.Equal(list.Sum(), vals.Sum());
}
catch (Exception ex)
{
throw new InvalidOperationException($"Error when i={i}: {ex.Message}", ex);
}
}
}
finally
{
SqlMapper.Settings.InListStringSplitCount = oldVal;
}
}
private static void Incr(ref int i)
{
if (i <= 15) i++;
else if (i <= 80) i += 5;
else if (i <= 200) i += 10;
else if (i <= 1000) i += 50;
else i += 100;
}
[Fact]
public void Issue601_InternationalParameterNamesWork()
{
// regular parameter
var result = connection.QuerySingle<int>("select @æøå٦", new { æøå٦ = 42 });
Assert.Equal(42, result);
}
[FactLongRunning]
public void TestListExpansionPadding_Enabled() => TestListExpansionPadding(true);
[FactLongRunning]
public void TestListExpansionPadding_Disabled() => TestListExpansionPadding(false);
[Theory]
[InlineData(true)]
[InlineData(false)]
public void OleDbParamFilterFails(bool legacyParameterToken)
{
SqlMapper.PurgeQueryCache();
var oldValue = SqlMapper.Settings.SupportLegacyParameterTokens;
try
{
SqlMapper.Settings.SupportLegacyParameterTokens = legacyParameterToken;
if (legacyParameterToken) // OLE DB parameter support enabled; can false-positive
{
Assert.Throws<NotSupportedException>(() => GetValue(connection));
}
else // OLE DB parameter support disabled; more reliable
{
Assert.Equal("this ? could be awkward", GetValue(connection));
}
}
finally
{
SqlMapper.Settings.SupportLegacyParameterTokens = oldValue;
}
static string GetValue(DbConnection connection)
=> connection.QuerySingle<string>("select 'this ? could be awkward'",
new TypeWithDodgyProperties());
}
private void TestListExpansionPadding(bool enabled)
{
bool oldVal = SqlMapper.Settings.PadListExpansions;
try
{
SqlMapper.Settings.PadListExpansions = enabled;
Assert.Equal(4096, connection.ExecuteScalar<int>(@"
create table #ListExpansion(id int not null identity(1,1), value int null);
insert #ListExpansion (value) values (null);
declare @loop int = 0;
while (@loop < 12)
begin -- double it
insert #ListExpansion (value) select value from #ListExpansion;
set @loop = @loop + 1;
end
select count(1) as [Count] from #ListExpansion"));
var list = new List<int>();
int nextId = 1, batchCount;
var rand = new Random(12345);
const int SQL_SERVER_MAX_PARAMS = 2095;
TestListForExpansion(list, enabled); // test while empty
while (list.Count < SQL_SERVER_MAX_PARAMS)
{
try
{
if (list.Count <= 20) batchCount = 1;
else if (list.Count <= 200) batchCount = rand.Next(1, 40);
else batchCount = rand.Next(1, 100);
for (int j = 0; j < batchCount && list.Count < SQL_SERVER_MAX_PARAMS; j++)
list.Add(nextId++);
TestListForExpansion(list, enabled);
}
catch (Exception ex)
{
throw new InvalidOperationException($"Failure with {list.Count} items: {ex.Message}", ex);
}
}
}
finally
{
SqlMapper.Settings.PadListExpansions = oldVal;
}
}
private void TestListForExpansion(List<int> list, bool enabled)
{
var row = connection.QuerySingle(@"
declare @hits int, @misses int, @count int;
select @count = count(1) from #ListExpansion;
select @hits = count(1) from #ListExpansion where id in @ids ;
select @misses = count(1) from #ListExpansion where not id in @ids ;
declare @query nvarchar(max) = N' in @ids '; -- ok, I confess to being pleased with this hack ;p
select @hits as [Hits], (@count - @misses) as [Misses], @query as [Query];
", new { ids = list });
int hits = row.Hits, misses = row.Misses;
string query = row.Query;
int argCount = Regex.Matches(query, "@ids[0-9]").Count;
int expectedCount = GetExpectedListExpansionCount(list.Count, enabled);
Assert.Equal(hits, list.Count);
Assert.Equal(misses, list.Count);
Assert.Equal(argCount, expectedCount);
}
private static int GetExpectedListExpansionCount(int count, bool enabled)
{
if (!enabled) return count;
if (count <= 5 || count > 2070) return count;
int padFactor;
if (count <= 150) padFactor = 10;
else if (count <= 750) padFactor = 50;
else if (count <= 2000) padFactor = 100;
else if (count <= 2070) padFactor = 10;
else padFactor = 200;
int blocks = count / padFactor, delta = count % padFactor;
if (delta != 0) blocks++;
return blocks * padFactor;
}
[Fact]
public void Issue1907_SqlDecimalPreciseValues()
{
bool close = false;
try
{
if (connection.State != ConnectionState.Open)
{
connection.Open();
close = true;
}
connection.Execute(@"
create table #Issue1907 (
Id int not null primary key identity(1,1),
Value numeric(30,15) not null);");
const string PreciseValue = "999999999999999.999999999999999";
SqlDecimal sentValue = SqlDecimal.Parse(PreciseValue), recvValue;
connection.Execute("insert #Issue1907 (Value) values (@value)", new { value = sentValue });
// access via vendor-specific API; if this fails, nothing else can work
using (var wrappedReader = connection.ExecuteReader("select Id, Value from #Issue1907"))
{
var reader = Assert.IsAssignableFrom<IWrappedDataReader>(wrappedReader).Reader;
Assert.True(reader.Read());
if (reader is Microsoft.Data.SqlClient.SqlDataReader msReader)
{
recvValue = msReader.GetSqlDecimal(1);
}
else if (reader is System.Data.SqlClient.SqlDataReader sdReader)
{
recvValue = sdReader.GetSqlDecimal(1);
}
else
{
throw new InvalidOperationException($"unexpected reader type: {reader.GetType().FullName}");
}
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
Assert.False(reader.Read());
Assert.False(reader.NextResult());
}
// access via generic API
using (var wrappedReader = connection.ExecuteReader("select Id, Value from #Issue1907"))
{
var reader = Assert.IsAssignableFrom<DbDataReader>(Assert.IsAssignableFrom<IWrappedDataReader>(wrappedReader).Reader);
Assert.True(reader.Read());
recvValue = reader.GetFieldValue<SqlDecimal>(1);
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
Assert.False(reader.Read());
Assert.False(reader.NextResult());
}
// prove that we **cannot** fix ExecuteScalar, because ADO.NET itself doesn't work for that
Assert.Throws<OverflowException>(() =>
{
using var cmd = connection.CreateCommand();
cmd.CommandText = "select Value from #Issue1907";
cmd.CommandType = CommandType.Text;
_ = cmd.ExecuteScalar();
});
// prove that simple read: works
recvValue = connection.QuerySingle<SqlDecimal>("select Value from #Issue1907");
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
recvValue = connection.QuerySingle<SqlDecimal?>("select Value from #Issue1907")!.Value;
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
// prove that object read: works
recvValue = connection.QuerySingle<HazSqlDecimal>("select Id, Value from #Issue1907").Value;
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
recvValue = connection.QuerySingle<HazNullableSqlDecimal>("select Id, Value from #Issue1907").Value!.Value;
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
// prove that value-tuple read: works
recvValue = connection.QuerySingle<(int Id, SqlDecimal Value)>("select Id, Value from #Issue1907").Value;
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
recvValue = connection.QuerySingle<(int Id, SqlDecimal? Value)>("select Id, Value from #Issue1907").Value!.Value;
Assert.Equal(sentValue, recvValue);
Assert.Equal(PreciseValue, recvValue.ToString());
}
finally
{
if (close) connection.Close();
}
}
class HazSqlDecimal
{
public int Id { get; set; }
public SqlDecimal Value { get; set; }
}
class HazNullableSqlDecimal
{
public int Id { get; set; }
public SqlDecimal? Value { get; set; }
}
class TypeWithDodgyProperties
{
public string Name => throw new NotSupportedException();
}
}
}