using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Linq; using System.Reflection; using Xunit; namespace Dapper.Tests { public class TypeHandlerTests : TestBase { [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"); 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.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" }; var result01 = connection.QueryFirstOrDefault(sql, param); result01.IsEqualTo("nvarchar"); SqlMapper.PurgeQueryCache(); 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 } [Fact] public void Issue136_ValueTypeHandlers() { SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(typeof(LocalDate), LocalDateHandler.Default); var param = new LocalDateResult { NotNullable = new LocalDate { Year = 2014, Month = 7, Day = 25 }, NullableNotNull = new LocalDate { Year = 2014, Month = 7, Day = 26 }, NullableIsNull = null, }; var result = connection.Query("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull", param).Single(); SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(typeof(LocalDate?), LocalDateHandler.Default); result = connection.Query("SELECT @NotNullable AS NotNullable, @NullableNotNull AS NullableNotNull, @NullableIsNull AS NullableIsNull", param).Single(); } public class LocalDateHandler : SqlMapper.TypeHandler { private LocalDateHandler() { } // Make the field type ITypeHandler to ensure it cannot be used with SqlMapper.AddTypeHandler(TypeHandler) // by mistake. public static readonly SqlMapper.ITypeHandler Default = new LocalDateHandler(); public override LocalDate Parse(object value) { var date = (DateTime)value; return new LocalDate { Year = date.Year, Month = date.Month, Day = date.Day }; } public override void SetValue(IDbDataParameter parameter, LocalDate value) { parameter.DbType = DbType.DateTime; parameter.Value = new DateTime(value.Year, value.Month, value.Day); } } public struct LocalDate { public int Year { get; set; } public int Month { get; set; } public int Day { get; set; } } public class LocalDateResult { public LocalDate NotNullable { get; set; } public LocalDate? NullableNotNull { get; set; } public LocalDate? NullableIsNull { get; set; } } public class LotsOfNumerics { public enum E_Byte : byte { A = 0, B = 1 } public enum E_SByte : sbyte { A = 0, B = 1 } public enum E_Short : short { A = 0, B = 1 } public enum E_UShort : ushort { A = 0, B = 1 } public enum E_Int : int { A = 0, B = 1 } public enum E_UInt : uint { A = 0, B = 1 } public enum E_Long : long { A = 0, B = 1 } public enum E_ULong : ulong { A = 0, B = 1 } public E_Byte P_Byte { get; set; } public E_SByte P_SByte { get; set; } public E_Short P_Short { get; set; } public E_UShort P_UShort { get; set; } public E_Int P_Int { get; set; } public E_UInt P_UInt { get; set; } public E_Long P_Long { get; set; } public E_ULong P_ULong { get; set; } public bool N_Bool { get; set; } public byte N_Byte { get; set; } public sbyte N_SByte { get; set; } public short N_Short { get; set; } public ushort N_UShort { get; set; } public int N_Int { get; set; } public uint N_UInt { get; set; } public long N_Long { get; set; } public ulong N_ULong { get; set; } public float N_Float { get; set; } public double N_Double { get; set; } public decimal N_Decimal { get; set; } public E_Byte? N_P_Byte { get; set; } public E_SByte? N_P_SByte { get; set; } public E_Short? N_P_Short { get; set; } public E_UShort? N_P_UShort { get; set; } public E_Int? N_P_Int { get; set; } public E_UInt? N_P_UInt { get; set; } public E_Long? N_P_Long { get; set; } public E_ULong? N_P_ULong { get; set; } public bool? N_N_Bool { get; set; } public byte? N_N_Byte { get; set; } public sbyte? N_N_SByte { get; set; } public short? N_N_Short { get; set; } public ushort? N_N_UShort { get; set; } public int? N_N_Int { get; set; } public uint? N_N_UInt { get; set; } public long? N_N_Long { get; set; } public ulong? N_N_ULong { get; set; } public float? N_N_Float { get; set; } public double? N_N_Double { get; set; } public decimal? N_N_Decimal { get; set; } } [Fact] public void TestBigIntForEverythingWorks() { TestBigIntForEverythingWorks_ByDataType("bigint"); TestBigIntForEverythingWorks_ByDataType("int"); TestBigIntForEverythingWorks_ByDataType("tinyint"); TestBigIntForEverythingWorks_ByDataType("smallint"); TestBigIntForEverythingWorks_ByDataType("bit"); TestBigIntForEverythingWorks_ByDataType("float(24)"); TestBigIntForEverythingWorks_ByDataType("float(53)"); } private void TestBigIntForEverythingWorks_ByDataType(string dbType) { using (var reader = connection.ExecuteReader("select cast(1 as " + dbType + ")")) { reader.Read().IsTrue(); reader.GetFieldType(0).Equals(typeof(T)); reader.Read().IsFalse(); reader.NextResult().IsFalse(); } string sql = "select " + string.Join(",", typeof(LotsOfNumerics).GetProperties().Select( x => "cast (1 as " + dbType + ") as [" + x.Name + "]")); var row = connection.Query(sql).Single(); row.N_Bool.IsTrue(); row.N_SByte.IsEqualTo((sbyte)1); row.N_Byte.IsEqualTo((byte)1); row.N_Int.IsEqualTo((int)1); row.N_UInt.IsEqualTo((uint)1); row.N_Short.IsEqualTo((short)1); row.N_UShort.IsEqualTo((ushort)1); row.N_Long.IsEqualTo((long)1); row.N_ULong.IsEqualTo((ulong)1); row.N_Float.IsEqualTo((float)1); row.N_Double.IsEqualTo((double)1); row.N_Decimal.IsEqualTo((decimal)1); row.P_Byte.IsEqualTo(LotsOfNumerics.E_Byte.B); row.P_SByte.IsEqualTo(LotsOfNumerics.E_SByte.B); row.P_Short.IsEqualTo(LotsOfNumerics.E_Short.B); row.P_UShort.IsEqualTo(LotsOfNumerics.E_UShort.B); row.P_Int.IsEqualTo(LotsOfNumerics.E_Int.B); row.P_UInt.IsEqualTo(LotsOfNumerics.E_UInt.B); row.P_Long.IsEqualTo(LotsOfNumerics.E_Long.B); row.P_ULong.IsEqualTo(LotsOfNumerics.E_ULong.B); row.N_N_Bool.Value.IsTrue(); row.N_N_SByte.Value.IsEqualTo((sbyte)1); row.N_N_Byte.Value.IsEqualTo((byte)1); row.N_N_Int.Value.IsEqualTo((int)1); row.N_N_UInt.Value.IsEqualTo((uint)1); row.N_N_Short.Value.IsEqualTo((short)1); row.N_N_UShort.Value.IsEqualTo((ushort)1); row.N_N_Long.Value.IsEqualTo((long)1); row.N_N_ULong.Value.IsEqualTo((ulong)1); row.N_N_Float.Value.IsEqualTo((float)1); row.N_N_Double.Value.IsEqualTo((double)1); row.N_N_Decimal.IsEqualTo((decimal)1); row.N_P_Byte.Value.IsEqualTo(LotsOfNumerics.E_Byte.B); row.N_P_SByte.Value.IsEqualTo(LotsOfNumerics.E_SByte.B); row.N_P_Short.Value.IsEqualTo(LotsOfNumerics.E_Short.B); row.N_P_UShort.Value.IsEqualTo(LotsOfNumerics.E_UShort.B); row.N_P_Int.Value.IsEqualTo(LotsOfNumerics.E_Int.B); row.N_P_UInt.Value.IsEqualTo(LotsOfNumerics.E_UInt.B); row.N_P_Long.Value.IsEqualTo(LotsOfNumerics.E_Long.B); row.N_P_ULong.Value.IsEqualTo(LotsOfNumerics.E_ULong.B); TestBigIntForEverythingWorks(true, dbType); TestBigIntForEverythingWorks((sbyte)1, dbType); TestBigIntForEverythingWorks((byte)1, dbType); TestBigIntForEverythingWorks((int)1, dbType); TestBigIntForEverythingWorks((uint)1, dbType); TestBigIntForEverythingWorks((short)1, dbType); TestBigIntForEverythingWorks((ushort)1, dbType); TestBigIntForEverythingWorks((long)1, dbType); TestBigIntForEverythingWorks((ulong)1, dbType); TestBigIntForEverythingWorks((float)1, dbType); TestBigIntForEverythingWorks((double)1, dbType); TestBigIntForEverythingWorks((decimal)1, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Byte.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_SByte.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Int.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_UInt.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Short.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_UShort.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Long.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_ULong.B, dbType); TestBigIntForEverythingWorks(true, dbType); TestBigIntForEverythingWorks((sbyte)1, dbType); TestBigIntForEverythingWorks((byte)1, dbType); TestBigIntForEverythingWorks((int)1, dbType); TestBigIntForEverythingWorks((uint)1, dbType); TestBigIntForEverythingWorks((short)1, dbType); TestBigIntForEverythingWorks((ushort)1, dbType); TestBigIntForEverythingWorks((long)1, dbType); TestBigIntForEverythingWorks((ulong)1, dbType); TestBigIntForEverythingWorks((float)1, dbType); TestBigIntForEverythingWorks((double)1, dbType); TestBigIntForEverythingWorks((decimal)1, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Byte.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_SByte.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Int.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_UInt.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Short.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_UShort.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_Long.B, dbType); TestBigIntForEverythingWorks(LotsOfNumerics.E_ULong.B, dbType); } private void TestBigIntForEverythingWorks(T expected, string dbType) { var query = connection.Query("select cast(1 as " + dbType + ")").Single(); query.IsEqualTo(expected); var scalar = connection.ExecuteScalar("select cast(1 as " + dbType + ")"); scalar.IsEqualTo(expected); } [Fact] public void TestSubsequentQueriesSuccess() { var data0 = connection.Query("select 1 as [Id] where 1 = 0").ToList(); data0.Count.IsEqualTo(0); var data1 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.Buffered)).ToList(); data1.Count.IsEqualTo(0); var data2 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.None)).ToList(); data2.Count.IsEqualTo(0); data0 = connection.Query("select 1 as [Id] where 1 = 0").ToList(); data0.Count.IsEqualTo(0); data1 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.Buffered)).ToList(); data1.Count.IsEqualTo(0); data2 = connection.Query(new CommandDefinition("select 1 as [Id] where 1 = 0", flags: CommandFlags.None)).ToList(); data2.Count.IsEqualTo(0); } private class Fooz0 { public int Id { get; set; } } private class Fooz1 { public int Id { get; set; } } private class Fooz2 { public int Id { get; set; } } public class RatingValueHandler : SqlMapper.TypeHandler { private RatingValueHandler() { } public static readonly RatingValueHandler Default = new RatingValueHandler(); public override RatingValue Parse(object value) { if (value is Int32) { return new RatingValue() { Value = (Int32)value }; } throw new FormatException("Invalid conversion to RatingValue"); } public override void SetValue(IDbDataParameter parameter, RatingValue value) { // ... null, range checks etc ... parameter.DbType = System.Data.DbType.Int32; parameter.Value = value.Value; } } public class RatingValue { public Int32 Value { get; set; } // ... some other properties etc ... } public class MyResult { public String CategoryName { get; set; } public RatingValue CategoryRating { get; set; } } [Fact] public void SO24740733_TestCustomValueHandler() { SqlMapper.AddTypeHandler(RatingValueHandler.Default); var foo = connection.Query("SELECT 'Foo' AS CategoryName, 200 AS CategoryRating").Single(); foo.CategoryName.IsEqualTo("Foo"); foo.CategoryRating.Value.IsEqualTo(200); } [Fact] public void SO24740733_TestCustomValueSingleColumn() { SqlMapper.AddTypeHandler(RatingValueHandler.Default); var foo = connection.Query("SELECT 200 AS CategoryRating").Single(); foo.Value.IsEqualTo(200); } public class StringListTypeHandler : SqlMapper.TypeHandler> { private StringListTypeHandler() { } public static readonly StringListTypeHandler Default = new StringListTypeHandler(); //Just a simple List type handler implementation public override void SetValue(IDbDataParameter parameter, List value) { parameter.Value = String.Join(",", value); } public override List Parse(object value) { return ((value as String) ?? "").Split(',').ToList(); } } public class MyObjectWithStringList { public List Names { get; set; } } [Fact] public void Issue253_TestIEnumerableTypeHandlerParsing() { SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(StringListTypeHandler.Default); var foo = connection.Query("SELECT 'Sam,Kyro' AS Names").Single(); foo.Names.IsSequenceEqualTo(new[] { "Sam", "Kyro" }); } [Fact] public void Issue253_TestIEnumerableTypeHandlerSetParameterValue() { SqlMapper.ResetTypeHandlers(); SqlMapper.AddTypeHandler(StringListTypeHandler.Default); connection.Execute("CREATE TABLE #Issue253 (Names VARCHAR(50) NOT NULL);"); try { const string names = "Sam,Kyro"; List names_list = names.Split(',').ToList(); var foo = connection.Query("INSERT INTO #Issue253 (Names) VALUES (@Names); SELECT Names FROM #Issue253;", new { Names = names_list }).Single(); foo.IsEqualTo(names); } finally { connection.Execute("DROP TABLE #Issue253;"); } } public class RecordingTypeHandler : SqlMapper.TypeHandler { public override void SetValue(IDbDataParameter parameter, T value) { SetValueWasCalled = true; parameter.Value = value; } public override T Parse(object value) { ParseWasCalled = true; return (T)value; } public bool SetValueWasCalled { get; set; } public bool ParseWasCalled { get; set; } } [Fact] public void Test_RemoveTypeMap() { SqlMapper.ResetTypeHandlers(); SqlMapper.RemoveTypeMap(typeof(DateTime)); var dateTimeHandler = new RecordingTypeHandler(); SqlMapper.AddTypeHandler(dateTimeHandler); connection.Execute("CREATE TABLE #Test_RemoveTypeMap (x datetime NOT NULL);"); try { connection.Execute(@"INSERT INTO #Test_RemoveTypeMap VALUES (@Now)", new { DateTime.Now }); connection.Query("SELECT * FROM #Test_RemoveTypeMap"); dateTimeHandler.ParseWasCalled.IsTrue(); dateTimeHandler.SetValueWasCalled.IsTrue(); } finally { connection.Execute("DROP TABLE #Test_RemoveTypeMap"); SqlMapper.AddTypeMap(typeof(DateTime), DbType.DateTime); // or an option to reset type map? } } [Fact] public void TestReaderWhenResultsChange() { try { connection.Execute("create table #ResultsChange (X int);create table #ResultsChange2 (Y int);insert #ResultsChange (X) values(1);insert #ResultsChange2 (Y) values(1);"); var obj1 = connection.Query("select * from #ResultsChange").Single(); obj1.X.IsEqualTo(1); obj1.Y.IsEqualTo(0); obj1.Z.IsEqualTo(0); var obj2 = connection.Query("select * from #ResultsChange rc inner join #ResultsChange2 rc2 on rc2.Y=rc.X").Single(); obj2.X.IsEqualTo(1); obj2.Y.IsEqualTo(1); obj2.Z.IsEqualTo(0); connection.Execute("alter table #ResultsChange add Z int null"); connection.Execute("update #ResultsChange set Z = 2"); var obj3 = connection.Query("select * from #ResultsChange").Single(); obj3.X.IsEqualTo(1); obj3.Y.IsEqualTo(0); obj3.Z.IsEqualTo(2); var obj4 = connection.Query("select * from #ResultsChange rc inner join #ResultsChange2 rc2 on rc2.Y=rc.X").Single(); obj4.X.IsEqualTo(1); obj4.Y.IsEqualTo(1); obj4.Z.IsEqualTo(2); } finally { connection.Execute("drop table #ResultsChange;drop table #ResultsChange2;"); } } private class ResultsChangeType { public int X { get; set; } public int Y { get; set; } 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 COREFX 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; } public double B { get; set; } public long C { get; set; } public bool D { get; set; } } [Fact] public void TestWrongTypes_WithRightTypes() { var item = connection.Query("select 1 as A, cast(2.0 as float) as B, cast(3 as bigint) as C, cast(1 as bit) as D").Single(); item.A.Equals(1); item.B.Equals(2.0); item.C.Equals(3L); item.D.Equals(true); } [Fact] public void TestWrongTypes_WithWrongTypes() { var item = connection.Query("select cast(1.0 as float) as A, 2 as B, 3 as C, cast(1 as bigint) as D").Single(); item.A.Equals(1); item.B.Equals(2.0); item.C.Equals(3L); item.D.Equals(true); } [Fact] public void SO24607639_NullableBools() { var obj = connection.Query( @"declare @vals table (A bit null, B bit null, C bit null); insert @vals (A,B,C) values (1,0,null); select * from @vals").Single(); obj.IsNotNull(); obj.A.Value.IsEqualTo(true); obj.B.Value.IsEqualTo(false); obj.C.IsNull(); } private class HazBools { public bool? A { get; set; } public bool? B { get; set; } public bool? C { get; set; } } [Fact] public void Issue130_IConvertible() { dynamic row = connection.Query("select 1 as [a], '2' as [b]").Single(); int a = row.a; string b = row.b; a.IsEqualTo(1); b.IsEqualTo("2"); row = connection.Query("select 3 as [a], '4' as [b]").Single(); a = row.a; b = row.b; a.IsEqualTo(3); b.IsEqualTo("4"); } [Fact] public void Issue149_TypeMismatch_SequentialAccess() { string error; Guid guid = Guid.Parse("cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e"); try { var result = connection.Query(@"select @guid as Id", new { guid }).First(); error = null; } catch (Exception ex) { error = ex.Message; } error.IsEqualTo("Error parsing column 0 (Id=cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e - Object)"); } public class Issue149_Person { public string Id { get; set; } } [Fact] public void Issue295_NullableDateTime_SqlServer() => Common.TestDateTime(connection); [Fact] public void SO29343103_UtcDates() { const string sql = "select @date"; var date = DateTime.UtcNow; var returned = connection.Query(sql, new { date }).Single(); var delta = returned - date; Assert.IsTrue(delta.TotalMilliseconds >= -10 && delta.TotalMilliseconds <= 10); } } }