using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Data.Common; using System.Diagnostics; using System.Linq; using System.Threading.Tasks; using Microsoft.CSharp.RuntimeBinder; using Xunit; #if NETCOREAPP3_1 || NET462 || NET472 namespace System.Runtime.CompilerServices { [EditorBrowsable(EditorBrowsableState.Never)] internal static class IsExternalInit // yeah, don't do this! { } } #endif namespace Dapper.Tests { [Collection("MiscTests")] public sealed class SystemSqlClientMiscTests : MiscTests { } #if MSSQLCLIENT [Collection("MiscTests")] public sealed class MicrosoftSqlClientMiscTests : MiscTests { } #endif public abstract class MiscTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestNullableGuidSupport() { var guid = connection.Query("select null").First(); Assert.Null(guid); guid = Guid.NewGuid(); var guid2 = connection.Query("select @guid", new { guid }).First(); Assert.Equal(guid, guid2); } [Fact] public void TestNonNullableGuidSupport() { var guid = Guid.NewGuid(); var guid2 = connection.Query("select @guid", new { guid }).First(); Assert.True(guid == guid2); } private struct Car { public enum TrapEnum : int { A = 1, B = 2 } #pragma warning disable 0649 public string Name; #pragma warning restore 0649 public int Age { get; set; } public TrapEnum Trap { get; set; } } private struct CarWithAllProps { public string Name { get; set; } public int Age { get; init; } public Car.TrapEnum Trap { get; init; } } private record PositionalCarRecord(int Age, Car.TrapEnum Trap, string? Name) { public PositionalCarRecord() : this(default, default, default) { } } private record NominalCarRecord { public int Age { get; init; } public Car.TrapEnum Trap { get; init; } public string? Name { get; init; } } [Fact] public void TestStructs() { var car = connection.Query("select 'Ford' Name, 21 Age, 2 Trap").First(); Assert.Equal(21, car.Age); Assert.Equal("Ford", car.Name); Assert.Equal(2, (int)car.Trap); } [Fact] public void TestPositionalRecord() { var car = connection.Query("select 'Ford' Name, 21 Age, 2 Trap").First(); Assert.Equal(21, car.Age); Assert.Equal("Ford", car.Name); Assert.Equal(2, (int)car.Trap); } [Fact] public void TestNominalRecord() { var car = connection.Query("select 'Ford' Name, 21 Age, 2 Trap").First(); Assert.Equal(21, car.Age); Assert.Equal("Ford", car.Name); Assert.Equal(2, (int)car.Trap); } [Fact] public void TestStructAsParam() { var car1 = new CarWithAllProps { Name = "Ford", Age = 21, Trap = Car.TrapEnum.B }; // note Car has Name as a field; parameters only respect properties at the moment var car2 = connection.Query("select @Name Name, @Age Age, @Trap Trap", car1).First(); Assert.Equal(car2.Name, car1.Name); Assert.Equal(car2.Age, car1.Age); Assert.Equal(car2.Trap, car1.Trap); } [Fact] public void SelectListInt() { Assert.Equal(new[] { 1, 2, 3 }, connection.Query("select 1 union all select 2 union all select 3")); } [Fact] public void SelectBinary() { connection.Query("select cast(1 as varbinary(4))").First().SequenceEqual(new byte[] { 1 }); } [Fact] public void TestSchemaChanged() { connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')"); try { var d = connection.Query("select * from #dog").Single(); Assert.Equal("Alf", d.Name); Assert.Equal(1, d.Age); connection.Execute("alter table #dog drop column Name"); d = connection.Query("select * from #dog").Single(); Assert.Null(d.Name); Assert.Equal(1, d.Age); } finally { connection.Execute("drop table #dog"); } } [Fact] public void TestSchemaChangedViaFirstOrDefault() { connection.Execute("create table #dog(Age int, Name nvarchar(max)) insert #dog values(1, 'Alf')"); try { var d = connection.QueryFirstOrDefault("select * from #dog"); Assert.NotNull(d); Assert.Equal("Alf", d.Name); Assert.Equal(1, d.Age); connection.Execute("alter table #dog drop column Name"); d = connection.QueryFirstOrDefault("select * from #dog"); Assert.NotNull(d); Assert.Null(d.Name); Assert.Equal(1, d.Age); } finally { connection.Execute("drop table #dog"); } } [Fact] public void Test_Single_First_Default() { var sql = "select 0 where 1 = 0;"; // no rows var ex = Assert.Throws(() => connection.QueryFirst(sql)); Assert.Equal("Sequence contains no elements", ex.Message); ex = Assert.Throws(() => connection.QuerySingle(sql)); Assert.Equal("Sequence contains no elements", ex.Message); Assert.Equal(0, connection.QueryFirstOrDefault(sql)); Assert.Equal(0, connection.QuerySingleOrDefault(sql)); sql = "select 1;"; // one row Assert.Equal(1, connection.QueryFirst(sql)); Assert.Equal(1, connection.QuerySingle(sql)); Assert.Equal(1, connection.QueryFirstOrDefault(sql)); Assert.Equal(1, connection.QuerySingleOrDefault(sql)); sql = "select 2 union select 3 order by 1;"; // two rows Assert.Equal(2, connection.QueryFirst(sql)); ex = Assert.Throws(() => connection.QuerySingle(sql)); Assert.Equal("Sequence contains more than one element", ex.Message); Assert.Equal(2, connection.QueryFirstOrDefault(sql)); ex = Assert.Throws(() => connection.QuerySingleOrDefault(sql)); Assert.Equal("Sequence contains more than one element", ex.Message); } /// /// This test is ensuring our "single row" methods also behave like a type being deserialized /// and give a useful error message when the types don't match. /// [Fact] public async Task TestConversionExceptionMessages() { const string sql = "Select Null;"; // Nullable is expected to work if we get a null in all cases // List paths var list = connection.Query(sql); Assert.Null(Assert.Single(list)); list = await connection.QueryAsync(sql); Assert.Null(Assert.Single(list)); // Single row paths Assert.Null(connection.QueryFirst(sql)); Assert.Null(connection.QueryFirstOrDefault(sql)); Assert.Null(connection.QuerySingle(sql)); Assert.Null(connection.QuerySingleOrDefault(sql)); Assert.Null(await connection.QueryFirstAsync(sql)); Assert.Null(await connection.QueryFirstOrDefaultAsync(sql)); Assert.Null(await connection.QuerySingleAsync(sql)); Assert.Null(await connection.QuerySingleOrDefaultAsync(sql)); static async Task TestExceptionsAsync(DbConnection connection, string sql, string exception) { var ex = Assert.Throws(() => connection.Query(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QueryFirst(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QueryFirstOrDefault(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QuerySingle(sql)); Assert.Equal(exception, ex.Message); ex = Assert.Throws(() => connection.QuerySingleOrDefault(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QueryAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QueryFirstAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QueryFirstOrDefaultAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QuerySingleAsync(sql)); Assert.Equal(exception, ex.Message); ex = await Assert.ThrowsAsync(() => connection.QuerySingleOrDefaultAsync(sql)); Assert.Equal(exception, ex.Message); } // Null value throws await TestExceptionsAsync( connection, "Select null as Foo", "Error parsing column 0 (Foo=n/a - Null object cannot be converted to a value type.)"); // Incompatible value throws (testing unnamed column bits here too) await TestExceptionsAsync( connection, "Select 'bar'", "Error parsing column 0 ((Unnamed Column)=bar - String)"); // Null with a full type (testing position too) await TestExceptionsAsync( connection, "Select 1 Id, 'bar' Foo", "Error parsing column 1 (Foo=bar - String)"); // And a ValueTuple! (testing position too) // Still needs love, because we handle ValueTuple differently today // It'll yield a raw: typeof(System.FormatException): Input string was not in a correct format. // Note: not checking the "Select 1 Id, null Foo" case here, because we won't attempt to set the column // ...and there will no error in that case. //await TestExceptionsAsync<(int Id, int Foo)>( // connection, // "Select 1 Id, 'bar' Foo", // "Error parsing column 1 (Foo=bar - String)"); } private class NullTestType { public int Id { get; } public int Foo { get; } } [Fact] public void TestStrings() { Assert.Equal(new[] { "a", "b" }, connection.Query("select 'a' a union select 'b'")); } // see https://stackoverflow.com/questions/16726709/string-format-with-sql-wildcard-causing-dapper-query-to-break [Fact] public void CheckComplexConcat() { const string end_wildcard = @" SELECT * FROM #users16726709 WHERE (first_name LIKE CONCAT(@search_term, '%') OR last_name LIKE CONCAT(@search_term, '%'));"; const string both_wildcards = @" SELECT * FROM #users16726709 WHERE (first_name LIKE CONCAT('%', @search_term, '%') OR last_name LIKE CONCAT('%', @search_term, '%'));"; const string formatted = @" SELECT * FROM #users16726709 WHERE (first_name LIKE {0} OR last_name LIKE {0});"; const string use_end_only = "CONCAT(@search_term, '%')"; const string use_both = "CONCAT('%', @search_term, '%')"; // if true, slower query due to not being able to use indices, but will allow searching inside strings const bool allow_start_wildcards = false; string query = string.Format(formatted, allow_start_wildcards ? use_both : use_end_only); const string term = "F"; // the term the user searched for connection.Execute(@"create table #users16726709 (first_name varchar(200), last_name varchar(200)) insert #users16726709 values ('Fred','Bloggs') insert #users16726709 values ('Tony','Farcus') insert #users16726709 values ('Albert','TenoF')"); // Using Dapper Assert.Equal(2, connection.Query(end_wildcard, new { search_term = term }).Count()); Assert.Equal(3, connection.Query(both_wildcards, new { search_term = term }).Count()); Assert.Equal(2, connection.Query(query, new { search_term = term }).Count()); } [Fact] public void TestExtraFields() { var guid = Guid.NewGuid(); var dog = connection.Query("select '' as Extra, 1 as Age, 0.1 as Name1 , Id = @id", new { id = guid }); Assert.Single(dog); Assert.Equal(1, dog.First().Age); Assert.Equal(dog.First().Id, guid); } [Fact] public void TestStrongType() { var guid = Guid.NewGuid(); var dog = connection.Query("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid }); Assert.Single(dog); Assert.Null(dog.First().Age); Assert.Equal(dog.First().Id, guid); } [Fact] public void TestSimpleNull() { Assert.Null(connection.Query("select null").First()); } [Fact] public void TestExpando() { var rows = connection.Query("select 1 A, 2 B union all select 3, 4").ToList(); Assert.Equal(1, (int)rows[0].A); Assert.Equal(2, (int)rows[0].B); Assert.Equal(3, (int)rows[1].A); Assert.Equal(4, (int)rows[1].B); } [Fact] public void TestStringList() { Assert.Equal( new[] { "a", "b", "c" }, connection.Query("select * from (select 'a' as x union all select 'b' union all select 'c') as T where x in @strings", new { strings = new[] { "a", "b", "c" } }) ); Assert.Equal( new string[0], connection.Query("select * from (select 'a' as x union all select 'b' union all select 'c') as T where x in @strings", new { strings = new string[0] }) ); } [Fact] public void TestExecuteCommand() { Assert.Equal(2, connection.Execute(@" set nocount on create table #t(i int) set nocount off insert #t select @a a union all select @b set nocount on drop table #t", new { a = 1, b = 2 })); } [Fact] public void TestExecuteMultipleCommand() { connection.Execute("create table #t(i int)"); try { int tally = connection.Execute("insert #t (i) values(@a)", new[] { new { a = 1 }, new { a = 2 }, new { a = 3 }, new { a = 4 } }); int sum = connection.Query("select sum(i) from #t").First(); Assert.Equal(4, tally); Assert.Equal(10, sum); } finally { connection.Execute("drop table #t"); } } private class Student { public string? Name { get; set; } public int Age { get; set; } } [Fact] public void TestExecuteMultipleCommandStrongType() { connection.Execute("create table #t(Name nvarchar(max), Age int)"); try { int tally = connection.Execute("insert #t (Name,Age) values(@Name, @Age)", new List(2) { new Student{Age = 1, Name = "sam"}, new Student{Age = 2, Name = "bob"} }); int sum = connection.Query("select sum(Age) from #t").First(); Assert.Equal(2, tally); Assert.Equal(3, sum); } finally { connection.Execute("drop table #t"); } } [Fact] public void TestExecuteMultipleCommandObjectArray() { connection.Execute("create table #t(i int)"); int tally = connection.Execute("insert #t (i) values(@a)", new object[] { new { a = 1 }, new { a = 2 }, new { a = 3 }, new { a = 4 } }); int sum = connection.Query("select sum(i) from #t drop table #t").First(); Assert.Equal(4, tally); Assert.Equal(10, sum); } private class TestObj { public int _internal; internal int Internal { set { _internal = value; } } public int _priv; private int Priv { set { _priv = value; } } private int PrivGet => _priv; } [Fact] public void TestSetInternal() { Assert.Equal(10, connection.Query("select 10 as [Internal]").First()._internal); } [Fact] public void TestSetPrivate() { Assert.Equal(10, connection.Query("select 10 as [Priv]").First()._priv); } [Fact] public void TestExpandWithNullableFields() { var row = connection.Query("select null A, 2 B").Single(); Assert.Null((int?)row.A); Assert.Equal(2, (int?)row.B); } [Fact] public void TestEnumeration() { var en = connection.Query("select 1 as one union all select 2 as one", buffered: false); var i = en.GetEnumerator(); i.MoveNext(); bool gotException = false; try { var x = connection.Query("select 1 as one", buffered: false).First(); } catch (Exception) { gotException = true; } while (i.MoveNext()) { } // should not exception, since enumerated en = connection.Query("select 1 as one", buffered: false); Assert.True(gotException); } [Fact] public void TestEnumerationDynamic() { var en = connection.Query("select 1 as one union all select 2 as one", buffered: false); var i = en.GetEnumerator(); i.MoveNext(); bool gotException = false; try { var x = connection.Query("select 1 as one", buffered: false).First(); } catch (Exception) { gotException = true; } while (i.MoveNext()) { } // should not exception, since enumertated en = connection.Query("select 1 as one", buffered: false); Assert.True(gotException); } [Fact] public void TestNakedBigInt() { const long foo = 12345; var result = connection.Query("select @foo", new { foo }).Single(); Assert.Equal(foo, result); } [Fact] public void TestBigIntMember() { const long foo = 12345; var result = connection.Query(@" declare @bar table(Value bigint) insert @bar values (@foo) select * from @bar", new { foo }).Single(); Assert.Equal(foo, result.Value); } private class WithBigInt { public long Value { get; set; } } [Fact] public void TestFieldsAndPrivates() { var data = connection.Query( "select a=1,b=2,c=3,d=4,f='5'").Single(); Assert.Equal(1, data.a); Assert.Equal(2, data.GetB()); Assert.Equal(3, data.c); Assert.Equal(4, data.GetD()); Assert.Equal(5, data.e); } private class TestFieldCaseAndPrivatesEntity { #pragma warning disable IDE1006 // Naming Styles public int a { get; set; } private int b { get; set; } public int GetB() { return b; } public int c = 0; #pragma warning disable RCS1169 // Mark field as read-only. private int d = 0; #pragma warning restore RCS1169 // Mark field as read-only. public int GetD() { return d; } public int e { get; set; } private string f { get { return e.ToString(); } set { e = int.Parse(value); } } #pragma warning restore IDE1006 // Naming Styles } private class InheritanceTest1 { public string? Base1 { get; set; } public string? Base2 { get; private set; } } private class InheritanceTest2 : InheritanceTest1 { public string? Derived1 { get; set; } public string? Derived2 { get; private set; } } [Fact] public void TestInheritance() { // Test that inheritance works. var list = connection.Query("select 'One' as Derived1, 'Two' as Derived2, 'Three' as Base1, 'Four' as Base2"); Assert.Equal("One", list.First().Derived1); Assert.Equal("Two", list.First().Derived2); Assert.Equal("Three", list.First().Base1); Assert.Equal("Four", list.First().Base2); } [Fact] public void ExecuteReader() { var dt = new DataTable(); dt.Load(connection.ExecuteReader("select 3 as [three], 4 as [four]")); Assert.Equal(2, dt.Columns.Count); Assert.Equal("three", dt.Columns[0].ColumnName); Assert.Equal("four", dt.Columns[1].ColumnName); Assert.Equal(1, dt.Rows.Count); Assert.Equal(3, (int)dt.Rows[0][0]); Assert.Equal(4, (int)dt.Rows[0][1]); } [Fact] public void TestDbString() { var obj = connection.Query("select datalength(@a) as a, datalength(@b) as b, datalength(@c) as c, datalength(@d) as d, datalength(@e) as e, datalength(@f) as f", new { a = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }, b = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = false }, c = new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = true }, d = new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = false }, e = new DbString { Value = "abcde", IsAnsi = true }, f = new DbString { Value = "abcde", IsAnsi = false }, }).First(); Assert.Equal(10, (int)obj.a); Assert.Equal(20, (int)obj.b); Assert.Equal(5, (int)obj.c); Assert.Equal(10, (int)obj.d); Assert.Equal(5, (int)obj.e); Assert.Equal(10, (int)obj.f); } [Fact] public void DbStringNullHandling() { // without lengths var obj = new { x = new DbString("abc"), y = (DbString?)new DbString(null) }; var row = connection.QuerySingle<(string? x,string? y)>("select @x as x, @y as y", obj); Assert.Equal("abc", row.x); Assert.Null(row.y); // with lengths obj = new { x = new DbString("abc", 200), y = (DbString?)new DbString(null, 200) }; row = connection.QuerySingle<(string? x, string? y)>("select @x as x, @y as y", obj); Assert.Equal("abc", row.x); Assert.Null(row.y); // null raw value - give clear message, at least obj = obj with { y = null }; var ex = Assert.Throws(() => connection.QuerySingle<(string? x, string? y)>("select @x as x, @y as y", obj)); Assert.Equal("Member 'y' is an ICustomQueryParameter and cannot be null", ex.Message); } [Fact] public void TestDbStringToString() { Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: True, IsFixedLength: True)", new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: False, IsFixedLength: True)", new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = false }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: True, IsFixedLength: False)", new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = true }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: 10, IsAnsi: False, IsFixedLength: False)", new DbString { Value = "abcde", IsFixedLength = false, Length = 10, IsAnsi = false }.ToString()); Assert.Equal("Dapper.DbString (Value: null, Length: -1, IsAnsi: False, IsFixedLength: False)", new DbString { Value = null }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: -1, IsAnsi: True, IsFixedLength: False)", new DbString { Value = "abcde", IsAnsi = true }.ToString()); Assert.Equal("Dapper.DbString (Value: 'abcde', Length: -1, IsAnsi: False, IsFixedLength: False)", new DbString { Value = "abcde", IsAnsi = false }.ToString()); } [Fact] public void TestDefaultDbStringDbType() { var origDefaultStringDbType = DbString.IsAnsiDefault; try { DbString.IsAnsiDefault = true; var a = new DbString { Value = "abcde" }; var b = new DbString { Value = "abcde", IsAnsi = false }; Assert.True(a.IsAnsi); Assert.False(b.IsAnsi); } finally { DbString.IsAnsiDefault = origDefaultStringDbType; } } [Fact] public void TestFastExpandoSupportsIDictionary() { var row = connection.Query("select 1 A, 'two' B").First() as IDictionary; Assert.NotNull(row); Assert.Equal(1, row["A"]); Assert.Equal("two", row["B"]); } [Fact] public void TestDapperSetsPrivates() { Assert.Equal(1, connection.Query("select 'one' ShadowInDB").First().Shadow); Assert.Equal(1, connection.QueryFirstOrDefault("select 'one' ShadowInDB")?.Shadow); } private class PrivateDan { public int Shadow { get; set; } private string ShadowInDB { set { Shadow = value == "one" ? 1 : 0; } } } [Fact] public void TestUnexpectedDataMessage() { string? msg = null; try { connection.Query("select count(1) where 1 = @Foo", new WithBizarreData { Foo = new GenericUriParser(GenericUriParserOptions.Default), Bar = 23 }).First(); } catch (Exception ex) { msg = ex.Message; } Assert.Equal("The member Foo of type System.GenericUriParser cannot be used as a parameter value", msg); } [Fact] public void TestUnexpectedButFilteredDataMessage() { int i = connection.Query("select @Bar", new WithBizarreData { Foo = new GenericUriParser(GenericUriParserOptions.Default), Bar = 23 }).Single(); Assert.Equal(23, i); } private class WithBizarreData { public GenericUriParser? Foo { get; set; } public int Bar { get; set; } } private class WithCharValue { public char Value { get; set; } public char? ValueNullable { get; set; } } [Fact] public void TestCharInputAndOutput() { const char test = '〠'; char c = connection.Query("select @c", new { c = test }).Single(); Assert.Equal(test, c); var obj = connection.Query("select @Value as Value", new WithCharValue { Value = c }).Single(); Assert.Equal(test, obj.Value); } [Fact] public void TestNullableCharInputAndOutputNonNull() { char? test = '〠'; char? c = connection.Query("select @c", new { c = test }).Single(); Assert.Equal(c, test); var obj = connection.Query("select @ValueNullable as ValueNullable", new WithCharValue { ValueNullable = c }).Single(); Assert.Equal(obj.ValueNullable, test); } [Fact] public void TestNullableCharInputAndOutputNull() { char? test = null; char? c = connection.Query("select @c", new { c = test }).Single(); Assert.Equal(c, test); var obj = connection.Query("select @ValueNullable as ValueNullable", new WithCharValue { ValueNullable = c }).Single(); Assert.Equal(obj.ValueNullable, test); } [Fact] public void WorkDespiteHavingWrongStructColumnTypes() { var hazInt = connection.Query("select cast(1 as bigint) Value").Single(); Assert.Equal(1, hazInt.Value); } private struct CanHazInt { public int Value { get; set; } } [Fact] public void TestInt16Usage() { Assert.Equal((short)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((short?)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((short?)null, connection.Query("select cast(null as smallint)").Single()); Assert.Equal((ShortEnum)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((ShortEnum?)42, connection.Query("select cast(42 as smallint)").Single()); Assert.Equal((ShortEnum?)null, connection.Query("select cast(null as smallint)").Single()); var row = connection.Query( "select cast(1 as smallint) as NonNullableInt16, cast(2 as smallint) as NullableInt16, cast(3 as smallint) as NonNullableInt16Enum, cast(4 as smallint) as NullableInt16Enum") .Single(); Assert.Equal((short)1, row.NonNullableInt16); Assert.Equal((short)2, row.NullableInt16); Assert.Equal(ShortEnum.Three, row.NonNullableInt16Enum); Assert.Equal(ShortEnum.Four, row.NullableInt16Enum); row = connection.Query( "select cast(5 as smallint) as NonNullableInt16, cast(null as smallint) as NullableInt16, cast(6 as smallint) as NonNullableInt16Enum, cast(null as smallint) as NullableInt16Enum") .Single(); Assert.Equal((short)5, row.NonNullableInt16); Assert.Equal((short?)null, row.NullableInt16); Assert.Equal(ShortEnum.Six, row.NonNullableInt16Enum); Assert.Equal((ShortEnum?)null, row.NullableInt16Enum); } [Fact] public void TestInt32Usage() { Assert.Equal((int)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((int?)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((int?)null, connection.Query("select cast(null as int)").Single()); Assert.Equal((IntEnum)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((IntEnum?)42, connection.Query("select cast(42 as int)").Single()); Assert.Equal((IntEnum?)null, connection.Query("select cast(null as int)").Single()); var row = connection.Query( "select cast(1 as int) as NonNullableInt32, cast(2 as int) as NullableInt32, cast(3 as int) as NonNullableInt32Enum, cast(4 as int) as NullableInt32Enum") .Single(); Assert.Equal((int)1, row.NonNullableInt32); Assert.Equal((int)2, row.NullableInt32); Assert.Equal(IntEnum.Three, row.NonNullableInt32Enum); Assert.Equal(IntEnum.Four, row.NullableInt32Enum); row = connection.Query( "select cast(5 as int) as NonNullableInt32, cast(null as int) as NullableInt32, cast(6 as int) as NonNullableInt32Enum, cast(null as int) as NullableInt32Enum") .Single(); Assert.Equal((int)5, row.NonNullableInt32); Assert.Equal((int?)null, row.NullableInt32); Assert.Equal(IntEnum.Six, row.NonNullableInt32Enum); Assert.Equal((IntEnum?)null, row.NullableInt32Enum); } public class WithInt16Values { public short NonNullableInt16 { get; set; } public short? NullableInt16 { get; set; } public ShortEnum NonNullableInt16Enum { get; set; } public ShortEnum? NullableInt16Enum { get; set; } } public class WithInt32Values { public int NonNullableInt32 { get; set; } public int? NullableInt32 { get; set; } public IntEnum NonNullableInt32Enum { get; set; } public IntEnum? NullableInt32Enum { get; set; } } public enum IntEnum : int { Zero = 0, One = 1, Two = 2, Three = 3, Four = 4, Five = 5, Six = 6 } [Fact] public void Issue_40_AutomaticBoolConversion() { var user = connection.Query("select UserId=1,Email='abc',Password='changeme',Active=cast(1 as tinyint)").Single(); Assert.True(user.Active); Assert.Equal(1, user.UserID); Assert.Equal("abc", user.Email); Assert.Equal("changeme", user.Password); } public class Issue40_User { public Issue40_User() { Email = Password = string.Empty; } public int UserID { get; set; } public string Email { get; set; } public string Password { get; set; } public bool Active { get; set; } } [Fact] public void ExecuteFromClosed() { using var conn = GetClosedConnection(); conn.Execute("-- nop"); Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void ExecuteInvalidFromClosed() { using var conn = GetClosedConnection(); var ex = Assert.ThrowsAny(() => conn.Execute("nop")); Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void QueryFromClosed() { using var conn = GetClosedConnection(); var i = conn.Query("select 1").Single(); Assert.Equal(ConnectionState.Closed, conn.State); Assert.Equal(1, i); } [Fact] public void QueryInvalidFromClosed() { using var conn = GetClosedConnection(); Assert.ThrowsAny(() => conn.Query("select gibberish").Single()); Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void TestDynamicMutation() { var obj = connection.Query("select 1 as [a], 2 as [b], 3 as [c]").Single(); Assert.Equal(1, (int)obj.a); IDictionary dict = obj; Assert.Equal(3, dict.Count); Assert.True(dict.Remove("a")); Assert.False(dict.Remove("d")); Assert.Equal(2, dict.Count); dict.Add("d", 4); Assert.Equal(3, dict.Count); Assert.Equal("b,c,d", string.Join(",", dict.Keys.OrderBy(x => x))); Assert.Equal("2,3,4", string.Join(",", dict.OrderBy(x => x.Key).Select(x => x.Value))); Assert.Equal(2, (int)obj.b); Assert.Equal(3, (int)obj.c); Assert.Equal(4, (int)obj.d); try { Assert.Equal(1, (int)obj.a); throw new InvalidOperationException("should have thrown"); } catch (RuntimeBinderException) { // pass } } [Fact] public void TestIssue131() { var results = connection.Query( "SELECT 1 Id, 'Mr' Title, 'John' Surname, 4 AddressCount", (person, addressCount) => person, splitOn: "AddressCount" ).First(); var asDict = (IDictionary)results; Assert.True(asDict.ContainsKey("Id")); Assert.True(asDict.ContainsKey("Title")); Assert.True(asDict.ContainsKey("Surname")); Assert.False(asDict.ContainsKey("AddressCount")); } // see https://stackoverflow.com/questions/13127886/dapper-returns-null-for-singleordefaultdatediff [Fact] public void TestNullFromInt_NoRows() { var result = connection.Query( // case with rows "select DATEDIFF(day, GETUTCDATE(), @date)", new { date = DateTime.UtcNow.AddDays(20) }) .SingleOrDefault(); Assert.Equal(20, result); result = connection.Query( // case without rows "select DATEDIFF(day, GETUTCDATE(), @date) where 1 = 0", new { date = DateTime.UtcNow.AddDays(20) }) .SingleOrDefault(); Assert.Equal(0, result); // zero rows; default of int over zero rows is zero } [Fact] public void TestDapperTableMetadataRetrieval() { // Test for a bug found in CS 51509960 where the following sequence would result in an InvalidOperationException being // thrown due to an attempt to access a disposed of DataReader: // // - Perform a dynamic query that yields no results // - Add data to the source of that query // - Perform a the same query again connection.Execute("CREATE TABLE #sut (value varchar(10) NOT NULL PRIMARY KEY)"); Assert.Equal(Enumerable.Empty(), connection.Query("SELECT value FROM #sut")); Assert.Equal(1, connection.Execute("INSERT INTO #sut (value) VALUES ('test')")); var result = connection.Query("SELECT value FROM #sut"); var first = result.First(); Assert.Equal("test", (string)first.value); } [Fact] public void DbStringAnsi() { var a = connection.Query("select datalength(@x)", new { x = new DbString { Value = "abc", IsAnsi = true } }).Single(); var b = connection.Query("select datalength(@x)", new { x = new DbString { Value = "abc", IsAnsi = false } }).Single(); Assert.Equal(3, a); Assert.Equal(6, b); } private class HasInt32 { public int Value { get; set; } } // https://stackoverflow.com/q/23696254/23354 [Fact] public void DownwardIntegerConversion() { const string sql = "select cast(42 as bigint) as Value"; int i = connection.Query(sql).Single().Value; Assert.Equal(42, i); i = connection.Query(sql).Single(); Assert.Equal(42, i); } [Fact] public void TypeBasedViaDynamic() { Type type = Common.GetSomeType(); dynamic template = Activator.CreateInstance(type)!; dynamic actual = CheetViaDynamic(template, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }); Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; Assert.Equal(123, a); Assert.Equal("abc", b); } [Fact] public void TypeBasedViaType() { Type type = Common.GetSomeType(); dynamic actual = connection.Query(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).First(); Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; Assert.Equal(123, a); Assert.Equal("abc", b); } private T CheetViaDynamic(T template, string query, object args) { return connection.Query(query, args).Single(); } [Fact] public void Issue22_ExecuteScalar() { int i = connection.ExecuteScalar("select 123"); Assert.Equal(123, i); i = connection.ExecuteScalar("select cast(123 as bigint)"); Assert.Equal(123, i); long j = connection.ExecuteScalar("select 123"); Assert.Equal(123L, j); j = connection.ExecuteScalar("select cast(123 as bigint)"); Assert.Equal(123L, j); int? k = connection.ExecuteScalar("select @i", new { i = default(int?) }); Assert.Null(k); } [Fact] public void Issue142_FailsNamedStatus() { var row1 = connection.Query("select @Status as [Status]", new { Status = StatusType.Started }).Single(); Assert.Equal(StatusType.Started, row1.Status); var row2 = connection.Query("select @Status as [Status]", new { Status = Status.Started }).Single(); Assert.Equal(Status.Started, row2.Status); } public class Issue142_Status { public StatusType Status { get; set; } } public class Issue142_StatusType { public Status Status { get; set; } } public enum StatusType : byte { NotStarted = 1, Started = 2, Finished = 3 } public enum Status : byte { NotStarted = 1, Started = 2, Finished = 3 } [Fact] public void Issue178_SqlServer() { const string sql = "select count(*) from Issue178"; try { connection.Execute("drop table Issue178"); } catch { /* don't care */ } try { connection.Execute("create table Issue178(id int not null)"); } catch { /* don't care */ } // raw ADO.net using (var sqlCmd = connection.CreateCommand()) { sqlCmd.CommandText = sql; using IDataReader reader1 = sqlCmd.ExecuteReader(); Assert.True(reader1.Read()); Assert.Equal(0, reader1.GetInt32(0)); Assert.False(reader1.Read()); Assert.False(reader1.NextResult()); } // dapper using (var reader2 = connection.ExecuteReader(sql)) { Assert.True(reader2.Read()); Assert.Equal(0, reader2.GetInt32(0)); Assert.False(reader2.Read()); Assert.False(reader2.NextResult()); } } [Fact] public void QueryBasicWithoutQuery() { int? i = connection.Query("print 'not a query'").FirstOrDefault(); Assert.Null(i); } [Fact] public void QueryComplexWithoutQuery() { var obj = connection.Query("print 'not a query'").FirstOrDefault(); Assert.Null(obj); } [FactLongRunning] public void Issue263_Timeout() { var watch = Stopwatch.StartNew(); var i = connection.Query("waitfor delay '00:01:00'; select 42;", commandTimeout: 300, buffered: false).Single(); watch.Stop(); Assert.Equal(42, i); var minutes = watch.ElapsedMilliseconds / 1000 / 60; Assert.True(minutes >= 0.95 && minutes <= 1.05); } [Fact] public void SO30435185_InvalidTypeOwner() { var ex = Assert.Throws(() => { const string sql = @" INSERT INTO #XXX (XXXId, AnotherId, ThirdId, Value, Comment) VALUES (@XXXId, @AnotherId, @ThirdId, @Value, @Comment); select @@rowcount as [Foo]"; var command = new { MyModels = new[] { new {XXXId = 1, AnotherId = 2, ThirdId = 3, Value = "abc", Comment = "def" } } }; var parameters = command .MyModels .Select(model => new { XXXId = model.XXXId, AnotherId = model.AnotherId, ThirdId = model.ThirdId, Value = model.Value, Comment = model.Comment }) .ToArray(); var rowcount = (int)connection.Query(sql, parameters).Single().Foo; Assert.Equal(1, rowcount); }); Assert.Equal("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context", ex.Message); } [Fact] public async void SO35470588_WrongValuePidValue() { // nuke, rebuild, and populate the table try { connection.Execute("drop table TPTable"); } catch { /* don't care */ } connection.Execute(@" create table TPTable (Pid int not null primary key identity(1,1), Value int not null); insert TPTable (Value) values (2), (568)"); // fetch the data using the query in the question, then force to a dictionary var rows = (await connection.QueryAsync("select * from TPTable").ConfigureAwait(false)) .ToDictionary(x => x.Pid); // check the number of rows Assert.Equal(2, rows.Count); // check row 1 var row = rows[1]; Assert.Equal(1, row.Pid); Assert.Equal(2, row.Value); // check row 2 row = rows[2]; Assert.Equal(2, row.Pid); Assert.Equal(568, row.Value); } public class TPTable { public int Pid { get; set; } public int Value { get; set; } } [Fact] public void GetOnlyProperties() { var obj = connection.QuerySingle("select 42 as [Id], 'def' as [Name];"); Assert.Equal(42, obj.Id); Assert.Equal("def", obj.Name); } private class HazGetOnly { public int Id { get; } public string Name { get; } = "abc"; } [Fact] public void TestConstructorParametersWithUnderscoredColumns() { DefaultTypeMap.MatchNamesWithUnderscores = true; var obj = connection.QuerySingle("select 42 as [id_property], 'def' as [name_property];"); Assert.Equal(42, obj.IdProperty); Assert.Equal("def", obj.NameProperty); } private class HazGetOnlyAndCtor { public int IdProperty { get; } public string NameProperty { get; } public HazGetOnlyAndCtor(int idProperty, string nameProperty) { IdProperty = idProperty; NameProperty = nameProperty; } } [Fact] public void Issue1164_OverflowExceptionForByte() { const string sql = "select cast(200 as smallint) as [value]"; // 200 more than sbyte.MaxValue but less than byte.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(200, obj.Value); } [Fact] public void Issue1164_OverflowExceptionForUInt16() { const string sql = "select cast(40000 as bigint) as [value]"; // 40000 more than short.MaxValue but less than ushort.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(40000, obj.Value); } [Fact] public void Issue1164_OverflowExceptionForUInt32() { const string sql = "select cast(4000000000 as bigint) as [value]"; // 4000000000 more than int.MaxValue but less than uint.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(4000000000, obj.Value); } [Fact] public void Issue1164_OverflowExceptionForUInt64() { const string sql = "select cast(10000000000000000000.0 as float) as [value]"; // 10000000000000000000 more than long.MaxValue but less than ulong.MaxValue Issue1164Object obj = connection.QuerySingle>(sql); Assert.StrictEqual(10000000000000000000, obj.Value); } private class Issue1164Object { public T Value = default!; } internal record struct One(int OID); internal record struct Two(int OID, string Name); [Fact] public async Task QuerySplitStruct() // https://github.com/DapperLib/Dapper/issues/2005 { var results = await connection.QueryAsync(@"SELECT 1 AS OID, 2 AS OID, 'Name' AS Name", (x,y) => (x,y), splitOn: "OID"); Assert.Single(results); } [Fact] public void SetDynamicProperty_WithReferenceType_Succeeds() { var obj = connection.QueryFirst("select 1 as ExistingProperty"); obj.ExistingProperty = "foo"; Assert.Equal("foo", (string)obj.ExistingProperty); obj.NewProperty = new Uri("http://example.net/"); Assert.Equal(new Uri("http://example.net/"), (Uri)obj.NewProperty); } [Fact] public void SetDynamicProperty_WithBoxedValueType_Succeeds() { var obj = connection.QueryFirst("select 'foo' as ExistingProperty"); obj.ExistingProperty = (object)1; Assert.Equal(1, (int)obj.ExistingProperty); obj.NewProperty = (object)true; Assert.True(obj.NewProperty); } [Fact] public void SetDynamicProperty_WithValueType_Succeeds() { var obj = connection.QueryFirst("select 'foo' as ExistingProperty"); obj.ExistingProperty = 1; Assert.Equal(1, (int)obj.ExistingProperty); obj.NewProperty = true; Assert.True(obj.NewProperty); } } }