diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index dee9414e2..36abdcb67 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -22,7 +22,7 @@ - + @@ -31,7 +31,8 @@ - + + diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 45fb1a4e0..520dfb246 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -19,7 +19,7 @@ - + diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index 0cd688e5e..c7a3cf9ad 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -5,10 +5,8 @@ using Dapper.Contrib.Extensions; -#if !NETCOREAPP1_0 -using System.Transactions; -#endif #if !NETCOREAPP1_0 && !NETCOREAPP2_0 +using System.Transactions; using System.Data.SqlServerCe; #endif using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute; @@ -527,7 +525,7 @@ public void Transactions() } } -#if !NETCOREAPP1_0 +#if !NETCOREAPP1_0 && !NETCOREAPP2_0 [Fact] public void TransactionScope() { diff --git a/Dapper.Tests/AsyncTests.cs b/Dapper.Tests/AsyncTests.cs index 27fd24f8f..bfd57502d 100644 --- a/Dapper.Tests/AsyncTests.cs +++ b/Dapper.Tests/AsyncTests.cs @@ -353,28 +353,35 @@ public void RunSequentialVersusParallelSync() Console.WriteLine("Pipeline: {0}ms", watch.ElapsedMilliseconds); } - [Fact] - public void AssertNoCacheWorksForQueryMultiple() + [Collection("QueryCacheTests")] + public class AsyncQueryCacheTests : TestBase { - const int a = 123, b = 456; - var cmdDef = new CommandDefinition(@"select @a; select @b;", new - { - a, b - }, commandType: CommandType.Text, flags: CommandFlags.NoCache); + private SqlConnection _marsConnection; + private SqlConnection MarsConnection => _marsConnection ?? (_marsConnection = GetOpenConnection(true)); - int c, d; - SqlMapper.PurgeQueryCache(); - int before = SqlMapper.GetCachedSQLCount(); - using (var multi = MarsConnection.QueryMultiple(cmdDef)) + [Fact] + public void AssertNoCacheWorksForQueryMultiple() { - c = multi.Read().Single(); - d = multi.Read().Single(); + const int a = 123, b = 456; + var cmdDef = new CommandDefinition(@"select @a; select @b;", new + { + a, b + }, commandType: CommandType.Text, flags: CommandFlags.NoCache); + + int c, d; + SqlMapper.PurgeQueryCache(); + int before = SqlMapper.GetCachedSQLCount(); + using (var multi = MarsConnection.QueryMultiple(cmdDef)) + { + c = multi.Read().Single(); + d = multi.Read().Single(); + } + int after = SqlMapper.GetCachedSQLCount(); + before.IsEqualTo(0); + after.IsEqualTo(0); + c.IsEqualTo(123); + d.IsEqualTo(456); } - int after = SqlMapper.GetCachedSQLCount(); - before.IsEqualTo(0); - after.IsEqualTo(0); - c.IsEqualTo(123); - d.IsEqualTo(456); } private class BasicType diff --git a/Dapper.Tests/ConstructorTests.cs b/Dapper.Tests/ConstructorTests.cs index 0613cbc7b..619286155 100644 --- a/Dapper.Tests/ConstructorTests.cs +++ b/Dapper.Tests/ConstructorTests.cs @@ -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("SELECT * FROM #Issue461"); - parameterlessWorks.Id.IsEqualTo(1); - parameterlessWorks.SomeValue.IsEqualTo("what up?"); - parameterlessWorks.SomeBlargValue.Value.IsEqualTo(Expected); - - // test: via constructor - var parameterDoesNot = connection.QuerySingle("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 - { - 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 abstract class Order diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index fc18b2b46..85d1fa3bc 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -23,7 +23,7 @@ - + diff --git a/Dapper.Tests/DataReaderTests.cs b/Dapper.Tests/DataReaderTests.cs index 2a78f33c4..15415c51a 100644 --- a/Dapper.Tests/DataReaderTests.cs +++ b/Dapper.Tests/DataReaderTests.cs @@ -6,36 +6,40 @@ namespace Dapper.Tests { public partial class DataReaderTests : TestBase { - [Fact] - public void GetSameReaderForSameShape() + [Collection("QueryCacheTests")] + public class DataReaderQueryCacheTests : TestBase { - var origReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); - var origParser = origReader.GetRowParser(typeof(HazNameId)); + [Fact] + public void GetSameReaderForSameShape() + { + var origReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); + var origParser = origReader.GetRowParser(typeof(HazNameId)); - var typedParser = origReader.GetRowParser(); + var typedParser = origReader.GetRowParser(); - ReferenceEquals(origParser, typedParser).IsEqualTo(true); + ReferenceEquals(origParser, typedParser).IsEqualTo(true); - var list = origReader.Parse().ToList(); - list.Count.IsEqualTo(1); - list[0].Name.IsEqualTo("abc"); - list[0].Id.IsEqualTo(123); - origReader.Dispose(); + var list = origReader.Parse().ToList(); + list.Count.IsEqualTo(1); + list[0].Name.IsEqualTo("abc"); + list[0].Id.IsEqualTo(123); + origReader.Dispose(); - var secondReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); - var secondParser = secondReader.GetRowParser(typeof(HazNameId)); - var thirdParser = secondReader.GetRowParser(typeof(HazNameId), 1); + var secondReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); + var secondParser = secondReader.GetRowParser(typeof(HazNameId)); + var thirdParser = secondReader.GetRowParser(typeof(HazNameId), 1); - list = secondReader.Parse().ToList(); - list.Count.IsEqualTo(1); - list[0].Name.IsEqualTo("abc"); - list[0].Id.IsEqualTo(123); - secondReader.Dispose(); + list = secondReader.Parse().ToList(); + list.Count.IsEqualTo(1); + list[0].Name.IsEqualTo("abc"); + list[0].Id.IsEqualTo(123); + secondReader.Dispose(); - // now: should be different readers, but same parser - ReferenceEquals(origReader, secondReader).IsEqualTo(false); - ReferenceEquals(origParser, secondParser).IsEqualTo(true); - ReferenceEquals(secondParser, thirdParser).IsEqualTo(false); + // now: should be different readers, but same parser + ReferenceEquals(origReader, secondReader).IsEqualTo(false); + ReferenceEquals(origParser, secondParser).IsEqualTo(true); + ReferenceEquals(secondParser, thirdParser).IsEqualTo(false); + } } [Fact] diff --git a/Dapper.Tests/NullTests.cs b/Dapper.Tests/NullTests.cs index 9199cba0d..20d52e091 100644 --- a/Dapper.Tests/NullTests.cs +++ b/Dapper.Tests/NullTests.cs @@ -2,6 +2,7 @@ using System.Linq; namespace Dapper.Tests { + [Collection("QueryCacheTests")] public class NullTests : TestBase { [Fact] diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index dba446854..6f40f9111 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -38,6 +38,24 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id } } + private static List CreateSqlDataRecordList(IEnumerable numbers) + { + var number_list = new List(); + + // 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 class IntDynamicParam : SqlMapper.IDynamicParameters { private readonly IEnumerable numbers; @@ -51,19 +69,8 @@ public void AddParameters(IDbCommand command, SqlMapper.Identity identity) var sqlCommand = (SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; - var number_list = new List(); - - // 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. - } - + var number_list = CreateSqlDataRecordList(numbers); + // Add the table parameter. var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); p.Direction = ParameterDirection.Input; @@ -85,19 +92,8 @@ public void AddParameter(IDbCommand command, string name) var sqlCommand = (SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; - var number_list = new List(); - - // 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. - } - + var number_list = CreateSqlDataRecordList(numbers); + // Add the table parameter. var p = sqlCommand.Parameters.Add(name, SqlDbType.Structured); p.Direction = ParameterDirection.Input; @@ -218,7 +214,6 @@ public void TestMassiveStrings() .IsEqualTo(str); } -#if !NETCOREAPP1_0 [Fact] public void TestTVPWithAnonymousObject() { @@ -289,19 +284,8 @@ public DynamicParameterWithIntTVP(IEnumerable numbers) var sqlCommand = (SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; - var number_list = new List(); - - // 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. - } - + var number_list = CreateSqlDataRecordList(numbers); + // Add the table parameter. var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); p.Direction = ParameterDirection.Input; @@ -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("get_ints", new { integers = records.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList(); + nums.IsSequenceEqualTo(new int[] { 1, 2, 3 }); + + nums = connection.Query("select * from @integers", new { integers = records.AsTableValuedParameter("int_list_type") }).ToList(); + nums.IsSequenceEqualTo(new int[] { 1, 2, 3 }); + + try + { + connection.Query("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 for TypeHandler to kick in. + IEnumerable records = CreateSqlDataRecordList(new int[] { 1, 2, 3 }); + + var nums = connection.Query("get_ints", new { integers = records }, commandType: CommandType.StoredProcedure).ToList(); + nums.IsSequenceEqualTo(new int[] { 1, 2, 3 }); + + try + { + connection.Query("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] public void DataTableParameters() { diff --git a/Dapper.Tests/Providers/EntityFrameworkTests.cs b/Dapper.Tests/Providers/EntityFrameworkTests.cs index 745f9148c..be9d54d63 100644 --- a/Dapper.Tests/Providers/EntityFrameworkTests.cs +++ b/Dapper.Tests/Providers/EntityFrameworkTests.cs @@ -4,6 +4,7 @@ namespace Dapper.Tests.Providers { + [Collection("TypeHandlerTests")] public class EntityFrameworkTests : TestBase { public EntityFrameworkTests() diff --git a/Dapper.Tests/Providers/SqliteTests.cs b/Dapper.Tests/Providers/SqliteTests.cs index b3d3e798d..25304f1cf 100644 --- a/Dapper.Tests/Providers/SqliteTests.cs +++ b/Dapper.Tests/Providers/SqliteTests.cs @@ -24,41 +24,45 @@ public void DapperEnumValue_Sqlite() } } - [FactSqlite] - public void Issue466_SqliteHatesOptimizations() + [Collection("TypeHandlerTests")] + public class SqliteTypeHandlerTests : TestBase { - using (var connection = GetSQLiteConnection()) + [FactSqlite] + public void Issue466_SqliteHatesOptimizations() { - SqlMapper.ResetTypeHandlers(); - var row = connection.Query("select 42 as Id").First(); - row.Id.IsEqualTo(42); - row = connection.Query("select 42 as Id").First(); - row.Id.IsEqualTo(42); + using (var connection = GetSQLiteConnection()) + { + SqlMapper.ResetTypeHandlers(); + var row = connection.Query("select 42 as Id").First(); + row.Id.IsEqualTo(42); + row = connection.Query("select 42 as Id").First(); + row.Id.IsEqualTo(42); - SqlMapper.ResetTypeHandlers(); - row = connection.QueryFirst("select 42 as Id"); - row.Id.IsEqualTo(42); - row = connection.QueryFirst("select 42 as Id"); - row.Id.IsEqualTo(42); + SqlMapper.ResetTypeHandlers(); + row = connection.QueryFirst("select 42 as Id"); + row.Id.IsEqualTo(42); + row = connection.QueryFirst("select 42 as Id"); + row.Id.IsEqualTo(42); + } } - } - [FactSqlite] - public async Task Issue466_SqliteHatesOptimizations_Async() - { - using (var connection = GetSQLiteConnection()) + [FactSqlite] + public async Task Issue466_SqliteHatesOptimizations_Async() { - SqlMapper.ResetTypeHandlers(); - var row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); - row.Id.IsEqualTo(42); - row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); - row.Id.IsEqualTo(42); + using (var connection = GetSQLiteConnection()) + { + SqlMapper.ResetTypeHandlers(); + var row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); + row.Id.IsEqualTo(42); + row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); + row.Id.IsEqualTo(42); - SqlMapper.ResetTypeHandlers(); - row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); - row.Id.IsEqualTo(42); - row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); - row.Id.IsEqualTo(42); + SqlMapper.ResetTypeHandlers(); + row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); + row.Id.IsEqualTo(42); + row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); + row.Id.IsEqualTo(42); + } } } diff --git a/Dapper.Tests/TypeHandlerTests.cs b/Dapper.Tests/TypeHandlerTests.cs index 722f1e887..0ea50a7e7 100644 --- a/Dapper.Tests/TypeHandlerTests.cs +++ b/Dapper.Tests/TypeHandlerTests.cs @@ -8,46 +8,96 @@ namespace Dapper.Tests { + [Collection("TypeHandlerTests")] public class TypeHandlerTests : TestBase { - [Fact] - public void TestChangingDefaultStringTypeMappingToAnsiString() + [Collection("QueryCacheTests")] + public class TypeHandlerQueryCacheTests : TestBase { - const string sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType"; - var param = new { testParam = "TestString" }; + [Fact] + public void TestChangingDefaultStringTypeMappingToAnsiString() + { + const string sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType"; + var param = new { testParam = "TestString" }; - var result01 = connection.Query(sql, param).FirstOrDefault(); - result01.IsEqualTo("nvarchar"); + var result01 = connection.Query(sql, param).FirstOrDefault(); + result01.IsEqualTo("nvarchar"); - SqlMapper.PurgeQueryCache(); + SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString - var result02 = connection.Query(sql, param).FirstOrDefault(); - result02.IsEqualTo("varchar"); + SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString + var result02 = connection.Query(sql, param).FirstOrDefault(); + result02.IsEqualTo("varchar"); - SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String - } + SqlMapper.PurgeQueryCache(); + SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String + } - [Fact] - public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault() - { - const string sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType"; - var param = new { testParam = "TestString" }; + [Fact] + public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault() + { + const string sql = "SELECT SQL_VARIANT_PROPERTY(CONVERT(sql_variant, @testParam),'BaseType') AS BaseType"; + var param = new { testParam = "TestString" }; - var result01 = connection.QueryFirstOrDefault(sql, param); - result01.IsEqualTo("nvarchar"); + var result01 = connection.QueryFirstOrDefault(sql, param); + result01.IsEqualTo("nvarchar"); - SqlMapper.PurgeQueryCache(); + SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString - var result02 = connection.QueryFirstOrDefault(sql, param); - result02.IsEqualTo("varchar"); + SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString + var result02 = connection.QueryFirstOrDefault(sql, param); + result02.IsEqualTo("varchar"); - SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String - } + SqlMapper.PurgeQueryCache(); + SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String + } + + [Fact] + public void TestCustomTypeMap() + { + // default mapping + var item = connection.Query("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("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("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] public void Issue136_ValueTypeHandlers() { @@ -518,51 +568,6 @@ private class ResultsChangeType public int Z { get; set; } } - [Fact] - public void TestCustomTypeMap() - { - // default mapping - var item = connection.Query("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("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("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 int A { get; set; } @@ -658,5 +663,84 @@ public void SO29343103_UtcDates() var delta = returned - date; 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("SELECT * FROM #Issue461"); + parameterlessWorks.Id.IsEqualTo(1); + parameterlessWorks.SomeValue.IsEqualTo("what up?"); + parameterlessWorks.SomeBlargValue.Value.IsEqualTo(Expected); + + // test: via constructor + var parameterDoesNot = connection.QuerySingle("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 + { + 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; } + } } } diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 75459323d..e4007559b 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -17,7 +17,7 @@ - + @@ -26,7 +26,8 @@ - + + diff --git a/Dapper/SqlDataRecordHandler.cs b/Dapper/SqlDataRecordHandler.cs index 358048039..301aa36ab 100644 --- a/Dapper/SqlDataRecordHandler.cs +++ b/Dapper/SqlDataRecordHandler.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data; -#if !NETSTANDARD1_3 namespace Dapper { internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler @@ -18,4 +17,3 @@ public void SetValue(IDbDataParameter parameter, object value) } } } -#endif \ No newline at end of file diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index ccae089ed..8e8384705 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -1,8 +1,7 @@ using System; using System.Collections.Generic; using System.Data; -using System.Reflection; -#if !NETSTANDARD1_3 + namespace Dapper { /// @@ -23,18 +22,6 @@ public SqlDataRecordListTVPParameter(IEnumerable 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) - Delegate.CreateDelegate(typeof(Action), prop.GetSetMethod()); - } - } - void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string name) { var param = command.CreateParameter(); @@ -54,4 +41,3 @@ internal static void Set(IDbDataParameter parameter, IEnumerable(); #if !NETSTANDARD1_3 AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); +#endif try { AddSqlDataRecordsTypeHandler(clone); } catch { /* https://github.com/StackExchange/dapper-dot-net/issues/424 */ } -#endif AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); } -#if !NETSTANDARD1_3 [MethodImpl(MethodImplOptions.NoInlining)] private static void AddSqlDataRecordsTypeHandler(bool clone) { AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone); } -#endif /// /// 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) /// The that has a type name associated with it. public static string GetTypeName(this DataTable table) => table?.ExtendedProperties[DataTableTypeNameKey] as string; +#endif /// - /// Used to pass a IEnumerable<SqlDataRecord> as a . + /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. /// - /// Thhe list of records to convert to TVPs. + /// The list of records to convert to TVPs. /// The sql parameter type name. public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) => new SqlDataRecordListTVPParameter(list, typeName); -#endif // one per thread [ThreadStatic]