Commit a9560b31 authored by Sergii Volchkov's avatar Sergii Volchkov Committed by Nick Craver

Enable SqlDataRecord TVPs on netstandard1.3. (#802)

* Enable SqlDataRecord TVPs on netstandard1.3.
* Update netstandard2.0 SqlClient references to Preview 2.
* Skip TransactionScope tests for netcoreapp2.0.
- According to https://github.com/dotnet/corefx/issues/12534, ambient transaction enlistment is not included with .net core 2.0.
* Update references in Dapper.StrongName.
* Disable parallel test run to stabilize flaky type handler tests.
* Disable parallel test run by assembly attribute.
* Move Issue461 tests to TypeHandlerTests.
* Group type handler tests in a collection, re-enable parallelization.
- Being part of the same collection, type handler tests will run sequentially. All other tests can run in parallel.
* Move tests relying on query cache & type maps to a separate collection.
parent 910246bf
...@@ -22,7 +22,7 @@ ...@@ -22,7 +22,7 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Collections.Concurrent" Version="4.3.0" /> <PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
<PackageReference Include="System.Collections.NonGeneric" Version="4.3.0" /> <PackageReference Include="System.Collections.NonGeneric" Version="4.3.0" />
<PackageReference Include="System.Data.Common" Version="4.3.0" /> <PackageReference Include="System.Data.SqlClient" Version="4.3.0" />
<PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" /> <PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
...@@ -31,7 +31,8 @@ ...@@ -31,7 +31,8 @@
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview1-25305-02" /> <PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview2-25405-01" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
</ItemGroup> </ItemGroup>
......
...@@ -19,7 +19,7 @@ ...@@ -19,7 +19,7 @@
<PackageReference Include="Microsoft.Data.Sqlite" Version="1.1.0" /> <PackageReference Include="Microsoft.Data.Sqlite" Version="1.1.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="MySql.Data" Version="7.0.7-m61" /> <PackageReference Include="MySql.Data" Version="7.0.7-m61" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview1-25305-02" /> <PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview2-25405-01" />
<PackageReference Include="xunit" Version="2.3.0-beta1-build3642" /> <PackageReference Include="xunit" Version="2.3.0-beta1-build3642" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta1-build1309" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta1-build1309" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta1-build3642" /> <DotNetCliToolReference Include="dotnet-xunit" Version="2.3.0-beta1-build3642" />
......
...@@ -5,10 +5,8 @@ ...@@ -5,10 +5,8 @@
using Dapper.Contrib.Extensions; using Dapper.Contrib.Extensions;
#if !NETCOREAPP1_0
using System.Transactions;
#endif
#if !NETCOREAPP1_0 && !NETCOREAPP2_0 #if !NETCOREAPP1_0 && !NETCOREAPP2_0
using System.Transactions;
using System.Data.SqlServerCe; using System.Data.SqlServerCe;
#endif #endif
using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute; using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute;
...@@ -527,7 +525,7 @@ public void Transactions() ...@@ -527,7 +525,7 @@ public void Transactions()
} }
} }
#if !NETCOREAPP1_0 #if !NETCOREAPP1_0 && !NETCOREAPP2_0
[Fact] [Fact]
public void TransactionScope() public void TransactionScope()
{ {
......
...@@ -353,6 +353,12 @@ public void RunSequentialVersusParallelSync() ...@@ -353,6 +353,12 @@ public void RunSequentialVersusParallelSync()
Console.WriteLine("Pipeline: {0}ms", watch.ElapsedMilliseconds); Console.WriteLine("Pipeline: {0}ms", watch.ElapsedMilliseconds);
} }
[Collection("QueryCacheTests")]
public class AsyncQueryCacheTests : TestBase
{
private SqlConnection _marsConnection;
private SqlConnection MarsConnection => _marsConnection ?? (_marsConnection = GetOpenConnection(true));
[Fact] [Fact]
public void AssertNoCacheWorksForQueryMultiple() public void AssertNoCacheWorksForQueryMultiple()
{ {
...@@ -376,6 +382,7 @@ public void AssertNoCacheWorksForQueryMultiple() ...@@ -376,6 +382,7 @@ public void AssertNoCacheWorksForQueryMultiple()
c.IsEqualTo(123); c.IsEqualTo(123);
d.IsEqualTo(456); d.IsEqualTo(456);
} }
}
private class BasicType private class BasicType
{ {
......
...@@ -104,85 +104,6 @@ public bool GetWentThroughProperConstructor() ...@@ -104,85 +104,6 @@ public bool GetWentThroughProperConstructor()
} }
} }
[Fact]
public void Issue461_TypeHandlerWorksInConstructor()
{
SqlMapper.AddTypeHandler(new Issue461_BlargHandler());
connection.Execute(@"CREATE TABLE #Issue461 (
Id int not null IDENTITY(1,1),
SomeValue nvarchar(50),
SomeBlargValue nvarchar(200),
)");
const string Expected = "abc123def";
var blarg = new Blarg(Expected);
connection.Execute(
"INSERT INTO #Issue461 (SomeValue, SomeBlargValue) VALUES (@value, @blarg)",
new { value = "what up?", blarg });
// test: without constructor
var parameterlessWorks = connection.QuerySingle<Issue461_ParameterlessTypeConstructor>("SELECT * FROM #Issue461");
parameterlessWorks.Id.IsEqualTo(1);
parameterlessWorks.SomeValue.IsEqualTo("what up?");
parameterlessWorks.SomeBlargValue.Value.IsEqualTo(Expected);
// test: via constructor
var parameterDoesNot = connection.QuerySingle<Issue461_ParameterisedTypeConstructor>("SELECT * FROM #Issue461");
parameterDoesNot.Id.IsEqualTo(1);
parameterDoesNot.SomeValue.IsEqualTo("what up?");
parameterDoesNot.SomeBlargValue.Value.IsEqualTo(Expected);
}
// I would usually expect this to be a struct; using a class
// so that we can't pass unexpectedly due to forcing an unsafe cast - want
// to see an InvalidCastException if it is wrong
private class Blarg
{
public Blarg(string value) { Value = value; }
public string Value { get; }
public override string ToString()
{
return Value;
}
}
private class Issue461_BlargHandler : SqlMapper.TypeHandler<Blarg>
{
public override void SetValue(IDbDataParameter parameter, Blarg value)
{
parameter.Value = ((object)value.Value) ?? DBNull.Value;
}
public override Blarg Parse(object value)
{
string s = (value == null || value is DBNull) ? null : Convert.ToString(value);
return new Blarg(s);
}
}
private class Issue461_ParameterlessTypeConstructor
{
public int Id { get; set; }
public string SomeValue { get; set; }
public Blarg SomeBlargValue { get; set; }
}
private class Issue461_ParameterisedTypeConstructor
{
public Issue461_ParameterisedTypeConstructor(int id, string someValue, Blarg someBlargValue)
{
Id = id;
SomeValue = someValue;
SomeBlargValue = someBlargValue;
}
public int Id { get; }
public string SomeValue { get; }
public Blarg SomeBlargValue { get; }
}
public static class AbstractInheritance public static class AbstractInheritance
{ {
public abstract class Order public abstract class Order
......
...@@ -23,7 +23,7 @@ ...@@ -23,7 +23,7 @@
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="15.0.0" />
<PackageReference Include="MySql.Data" Version="7.0.7-m61" /> <PackageReference Include="MySql.Data" Version="7.0.7-m61" />
<PackageReference Include="Npgsql" Version="3.2.2" /> <PackageReference Include="Npgsql" Version="3.2.2" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview1-25305-02" /> <PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview2-25405-01" />
<PackageReference Include="System.ValueTuple" Version="4.3.0" /> <PackageReference Include="System.ValueTuple" Version="4.3.0" />
<PackageReference Include="xunit" Version="2.3.0-beta1-build3642" /> <PackageReference Include="xunit" Version="2.3.0-beta1-build3642" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta1-build1309" /> <PackageReference Include="xunit.runner.visualstudio" Version="2.3.0-beta1-build1309" />
......
...@@ -5,6 +5,9 @@ ...@@ -5,6 +5,9 @@
namespace Dapper.Tests namespace Dapper.Tests
{ {
public partial class DataReaderTests : TestBase public partial class DataReaderTests : TestBase
{
[Collection("QueryCacheTests")]
public class DataReaderQueryCacheTests : TestBase
{ {
[Fact] [Fact]
public void GetSameReaderForSameShape() public void GetSameReaderForSameShape()
...@@ -37,6 +40,7 @@ public void GetSameReaderForSameShape() ...@@ -37,6 +40,7 @@ public void GetSameReaderForSameShape()
ReferenceEquals(origParser, secondParser).IsEqualTo(true); ReferenceEquals(origParser, secondParser).IsEqualTo(true);
ReferenceEquals(secondParser, thirdParser).IsEqualTo(false); ReferenceEquals(secondParser, thirdParser).IsEqualTo(false);
} }
}
[Fact] [Fact]
public void DiscriminatedUnion() public void DiscriminatedUnion()
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
using System.Linq; using System.Linq;
namespace Dapper.Tests namespace Dapper.Tests
{ {
[Collection("QueryCacheTests")]
public class NullTests : TestBase public class NullTests : TestBase
{ {
[Fact] [Fact]
......
...@@ -38,19 +38,8 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id ...@@ -38,19 +38,8 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id
} }
} }
private class IntDynamicParam : SqlMapper.IDynamicParameters private static List<Microsoft.SqlServer.Server.SqlDataRecord> CreateSqlDataRecordList(IEnumerable<int> numbers)
{
private readonly IEnumerable<int> numbers;
public IntDynamicParam(IEnumerable<int> numbers)
{ {
this.numbers = numbers;
}
public void AddParameters(IDbCommand command, SqlMapper.Identity identity)
{
var sqlCommand = (SqlCommand)command;
sqlCommand.CommandType = CommandType.StoredProcedure;
var number_list = new List<Microsoft.SqlServer.Server.SqlDataRecord>(); var number_list = new List<Microsoft.SqlServer.Server.SqlDataRecord>();
// Create an SqlMetaData object that describes our table type. // Create an SqlMetaData object that describes our table type.
...@@ -64,6 +53,24 @@ public void AddParameters(IDbCommand command, SqlMapper.Identity identity) ...@@ -64,6 +53,24 @@ public void AddParameters(IDbCommand command, SqlMapper.Identity identity)
number_list.Add(rec); // Add it to the list. 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)
{
var sqlCommand = (SqlCommand)command;
sqlCommand.CommandType = CommandType.StoredProcedure;
var number_list = CreateSqlDataRecordList(numbers);
// Add the table parameter. // Add the table parameter.
var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured);
p.Direction = ParameterDirection.Input; p.Direction = ParameterDirection.Input;
...@@ -85,18 +92,7 @@ public void AddParameter(IDbCommand command, string name) ...@@ -85,18 +92,7 @@ public void AddParameter(IDbCommand command, string name)
var sqlCommand = (SqlCommand)command; var sqlCommand = (SqlCommand)command;
sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.CommandType = CommandType.StoredProcedure;
var number_list = new List<Microsoft.SqlServer.Server.SqlDataRecord>(); var number_list = CreateSqlDataRecordList(numbers);
// 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.
}
// Add the table parameter. // Add the table parameter.
var p = sqlCommand.Parameters.Add(name, SqlDbType.Structured); var p = sqlCommand.Parameters.Add(name, SqlDbType.Structured);
...@@ -218,7 +214,6 @@ public void TestMassiveStrings() ...@@ -218,7 +214,6 @@ public void TestMassiveStrings()
.IsEqualTo(str); .IsEqualTo(str);
} }
#if !NETCOREAPP1_0
[Fact] [Fact]
public void TestTVPWithAnonymousObject() public void TestTVPWithAnonymousObject()
{ {
...@@ -289,18 +284,7 @@ public new void AddParameters(IDbCommand command, SqlMapper.Identity identity) ...@@ -289,18 +284,7 @@ public new void AddParameters(IDbCommand command, SqlMapper.Identity identity)
var sqlCommand = (SqlCommand)command; var sqlCommand = (SqlCommand)command;
sqlCommand.CommandType = CommandType.StoredProcedure; sqlCommand.CommandType = CommandType.StoredProcedure;
var number_list = new List<Microsoft.SqlServer.Server.SqlDataRecord>(); var number_list = CreateSqlDataRecordList(numbers);
// 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.
}
// Add the table parameter. // Add the table parameter.
var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured);
...@@ -344,6 +328,83 @@ public void TestTVPWithAdditionalParams() ...@@ -344,6 +328,83 @@ public void TestTVPWithAdditionalParams()
} }
} }
[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(new int[] { 1, 2, 3 });
var nums = connection.Query<int>("get_ints", new { integers = records.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList();
nums.IsSequenceEqualTo(new int[] { 1, 2, 3 });
nums = connection.Query<int>("select * from @integers", new { integers = records.AsTableValuedParameter("int_list_type") }).ToList();
nums.IsSequenceEqualTo(new int[] { 1, 2, 3 });
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 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.
IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> records = CreateSqlDataRecordList(new int[] { 1, 2, 3 });
var nums = connection.Query<int>("get_ints", new { integers = records }, commandType: CommandType.StoredProcedure).ToList();
nums.IsSequenceEqualTo(new int[] { 1, 2, 3 });
try
{
connection.Query<int>("select * from @integers", new { integers = records }).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");
}
}
}
#if !NETCOREAPP1_0
[Fact] [Fact]
public void DataTableParameters() public void DataTableParameters()
{ {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
namespace Dapper.Tests.Providers namespace Dapper.Tests.Providers
{ {
[Collection("TypeHandlerTests")]
public class EntityFrameworkTests : TestBase public class EntityFrameworkTests : TestBase
{ {
public EntityFrameworkTests() public EntityFrameworkTests()
......
...@@ -24,6 +24,9 @@ public void DapperEnumValue_Sqlite() ...@@ -24,6 +24,9 @@ public void DapperEnumValue_Sqlite()
} }
} }
[Collection("TypeHandlerTests")]
public class SqliteTypeHandlerTests : TestBase
{
[FactSqlite] [FactSqlite]
public void Issue466_SqliteHatesOptimizations() public void Issue466_SqliteHatesOptimizations()
{ {
...@@ -61,6 +64,7 @@ public async Task Issue466_SqliteHatesOptimizations_Async() ...@@ -61,6 +64,7 @@ public async Task Issue466_SqliteHatesOptimizations_Async()
row.Id.IsEqualTo(42); row.Id.IsEqualTo(42);
} }
} }
}
[FactSqlite] [FactSqlite]
public void Isse467_SqliteLikesParametersWithPrefix() public void Isse467_SqliteLikesParametersWithPrefix()
......
...@@ -8,7 +8,11 @@ ...@@ -8,7 +8,11 @@
namespace Dapper.Tests namespace Dapper.Tests
{ {
[Collection("TypeHandlerTests")]
public class TypeHandlerTests : TestBase public class TypeHandlerTests : TestBase
{
[Collection("QueryCacheTests")]
public class TypeHandlerQueryCacheTests : TestBase
{ {
[Fact] [Fact]
public void TestChangingDefaultStringTypeMappingToAnsiString() public void TestChangingDefaultStringTypeMappingToAnsiString()
...@@ -48,6 +52,52 @@ public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault() ...@@ -48,6 +52,52 @@ public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault()
SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String
} }
[Fact]
public void TestCustomTypeMap()
{
// default mapping
var item = connection.Query<TypeWithMapping>("Select 'AVal' as A, 'BVal' as B").Single();
item.A.IsEqualTo("AVal");
item.B.IsEqualTo("BVal");
// custom mapping
var map = new CustomPropertyTypeMap(typeof(TypeWithMapping),
(type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);
item = connection.Query<TypeWithMapping>("Select 'AVal' as A, 'BVal' as B").Single();
item.A.IsEqualTo("BVal");
item.B.IsEqualTo("AVal");
// reset to default
SqlMapper.SetTypeMap(typeof(TypeWithMapping), null);
item = connection.Query<TypeWithMapping>("Select 'AVal' as A, 'BVal' as B").Single();
item.A.IsEqualTo("AVal");
item.B.IsEqualTo("BVal");
}
private static string GetDescriptionFromAttribute(MemberInfo member)
{
if (member == null) return null;
#if NETCOREAPP1_0
var data = member.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(DescriptionAttribute));
return (string)data?.ConstructorArguments.Single().Value;
#else
var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
return attrib?.Description;
#endif
}
public class TypeWithMapping
{
[Description("B")]
public string A { get; set; }
[Description("A")]
public string B { get; set; }
}
}
[Fact] [Fact]
public void Issue136_ValueTypeHandlers() public void Issue136_ValueTypeHandlers()
{ {
...@@ -518,51 +568,6 @@ private class ResultsChangeType ...@@ -518,51 +568,6 @@ private class ResultsChangeType
public int Z { get; set; } public int Z { get; set; }
} }
[Fact]
public void TestCustomTypeMap()
{
// default mapping
var item = connection.Query<TypeWithMapping>("Select 'AVal' as A, 'BVal' as B").Single();
item.A.IsEqualTo("AVal");
item.B.IsEqualTo("BVal");
// custom mapping
var map = new CustomPropertyTypeMap(typeof(TypeWithMapping),
(type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName));
SqlMapper.SetTypeMap(typeof(TypeWithMapping), map);
item = connection.Query<TypeWithMapping>("Select 'AVal' as A, 'BVal' as B").Single();
item.A.IsEqualTo("BVal");
item.B.IsEqualTo("AVal");
// reset to default
SqlMapper.SetTypeMap(typeof(TypeWithMapping), null);
item = connection.Query<TypeWithMapping>("Select 'AVal' as A, 'BVal' as B").Single();
item.A.IsEqualTo("AVal");
item.B.IsEqualTo("BVal");
}
private static string GetDescriptionFromAttribute(MemberInfo member)
{
if (member == null) return null;
#if NETCOREAPP1_0
var data = member.CustomAttributes.FirstOrDefault(x => x.AttributeType == typeof(DescriptionAttribute));
return (string)data?.ConstructorArguments.Single().Value;
#else
var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false);
return attrib?.Description;
#endif
}
public class TypeWithMapping
{
[Description("B")]
public string A { get; set; }
[Description("A")]
public string B { get; set; }
}
public class WrongTypes public class WrongTypes
{ {
public int A { get; set; } public int A { get; set; }
...@@ -658,5 +663,84 @@ public void SO29343103_UtcDates() ...@@ -658,5 +663,84 @@ public void SO29343103_UtcDates()
var delta = returned - date; var delta = returned - date;
Assert.IsTrue(delta.TotalMilliseconds >= -10 && delta.TotalMilliseconds <= 10); Assert.IsTrue(delta.TotalMilliseconds >= -10 && delta.TotalMilliseconds <= 10);
} }
[Fact]
public void Issue461_TypeHandlerWorksInConstructor()
{
SqlMapper.AddTypeHandler(new Issue461_BlargHandler());
connection.Execute(@"CREATE TABLE #Issue461 (
Id int not null IDENTITY(1,1),
SomeValue nvarchar(50),
SomeBlargValue nvarchar(200),
)");
const string Expected = "abc123def";
var blarg = new Blarg(Expected);
connection.Execute(
"INSERT INTO #Issue461 (SomeValue, SomeBlargValue) VALUES (@value, @blarg)",
new { value = "what up?", blarg });
// test: without constructor
var parameterlessWorks = connection.QuerySingle<Issue461_ParameterlessTypeConstructor>("SELECT * FROM #Issue461");
parameterlessWorks.Id.IsEqualTo(1);
parameterlessWorks.SomeValue.IsEqualTo("what up?");
parameterlessWorks.SomeBlargValue.Value.IsEqualTo(Expected);
// test: via constructor
var parameterDoesNot = connection.QuerySingle<Issue461_ParameterisedTypeConstructor>("SELECT * FROM #Issue461");
parameterDoesNot.Id.IsEqualTo(1);
parameterDoesNot.SomeValue.IsEqualTo("what up?");
parameterDoesNot.SomeBlargValue.Value.IsEqualTo(Expected);
}
// I would usually expect this to be a struct; using a class
// so that we can't pass unexpectedly due to forcing an unsafe cast - want
// to see an InvalidCastException if it is wrong
private class Blarg
{
public Blarg(string value) { Value = value; }
public string Value { get; }
public override string ToString()
{
return Value;
}
}
private class Issue461_BlargHandler : SqlMapper.TypeHandler<Blarg>
{
public override void SetValue(IDbDataParameter parameter, Blarg value)
{
parameter.Value = ((object)value.Value) ?? DBNull.Value;
}
public override Blarg Parse(object value)
{
string s = (value == null || value is DBNull) ? null : Convert.ToString(value);
return new Blarg(s);
}
}
private class Issue461_ParameterlessTypeConstructor
{
public int Id { get; set; }
public string SomeValue { get; set; }
public Blarg SomeBlargValue { get; set; }
}
private class Issue461_ParameterisedTypeConstructor
{
public Issue461_ParameterisedTypeConstructor(int id, string someValue, Blarg someBlargValue)
{
Id = id;
SomeValue = someValue;
SomeBlargValue = someBlargValue;
}
public int Id { get; }
public string SomeValue { get; }
public Blarg SomeBlargValue { get; }
}
} }
} }
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' ">
<PackageReference Include="System.Collections.Concurrent" Version="4.3.0" /> <PackageReference Include="System.Collections.Concurrent" Version="4.3.0" />
<PackageReference Include="System.Collections.NonGeneric" Version="4.3.0" /> <PackageReference Include="System.Collections.NonGeneric" Version="4.3.0" />
<PackageReference Include="System.Data.Common" Version="4.3.0" /> <PackageReference Include="System.Data.SqlClient" Version="4.3.0" />
<PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" /> <PackageReference Include="System.Dynamic.Runtime" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
...@@ -26,7 +26,8 @@ ...@@ -26,7 +26,8 @@
<PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" /> <PackageReference Include="System.Xml.XmlDocument" Version="4.3.0" />
</ItemGroup> </ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' "> <ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview1-25305-02" /> <PackageReference Include="System.Data.Common" Version="4.3.0" />
<PackageReference Include="System.Data.SqlClient" Version="4.4.0-preview2-25405-01" />
<PackageReference Include="System.Reflection.Emit" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit" Version="4.3.0" />
<PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" /> <PackageReference Include="System.Reflection.Emit.Lightweight" Version="4.3.0" />
</ItemGroup> </ItemGroup>
......
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
#if !NETSTANDARD1_3
namespace Dapper namespace Dapper
{ {
internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler
...@@ -18,4 +17,3 @@ public void SetValue(IDbDataParameter parameter, object value) ...@@ -18,4 +17,3 @@ public void SetValue(IDbDataParameter parameter, object value)
} }
} }
} }
#endif
\ No newline at end of file
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Data; using System.Data;
using System.Reflection;
#if !NETSTANDARD1_3
namespace Dapper namespace Dapper
{ {
/// <summary> /// <summary>
...@@ -23,18 +22,6 @@ public SqlDataRecordListTVPParameter(IEnumerable<Microsoft.SqlServer.Server.SqlD ...@@ -23,18 +22,6 @@ public SqlDataRecordListTVPParameter(IEnumerable<Microsoft.SqlServer.Server.SqlD
this.typeName = typeName; this.typeName = typeName;
} }
private static readonly Action<System.Data.SqlClient.SqlParameter, string> setTypeName;
static SqlDataRecordListTVPParameter()
{
var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty(nameof(System.Data.SqlClient.SqlParameter.TypeName), BindingFlags.Instance | BindingFlags.Public);
if (prop != null && prop.PropertyType == typeof(string) && prop.CanWrite)
{
setTypeName = (Action<System.Data.SqlClient.SqlParameter, string>)
Delegate.CreateDelegate(typeof(Action<System.Data.SqlClient.SqlParameter, string>), prop.GetSetMethod());
}
}
void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name)
{ {
var param = command.CreateParameter(); var param = command.CreateParameter();
...@@ -54,4 +41,3 @@ internal static void Set(IDbDataParameter parameter, IEnumerable<Microsoft.SqlSe ...@@ -54,4 +41,3 @@ internal static void Set(IDbDataParameter parameter, IEnumerable<Microsoft.SqlSe
} }
} }
} }
#endif
\ No newline at end of file
...@@ -219,24 +219,22 @@ private static void ResetTypeHandlers(bool clone) ...@@ -219,24 +219,22 @@ private static void ResetTypeHandlers(bool clone)
typeHandlers = new Dictionary<Type, ITypeHandler>(); typeHandlers = new Dictionary<Type, ITypeHandler>();
#if !NETSTANDARD1_3 #if !NETSTANDARD1_3
AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone);
#endif
try try
{ {
AddSqlDataRecordsTypeHandler(clone); AddSqlDataRecordsTypeHandler(clone);
} }
catch { /* https://github.com/StackExchange/dapper-dot-net/issues/424 */ } catch { /* https://github.com/StackExchange/dapper-dot-net/issues/424 */ }
#endif
AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone);
AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone);
AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone);
} }
#if !NETSTANDARD1_3
[MethodImpl(MethodImplOptions.NoInlining)] [MethodImpl(MethodImplOptions.NoInlining)]
private static void AddSqlDataRecordsTypeHandler(bool clone) private static void AddSqlDataRecordsTypeHandler(bool clone)
{ {
AddTypeHandlerImpl(typeof(IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord>), new SqlDataRecordHandler(), clone); AddTypeHandlerImpl(typeof(IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord>), new SqlDataRecordHandler(), clone);
} }
#endif
/// <summary> /// <summary>
/// Configure the specified type to be mapped to a given db-type. /// Configure the specified type to be mapped to a given db-type.
...@@ -3662,15 +3660,15 @@ public static void SetTypeName(this DataTable table, string typeName) ...@@ -3662,15 +3660,15 @@ public static void SetTypeName(this DataTable table, string typeName)
/// <param name="table">The <see cref="DataTable"/> that has a type name associated with it.</param> /// <param name="table">The <see cref="DataTable"/> that has a type name associated with it.</param>
public static string GetTypeName(this DataTable table) => public static string GetTypeName(this DataTable table) =>
table?.ExtendedProperties[DataTableTypeNameKey] as string; table?.ExtendedProperties[DataTableTypeNameKey] as string;
#endif
/// <summary> /// <summary>
/// Used to pass a IEnumerable&lt;SqlDataRecord&gt; as a <see cref="TableValuedParameter"/>. /// Used to pass a IEnumerable&lt;SqlDataRecord&gt; as a TableValuedParameter.
/// </summary> /// </summary>
/// <param name="list">Thhe list of records to convert to TVPs.</param> /// <param name="list">The list of records to convert to TVPs.</param>
/// <param name="typeName">The sql parameter type name.</param> /// <param name="typeName">The sql parameter type name.</param>
public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> list, string typeName = null) => public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable<Microsoft.SqlServer.Server.SqlDataRecord> list, string typeName = null) =>
new SqlDataRecordListTVPParameter(list, typeName); new SqlDataRecordListTVPParameter(list, typeName);
#endif
// one per thread // one per thread
[ThreadStatic] [ThreadStatic]
......
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