From 17ffdc2437b14ffbdc708043d6e232037e997854 Mon Sep 17 00:00:00 2001 From: Andrey Dorokhov Date: Fri, 2 Feb 2018 02:33:25 +0300 Subject: [PATCH 001/312] Fix benchmark description typos (#932) --- Dapper.Tests.Performance/Benchmarks.Dapper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.Dapper.cs b/Dapper.Tests.Performance/Benchmarks.Dapper.cs index e51c0c18b..a8593b931 100644 --- a/Dapper.Tests.Performance/Benchmarks.Dapper.cs +++ b/Dapper.Tests.Performance/Benchmarks.Dapper.cs @@ -19,7 +19,7 @@ public Post QueryBuffered() return _connection.Query("select * from Posts where Id = @Id", new { Id = i }, buffered: true).First(); } - [Benchmark(Description = "Query (buffered)")] + [Benchmark(Description = "Query (buffered)")] public dynamic QueryBufferedDynamic() { Step(); @@ -33,7 +33,7 @@ public Post QueryUnbuffered() return _connection.Query("select * from Posts where Id = @Id", new { Id = i }, buffered: false).First(); } - [Benchmark(Description = "Query (unbuffered)")] + [Benchmark(Description = "Query (unbuffered)")] public dynamic QueryUnbufferedDynamic() { Step(); @@ -47,7 +47,7 @@ public Post QueryFirstOrDefault() return _connection.QueryFirstOrDefault("select * from Posts where Id = @Id", new { Id = i }); } - [Benchmark(Description = "QueryFirstOrDefault")] + [Benchmark(Description = "QueryFirstOrDefault")] public dynamic QueryFirstOrDefaultDynamic() { Step(); From 80231b4d2b2391bbb01c2cd7be75fbecfe0c0296 Mon Sep 17 00:00:00 2001 From: Dominik Herold Date: Wed, 14 Feb 2018 03:22:50 +0100 Subject: [PATCH 002/312] fix equal tests (#944) * fix equal tests * fix test --- Dapper.Tests/AsyncTests.cs | 4 ++-- Dapper.Tests/DecimalTests.cs | 20 +++++++++--------- Dapper.Tests/LiteralTests.cs | 12 +++++------ Dapper.Tests/MiscTests.cs | 2 +- Dapper.Tests/MultiMapTests.cs | 12 +++++------ Dapper.Tests/ParameterTests.cs | 34 +++++++++++++++--------------- Dapper.Tests/QueryMultipleTests.cs | 16 +++++++------- Dapper.Tests/TypeHandlerTests.cs | 16 +++++++------- 8 files changed, 58 insertions(+), 58 deletions(-) diff --git a/Dapper.Tests/AsyncTests.cs b/Dapper.Tests/AsyncTests.cs index 682b4a0c7..186a6cf6e 100644 --- a/Dapper.Tests/AsyncTests.cs +++ b/Dapper.Tests/AsyncTests.cs @@ -132,7 +132,7 @@ public async Task TestClassWithStringUsageAsync() public async Task TestExecuteAsync() { var val = await connection.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }).ConfigureAwait(false); - val.Equals(1); + Assert.Equal(1, val); } [Fact] @@ -140,7 +140,7 @@ public void TestExecuteClosedConnAsyncInner() { var query = connection.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }); var val = query.Result; - val.Equals(1); + Assert.Equal(1, val); } [Fact] diff --git a/Dapper.Tests/DecimalTests.cs b/Dapper.Tests/DecimalTests.cs index 5d2fef7b3..c26ef5f12 100644 --- a/Dapper.Tests/DecimalTests.cs +++ b/Dapper.Tests/DecimalTests.cs @@ -77,10 +77,10 @@ public void TestDoubleDecimalConversions_SO18228523_RightWay() { var row = connection.Query( "select cast(1 as float) as A, cast(2 as float) as B, cast(3 as decimal) as C, cast(4 as decimal) as D").Single(); - row.A.Equals(1.0); - row.B.Equals(2.0); - row.C.Equals(3.0M); - row.D.Equals(4.0M); + Assert.Equal(1.0, row.A); + Assert.Equal(2.0, row.B); + Assert.Equal(3.0M, row.C); + Assert.Equal(4.0M, row.D); } [Fact] @@ -88,10 +88,10 @@ public void TestDoubleDecimalConversions_SO18228523_WrongWay() { var row = connection.Query( "select cast(1 as decimal) as A, cast(2 as decimal) as B, cast(3 as float) as C, cast(4 as float) as D").Single(); - row.A.Equals(1.0); - row.B.Equals(2.0); - row.C.Equals(3.0M); - row.D.Equals(4.0M); + Assert.Equal(1.0, row.A); + Assert.Equal(2.0, row.B); + Assert.Equal(3.0M, row.C); + Assert.Equal(4.0M, row.D); } [Fact] @@ -99,9 +99,9 @@ public void TestDoubleDecimalConversions_SO18228523_Nulls() { var row = connection.Query( "select cast(null as decimal) as A, cast(null as decimal) as B, cast(null as float) as C, cast(null as float) as D").Single(); - row.A.Equals(0.0); + Assert.Equal(0.0, row.A); Assert.Null(row.B); - row.C.Equals(0.0M); + Assert.Equal(0.0M, row.C); Assert.Null(row.D); } diff --git a/Dapper.Tests/LiteralTests.cs b/Dapper.Tests/LiteralTests.cs index aba2877bb..f176fa34a 100644 --- a/Dapper.Tests/LiteralTests.cs +++ b/Dapper.Tests/LiteralTests.cs @@ -13,9 +13,9 @@ public void LiteralReplacementEnumAndString() AnEnum x = (AnEnum)(int)row.x; decimal y = row.y; AnotherEnum z = (AnotherEnum)(byte)row.z; - x.Equals(AnEnum.B); - y.Equals(123.45M); - z.Equals(AnotherEnum.A); + Assert.Equal(AnEnum.B, x); + Assert.Equal(123.45M, y); + Assert.Equal(AnotherEnum.A, z); } [Fact] @@ -29,9 +29,9 @@ public void LiteralReplacementDynamicEnumAndString() AnEnum x = (AnEnum)(int)row.x; decimal y = row.y; AnotherEnum z = (AnotherEnum)(byte)row.z; - x.Equals(AnEnum.B); - y.Equals(123.45M); - z.Equals(AnotherEnum.A); + Assert.Equal(AnEnum.B, x); + Assert.Equal(123.45M, y); + Assert.Equal(AnotherEnum.A, z); } [Fact] diff --git a/Dapper.Tests/MiscTests.cs b/Dapper.Tests/MiscTests.cs index 3e46ee05b..b118eabee 100644 --- a/Dapper.Tests/MiscTests.cs +++ b/Dapper.Tests/MiscTests.cs @@ -675,7 +675,7 @@ public void TestNullableCharInputAndOutputNull() public void WorkDespiteHavingWrongStructColumnTypes() { var hazInt = connection.Query("select cast(1 as bigint) Value").Single(); - hazInt.Value.Equals(1); + Assert.Equal(1, hazInt.Value); } private struct CanHazInt diff --git a/Dapper.Tests/MultiMapTests.cs b/Dapper.Tests/MultiMapTests.cs index ec612d03d..fbec87bcb 100644 --- a/Dapper.Tests/MultiMapTests.cs +++ b/Dapper.Tests/MultiMapTests.cs @@ -599,15 +599,15 @@ public void TestSplitWithMissingMembers() (T, P) => { T.Author = P; return T; }, null, null, true, "ID,Name").Single(); - result.ID.Equals(123); - result.Title.Equals("abc"); - result.CreateDate.Equals(new DateTime(2013, 2, 1)); + Assert.Equal(123, result.ID); + Assert.Equal("abc", result.Title); + Assert.Equal(new DateTime(2013, 2, 1), result.CreateDate); Assert.Null(result.Name); Assert.Null(result.Content); - result.Author.Phone.Equals("def"); - result.Author.Name.Equals("ghi"); - result.Author.ID.Equals(0); + Assert.Equal("def", result.Author.Phone); + Assert.Equal("ghi", result.Author.Name); + Assert.Equal(0, result.Author.ID); Assert.Null(result.Author.Address); } diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index 2fbbc37aa..fc76e87fa 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -174,20 +174,20 @@ public void GuidIn_SO_24177902() // check that rows 2&3 yield guids b&c var guids = connection.Query("select g from #foo where i in (2,3)").ToArray(); - guids.Length.Equals(2); - guids.Contains(a).Equals(false); - guids.Contains(b).Equals(true); - guids.Contains(c).Equals(true); - guids.Contains(d).Equals(false); + Assert.Equal(2, guids.Length); + Assert.DoesNotContain(a, guids); + Assert.Contains(b, guids); + Assert.Contains(c, guids); + Assert.DoesNotContain(d, guids); // in query on the guids var rows = connection.Query("select * from #foo where g in @guids order by i", new { guids }) .Select(row => new { i = (int)row.i, g = (Guid)row.g }).ToArray(); - rows.Length.Equals(2); - rows[0].i.Equals(2); - rows[0].g.Equals(b); - rows[1].i.Equals(3); - rows[1].g.Equals(c); + Assert.Equal(2, rows.Length); + Assert.Equal(2, rows[0].i); + Assert.Equal(b, rows[0].g); + Assert.Equal(3, rows[1].i); + Assert.Equal(c, rows[1].g); } [FactUnlessCaseSensitiveDatabase] @@ -514,8 +514,8 @@ public void DataTableParametersWithExtendedProperty() public void SupportInit() { var obj = connection.Query("select 'abc' as Value").Single(); - obj.Value.Equals("abc"); - obj.Flags.Equals(31); + Assert.Equal("abc", obj.Value); + Assert.Equal(31, obj.Flags); } public class WithInit : ISupportInitialize @@ -656,7 +656,7 @@ public void SqlHierarchyId_SO18888911() { SqlMapper.ResetTypeHandlers(); var row = connection.Query("select 3 as [Id], hierarchyid::Parse('/1/2/3/') as [Path]").Single(); - row.Id.Equals(3); + Assert.Equal(3, row.Id); Assert.NotEqual(default(SqlHierarchyId), row.Path); var val = connection.Query("select @Path", row).Single(); @@ -1189,8 +1189,8 @@ public void Issue151_ExpandoObjectArgsQuery() args.Name = "abc"; var row = connection.Query("select @Id as [Id], @Name as [Name]", (object)args).Single(); - ((int)row.Id).Equals(123); - ((string)row.Name).Equals("abc"); + Assert.Equal(123, (int)row.Id); + Assert.Equal("abc", (string)row.Name); } [Fact] @@ -1202,8 +1202,8 @@ public void Issue151_ExpandoObjectArgsExec() connection.Execute("create table #issue151 (Id int not null, Name nvarchar(20) not null)"); Assert.Equal(1, connection.Execute("insert #issue151 values(@Id, @Name)", (object)args)); var row = connection.Query("select Id, Name from #issue151").Single(); - ((int)row.Id).Equals(123); - ((string)row.Name).Equals("abc"); + Assert.Equal(123, (int)row.Id); + Assert.Equal("abc", (string)row.Name); } [Fact] diff --git a/Dapper.Tests/QueryMultipleTests.cs b/Dapper.Tests/QueryMultipleTests.cs index a087c1a78..19dcb557f 100644 --- a/Dapper.Tests/QueryMultipleTests.cs +++ b/Dapper.Tests/QueryMultipleTests.cs @@ -18,10 +18,10 @@ public void TestQueryMultipleBuffered() var c = grid.Read(); var d = grid.Read(); - a.Single().Equals(1); - b.Single().Equals(2); - c.Single().Equals(3); - d.Single().Equals(4); + Assert.Equal(1, a.Single()); + Assert.Equal(2, b.Single()); + Assert.Equal(3, c.Single()); + Assert.Equal(4, d.Single()); } } @@ -53,10 +53,10 @@ public void TestQueryMultipleNonBufferedCorrectOrder() var c = grid.Read(false).Single(); var d = grid.Read(false).Single(); - a.Equals(1); - b.Equals(2); - c.Equals(3); - d.Equals(4); + Assert.Equal(1, a); + Assert.Equal(2, b); + Assert.Equal(3, c); + Assert.Equal(4, d); } } diff --git a/Dapper.Tests/TypeHandlerTests.cs b/Dapper.Tests/TypeHandlerTests.cs index 66440424d..3478835ca 100644 --- a/Dapper.Tests/TypeHandlerTests.cs +++ b/Dapper.Tests/TypeHandlerTests.cs @@ -576,20 +576,20 @@ public class WrongTypes 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); + Assert.Equal(1, item.A); + Assert.Equal(2.0, item.B); + Assert.Equal(3L, item.C); + Assert.True(item.D); } [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); + Assert.Equal(1, item.A); + Assert.Equal(2.0, item.B); + Assert.Equal(3L, item.C); + Assert.True(item.D); } [Fact] From d603d43da1d703aadf123210b77babbd65d8b46d Mon Sep 17 00:00:00 2001 From: James Crowley Date: Tue, 20 Feb 2018 17:41:44 +0000 Subject: [PATCH 003/312] In order to allow re-use of parameters across multiple commands, remove them from any commands that are created prior to calling Dispose (as that is not sufficient). This will allow use cases where queries are retried (using Polly) for instance, and the parameters are being passed via IDynamicParameters or ICustomQueryParameter --- Dapper.Tests/ParameterTests.cs | 67 ++++++++++++++++++++++++++++++++-- Dapper/SqlMapper.cs | 15 +++++++- 2 files changed, 78 insertions(+), 4 deletions(-) diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index fc76e87fa..6075b9984 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -21,7 +21,7 @@ namespace Dapper.Tests { public class ParameterTests : TestBase { - public class DbParams : SqlMapper.IDynamicParameters, IEnumerable + public class DbDynamicParams : SqlMapper.IDynamicParameters, IEnumerable { private readonly List parameters = new List(); public IEnumerator GetEnumerator() { return parameters.GetEnumerator(); } @@ -37,6 +37,21 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id command.Parameters.Add(parameter); } } + + public class DbCustomParam : SqlMapper.ICustomQueryParameter + { + private readonly IDbDataParameter _sqlParameter; + + public DbCustomParam(IDbDataParameter sqlParameter) + { + _sqlParameter = sqlParameter; + } + + public void AddParameter(IDbCommand command, string name) + { + command.Parameters.Add(_sqlParameter); + } + } private static List CreateSqlDataRecordList(IEnumerable numbers) { @@ -672,9 +687,9 @@ public class HazSqlHierarchy #endif [Fact] - public void TestCustomParameters() + public void TestDynamicParameters() { - var args = new DbParams { + var args = new DbDynamicParams { new SqlParameter("foo", 123), new SqlParameter("bar", "abc") }; @@ -684,7 +699,53 @@ public void TestCustomParameters() Assert.Equal(123, foo); Assert.Equal("abc", bar); } + + [Fact] + public void TestDynamicParametersReuse() + { + var args = new DbDynamicParams { + new SqlParameter("foo", 123), + new SqlParameter("bar", "abc") + }; + var result1 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); + var result2 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); + Assert.Equal(123, result1.Foo); + Assert.Equal("abc", result1.Bar); + Assert.Equal(123, result2.Foo); + Assert.Equal("abc", result2.Bar); + } + + [Fact] + public void TestCustomParameter() + { + var args = new { + foo = new DbCustomParam(new SqlParameter("foo", 123)), + bar = "abc" + }; + var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); + int foo = result.Foo; + string bar = result.Bar; + Assert.Equal(123, foo); + Assert.Equal("abc", bar); + } + + [Fact] + public void TestCustomParameterReuse() + { + var args = new { + foo = new DbCustomParam(new SqlParameter("foo", 123)), + bar = "abc" + }; + var result1 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); + var result2 = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); + Assert.Equal(123, result1.Foo); + Assert.Equal("abc", result1.Bar); + Assert.Equal(123, result2.Foo); + Assert.Equal("abc", result2.Bar); + } + + [Fact] public void TestDynamicParamNullSupport() { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 96eb11805..02a056b27 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1040,6 +1040,8 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD } reader.Dispose(); } + + cmd?.Parameters.Clear(); cmd?.Dispose(); if (wasClosed) cnn.Close(); throw; @@ -1127,6 +1129,8 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini reader.Dispose(); } if (wasClosed) cnn.Close(); + + cmd?.Parameters.Clear(); cmd?.Dispose(); } } @@ -1234,6 +1238,7 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti reader.Dispose(); } if (wasClosed) cnn.Close(); + cmd?.Parameters.Clear(); cmd?.Dispose(); } } @@ -1458,6 +1463,7 @@ private static IEnumerable MultiMapImpl MultiMapImpl(this IDbConnection cnn } finally { + ownedCommand?.Parameters.Clear(); ownedCommand?.Dispose(); if (wasClosed) cnn.Close(); } @@ -2831,6 +2838,7 @@ private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition comma finally { if (wasClosed) cnn.Close(); + cmd?.Parameters.Clear(); cmd?.Dispose(); } } @@ -2858,6 +2866,7 @@ private static T ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition c finally { if (wasClosed) cnn.Close(); + cmd?.Parameters.Clear(); cmd?.Dispose(); } return Parse(result); @@ -2881,7 +2890,11 @@ private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefin finally { if (wasClosed) cnn.Close(); - if (cmd != null && disposeCommand) cmd.Dispose(); + if (cmd != null && disposeCommand) + { + cmd.Parameters.Clear(); + cmd.Dispose(); + } } } From dfbcaa16019875f9951fd461b7d30da746947268 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 6 Mar 2018 22:54:06 -0800 Subject: [PATCH 004/312] Test and fix for #591 This brings behavior QueryAsync behavior on par with Query (sync) and properly returns nothing when an empty result set is encountered. --- Dapper.Tests/ProcedureTests.cs | 25 ++++++++++++++++++++++++- Dapper/SqlMapper.Async.cs | 2 ++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/Dapper.Tests/ProcedureTests.cs b/Dapper.Tests/ProcedureTests.cs index 120460205..a560c680c 100644 --- a/Dapper.Tests/ProcedureTests.cs +++ b/Dapper.Tests/ProcedureTests.cs @@ -1,6 +1,7 @@ using System; using System.Data; using System.Linq; +using System.Threading.Tasks; using Xunit; namespace Dapper.Tests @@ -219,7 +220,7 @@ select DATEADD(ns, -100, @b) Assert.Equal(datetime2, p.Get("b")); } - [Theory()] + [Theory] [InlineData(null)] [InlineData(DbType.DateTime)] public void TestDateTime2LosePrecisionInDynamicParameters(DbType? dbType) @@ -254,5 +255,27 @@ select @b // @b gets set to datetime2 value but is truncated back to DbType.DateTime by DynamicParameter's Output declaration Assert.Equal(datetimeDefault, p.Get("b")); } + + + [Fact] + public async Task Issue591_NoResultsAsync() + { + const string tempSPName = "#" + nameof(Issue591_NoResultsAsync); + + var result = await connection.QueryAsync( + $@"create proc {tempSPName} + as + begin + -- basically a failed if statement, so the select is not happening and the stored proc return nothing + if 1=0 + begin + select 1 as Num + end + end + + exec {tempSPName}"); + + Assert.Empty(result); + } } } diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index ddc2ed6f6..7ce4877bb 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -392,6 +392,8 @@ private static async Task> QueryAsync(this IDbConnection cnn, int hash = GetColumnHash(reader); if (tuple.Func == null || tuple.Hash != hash) { + if (reader.FieldCount == 0) + return Enumerable.Empty(); tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } From bff5e750865919e4c0dc187585781cb22cf93701 Mon Sep 17 00:00:00 2001 From: Alexey Zimarev Date: Wed, 7 Mar 2018 08:13:12 +0100 Subject: [PATCH 005/312] Code formatting fix (tabs to spaces) (#896) --- Readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 0932f6026..1605cc3c6 100644 --- a/Readme.md +++ b/Readme.md @@ -321,9 +321,9 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) var squareParser = reader.GetRowParser(typeof(Square)); var triangleParser = reader.GetRowParser(typeof(Triangle)); - var typeColumnIndex = reader.GetOrdinal("Type"); + var typeColumnIndex = reader.GetOrdinal("Type"); - while (reader.Read()) + while (reader.Read()) { IShape shape; var type = (ShapeType)reader.GetInt32(typeColumnIndex); @@ -335,7 +335,7 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) case ShapeType.Square: shape = squareParser(reader); break; - case ShapeType.Triangle: + case ShapeType.Triangle: shape = triangleParser(reader); break; default: From 671419c326edbd910c634925a735a162388977f2 Mon Sep 17 00:00:00 2001 From: Marcell Toth Date: Wed, 7 Mar 2018 08:17:46 +0100 Subject: [PATCH 006/312] Make DapperRow implement IReadOnlyDictionary (#913) --- Dapper/SqlMapper.DapperRow.cs | 36 +++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Dapper/SqlMapper.DapperRow.cs b/Dapper/SqlMapper.DapperRow.cs index 399ca7e74..f4ebaedc5 100644 --- a/Dapper/SqlMapper.DapperRow.cs +++ b/Dapper/SqlMapper.DapperRow.cs @@ -10,6 +10,7 @@ public static partial class SqlMapper private sealed class DapperRow : System.Dynamic.IDynamicMetaObjectProvider , IDictionary + , IReadOnlyDictionary { private readonly DapperTable table; private object[] values; @@ -208,6 +209,41 @@ ICollection IDictionary.Values } #endregion + + + #region Implementation of IReadOnlyDictionary + + + int IReadOnlyCollection>.Count + { + get + { + return values.Count(t => !(t is DeadValue)); + } + } + + bool IReadOnlyDictionary.ContainsKey(string key) + { + int index = table.IndexOfName(key); + return index >= 0 && index < values.Length && !(values[index] is DeadValue); + } + + object IReadOnlyDictionary.this[string key] + { + get { TryGetValue(key, out object val); return val; } + } + + IEnumerable IReadOnlyDictionary.Keys + { + get { return this.Select(kv => kv.Key); } + } + + IEnumerable IReadOnlyDictionary.Values + { + get { return this.Select(kv => kv.Value); } + } + + #endregion } } } From 07fe543f1530b33818c9ce989bf0672e145c954d Mon Sep 17 00:00:00 2001 From: Jase <35100435+JaseRandall@users.noreply.github.com> Date: Sat, 10 Mar 2018 01:41:01 +1100 Subject: [PATCH 007/312] =?UTF-8?q?Change=20to=20GetAll=20to=20allow=20the?= =?UTF-8?q?=20usage=20of=20a=20nullable=20type=20in=20T=20when=20T=20is=20?= =?UTF-8?q?an=20interface=E2=80=A6=20#933=20(#936)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Change to GetAll to allow the usage of a nullable type in T when T is an interface. * Change to GetAll to allow the usage of a nullable type in T when T is an interface: further change to check if val == null and if so then don't try to assign it as a property value. * Change to Get to allow the usage of a nullable type in T when T is an interface. (effectively the same change as the previous change to GetAll.) * Change to GetAsync and GetAllAsync to allow the usage of a nullable type in T when T is an interface. * Added in UserWithNullableDob and IUserWithNullableDob. Added in tests for GetAndGetAllWithNullableValues and GetAsyncAndGetAllAsyncWithNullableValues. * Added in .ConfigureAwait(false) in the GetAsync Test. Added comment to identify which issue the test relates to. * Added NullableDates tables to the test databases. Adjusted variable names in GetAndGetAllWithNullableValues and GetAsyncAndGetAllAsyncWithNullableValues. * Changed IUserWithNullableDob to INullableDate. --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 22 +++++++++++-- Dapper.Contrib/SqlMapperExtensions.cs | 22 +++++++++++-- Dapper.Tests.Contrib/TestSuite.Async.cs | 24 ++++++++++++++ Dapper.Tests.Contrib/TestSuite.cs | 36 +++++++++++++++++++++ Dapper.Tests.Contrib/TestSuites.cs | 6 ++++ 5 files changed, 106 insertions(+), 4 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 8d283f07a..320bb42eb 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -50,7 +50,16 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + if (val == null) continue; + if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var genericType = Nullable.GetUnderlyingType(property.PropertyType); + if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); + } + else + { + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + } } ((IProxy)obj).IsDirty = false; //reset change tracking and return @@ -100,7 +109,16 @@ private static async Task> GetAllAsyncImpl(IDbConnection conne foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + if (val == null) continue; + if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var genericType = Nullable.GetUnderlyingType(property.PropertyType); + if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); + } + else + { + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + } } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 696dd6e9d..8d281701c 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -202,7 +202,16 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + if (val == null) continue; + if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var genericType = Nullable.GetUnderlyingType(property.PropertyType); + if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); + } + else + { + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + } } ((IProxy)obj).IsDirty = false; //reset change tracking and return @@ -249,7 +258,16 @@ public static IEnumerable GetAll(this IDbConnection connection, IDbTransac foreach (var property in TypePropertiesCache(type)) { var val = res[property.Name]; - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + if (val == null) continue; + if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + { + var genericType = Nullable.GetUnderlyingType(property.PropertyType); + if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); + } + else + { + property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); + } } ((IProxy)obj).IsDirty = false; //reset change tracking and return list.Add(obj); diff --git a/Dapper.Tests.Contrib/TestSuite.Async.cs b/Dapper.Tests.Contrib/TestSuite.Async.cs index 7c57a3634..413ed5fff 100644 --- a/Dapper.Tests.Contrib/TestSuite.Async.cs +++ b/Dapper.Tests.Contrib/TestSuite.Async.cs @@ -355,6 +355,30 @@ public async Task GetAllAsync() } } + /// + /// Test for issue #933 + /// + [Fact] + public async void GetAsyncAndGetAllAsyncWithNullableValues() + { + using (var connection = GetOpenConnection()) + { + var id1 = connection.Insert(new NullableDate { DateValue = new DateTime(2011, 07, 14) }); + var id2 = connection.Insert(new NullableDate { DateValue = null }); + + var value1 = await connection.GetAsync(id1).ConfigureAwait(false); + Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value); + + var value2 = await connection.GetAsync(id2).ConfigureAwait(false); + Assert.True(value2.DateValue == null); + + var value3 = await connection.GetAllAsync().ConfigureAwait(false); + var valuesList = value3.ToList(); + Assert.Equal(new DateTime(2011, 07, 14), valuesList[0].DateValue.Value); + Assert.True(valuesList[1].DateValue == null); + } + } + [Fact] public async Task InsertFieldWithReservedNameAsync() { diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index 44c2bc280..d5d373df0 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -53,6 +53,19 @@ public class User : IUser public int Age { get; set; } } + public interface INullableDate + { + [Key] + int Id { get; set; } + DateTime? DateValue { get; set; } + } + + public class NullableDate : INullableDate + { + public int Id { get; set; } + public DateTime? DateValue { get; set; } + } + public class Person { public int Id { get; set; } @@ -543,6 +556,29 @@ public void GetAll() } } + /// + /// Test for issue #933 + /// + [Fact] + public void GetAndGetAllWithNullableValues() + { + using (var connection = GetOpenConnection()) + { + var id1 = connection.Insert(new NullableDate { DateValue = new DateTime(2011, 07, 14) }); + var id2 = connection.Insert(new NullableDate { DateValue = null }); + + var value1 = connection.Get(id1); + Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value); + + var value2 = connection.Get(id2); + Assert.True(value2.DateValue == null); + + var value3 = connection.GetAll().ToList(); + Assert.Equal(new DateTime(2011, 07, 14), value3[0].DateValue.Value); + Assert.True(value3[1].DateValue == null); + } + } + [Fact] public void Transactions() { diff --git a/Dapper.Tests.Contrib/TestSuites.cs b/Dapper.Tests.Contrib/TestSuites.cs index 3949138b7..88cafecef 100644 --- a/Dapper.Tests.Contrib/TestSuites.cs +++ b/Dapper.Tests.Contrib/TestSuites.cs @@ -57,6 +57,8 @@ static SqlServerTestSuite() connection.Execute("CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); dropTable("GenericType"); connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null);"); + dropTable("NullableDates"); + connection.Execute("CREATE TABLE NullableDates (Id int IDENTITY(1,1) not null, DateValue DateTime null);"); } } } @@ -106,6 +108,8 @@ static MySqlServerTestSuite() connection.Execute("CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); dropTable("GenericType"); connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null);"); + dropTable("NullableDates"); + connection.Execute("CREATE TABLE NullableDates (Id int not null AUTO_INCREMENT PRIMARY KEY, DateValue DateTime);"); } } catch (MySqlException e) @@ -142,6 +146,7 @@ static SQLiteTestSuite() connection.Execute("CREATE TABLE ObjectY (ObjectYId integer not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE ObjectZ (Id integer not null, Name nvarchar(100) not null) "); connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null) "); + connection.Execute("CREATE TABLE NullableDates (Id integer primary key autoincrement not null, DateValue DateTime) "); } } } @@ -173,6 +178,7 @@ static SqlCETestSuite() connection.Execute(@"CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null) "); connection.Execute(@"CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null) "); + connection.Execute(@"CREATE TABLE NullableDates (Id int IDENTITY(1,1) not null, DateValue DateTime null) "); } Console.WriteLine("Created database"); } From 6e9099d68e42b76d67144be37c1f2ad17d824b6b Mon Sep 17 00:00:00 2001 From: Brian Drupieski Date: Thu, 29 Mar 2018 21:12:46 -0400 Subject: [PATCH 008/312] fix Insert when T is declared as IEnumerable (#948) --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 14 +++++++++++--- Dapper.Contrib/SqlMapperExtensions.cs | 14 +++++++++++--- Dapper.Tests.Contrib/TestSuite.Async.cs | 6 ++++++ Dapper.Tests.Contrib/TestSuite.cs | 6 ++++++ 4 files changed, 34 insertions(+), 6 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 320bb42eb..23b10989d 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -148,10 +148,18 @@ public static Task InsertAsync(this IDbConnection connection, T entityTo isList = true; type = type.GetElementType(); } - else if (type.IsGenericType() && type.GetTypeInfo().ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + else if (type.IsGenericType()) { - isList = true; - type = type.GetGenericArguments()[0]; + var typeInfo = type.GetTypeInfo(); + bool implementsGenericIEnumerableOrIsGenericIEnumerable = + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); + + if (implementsGenericIEnumerableOrIsGenericIEnumerable) + { + isList = true; + type = type.GetGenericArguments()[0]; + } } var name = GetTableName(type); diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 8d281701c..88c78034a 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -332,10 +332,18 @@ public static long Insert(this IDbConnection connection, T entityToInsert, ID isList = true; type = type.GetElementType(); } - else if (type.IsGenericType() && type.GetTypeInfo().ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>))) + else if (type.IsGenericType()) { - isList = true; - type = type.GetGenericArguments()[0]; + var typeInfo = type.GetTypeInfo(); + bool implementsGenericIEnumerableOrIsGenericIEnumerable = + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); + + if (implementsGenericIEnumerableOrIsGenericIEnumerable) + { + isList = true; + type = type.GetGenericArguments()[0]; + } } var name = GetTableName(type); diff --git a/Dapper.Tests.Contrib/TestSuite.Async.cs b/Dapper.Tests.Contrib/TestSuite.Async.cs index 413ed5fff..6ab99a0ec 100644 --- a/Dapper.Tests.Contrib/TestSuite.Async.cs +++ b/Dapper.Tests.Contrib/TestSuite.Async.cs @@ -225,6 +225,12 @@ public async Task BuilderTemplateWithoutCompositionAsync() } } + [Fact] + public async Task InsertEnumerableAsync() + { + await InsertHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); + } + [Fact] public async Task InsertArrayAsync() { diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index d5d373df0..caeafccdd 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -324,6 +324,12 @@ public void TestClosedConnection() } } + [Fact] + public void InsertEnumerable() + { + InsertHelper(src => src.AsEnumerable()); + } + [Fact] public void InsertArray() { From fc38c4234f637f4e030cd9091fe6ba0e0b4d7b2f Mon Sep 17 00:00:00 2001 From: Sergey <13222593+DevelAx@users.noreply.github.com> Date: Sat, 31 Mar 2018 00:32:28 +0300 Subject: [PATCH 009/312] Removing unjustified use of AppendFormat in StringBuilder class (#989) --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 6 +++--- Dapper.Contrib/SqlMapperExtensions.cs | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 23b10989d..1a8d2eef1 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -246,7 +246,7 @@ public static async Task UpdateAsync(this IDbConnection connection, T e var property = nonIdProps[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < nonIdProps.Count - 1) - sb.AppendFormat(", "); + sb.Append(", "); } sb.Append(" where "); for (var i = 0; i < keyProperties.Count; i++) @@ -254,7 +254,7 @@ public static async Task UpdateAsync(this IDbConnection connection, T e var property = keyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < keyProperties.Count - 1) - sb.AppendFormat(" and "); + sb.Append(" and "); } var updated = await connection.ExecuteAsync(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction).ConfigureAwait(false); return updated > 0; @@ -301,7 +301,7 @@ public static async Task DeleteAsync(this IDbConnection connection, T e var property = keyProperties[i]; sb.AppendFormat("{0} = @{1}", property.Name, property.Name); if (i < keyProperties.Count - 1) - sb.AppendFormat(" AND "); + sb.Append(" AND "); } var deleted = await connection.ExecuteAsync(sb.ToString(), entityToDelete, transaction, commandTimeout).ConfigureAwait(false); return deleted > 0; diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 88c78034a..8f111d89f 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -440,7 +440,7 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID var property = nonIdProps[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 if (i < nonIdProps.Count - 1) - sb.AppendFormat(", "); + sb.Append(", "); } sb.Append(" where "); for (var i = 0; i < keyProperties.Count; i++) @@ -448,7 +448,7 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID var property = keyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 if (i < keyProperties.Count - 1) - sb.AppendFormat(" and "); + sb.Append(" and "); } var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction); return updated > 0; @@ -497,7 +497,7 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID var property = keyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 if (i < keyProperties.Count - 1) - sb.AppendFormat(" and "); + sb.Append(" and "); } var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout); return deleted > 0; From c0ddd50470b6859367fe101b86092023a2495ed7 Mon Sep 17 00:00:00 2001 From: Mike McCaughan <1719723+mikemccaughan@users.noreply.github.com> Date: Tue, 3 Apr 2018 17:42:26 -0400 Subject: [PATCH 010/312] Remove reference to Stack Overflow docs Should resolve #992 --- docs/index.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index f3b3c3409..bcbbea2d1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,8 +4,6 @@ A brief guide is available [on github](https://github.com/StackExchange/dapper-dot-net/blob/master/Readme.md) -More examples coming soon on Stack Overflow docs. - Questions on Stack Overflow should be tagged [`dapper`](http://stackoverflow.com/questions/tagged/dapper) ## Installation From e199a8713c7942c05ed7dc6af8f389943568ed0f Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 3 Apr 2018 20:33:04 -0400 Subject: [PATCH 011/312] Better error messages for .*Async methods when not using DbConnection (#986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Better error messages for .*Async methods when not using DbConnection Perhaps in v2 we move these to DbConnection itself ratherr than IDbConnection for the extensions to eliminate the case, but that's breaking...for now we can at least throw a better error. See #757 for details. * Tweak errors to allow open IDbConnections and those generating DbCommands Same checks but a bit more lenient with more specific messaging. * Tweaking error messages and supporting IDbConnection implementations … (#987) --- Dapper/SqlMapper.Async.cs | 70 ++++++++++++++++++++++++++++----------- 1 file changed, 50 insertions(+), 20 deletions(-) diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 7ce4877bb..c25f06cfe 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -373,6 +373,36 @@ private static Task ExecuteReaderWithFlagsFallbackAsync(DbCommand return task; } + /// + /// Attempts to open a connection asynchronously, with a better error message for unsupported usages. + /// + private static Task TryOpenAsync(this IDbConnection cnn, CancellationToken cancel) + { + if (cnn is DbConnection dbConn) + { + return dbConn.OpenAsync(cancel); + } + else + { + throw new InvalidOperationException("Async operations require use of a DbConnection or an already-open IDbConnection"); + } + } + + /// + /// Attempts setup a on a , with a better error message for unsupported usages. + /// + private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action paramReader) + { + if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand) + { + return dbCommand; + } + else + { + throw new InvalidOperationException("Async operations require use of a DbConnection or an IDbConnection where .CreateCommand() returns a DbCommand"); + } + } + private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { object param = command.Parameters; @@ -380,12 +410,12 @@ private static async Task> QueryAsync(this IDbConnection cnn, var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; - using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) { DbDataReader reader = null; try { - if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false); + if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false); var tuple = info.Deserializer; @@ -444,12 +474,12 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; - using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) { DbDataReader reader = null; try { - if (wasClosed) await ((DbConnection)cnn).OpenAsync(cancel).ConfigureAwait(false); + if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0 ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false); @@ -546,7 +576,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD bool wasClosed = cnn.State == ConnectionState.Closed; try { - if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); CacheInfo info = null; string masterSql = null; @@ -562,7 +592,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD if (isFirst) { isFirst = false; - cmd = (DbCommand)command.SetupCommand(cnn, null); + cmd = command.TrySetupAsyncCommand(cnn, null); masterSql = cmd.CommandText; var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); info = GetCacheInfo(identity, obj, command.AddToCache); @@ -577,7 +607,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD } else { - cmd = (DbCommand)command.SetupCommand(cnn, null); + cmd = command.TrySetupAsyncCommand(cnn, null); } info.ParamReader(cmd, obj); @@ -604,7 +634,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD } else { - using (var cmd = (DbCommand)command.SetupCommand(cnn, null)) + using (var cmd = command.TrySetupAsyncCommand(cnn, null)) { foreach (var obj in multiExec) { @@ -640,11 +670,11 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType(), null); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; - using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) { try { - if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); command.OnCompleted(); return result; @@ -910,8 +940,8 @@ private static async Task> MultiMapAsync> MultiMapAsync(this IDbC bool wasClosed = cnn.State == ConnectionState.Closed; try { - if (wasClosed) await ((DbConnection)cnn).OpenAsync().ConfigureAwait(false); - using (var cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader)) + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false)) { var results = MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, true); @@ -1015,8 +1045,8 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, bool wasClosed = cnn.State == ConnectionState.Closed; try { - if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); - cmd = (DbCommand)command.SetupCommand(cnn, info.ParamReader); + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess, command.CancellationToken).ConfigureAwait(false); var result = new GridReader(cmd, reader, identity, command.Parameters as DynamicParameters, command.AddToCache, command.CancellationToken); @@ -1108,8 +1138,8 @@ private static async Task ExecuteReaderImplAsync(IDbConnection cnn, bool wasClosed = cnn.State == ConnectionState.Closed; try { - cmd = (DbCommand)command.SetupCommand(cnn, paramReader); - if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); + cmd = command.TrySetupAsyncCommand(cnn, paramReader); + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false); wasClosed = false; return reader; @@ -1182,8 +1212,8 @@ private static async Task ExecuteScalarImplAsync(IDbConnection cnn, Comman object result; try { - cmd = (DbCommand)command.SetupCommand(cnn, paramReader); - if (wasClosed) await ((DbConnection)cnn).OpenAsync(command.CancellationToken).ConfigureAwait(false); + cmd = command.TrySetupAsyncCommand(cnn, paramReader); + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); result = await cmd.ExecuteScalarAsync(command.CancellationToken).ConfigureAwait(false); command.OnCompleted(); } From bf8fdb9c9a7d5d282a0a475e3bc524bf5b6842ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Charles-Isaac=20C=C3=B4t=C3=A9?= Date: Mon, 9 Apr 2018 15:58:20 -0400 Subject: [PATCH 012/312] Fixed dead link in Readme.md Replaced a dead link in Readme.md by a link to the WaybackMachine --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 1605cc3c6..faae316f4 100644 --- a/Readme.md +++ b/Readme.md @@ -116,7 +116,7 @@ The performance tests are broken in to 3 lists: | Hand coded (using a `SqlDataReader`) | 47ms | | Dapper `ExecuteMapperQuery` | 49ms | | [ServiceStack.OrmLite](https://github.com/ServiceStack/ServiceStack.OrmLite) (QueryById) | 50ms | -| [PetaPoco](http://www.toptensoftware.com/petapoco/) | 52ms | [Can be faster](http://www.toptensoftware.com/blog/posts/94-PetaPoco-More-Speed) | +| [PetaPoco](http://www.toptensoftware.com/petapoco/) | 52ms | [Can be faster](http://web.archive.org/web/20170921124755/http://www.toptensoftware.com/blog/posts/94-PetaPoco-More-Speed) | | BLToolkit | 80ms | | SubSonic CodingHorror | 107ms | | NHibernate SQL | 104ms | From f29981a77b27a8b4ae1fdb674a081da2fa2ef19f Mon Sep 17 00:00:00 2001 From: Ben McCallum Date: Tue, 10 Apr 2018 10:05:14 +0200 Subject: [PATCH 013/312] Readme.md misc tidying --- Readme.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/Readme.md b/Readme.md index faae316f4..3fb05fe6b 100644 --- a/Readme.md +++ b/Readme.md @@ -5,7 +5,7 @@ Dapper - a simple object mapper for .Net Release Notes ------------- -[Located at stackexchange.github.io/Dapper](https://stackexchange.github.io/Dapper/) +Located at [stackexchange.github.io/Dapper](https://stackexchange.github.io/Dapper/) Features @@ -99,7 +99,7 @@ This works for any parameter that implements IEnumerable for some T. Performance ----------- -A key feature of Dapper is performance. The following metrics show how long it takes to execute 500 SELECT statements against a DB and map the data returned to objects. +A key feature of Dapper is performance. The following metrics show how long it takes to execute 500 `SELECT` statements against a DB and map the data returned to objects. The performance tests are broken in to 3 lists: @@ -109,8 +109,6 @@ The performance tests are broken in to 3 lists: ### Performance of SELECT mapping over 500 iterations - POCO serialization - - | Method | Duration | Remarks | | --------------------------------------------------- | -------- | ------- | | Hand coded (using a `SqlDataReader`) | 47ms | @@ -145,14 +143,14 @@ The performance tests are broken in to 3 lists: Performance benchmarks are available [here](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests.Performance). -Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (ctrl F5). +Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (Ctrl+F5). Alternatively, you might prefer Frans Bouma's [RawDataAccessBencher](https://github.com/FransBouma/RawDataAccessBencher) test suite or [OrmBenchmark](https://github.com/InfoTechBridge/OrmBenchmark). Parameterized queries --------------------- -Parameters are passed in as anonymous classes. This allow you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in Query analyzer. +Parameters are passed in as anonymous classes. This allow you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in your db platform's Query analyzer. ```csharp new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B @@ -160,7 +158,7 @@ new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B List Support ------------ -Dapper allows you to pass in IEnumerable and will automatically parameterize your query. +Dapper allows you to pass in `IEnumerable` and will automatically parameterize your query. For example: @@ -187,9 +185,9 @@ is actually a fixed value (for example, a fixed "category id", "status code" or Buffered vs Unbuffered readers --------------------- -Dapper's default behavior is to execute your sql and buffer the entire reader on return. This is ideal in most cases as it minimizes shared locks in the db and cuts down on db network time. +Dapper's default behavior is to execute your SQL and buffer the entire reader on return. This is ideal in most cases as it minimizes shared locks in the db and cuts down on db network time. -However when executing huge queries you may need to minimize memory footprint and only load objects as needed. To do so pass, buffered: false into the Query method. +However when executing huge queries you may need to minimize memory footprint and only load objects as needed. To do so pass, `buffered: false` into the `Query` method. Multi Mapping --------------------- @@ -301,7 +299,7 @@ Dapper supports varchar params, if you are executing a where clause on a varchar Query("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }); ``` -On SQL Server it is crucial to use the unicode when querying unicode and ansi when querying non unicode. +On SQL Server it is crucial to use the unicode when querying unicode and ANSI when querying non unicode. Type Switching Per Row --------------------- @@ -349,9 +347,9 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) Limitations and caveats --------------------- -Dapper caches information about every query it runs, this allow it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a ConcurrentDictionary object. The objects it stores are never flushed. If you are generating SQL strings on the fly without using parameters it is possible you will hit memory issues. We may convert the dictionaries to an LRU Cache. +Dapper caches information about every query it runs, this allow it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. The objects it stores are never flushed. If you are generating SQL strings on the fly without using parameters it is possible you will hit memory issues. We may convert the dictionaries to an LRU Cache. -Dapper's simplicity means that many feature that ORMs ship with are stripped out. It worries about the 95% scenario, and gives you the tools you need most of the time. It doesn't attempt to solve every problem. +Dapper's simplicity means that many feature that ORMs ship with are stripped out. It worries about the 95% scenario, and gives you the tools you need most of the time. It doesn't attempt to solve every problem. Will Dapper work with my DB provider? --------------------- @@ -359,7 +357,7 @@ Dapper has no DB specific implementation details, it works across all .NET ADO p Do you have a comprehensive list of examples? --------------------- -Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper.Tests) +Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper.Tests). Who is using this? --------------------- From 39b1fd5a1351837e6247756bfd2932e066605a19 Mon Sep 17 00:00:00 2001 From: Brian Drupieski Date: Mon, 23 Apr 2018 04:33:50 -0400 Subject: [PATCH 014/312] IEnumerable check when updating and deleting --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 20 ++++++++- Dapper.Contrib/SqlMapperExtensions.cs | 20 ++++++++- Dapper.Tests.Contrib/TestSuite.Async.cs | 49 +++++++++++++++++++++ Dapper.Tests.Contrib/TestSuite.cs | 49 +++++++++++++++++++++ 4 files changed, 134 insertions(+), 4 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 1a8d2eef1..167f77342 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -221,7 +221,15 @@ public static async Task UpdateAsync(this IDbConnection connection, T e } else if (type.IsGenericType()) { - type = type.GetGenericArguments()[0]; + var typeInfo = type.GetTypeInfo(); + bool implementsGenericIEnumerableOrIsGenericIEnumerable = + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); + + if (implementsGenericIEnumerableOrIsGenericIEnumerable) + { + type = type.GetGenericArguments()[0]; + } } var keyProperties = KeyPropertiesCache(type); @@ -282,7 +290,15 @@ public static async Task DeleteAsync(this IDbConnection connection, T e } else if (type.IsGenericType()) { - type = type.GetGenericArguments()[0]; + var typeInfo = type.GetTypeInfo(); + bool implementsGenericIEnumerableOrIsGenericIEnumerable = + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); + + if (implementsGenericIEnumerableOrIsGenericIEnumerable) + { + type = type.GetGenericArguments()[0]; + } } var keyProperties = KeyPropertiesCache(type); diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 8f111d89f..289620df9 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -415,7 +415,15 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID } else if (type.IsGenericType()) { - type = type.GetGenericArguments()[0]; + var typeInfo = type.GetTypeInfo(); + bool implementsGenericIEnumerableOrIsGenericIEnumerable = + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); + + if (implementsGenericIEnumerableOrIsGenericIEnumerable) + { + type = type.GetGenericArguments()[0]; + } } var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy @@ -476,7 +484,15 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID } else if (type.IsGenericType()) { - type = type.GetGenericArguments()[0]; + var typeInfo = type.GetTypeInfo(); + bool implementsGenericIEnumerableOrIsGenericIEnumerable = + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); + + if (implementsGenericIEnumerableOrIsGenericIEnumerable) + { + type = type.GetGenericArguments()[0]; + } } var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy diff --git a/Dapper.Tests.Contrib/TestSuite.Async.cs b/Dapper.Tests.Contrib/TestSuite.Async.cs index 6ab99a0ec..e9543e06e 100644 --- a/Dapper.Tests.Contrib/TestSuite.Async.cs +++ b/Dapper.Tests.Contrib/TestSuite.Async.cs @@ -46,6 +46,43 @@ public async Task TypeWithGenericParameterCanBeInsertedAsync() } } + [Fact] + public async Task TypeWithGenericParameterCanBeUpdatedAsync() + { + using (var connection = GetOpenConnection()) + { + var objectToInsert = new GenericType + { + Id = Guid.NewGuid().ToString(), + Name = "something" + }; + await connection.InsertAsync(objectToInsert); + + objectToInsert.Name = "somethingelse"; + await connection.UpdateAsync(objectToInsert); + + var updatedObject = connection.Get>(objectToInsert.Id); + Assert.Equal(objectToInsert.Name, updatedObject.Name); + } + } + + [Fact] + public async Task TypeWithGenericParameterCanBeDeletedAsync() + { + using (var connection = GetOpenConnection()) + { + var objectToInsert = new GenericType + { + Id = Guid.NewGuid().ToString(), + Name = "something" + }; + await connection.InsertAsync(objectToInsert); + + bool deleted = await connection.DeleteAsync(objectToInsert); + Assert.True(deleted); + } + } + /// /// Tests for issue #351 /// @@ -263,6 +300,12 @@ private async Task InsertHelperAsync(Func, T> helper) } } + [Fact] + public async Task UpdateEnumerableAsync() + { + await UpdateHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); + } + [Fact] public async Task UpdateArrayAsync() { @@ -302,6 +345,12 @@ private async Task UpdateHelperAsync(Func, T> helper) } } + [Fact] + public async Task DeleteEnumerableAsync() + { + await DeleteHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); + } + [Fact] public async Task DeleteArrayAsync() { diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index caeafccdd..fb457baa6 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -154,6 +154,43 @@ public void TypeWithGenericParameterCanBeInserted() } } + [Fact] + public void TypeWithGenericParameterCanBeUpdated() + { + using (var connection = GetOpenConnection()) + { + var objectToInsert = new GenericType + { + Id = Guid.NewGuid().ToString(), + Name = "something" + }; + connection.Insert(objectToInsert); + + objectToInsert.Name = "somethingelse"; + connection.Update(objectToInsert); + + var updatedObject = connection.Get>(objectToInsert.Id); + Assert.Equal(objectToInsert.Name, updatedObject.Name); + } + } + + [Fact] + public void TypeWithGenericParameterCanBeDeleted() + { + using (var connection = GetOpenConnection()) + { + var objectToInsert = new GenericType + { + Id = Guid.NewGuid().ToString(), + Name = "something" + }; + connection.Insert(objectToInsert); + + bool deleted = connection.Delete(objectToInsert); + Assert.True(deleted); + } + } + [Fact] public void Issue418() { @@ -362,6 +399,12 @@ private void InsertHelper(Func, T> helper) } } + [Fact] + public void UpdateEnumerable() + { + UpdateHelper(src => src.AsEnumerable()); + } + [Fact] public void UpdateArray() { @@ -401,6 +444,12 @@ private void UpdateHelper(Func, T> helper) } } + [Fact] + public void DeleteEnumerable() + { + DeleteHelper(src => src.AsEnumerable()); + } + [Fact] public void DeleteArray() { From 029ae842c836dedc19c8aada5aaaa9b082185151 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Wed, 16 May 2018 14:32:42 -0400 Subject: [PATCH 015/312] 1.50.5 Release --- semver.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/semver.txt b/semver.txt index 9bcab4a46..5e7700d29 100644 --- a/semver.txt +++ b/semver.txt @@ -1 +1 @@ -1.50.5-alpha1 \ No newline at end of file +1.50.5 \ No newline at end of file From e8bd27d8a9873b5fcc5fa9028f51d3300db2f35d Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 20 May 2018 07:29:51 -0400 Subject: [PATCH 016/312] Add release notes for 1.50.5, bump to 1.50.6-alpha for builds TODO: NerdBank versioning cutover --- docs/index.md | 11 +++++++++-- semver.txt | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index bcbbea2d1..bc59f4f7d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -4,7 +4,7 @@ A brief guide is available [on github](https://github.com/StackExchange/dapper-dot-net/blob/master/Readme.md) -Questions on Stack Overflow should be tagged [`dapper`](http://stackoverflow.com/questions/tagged/dapper) +Questions on Stack Overflow should be tagged [`dapper`](https://stackoverflow.com/questions/tagged/dapper) ## Installation @@ -20,6 +20,13 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes +### 1.50.5 + +- Fixes empty result set hanging with `QueryAsync` +- `DapperRow` now implements `IReadOnlyDictionary` +- Improved error messages for `Async` when the provided `IDbConnection` is not a `DbConnection` +- Contrib: `GetAll` now handles nullable types + ### 1.50.4 - Added back missing .NET Standard functionality (restored in `netstandard2.0`) @@ -86,7 +93,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command - Add `QueryFirstOrDefault` / `ReadFirstOrDefault` methods that optimize the single-row scenario - remove some legacy `dynamic` usage from the async API - make `DynamicTypeMap` public again (error during core-clr migration) -- use `Hashtable` again on core-clr +- use `Hashtable` again on core-clr ### 1.50-beta3 diff --git a/semver.txt b/semver.txt index 5e7700d29..5a0a9b9ee 100644 --- a/semver.txt +++ b/semver.txt @@ -1 +1 @@ -1.50.5 \ No newline at end of file +1.50.6-alpha \ No newline at end of file From 8f13e220f25cac8422ae99e8dfe2302b72fd4b5a Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 20 May 2018 07:30:09 -0400 Subject: [PATCH 017/312] Add packages to README --- Readme.md | 65 +++++++++++++++++++++++++++++++++---------------------- 1 file changed, 39 insertions(+), 26 deletions(-) diff --git a/Readme.md b/Readme.md index 3fb05fe6b..f6733fc74 100644 --- a/Readme.md +++ b/Readme.md @@ -4,9 +4,22 @@ Dapper - a simple object mapper for .Net Release Notes ------------- - Located at [stackexchange.github.io/Dapper](https://stackexchange.github.io/Dapper/) +Packages +-------- + +MyGet Pre-release feed: https://www.myget.org/gallery/dapper + +| Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet | +| ------- | ------------ | ----------------- | --------- | ----- | +| [Dapper](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/v/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/vpre/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/dt/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper) | +| [Dapper.Contrib](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/v/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/vpre/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/dt/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.Contrib.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.Contrib) | +| [Dapper.EntityFramework](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/v/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/vpre/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/dt/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.EntityFramework.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.EntityFramework) | +| [Dapper.EntityFramework.StrongName](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/v/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/vpre/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/dt/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.EntityFramework.StrongName.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.EntityFramework.StrongName) | +| [Dapper.Rainbow](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/v/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/vpre/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/dt/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.Rainbow.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.Rainbow) | +| [Dapper.SqlBuilder](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/v/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/vpre/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/dt/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.SqlBuilder.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.SqlBuilder) | +| [Dapper.StrongName](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/v/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/vpre/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/dt/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.StrongName.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.StrongName) | Features -------- @@ -31,8 +44,8 @@ public class Dog public float? Weight { get; set; } public int IgnoredProperty { get { return 1; } } -} - +} + var guid = Guid.NewGuid(); var dog = connection.Query("select Age = @Age, Id = @Id", new { Age = (int?)null, Id = guid }); @@ -71,12 +84,12 @@ Example usage: ```csharp var count = 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 + 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 }); Assert.Equal(2, count); ``` @@ -111,10 +124,10 @@ The performance tests are broken in to 3 lists: | Method | Duration | Remarks | | --------------------------------------------------- | -------- | ------- | -| Hand coded (using a `SqlDataReader`) | 47ms | +| Hand coded (using a `SqlDataReader`) | 47ms | | Dapper `ExecuteMapperQuery` | 49ms | | [ServiceStack.OrmLite](https://github.com/ServiceStack/ServiceStack.OrmLite) (QueryById) | 50ms | -| [PetaPoco](http://www.toptensoftware.com/petapoco/) | 52ms | [Can be faster](http://web.archive.org/web/20170921124755/http://www.toptensoftware.com/blog/posts/94-PetaPoco-More-Speed) | +| [PetaPoco](https://github.com/CollaboratingPlatypus/PetaPoco) | 52ms | [Can be faster](https://web.archive.org/web/20170921124755/http://www.toptensoftware.com/blog/posts/94-PetaPoco-More-Speed) | | BLToolkit | 80ms | | SubSonic CodingHorror | 107ms | | NHibernate SQL | 104ms | @@ -153,7 +166,7 @@ Parameterized queries Parameters are passed in as anonymous classes. This allow you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in your db platform's Query analyzer. ```csharp -new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B +new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B ``` List Support @@ -215,7 +228,7 @@ class User Now let us say that we want to map a query that joins both the posts and the users table. Until now if we needed to combine the result of 2 queries, we'd need a new object to express it but it makes more sense in this case to put the `User` object inside the `Post` object. -This is the user case for multi mapping. You tell dapper that the query returns a `Post` and a `User` object and then give it a function describing what you want to do with each of the rows containing both a `Post` and a `User` object. In our case, we want to take the user object and put it inside the post object. So we write the function: +This is the user case for multi mapping. You tell dapper that the query returns a `Post` and a `User` object and then give it a function describing what you want to do with each of the rows containing both a `Post` and a `User` object. In our case, we want to take the user object and put it inside the post object. So we write the function: ```csharp (post, user) => { post.Owner = user; return post; } @@ -230,11 +243,11 @@ The 3 type arguments to the `Query` method specify what objects dapper should us Everything put together, looks like this: ```csharp -var sql = -@"select * from #Posts p -left join #Users u on u.Id = p.OwnerId +var sql = +@"select * from #Posts p +left join #Users u on u.Id = p.OwnerId Order by p.Id"; - + var data = connection.Query(sql, (post, user) => { post.Owner = user; return post;}); var post = data.First(); @@ -253,19 +266,19 @@ Dapper allows you to process multiple result grids in a single query. Example: ```csharp -var sql = +var sql = @" select * from Customers where CustomerId = @id select * from Orders where CustomerId = @id select * from Returns where CustomerId = @id"; - + using (var multi = connection.QueryMultiple(sql, new {id=selectedId})) { var customer = multi.Read().Single(); var orders = multi.Read().ToList(); var returns = multi.Read().ToList(); ... -} +} ``` Stored Procedures @@ -273,7 +286,7 @@ Stored Procedures Dapper fully supports stored procs: ```csharp -var user = cnn.Query("spGetUser", new {Id = 1}, +var user = cnn.Query("spGetUser", new {Id = 1}, commandType: CommandType.StoredProcedure).SingleOrDefault(); ``` @@ -285,10 +298,10 @@ p.Add("@a", 11); p.Add("@b", dbType: DbType.Int32, direction: ParameterDirection.Output); p.Add("@c", dbType: DbType.Int32, direction: ParameterDirection.ReturnValue); -cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure); +cnn.Execute("spMagicProc", p, commandType: CommandType.StoredProcedure); int b = p.Get("@b"); -int c = p.Get("@c"); +int c = p.Get("@c"); ``` Ansi Strings and varchar @@ -318,9 +331,9 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) var circleParser = reader.GetRowParser(typeof(Circle)); var squareParser = reader.GetRowParser(typeof(Square)); var triangleParser = reader.GetRowParser(typeof(Triangle)); - + var typeColumnIndex = reader.GetOrdinal("Type"); - + while (reader.Read()) { IShape shape; @@ -339,7 +352,7 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) default: throw new NotImplementedException(); } - + shapes.Add(shape); } } From d3873cdffac56efe32a48eb3426d00a645b2df87 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 20 May 2018 08:02:31 -0400 Subject: [PATCH 018/312] Fix #397 --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index f6733fc74..5f20dd70d 100644 --- a/Readme.md +++ b/Readme.md @@ -370,7 +370,7 @@ Dapper has no DB specific implementation details, it works across all .NET ADO p Do you have a comprehensive list of examples? --------------------- -Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/dapper-dot-net/blob/master/Dapper.Tests). +Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests). Who is using this? --------------------- From 3c7cde28b55ecaa590c2eb5430d4eff460ae388e Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 20 May 2018 08:07:59 -0400 Subject: [PATCH 019/312] Fix #451 --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 5f20dd70d..efbaf7137 100644 --- a/Readme.md +++ b/Readme.md @@ -360,7 +360,7 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) Limitations and caveats --------------------- -Dapper caches information about every query it runs, this allow it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. The objects it stores are never flushed. If you are generating SQL strings on the fly without using parameters it is possible you will hit memory issues. We may convert the dictionaries to an LRU Cache. +Dapper caches information about every query it runs, this allow it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. Statements that are only used once are routinely flushed from this cache. Still, if you are generating SQL strings on the fly without using parameters it is possible you may hit memory issues. Dapper's simplicity means that many feature that ORMs ship with are stripped out. It worries about the 95% scenario, and gives you the tools you need most of the time. It doesn't attempt to solve every problem. From 4e62055c5775aced61b848282ed180de0ab62161 Mon Sep 17 00:00:00 2001 From: Alex Wiese Date: Mon, 18 Jun 2018 16:53:04 +0930 Subject: [PATCH 020/312] Update build.ps1 The -Contains operator doesn't do substring comparisons. > -Contains >Description: Containment operator. Tells whether a collection of reference values includes a single test value. Should be using -Match operator instead. --- build.ps1 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.ps1 b/build.ps1 index 8d81e3f6b..cc087bd89 100644 --- a/build.ps1 +++ b/build.ps1 @@ -30,7 +30,7 @@ function CalculateVersion() { Exit 1 } - if ($semVersion -contains "-") { + if ($semVersion -match "-") { return "$semVersion-$BuildNumber" #prerelease } else { return "$semVersion" #release @@ -127,4 +127,4 @@ foreach ($project in $projectsToBuild) { Write-Host "Done." -ForegroundColor "Green" Write-Host "" } -Write-Host "Build Complete." -ForegroundColor "Green" \ No newline at end of file +Write-Host "Build Complete." -ForegroundColor "Green" From 8916ccfd3573f98348d1566953db4fac01d11ff5 Mon Sep 17 00:00:00 2001 From: nikita maletin Date: Tue, 19 Jun 2018 08:31:20 +0100 Subject: [PATCH 021/312] upd comment for query t7 method - need more, use query with Type[] params --- Dapper/SqlMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 96eb11805..990ca6404 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1349,7 +1349,7 @@ public static IEnumerable Query(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// - /// Perform a multi-mapping query with 7 input types. + /// Perform a multi-mapping query with 7 input types. If you need more types -> use Query with Type[] parameter. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. From 307ab7eae89044e7a90c808267e482074d58cfd1 Mon Sep 17 00:00:00 2001 From: Alex Wiese Date: Tue, 19 Jun 2018 11:56:06 +0930 Subject: [PATCH 022/312] Update SqlMapperExtensions.Async.cs #418 - `Update` was fixed but `UpdateAsync` was not fixed. --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 167f77342..280e7128e 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -232,7 +232,7 @@ public static async Task UpdateAsync(this IDbConnection connection, T e } } - var keyProperties = KeyPropertiesCache(type); + var keyProperties = KeyPropertiesCache(type).ToList(); var explicitKeyProperties = ExplicitKeyPropertiesCache(type); if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); From 271af11beb550809818b4dc3917101b6211280a1 Mon Sep 17 00:00:00 2001 From: Alex Wiese Date: Tue, 19 Jun 2018 12:03:17 +0930 Subject: [PATCH 023/312] Update SqlMapperExtensions.Async.cs Made the same fix to `InsertAsync` --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 280e7128e..427361ec2 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -165,7 +165,7 @@ public static Task InsertAsync(this IDbConnection connection, T entityTo var name = GetTableName(type); var sbColumnList = new StringBuilder(null); var allProperties = TypePropertiesCache(type); - var keyProperties = KeyPropertiesCache(type); + var keyProperties = KeyPropertiesCache(type).ToList(); var computedProperties = ComputedPropertiesCache(type); var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); From 1367853feb647abc26dbb9db4ac8afea33eefa1c Mon Sep 17 00:00:00 2001 From: Alex Wiese Date: Mon, 18 Jun 2018 17:21:48 +0930 Subject: [PATCH 024/312] Update SqlMapperExtensions.Async.cs Use adapter to format column name equals, bringing it in line with `Delete` behaviour. --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 427361ec2..dc6e0b9b3 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -312,10 +312,12 @@ public static async Task DeleteAsync(this IDbConnection connection, T e var sb = new StringBuilder(); sb.AppendFormat("DELETE FROM {0} WHERE ", name); + var adapter = GetFormatter(connection); + for (var i = 0; i < keyProperties.Count; i++) { var property = keyProperties[i]; - sb.AppendFormat("{0} = @{1}", property.Name, property.Name); + adapter.AppendColumnNameEqualsValue(sb, property.Name); if (i < keyProperties.Count - 1) sb.Append(" AND "); } From 5bf9488c71ace3f5b6864aaa3a38c2d6f5dd3e38 Mon Sep 17 00:00:00 2001 From: Fabien Barbier Date: Thu, 23 Mar 2017 11:30:56 +0100 Subject: [PATCH 025/312] Fix handling of empty table-valued parameters. --- Dapper/SqlDataRecordListTVPParameter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index 8e8384705..3daad2df4 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -32,7 +32,7 @@ void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string nam internal static void Set(IDbDataParameter parameter, IEnumerable data, string typeName) { - parameter.Value = (object)data ?? DBNull.Value; + parameter.Value = (object)data; if (parameter is System.Data.SqlClient.SqlParameter sqlParam) { sqlParam.SqlDbType = SqlDbType.Structured; From 88348bd242fb1066a6b863e44a770e340054fcf3 Mon Sep 17 00:00:00 2001 From: Fabien Barbier Date: Fri, 18 May 2018 15:44:32 +0200 Subject: [PATCH 026/312] Fallback to Enumerable.Any() for detecting empty table-valued parameters. --- Dapper/SqlDataRecordListTVPParameter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index 3daad2df4..0e9606c52 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Linq; namespace Dapper { @@ -32,7 +33,7 @@ void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string nam internal static void Set(IDbDataParameter parameter, IEnumerable data, string typeName) { - parameter.Value = (object)data; + parameter.Value = data != null && data.Any() ? data : null; if (parameter is System.Data.SqlClient.SqlParameter sqlParam) { sqlParam.SqlDbType = SqlDbType.Structured; From cd751c21f67348a1327d1073b716f99563d44322 Mon Sep 17 00:00:00 2001 From: Hristo Stefanov Date: Sun, 1 Jul 2018 23:08:54 +0300 Subject: [PATCH 027/312] Fixed issue with empty sqldatarecord list as a table valued parameter. (#900) System.ArgumentException is thrown when passing empty SqlDataRecord list as an anonymous object or as table-valued parameter to Dapper. Added two unit tests. --- Dapper.Tests/ParameterTests.cs | 58 +++++++++++++++++++++++++ Dapper/SqlDataRecordListTVPParameter.cs | 3 +- 2 files changed, 59 insertions(+), 2 deletions(-) diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index fc76e87fa..2844fddc5 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -241,6 +241,37 @@ public void TestTVPWithAnonymousObject() } } + [Fact] + public void TestTVPWithAnonymousEmptyObject() + { + try + { + connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); + connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); + + var nums = connection.Query("get_ints", new { integers = new IntCustomParam(new int[] { }) }, commandType: CommandType.StoredProcedure).ToList(); + Assert.Equal(1, nums[0]); + Assert.Equal(2, nums[1]); + Assert.Equal(3, nums[2]); + Assert.Equal(3, nums.Count); + } + catch (ArgumentException ex) + { + Assert.True(string.Compare(ex.Message, "There are no records in the SqlDataRecord enumeration. To send a table-valued parameter with no rows, use a null reference for the value instead.") == 0); + } + finally + { + try + { + connection.Execute("DROP PROC get_ints"); + } + finally + { + connection.Execute("DROP TYPE int_list_type"); + } + } + } + // SQL Server specific test to demonstrate TVP [Fact] public void TestTVP() @@ -367,6 +398,33 @@ public void TestSqlDataRecordListParametersWithAsTableValuedParameter() } } + [Fact] + public void TestEmptySqlDataRecordListParametersWithAsTableValuedParameter() + { + try + { + connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); + connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); + + + var emptyRecord = CreateSqlDataRecordList(Enumerable.Empty()); + + var nums = connection.Query("get_ints", new { integers = emptyRecord.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList(); + Assert.True(nums.Count == 0); + } + finally + { + try + { + connection.Execute("DROP PROC get_ints"); + } + finally + { + connection.Execute("DROP TYPE int_list_type"); + } + } + } + [Fact] public void TestSqlDataRecordListParametersWithTypeHandlers() { diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index 0e9606c52..3d3dc9bea 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Data; using System.Linq; From e7ba3054c8ab7c7b6b9ca1487c630cec43f718f2 Mon Sep 17 00:00:00 2001 From: Jovan Popovic Date: Thu, 19 Jul 2018 21:30:23 +0200 Subject: [PATCH 028/312] Configured Belgrade Sql client to return data (#1066) --- Dapper.Tests.Performance/Benchmarks.Belgrade.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.Belgrade.cs b/Dapper.Tests.Performance/Benchmarks.Belgrade.cs index 8813912e9..ba7727632 100644 --- a/Dapper.Tests.Performance/Benchmarks.Belgrade.cs +++ b/Dapper.Tests.Performance/Benchmarks.Belgrade.cs @@ -1,4 +1,4 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using Belgrade.SqlClient.SqlDb; using System.Threading.Tasks; @@ -16,14 +16,13 @@ public void Setup() } [Benchmark(Description = "ExecuteReader")] - public async Task ExecuteReader() + public Post ExecuteReader() { Step(); - // TODO: How do you get a Post out of this thing? - await _mapper.ExecuteReader("SELECT TOP 1 * FROM Posts WHERE Id = " + i, + var post = new Post(); + _mapper.ExecuteReader("SELECT TOP 1 * FROM Posts WHERE Id = " + i, reader => { - var post = new Post(); post.Id = reader.GetInt32(0); post.Text = reader.GetString(1); post.CreationDate = reader.GetDateTime(2); @@ -39,6 +38,7 @@ await _mapper.ExecuteReader("SELECT TOP 1 * FROM Posts WHERE Id = " + i, post.Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11); post.Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12); }); + return post; } } -} \ No newline at end of file +} From 45460f3e5d6a943aa582e7118728806ca2e9de44 Mon Sep 17 00:00:00 2001 From: pug-pelle-p <33658649+pug-pelle-p@users.noreply.github.com> Date: Tue, 14 Aug 2018 12:47:16 +0200 Subject: [PATCH 029/312] Fixed indentation in Readme.md (#1098) Fixed incorrect indentation in Readme.md --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index efbaf7137..abf5980bc 100644 --- a/Readme.md +++ b/Readme.md @@ -340,7 +340,7 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) var type = (ShapeType)reader.GetInt32(typeColumnIndex); switch (type) { - case ShapeType.Circle: + case ShapeType.Circle: shape = circleParser(reader); break; case ShapeType.Square: @@ -349,7 +349,7 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) case ShapeType.Triangle: shape = triangleParser(reader); break; - default: + default: throw new NotImplementedException(); } From 10bfd02f1566a5de1dedb8e16aeff27df8da9cf5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=20Pa=C5=BEourek?= Date: Tue, 14 Aug 2018 11:48:24 +0100 Subject: [PATCH 030/312] Typo in readme (#1067) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index abf5980bc..29df19cc1 100644 --- a/Readme.md +++ b/Readme.md @@ -360,7 +360,7 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) Limitations and caveats --------------------- -Dapper caches information about every query it runs, this allow it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. Statements that are only used once are routinely flushed from this cache. Still, if you are generating SQL strings on the fly without using parameters it is possible you may hit memory issues. +Dapper caches information about every query it runs, this allows it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. Statements that are only used once are routinely flushed from this cache. Still, if you are generating SQL strings on the fly without using parameters it is possible you may hit memory issues. Dapper's simplicity means that many feature that ORMs ship with are stripped out. It worries about the 95% scenario, and gives you the tools you need most of the time. It doesn't attempt to solve every problem. From 1cd3efd89485ccd0bdcc6036eaa5a12a311189fd Mon Sep 17 00:00:00 2001 From: "John M. Wright" Date: Wed, 22 Aug 2018 08:00:02 -0500 Subject: [PATCH 031/312] More detailed error message on unsupported literal type (#1100) instead of exception message of `DateTime`, you'll get `The type 'DateTime' is not supported for SQL literals.`, which is much more helpful when trying to figure out what went wrong --- Dapper/SqlMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 990ca6404..c2e41441d 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2323,7 +2323,7 @@ public static string Format(object value) return sb.Append(')').__ToStringRecycle(); } } - throw new NotSupportedException(value.GetType().Name); + throw new NotSupportedException($"The type '{value.GetType().Name}' is not supported for SQL literals."); } } } From e16c69950cc2e2741c99c5a4495617cdd4749a9d Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 3 Sep 2018 20:35:06 -0400 Subject: [PATCH 032/312] Dapper build fixes, versioning, and SQL CE drop - Moves to NerdBank git-based versioning - Cleans up build.ps1 to current (will tweak for older moves later) - Updates xUnit to latest - Moves to dotnet test, since they killed dotnet xunit - Testing SQL Server CE has become unreasonable. It's gone (minimal cleanup in this commit disabling it). --- .../Dapper.Tests.Contrib.csproj | 4 - Dapper.Tests/Dapper.Tests.csproj | 8 +- Dapper.Tests/Helpers/XunitSkippable.cs | 6 +- Dapper.Tests/Providers/SQLCETests.cs | 56 ----------- Dapper.sln | 3 +- Directory.build.props | 13 ++- build.ps1 | 95 ++++--------------- version.json | 17 ++++ 8 files changed, 48 insertions(+), 154 deletions(-) delete mode 100644 Dapper.Tests/Providers/SQLCETests.cs create mode 100644 version.json diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index ea2efb865..dd9a7dd3b 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -22,9 +22,5 @@ - - - - diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 197dd121f..373fd7c98 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -11,7 +11,7 @@ - $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;SQL_CE;OLEDB + $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB @@ -27,13 +27,11 @@ - - @@ -52,10 +50,6 @@ - - - - if not exist "$(TargetDir)x86" md "$(TargetDir)x86" diff --git a/Dapper.Tests/Helpers/XunitSkippable.cs b/Dapper.Tests/Helpers/XunitSkippable.cs index ffd3406c9..aa0111ee1 100644 --- a/Dapper.Tests/Helpers/XunitSkippable.cs +++ b/Dapper.Tests/Helpers/XunitSkippable.cs @@ -29,7 +29,7 @@ public SkippableFactDiscoverer(IMessageSink diagnosticMessageSink) public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) { - yield return new SkippableFactTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), testMethod); + yield return new SkippableFactTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); } } @@ -40,8 +40,8 @@ public SkippableFactTestCase() { } - public SkippableFactTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, ITestMethod testMethod, object[] testMethodArguments = null) - : base(diagnosticMessageSink, defaultMethodDisplay, testMethod, testMethodArguments) + public SkippableFactTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) { } diff --git a/Dapper.Tests/Providers/SQLCETests.cs b/Dapper.Tests/Providers/SQLCETests.cs deleted file mode 100644 index f435bd4e8..000000000 --- a/Dapper.Tests/Providers/SQLCETests.cs +++ /dev/null @@ -1,56 +0,0 @@ -#if SQL_CE -using System.Data.SqlServerCe; -using System.IO; -using System.Linq; -using Xunit; - -namespace Dapper.Tests -{ - public class SQLCETests : TestBase - { - [Fact] - public void MultiRSSqlCE() - { - if (File.Exists("Test.DB.sdf")) - File.Delete("Test.DB.sdf"); - - const string cnnStr = "Data Source = Test.DB.sdf;"; - var engine = new SqlCeEngine(cnnStr); - engine.CreateDatabase(); - - using (var cnn = new SqlCeConnection(cnnStr)) - { - cnn.Open(); - - cnn.Execute("create table Posts (ID int, Title nvarchar(50), Body nvarchar(50), AuthorID int)"); - cnn.Execute("create table Authors (ID int, Name nvarchar(50))"); - - cnn.Execute("insert Posts values (1,'title','body',1)"); - cnn.Execute("insert Posts values(2,'title2','body2',null)"); - cnn.Execute("insert Authors values(1,'sam')"); - - var data = cnn.Query("select * from Posts p left join Authors a on a.ID = p.AuthorID", (post, author) => { post.Author = author; return post; }).ToList(); - var firstPost = data[0]; - Assert.Equal("title", firstPost.Title); - Assert.Equal("sam", firstPost.Author.Name); - Assert.Null(data[1].Author); - } - } - - public class PostCE - { - public int ID { get; set; } - public string Title { get; set; } - public string Body { get; set; } - - public AuthorCE Author { get; set; } - } - - public class AuthorCE - { - public int ID { get; set; } - public string Name { get; set; } - } - } -} -#endif diff --git a/Dapper.sln b/Dapper.sln index adb25c8a1..df80e9a5a 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -8,11 +8,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig appveyor.yml = appveyor.yml build.ps1 = build.ps1 - build.sh = build.sh + Directory.build.props = Directory.build.props License.txt = License.txt nuget.config = nuget.config Readme.md = Readme.md semver.txt = semver.txt + version.json = version.json EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "Dapper\Dapper.csproj", "{FAC24C3F-68F9-4247-A4B9-21D487E99275}" diff --git a/Directory.build.props b/Directory.build.props index 3200957e1..b2d84fe51 100644 --- a/Directory.build.props +++ b/Directory.build.props @@ -1,7 +1,5 @@ - 1.50.2 - 2017 Stack Exchange, Inc. true @@ -19,16 +17,17 @@ embedded en-US false - 2.3.0-beta5-build3769 + 2.4.1-pre.build.4059 - + - - - + + + + \ No newline at end of file diff --git a/build.ps1 b/build.ps1 index cc087bd89..bdbf83f33 100644 --- a/build.ps1 +++ b/build.ps1 @@ -1,53 +1,14 @@ [CmdletBinding(PositionalBinding=$false)] param( - [string] $Version, - [string] $BuildNumber, [bool] $CreatePackages, [bool] $RunTests = $true, [string] $PullRequestNumber ) -function CalculateVersion() { - if ($version) { - return $version - } - - $semVersion = ''; - $path = $pwd; - while (!$semVersion) { - if (Test-Path (Join-Path $path "semver.txt")) { - $semVersion = Get-Content (Join-Path $path "semver.txt") - break - } - if ($PSScriptRoot -eq $path) { - break - } - $path = Split-Path $path -Parent - } - - if (!$semVersion) { - Write-Error "semver.txt was not found in $pwd or any parent directory" - Exit 1 - } - - if ($semVersion -match "-") { - return "$semVersion-$BuildNumber" #prerelease - } else { - return "$semVersion" #release - } - -} - -if ($BuildNumber -and $BuildNumber.Length -lt 5) { - $BuildNumber = $BuildNumber.PadLeft(5, "0") -} - Write-Host "Run Parameters:" -ForegroundColor Cyan -Write-Host "Version: $Version" -Write-Host "BuildNumber: $BuildNumber" -Write-Host "CreatePackages: $CreatePackages" -Write-Host "RunTests: $RunTests" -Write-Host "Base Version: $(CalculateVersion)" +Write-Host " CreatePackages: $CreatePackages" +Write-Host " RunTests: $RunTests" +Write-Host " dotnet --version:" (dotnet --version) $packageOutputFolder = "$PSScriptRoot\.nupkgs" $projectsToBuild = @@ -63,26 +24,28 @@ $testsToRun = 'Dapper.Tests', 'Dapper.Tests.Contrib' -if (!$Version -and !$BuildNumber) { - Write-Host "ERROR: You must supply either a -Version or -BuildNumber argument. ` - Use -Version `"4.0.0`" for explicit version specification, or ` - Use -BuildNumber `"12345`" for generation using -" -ForegroundColor Yellow - Exit 1 -} - if ($PullRequestNumber) { Write-Host "Building for a pull request (#$PullRequestNumber), skipping packaging." -ForegroundColor Yellow $CreatePackages = $false } -if ($RunTests) { +Write-Host "Building projects..." -ForegroundColor "Magenta" +foreach ($project in $projectsToBuild + $testsToRun) { + Write-Host "Building $project (dotnet restore/build)..." -ForegroundColor "Magenta" + dotnet restore ".\$project\$project.csproj" /p:CI=true + dotnet build ".\$project\$project.csproj" -c Release /p:CI=true + Write-Host "" +} +Write-Host "Done building." -ForegroundColor "Green" + +if ($RunTests) { dotnet restore /ConsoleLoggerParameters:Verbosity=Quiet foreach ($project in $testsToRun) { Write-Host "Running tests: $project (all frameworks)" -ForegroundColor "Magenta" Push-Location ".\$project" - dotnet xunit - if ($LastExitCode -ne 0) { + dotnet test -c Release + if ($LastExitCode -ne 0) { Write-Host "Error with tests, aborting build." -Foreground "Red" Pop-Location Exit 1 @@ -100,31 +63,11 @@ if ($CreatePackages) { Write-Host "done." -ForegroundColor "Green" Write-Host "Building all packages" -ForegroundColor "Green" -} - -foreach ($project in $projectsToBuild) { - Write-Host "Working on $project`:" -ForegroundColor "Magenta" - - Push-Location ".\$project" - $semVer = CalculateVersion - $targets = "Restore" - - Write-Host " Restoring " -NoNewline -ForegroundColor "Magenta" - if ($CreatePackages) { - $targets += ";Pack" - Write-Host "and packing " -NoNewline -ForegroundColor "Magenta" + foreach ($project in $projectsToBuild) { + Write-Host "Packing $project (dotnet pack)..." -ForegroundColor "Magenta" + dotnet pack ".\$project\$project.csproj" --no-build -c Release /p:PackageOutputPath=$packageOutputFolder /p:NoPackageAnalysis=true /p:CI=true + Write-Host "" } - Write-Host "$project... (Version:" -NoNewline -ForegroundColor "Magenta" - Write-Host $semVer -NoNewline -ForegroundColor "Cyan" - Write-Host ")" -ForegroundColor "Magenta" - - - dotnet msbuild "/t:$targets" "/p:Configuration=Release" "/p:Version=$semVer" "/p:PackageOutputPath=$packageOutputFolder" "/p:CI=true" - - Pop-Location - - Write-Host "Done." -ForegroundColor "Green" - Write-Host "" } Write-Host "Build Complete." -ForegroundColor "Green" diff --git a/version.json b/version.json new file mode 100644 index 000000000..3288253cd --- /dev/null +++ b/version.json @@ -0,0 +1,17 @@ +{ + "version": "1.50.7-alpha.{height}", + "assemblyVersion": "1.50.0.0", + "publicReleaseRefSpec": [ + "^refs/heads/master$", + "^refs/tags/v\\d+\\.\\d+" + ], + "nugetPackageVersion": { + "semVer": 2 + }, + "cloudBuild": { + "buildNumber": { + "enabled": true, + "setVersionVariables": true + } + } +} \ No newline at end of file From ba16b54b4f95dfceb4d4fa194cf64b19b1b19eee Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 3 Sep 2018 20:36:51 -0400 Subject: [PATCH 033/312] Woops, fix appveyor config for new build.ps1 --- appveyor.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appveyor.yml b/appveyor.yml index e23831f3a..907205d0b 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -39,7 +39,7 @@ build_script: # MySQL - mysql -e "create database test;" --user=root # Our stuff - - ps: .\build.ps1 -BuildNumber "$env:APPVEYOR_BUILD_NUMBER" -Version "$env:APPVEYOR_REPO_TAG_NAME" -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages $true + - ps: .\build.ps1 -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages $true test: off artifacts: From 522d150e25639453496043df71f2ee42512604cc Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 3 Sep 2018 21:38:27 -0400 Subject: [PATCH 034/312] Faster builds + use MySqlConnector MySql.Data is a travesty at this point - moving to something more stable. Really we weren't testing Dapper with previous test failures, we were testing what mood MySql.Data and NuGet extraction are in and which phase of the moon is about. Also speeds up the builds with 1 restore and 1 build. We can simplify here because everything is relevant (no wasted builds). --- Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj | 2 +- Dapper.Tests.Contrib/TestSuites.cs | 11 ++++------- .../Dapper.Tests.Performance.csproj | 5 ++--- Dapper.Tests/Dapper.Tests.csproj | 2 +- build.ps1 | 14 ++++++-------- 5 files changed, 14 insertions(+), 20 deletions(-) diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index dd9a7dd3b..5c5d6ce6f 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -18,7 +18,7 @@ - + diff --git a/Dapper.Tests.Contrib/TestSuites.cs b/Dapper.Tests.Contrib/TestSuites.cs index 88cafecef..31ef7f37e 100644 --- a/Dapper.Tests.Contrib/TestSuites.cs +++ b/Dapper.Tests.Contrib/TestSuites.cs @@ -37,7 +37,7 @@ static SqlServerTestSuite() using (var connection = new SqlConnection(ConnectionString)) { // ReSharper disable once AccessToDisposedClosure - Action dropTable = name => connection.Execute($"IF OBJECT_ID('{name}', 'U') IS NOT NULL DROP TABLE [{name}]; "); + void dropTable(string name) => connection.Execute($"IF OBJECT_ID('{name}', 'U') IS NOT NULL DROP TABLE [{name}]; "); connection.Open(); dropTable("Stuff"); connection.Execute("CREATE TABLE Stuff (TheId int IDENTITY(1,1) not null, Name nvarchar(100) not null, Created DateTime null);"); @@ -65,12 +65,10 @@ static SqlServerTestSuite() public class MySqlServerTestSuite : TestSuite { - private const string DbName = "DapperContribTests"; - public static string ConnectionString { get; } = IsAppVeyor - ? "Server=localhost;Uid=root;Pwd=Password12!;" - : "Server=localhost;Uid=test;Pwd=pass;"; + ? "Server=localhost;Database=test;Uid=root;Pwd=Password12!;UseAffectedRows=false;" + : "Server=localhost;Database=tests;Uid=test;Pwd=pass;UseAffectedRows=false;"; public override IDbConnection GetConnection() { @@ -87,9 +85,8 @@ static MySqlServerTestSuite() using (var connection = new MySqlConnection(ConnectionString)) { // ReSharper disable once AccessToDisposedClosure - Action dropTable = name => connection.Execute($"DROP TABLE IF EXISTS `{name}`;"); + void dropTable(string name) => connection.Execute($"DROP TABLE IF EXISTS `{name}`;"); connection.Open(); - connection.Execute($"DROP DATABASE IF EXISTS {DbName}; CREATE DATABASE {DbName}; USE {DbName};"); dropTable("Stuff"); connection.Execute("CREATE TABLE Stuff (TheId int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null, Created DateTime null);"); dropTable("People"); diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index dd4ec0bdd..7299588b1 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -19,9 +19,8 @@ - - + @@ -46,6 +45,6 @@ - + diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 373fd7c98..13aea73f6 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -21,7 +21,7 @@ - + diff --git a/build.ps1 b/build.ps1 index bdbf83f33..de16c65b5 100644 --- a/build.ps1 +++ b/build.ps1 @@ -29,17 +29,15 @@ if ($PullRequestNumber) { $CreatePackages = $false } -Write-Host "Building projects..." -ForegroundColor "Magenta" -foreach ($project in $projectsToBuild + $testsToRun) { - Write-Host "Building $project (dotnet restore/build)..." -ForegroundColor "Magenta" - dotnet restore ".\$project\$project.csproj" /p:CI=true - dotnet build ".\$project\$project.csproj" -c Release /p:CI=true - Write-Host "" -} +Write-Host "Restoring all projects..." -ForegroundColor "Magenta" +dotnet restore +Write-Host "Done restoring." -ForegroundColor "Green" + +Write-Host "Building all projects..." -ForegroundColor "Magenta" +dotnet build -c Release --no-restore /p:CI=true Write-Host "Done building." -ForegroundColor "Green" if ($RunTests) { - dotnet restore /ConsoleLoggerParameters:Verbosity=Quiet foreach ($project in $testsToRun) { Write-Host "Running tests: $project (all frameworks)" -ForegroundColor "Magenta" Push-Location ".\$project" From 5189fcd43a06b5975e16082ac2fc6582bb404911 Mon Sep 17 00:00:00 2001 From: David Glassborow Date: Fri, 31 Aug 2018 12:30:11 +0100 Subject: [PATCH 035/312] Tests for #1111 - Inconsistent deserialisation between normal Query and SqlMapper.Parse --- Dapper.Tests/DataReaderTests.cs | 10 ++++++++++ Dapper.Tests/TypeHandlerTests.cs | 7 +++++++ 2 files changed, 17 insertions(+) diff --git a/Dapper.Tests/DataReaderTests.cs b/Dapper.Tests/DataReaderTests.cs index 0d9bd49da..8946d4551 100644 --- a/Dapper.Tests/DataReaderTests.cs +++ b/Dapper.Tests/DataReaderTests.cs @@ -38,6 +38,16 @@ public void GetSameReaderForSameShape() Assert.False(ReferenceEquals(secondParser, thirdParser)); } + [Fact] + public void TestTreatIntAsABool() + { + // Test we are consistent with direct call to database, see TypeHandlerTests.TestTreatIntAsABool + using(var reader = connection.ExecuteReader("select CAST(1 AS BIT)")) + Assert.True(SqlMapper.Parse(reader).Single()); + using (var reader = connection.ExecuteReader("select 1")) + Assert.True(SqlMapper.Parse(reader).Single()); + } + [Fact] public void DiscriminatedUnion() { diff --git a/Dapper.Tests/TypeHandlerTests.cs b/Dapper.Tests/TypeHandlerTests.cs index 3478835ca..c766190b4 100644 --- a/Dapper.Tests/TypeHandlerTests.cs +++ b/Dapper.Tests/TypeHandlerTests.cs @@ -592,6 +592,13 @@ public void TestWrongTypes_WithWrongTypes() Assert.True(item.D); } + [Fact] + public void TestTreatIntAsABool() + { + Assert.True(connection.Query("select CAST(1 AS BIT)").Single()); + Assert.True(connection.Query("select 1").Single()); + } + [Fact] public void SO24607639_NullableBools() { From 6e2774064d631374d3053eb8107d394dfa6e5e50 Mon Sep 17 00:00:00 2001 From: David Glassborow Date: Fri, 31 Aug 2018 12:34:22 +0100 Subject: [PATCH 036/312] Make SqlMapper.Parse consistent with QueryImpl, closes #1111 --- Dapper/SqlMapper.IDataReader.cs | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs index 196dc7d92..ac260172f 100644 --- a/Dapper/SqlMapper.IDataReader.cs +++ b/Dapper/SqlMapper.IDataReader.cs @@ -15,10 +15,20 @@ public static IEnumerable Parse(this IDataReader reader) { if (reader.Read()) { - var deser = GetDeserializer(typeof(T), reader, 0, -1, false); + var effectiveType = typeof(T); + var deser = GetDeserializer(effectiveType, reader, 0, -1, false); + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; do { - yield return (T)deser(reader); + object val = deser(reader); + if (val == null || val is T) + { + yield return (T)val; + } + else + { + yield return (T)Convert.ChangeType(val, convertToType, System.Globalization.CultureInfo.InvariantCulture); + } } while (reader.Read()); } } From 0b168789fd38a67ef401dd540a2723eb92a15267 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 10 Sep 2018 20:22:10 -0400 Subject: [PATCH 037/312] Benchmarks: updating libs and overall runner (#1123) This is prepping a run we can drop into the README easily. And more info on allocations and such along with it. It's finishing off the benchmark port getting it into a good output state (something we can copy/paste). It removes Simple.Data because the package was debug and causes a lot of issues. This doesn't output 100% like we want yet (fastest to slowest). BenchmarkDotNet joins summary results after ordering...we need to post-order that. I'm asking the maintainers how to do so. Also note Belgrade was not parameterizing before, giving it a huge unfair advantage. This has been fixed to be apples:apples. --- .gitignore | 3 +- .../Benchmarks.Belgrade.cs | 3 +- .../Benchmarks.HandCoded.cs | 32 +++---- Dapper.Tests.Performance/Benchmarks.Soma.cs | 23 ----- Dapper.Tests.Performance/Benchmarks.cs | 35 +------- Dapper.Tests.Performance/Config.cs | 46 ++++++++++ .../Dapper.Tests.Performance.csproj | 27 +++--- Dapper.Tests.Performance/Helpers/ORMColum.cs | 6 +- .../Helpers/ReturnColum.cs | 11 ++- Dapper.Tests.Performance/LegacyTests.cs | 16 ++-- Dapper.Tests.Performance/Program.cs | 26 ++---- Readme.md | 88 ++++++++++--------- 12 files changed, 155 insertions(+), 161 deletions(-) delete mode 100644 Dapper.Tests.Performance/Benchmarks.Soma.cs create mode 100644 Dapper.Tests.Performance/Config.cs diff --git a/.gitignore b/.gitignore index 360431748..f7d0ca7ad 100644 --- a/.gitignore +++ b/.gitignore @@ -18,4 +18,5 @@ Test.DB.* TestResults/ Dapper.Tests/*.sdf Dapper.Tests/SqlServerTypes/ -.dotnet/* \ No newline at end of file +.dotnet/* +BenchmarkDotNet.Artifacts/ \ No newline at end of file diff --git a/Dapper.Tests.Performance/Benchmarks.Belgrade.cs b/Dapper.Tests.Performance/Benchmarks.Belgrade.cs index ba7727632..5b9e3904a 100644 --- a/Dapper.Tests.Performance/Benchmarks.Belgrade.cs +++ b/Dapper.Tests.Performance/Benchmarks.Belgrade.cs @@ -1,6 +1,7 @@ using BenchmarkDotNet.Attributes; using Belgrade.SqlClient.SqlDb; using System.Threading.Tasks; +using Belgrade.SqlClient; namespace Dapper.Tests.Performance { @@ -20,7 +21,7 @@ public Post ExecuteReader() { Step(); var post = new Post(); - _mapper.ExecuteReader("SELECT TOP 1 * FROM Posts WHERE Id = " + i, + _mapper.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", i).Map( reader => { post.Id = reader.GetInt32(0); diff --git a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs index e100b32f8..17fd1057b 100644 --- a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs +++ b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs @@ -47,7 +47,7 @@ public void Setup() #endif } - [Benchmark(Description = "SqlCommand", Baseline = true)] + [Benchmark(Description = "SqlCommand")] public Post SqlCommand() { Step(); @@ -56,21 +56,23 @@ public Post SqlCommand() using (var reader = _postCommand.ExecuteReader()) { reader.Read(); - var post = new Post(); - post.Id = reader.GetInt32(0); - post.Text = reader.GetNullableString(1); - post.CreationDate = reader.GetDateTime(2); - post.LastChangeDate = reader.GetDateTime(3); + var post = new Post + { + Id = reader.GetInt32(0), + Text = reader.GetNullableString(1), + CreationDate = reader.GetDateTime(2), + LastChangeDate = reader.GetDateTime(3), - post.Counter1 = reader.GetNullableValue(4); - post.Counter2 = reader.GetNullableValue(5); - post.Counter3 = reader.GetNullableValue(6); - post.Counter4 = reader.GetNullableValue(7); - post.Counter5 = reader.GetNullableValue(8); - post.Counter6 = reader.GetNullableValue(9); - post.Counter7 = reader.GetNullableValue(10); - post.Counter8 = reader.GetNullableValue(11); - post.Counter9 = reader.GetNullableValue(12); + Counter1 = reader.IsDBNull(4) ? null : (int?)reader.GetInt32(4), + Counter2 = reader.IsDBNull(5) ? null : (int?)reader.GetInt32(5), + Counter3 = reader.IsDBNull(6) ? null : (int?)reader.GetInt32(6), + Counter4 = reader.IsDBNull(7) ? null : (int?)reader.GetInt32(7), + Counter5 = reader.IsDBNull(8) ? null : (int?)reader.GetInt32(8), + Counter6 = reader.IsDBNull(9) ? null : (int?)reader.GetInt32(9), + Counter7 = reader.IsDBNull(10) ? null : (int?)reader.GetInt32(10), + Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11), + Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12) + }; return post; } } diff --git a/Dapper.Tests.Performance/Benchmarks.Soma.cs b/Dapper.Tests.Performance/Benchmarks.Soma.cs deleted file mode 100644 index ae7db5c9b..000000000 --- a/Dapper.Tests.Performance/Benchmarks.Soma.cs +++ /dev/null @@ -1,23 +0,0 @@ -using BenchmarkDotNet.Attributes; - -namespace Dapper.Tests.Performance -{ - public class SomaBenchmarks : BenchmarkBase - { - private dynamic _sdb; - - [GlobalSetup] - public void Setup() - { - BaseSetup(); - _sdb = Simple.Data.Database.OpenConnection(ConnectionString); - } - - [Benchmark(Description = "FindById")] - public dynamic QueryDynamic() - { - Step(); - return _sdb.Posts.FindById(i).FirstOrDefault(); - } - } -} \ No newline at end of file diff --git a/Dapper.Tests.Performance/Benchmarks.cs b/Dapper.Tests.Performance/Benchmarks.cs index 9518318aa..b3575ca2f 100644 --- a/Dapper.Tests.Performance/Benchmarks.cs +++ b/Dapper.Tests.Performance/Benchmarks.cs @@ -1,24 +1,15 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Attributes.Columns; -using BenchmarkDotNet.Columns; -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Diagnosers; -using BenchmarkDotNet.Horology; -using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Order; -using Dapper.Tests.Performance.Helpers; using System; using System.Configuration; using System.Data.SqlClient; namespace Dapper.Tests.Performance { - [OrderProvider(SummaryOrderPolicy.FastestToSlowest)] - [RankColumn] - [Config(typeof(Config))] + [Orderer(SummaryOrderPolicy.FastestToSlowest)] + [BenchmarkCategory("ORM")] public abstract class BenchmarkBase { - public const int Iterations = 50; protected static readonly Random _rand = new Random(); protected SqlConnection _connection; public static string ConnectionString { get; } = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; @@ -37,22 +28,4 @@ protected void Step() if (i > 5000) i = 1; } } - - public class Config : ManualConfig - { - public Config() - { - Add(new MemoryDiagnoser()); - Add(new ORMColum()); - Add(new ReturnColum()); - Add(Job.Default - .WithUnrollFactor(BenchmarkBase.Iterations) - //.WithIterationTime(new TimeInterval(500, TimeUnit.Millisecond)) - .WithLaunchCount(1) - .WithWarmupCount(0) - .WithTargetCount(5) - .WithRemoveOutliers(true) - ); - } - } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/Config.cs b/Dapper.Tests.Performance/Config.cs new file mode 100644 index 000000000..6a4fc045b --- /dev/null +++ b/Dapper.Tests.Performance/Config.cs @@ -0,0 +1,46 @@ +using BenchmarkDotNet.Columns; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Diagnosers; +using BenchmarkDotNet.Exporters; +using BenchmarkDotNet.Exporters.Csv; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Loggers; +using BenchmarkDotNet.Order; +using Dapper.Tests.Performance.Helpers; + +namespace Dapper.Tests.Performance +{ + public class Config : ManualConfig + { + public const int Iterations = 5000; + + public Config() + { + Add(ConsoleLogger.Default); + + Add(CsvExporter.Default); + Add(MarkdownExporter.GitHub); + Add(HtmlExporter.Default); + + var md = new MemoryDiagnoser(); + Add(md); + Add(new ORMColum()); + Add(TargetMethodColumn.Method); + Add(new ReturnColum()); + Add(StatisticColumn.Mean); + Add(StatisticColumn.StdDev); + Add(StatisticColumn.Error); + Add(BaselineScaledColumn.Scaled); + Add(md.GetColumnProvider()); + + Add(Job.Dry + .WithLaunchCount(1) + .WithWarmupCount(1) + .WithInvocationCount(Iterations) + .WithIterationCount(10) + ); + Set(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest)); + SummaryPerType = false; + } + } +} diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 7299588b1..d5522fe9f 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -11,27 +11,24 @@ - - - + + - - - - + + + - - - - - - + + + + + - - + + diff --git a/Dapper.Tests.Performance/Helpers/ORMColum.cs b/Dapper.Tests.Performance/Helpers/ORMColum.cs index a2e610185..a56f1b6e3 100644 --- a/Dapper.Tests.Performance/Helpers/ORMColum.cs +++ b/Dapper.Tests.Performance/Helpers/ORMColum.cs @@ -10,9 +10,9 @@ public class ORMColum : IColumn public string ColumnName { get; } = "ORM"; public string Legend => "The object/relational mapper being tested"; - public bool IsDefault(Summary summary, Benchmark benchmark) => false; - public string GetValue(Summary summary, Benchmark benchmark) => benchmark.Target.Method.DeclaringType.Name.Replace("Benchmarks", string.Empty); - public string GetValue(Summary summary, Benchmark benchmark, ISummaryStyle style) => benchmark.Target.Method.DeclaringType.Name.Replace("Benchmarks", string.Empty); + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) => benchmarkCase.Descriptor.WorkloadMethod.DeclaringType.Name.Replace("Benchmarks", string.Empty); + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, ISummaryStyle style) => GetValue(summary, benchmarkCase); public bool IsAvailable(Summary summary) => true; public bool AlwaysShow => true; diff --git a/Dapper.Tests.Performance/Helpers/ReturnColum.cs b/Dapper.Tests.Performance/Helpers/ReturnColum.cs index 26105c4fe..47e13f09d 100644 --- a/Dapper.Tests.Performance/Helpers/ReturnColum.cs +++ b/Dapper.Tests.Performance/Helpers/ReturnColum.cs @@ -10,9 +10,14 @@ public class ReturnColum : IColumn public string ColumnName { get; } = "Return"; public string Legend => "The return type of the method"; - public bool IsDefault(Summary summary, Benchmark benchmark) => false; - public string GetValue(Summary summary, Benchmark benchmark) => benchmark.Target.Method.ReturnType.Name; - public string GetValue(Summary summary, Benchmark benchmark, ISummaryStyle style) => benchmark.Target.Method.ReturnType.Name; + public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + { + var type = benchmarkCase.Descriptor.WorkloadMethod.ReturnType; + return type == typeof(object) ? "dynamic" : type.Name; + } + + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, ISummaryStyle style) => GetValue(summary, benchmarkCase); public bool IsAvailable(Summary summary) => true; public bool AlwaysShow => true; diff --git a/Dapper.Tests.Performance/LegacyTests.cs b/Dapper.Tests.Performance/LegacyTests.cs index 190b5d9f1..a25aefac2 100644 --- a/Dapper.Tests.Performance/LegacyTests.cs +++ b/Dapper.Tests.Performance/LegacyTests.cs @@ -21,6 +21,7 @@ using System.Threading.Tasks; using Dapper.Tests.Performance.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; +using Belgrade.SqlClient; namespace Dapper.Tests.Performance { @@ -81,12 +82,16 @@ public async Task RunAsync(int iterations) } } + Console.WriteLine("|Time|Framework|"); foreach (var test in this.OrderBy(t => t.Watch.ElapsedMilliseconds)) { var ms = test.Watch.ElapsedMilliseconds.ToString(); + Console.Write("|"); Console.Write(ms); Program.WriteColor("ms ".PadRight(8 - ms.Length), ConsoleColor.DarkGray); - Console.WriteLine(test.Name); + Console.Write("|"); + Console.Write(test.Name); + Console.WriteLine("|"); } } } @@ -233,18 +238,11 @@ public async Task RunAsync(int iterations) tests.Add(id => nhSession5.Get(id), "NHibernate: Session.Get"); }, "NHibernate"); - // Simple.Data - Try(() => - { - var sdb = Simple.Data.Database.OpenConnection(ConnectionString); - tests.Add(id => sdb.Posts.FindById(id).FirstOrDefault(), "Simple.Data"); - }, "Simple.Data"); - // Belgrade Try(() => { var query = new Belgrade.SqlClient.SqlDb.QueryMapper(ConnectionString); - tests.AsyncAdd(id => query.ExecuteReader("SELECT TOP 1 * FROM Posts WHERE Id = " + id, + tests.AsyncAdd(id => query.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", id).Map( reader => { var post = new Post(); diff --git a/Dapper.Tests.Performance/Program.cs b/Dapper.Tests.Performance/Program.cs index f44d965b0..79032b602 100644 --- a/Dapper.Tests.Performance/Program.cs +++ b/Dapper.Tests.Performance/Program.cs @@ -1,9 +1,9 @@ -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Configs; +using BenchmarkDotNet.Order; +using BenchmarkDotNet.Running; using System; -using System.Collections.Generic; using System.Data.SqlClient; using System.Linq; -using System.Reflection; using static System.Console; namespace Dapper.Tests.Performance @@ -25,7 +25,7 @@ public static void Main(string[] args) if (args.Length == 0) { WriteLine("Optional arguments:"); - WriteColor(" --all", ConsoleColor.Blue); + WriteColor(" (no args)", ConsoleColor.Blue); WriteLine(": run all benchmarks"); WriteColor(" --legacy", ConsoleColor.Blue); WriteLine(": run the legacy benchmark suite/format", ConsoleColor.Gray); @@ -35,19 +35,7 @@ public static void Main(string[] args) EnsureDBSetup(); WriteLine("Database setup complete."); - if (args.Any(a => a == "--all")) - { - WriteLine("Iterations: " + BenchmarkBase.Iterations); - var benchmarks = new List(); - var benchTypes = Assembly.GetEntryAssembly().DefinedTypes.Where(t => t.IsSubclassOf(typeof(BenchmarkBase))); - WriteLineColor("Running full benchmarks suite", ConsoleColor.Green); - foreach (var b in benchTypes) - { - benchmarks.AddRange(BenchmarkConverter.TypeToBenchmarks(b)); - } - BenchmarkRunner.Run(benchmarks.ToArray(), null); - } - else if (args.Any(a => a == "--legacy")) + if (args.Any(a => a == "--legacy")) { var test = new LegacyTests(); const int iterations = 500; @@ -58,8 +46,8 @@ public static void Main(string[] args) } else { - WriteLine("Iterations: " + BenchmarkBase.Iterations); - BenchmarkSwitcher.FromAssembly(typeof(Program).GetTypeInfo().Assembly).Run(args); + WriteLine("Iterations: " + Config.Iterations); + new BenchmarkSwitcher(typeof(BenchmarkBase).Assembly).Run(args, new Config()); } } diff --git a/Readme.md b/Readme.md index 29df19cc1..65b9baecf 100644 --- a/Readme.md +++ b/Readme.md @@ -112,49 +112,55 @@ This works for any parameter that implements IEnumerable for some T. Performance ----------- -A key feature of Dapper is performance. The following metrics show how long it takes to execute 500 `SELECT` statements against a DB and map the data returned to objects. - -The performance tests are broken in to 3 lists: - -- POCO serialization for frameworks that support pulling static typed objects from the DB. Using raw SQL. -- Dynamic serialization for frameworks that support returning dynamic lists of objects. -- Typical framework usage. Often typical framework usage differs from the optimal usage performance wise. Often it will not involve writing SQL. - -### Performance of SELECT mapping over 500 iterations - POCO serialization - -| Method | Duration | Remarks | -| --------------------------------------------------- | -------- | ------- | -| Hand coded (using a `SqlDataReader`) | 47ms | -| Dapper `ExecuteMapperQuery` | 49ms | -| [ServiceStack.OrmLite](https://github.com/ServiceStack/ServiceStack.OrmLite) (QueryById) | 50ms | -| [PetaPoco](https://github.com/CollaboratingPlatypus/PetaPoco) | 52ms | [Can be faster](https://web.archive.org/web/20170921124755/http://www.toptensoftware.com/blog/posts/94-PetaPoco-More-Speed) | -| BLToolkit | 80ms | -| SubSonic CodingHorror | 107ms | -| NHibernate SQL | 104ms | -| Linq 2 SQL `ExecuteQuery` | 181ms | -| Entity framework `ExecuteStoreQuery` | 631ms | - -### Performance of SELECT mapping over 500 iterations - dynamic serialization - -| Method | Duration | Remarks | -| -------------------------------------------------------- | -------- | ------- | -| Dapper `ExecuteMapperQuery` (dynamic) | 48ms | -| [Massive](https://github.com/FransBouma/Massive) | 52ms | -| [Simple.Data](https://github.com/markrendle/Simple.Data) | 95ms | - - -### Performance of SELECT mapping over 500 iterations - typical usage - -| Method | Duration | Remarks | -| ------------------------------------- | -------- | ------- | -| Linq 2 SQL CompiledQuery | 81ms | Not super typical involves complex code | -| NHibernate HQL | 118ms | -| Linq 2 SQL | 559ms | -| Entity framework | 859ms | -| SubSonic ActiveRecord.SingleOrDefault | 3619ms | +A key feature of Dapper is performance. The following metrics show how long it takes to execute a `SELECT` statement against a DB (in various config, each labeled) and map the data returned to objects. +The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests.Performance) (contributions welcome!) and can be run once compiled via: +``` +Dapper.Tests.Performance.exe -f * --join +``` +Output from the latest run is: +``` ini +BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.254 (1803/April2018Update/Redstone4) +Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores +Frequency=2742188 Hz, Resolution=364.6723 ns, Timer=TSC + [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 + Dry : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 -Performance benchmarks are available [here](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests.Performance). +``` +| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Allocated | +|------------- |------------------------------ |-------- |------------:|-----------:|-----------:|--------:|-------:|----------:| +| Belgrade | ExecuteReader | Post | 93.20 us | 17.628 us | 26.652 us | 3.6000 | 1.0000 | 11.28 KB | +| PetaPoco | 'Fetch<Post> (Fast)' | Post | 95.47 us | 2.608 us | 3.943 us | 4.4000 | - | 13.65 KB | +| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 99.27 us | 6.661 us | 10.070 us | 4.2000 | - | 13.51 KB | +| Dapper | 'Query<T> (buffered)' | Post | 99.37 us | 6.892 us | 10.420 us | 4.4000 | - | 13.79 KB | +| Massive | 'Query (dynamic)' | dynamic | 100.11 us | 2.543 us | 3.845 us | 4.6000 | - | 14.21 KB | +| Dapper | 'Query<dynamic> (buffered)' | dynamic | 100.30 us | 4.362 us | 6.595 us | 4.4000 | - | 13.88 KB | +| HandCoded | SqlCommand | Post | 102.95 us | 1.909 us | 2.886 us | 3.8000 | - | 12.24 KB | +| HandCoded | DataTable | dynamic | 105.04 us | 4.730 us | 7.151 us | 2.2000 | 0.6000 | 12.45 KB | +| Susanoo | 'Mapping Static (dynamic)' | dynamic | 105.10 us | 8.457 us | 12.786 us | 4.8000 | - | 14.97 KB | +| Dapper | 'Contrib Get<T>' | Post | 107.35 us | 9.207 us | 13.920 us | 4.6000 | - | 14.45 KB | +| Susanoo | 'Mapping Static' | Post | 111.39 us | 7.716 us | 11.666 us | 4.8000 | - | 14.99 KB | +| Dapper | QueryFirstOrDefault<T> | Post | 112.32 us | 5.053 us | 7.639 us | 4.2000 | - | 13.47 KB | +| PetaPoco | Fetch<Post> | Post | 114.62 us | 3.273 us | 4.948 us | 4.6000 | - | 14.59 KB | +| Susanoo | 'Mapping Cache (dynamic)' | dynamic | 124.43 us | 3.182 us | 4.811 us | 6.6000 | - | 20.41 KB | +| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 124.43 us | 4.195 us | 6.342 us | 4.4000 | - | 13.87 KB | +| Linq2Sql | Compiled | Post | 125.92 us | 6.187 us | 9.354 us | 3.0000 | - | 9.82 KB | +| Susanoo | 'Mapping Cache' | Post | 128.99 us | 10.511 us | 15.891 us | 6.8000 | - | 20.9 KB | +| ServiceStack | SingleById | Post | 130.70 us | 5.525 us | 8.354 us | 5.6000 | - | 17.53 KB | +| Dapper | 'Query<T> (unbuffered)' | Post | 146.41 us | 12.281 us | 18.568 us | 4.4000 | - | 13.84 KB | +| EF6 | SqlQuery | Post | 197.36 us | 139.733 us | 211.257 us | 9.0000 | - | 27.86 KB | +| NHibernate | Get<T> | Post | 201.49 us | 7.650 us | 11.565 us | 10.4000 | - | 32.5 KB | +| NHibernate | HQL | Post | 231.44 us | 31.127 us | 47.060 us | 11.2000 | - | 35 KB | +| EFCore | Normal | Post | 244.87 us | 69.894 us | 105.670 us | 6.4000 | - | 20.25 KB | +| EFCore | 'No Tracking' | Post | 253.52 us | 61.048 us | 92.296 us | 6.8000 | - | 21.36 KB | +| Linq2Sql | ExecuteQuery | Post | 264.58 us | 4.516 us | 6.828 us | 13.6000 | - | 42.34 KB | +| EFCore | SqlQuery | Post | 273.44 us | 71.265 us | 107.742 us | 6.6000 | - | 20.75 KB | +| NHibernate | Criteria | Post | 274.41 us | 11.087 us | 16.762 us | 21.2000 | - | 65.37 KB | +| EF6 | Normal | Post | 317.09 us | 155.828 us | 235.589 us | 15.6000 | - | 48.29 KB | +| EF6 | 'No Tracking' | Post | 343.42 us | 150.279 us | 227.201 us | 17.8000 | - | 55.1 KB | +| NHibernate | SQL | Post | 355.03 us | 17.416 us | 26.330 us | 32.8000 | - | 101.06 KB | +| Linq2Sql | Normal | Post | 388.27 us | 260.226 us | 393.424 us | 4.6000 | 1.4000 | 14.68 KB | +| NHibernate | LINQ | Post | 1,156.97 us | 35.166 us | 53.167 us | 20.2000 | - | 62.13 KB | Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (Ctrl+F5). From 0b867bbfa2ee641cc58ebe5a6c77715711e75b7e Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 10 Sep 2018 20:37:19 -0400 Subject: [PATCH 038/312] Add EF compiled query to benchmarks --- .../Benchmarks.EntityFrameworkCore.cs | 22 +++--- Readme.md | 67 ++++++++++--------- 2 files changed, 48 insertions(+), 41 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs b/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs index 1425493c3..ef9a5a596 100644 --- a/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs +++ b/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs @@ -1,23 +1,22 @@ -using BenchmarkDotNet.Attributes; -using Dapper.Tests.Performance.Linq2Sql; +using BenchmarkDotNet.Attributes; +using Dapper.Tests.Performance.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using System; -using System.Data.Linq; using System.Linq; namespace Dapper.Tests.Performance { public class EFCoreBenchmarks : BenchmarkBase { - private EntityFrameworkCore.EFCoreContext Context; - private static readonly Func compiledQuery = - CompiledQuery.Compile((DataClassesDataContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); + private EFCoreContext Context; + private static readonly Func compiledQuery = + EF.CompileQuery((EFCoreContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); [GlobalSetup] public void Setup() { BaseSetup(); - Context = new EntityFrameworkCore.EFCoreContext(_connection.ConnectionString); + Context = new EFCoreContext(_connection.ConnectionString); } [Benchmark(Description = "Normal")] @@ -27,6 +26,13 @@ public Post Normal() return Context.Posts.First(p => p.Id == i); } + [Benchmark(Description = "Compiled")] + public Post Compiled() + { + Step(); + return compiledQuery(Context, i); + } + [Benchmark(Description = "SqlQuery")] public Post SqlQuery() { @@ -41,4 +47,4 @@ public Post NoTracking() return Context.Posts.AsNoTracking().First(p => p.Id == i); } } -} \ No newline at end of file +} diff --git a/Readme.md b/Readme.md index 65b9baecf..83feea30e 100644 --- a/Readme.md +++ b/Readme.md @@ -125,42 +125,43 @@ Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical co Frequency=2742188 Hz, Resolution=364.6723 ns, Timer=TSC [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 Dry : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 - ``` | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Allocated | |------------- |------------------------------ |-------- |------------:|-----------:|-----------:|--------:|-------:|----------:| -| Belgrade | ExecuteReader | Post | 93.20 us | 17.628 us | 26.652 us | 3.6000 | 1.0000 | 11.28 KB | -| PetaPoco | 'Fetch<Post> (Fast)' | Post | 95.47 us | 2.608 us | 3.943 us | 4.4000 | - | 13.65 KB | -| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 99.27 us | 6.661 us | 10.070 us | 4.2000 | - | 13.51 KB | -| Dapper | 'Query<T> (buffered)' | Post | 99.37 us | 6.892 us | 10.420 us | 4.4000 | - | 13.79 KB | -| Massive | 'Query (dynamic)' | dynamic | 100.11 us | 2.543 us | 3.845 us | 4.6000 | - | 14.21 KB | -| Dapper | 'Query<dynamic> (buffered)' | dynamic | 100.30 us | 4.362 us | 6.595 us | 4.4000 | - | 13.88 KB | -| HandCoded | SqlCommand | Post | 102.95 us | 1.909 us | 2.886 us | 3.8000 | - | 12.24 KB | -| HandCoded | DataTable | dynamic | 105.04 us | 4.730 us | 7.151 us | 2.2000 | 0.6000 | 12.45 KB | -| Susanoo | 'Mapping Static (dynamic)' | dynamic | 105.10 us | 8.457 us | 12.786 us | 4.8000 | - | 14.97 KB | -| Dapper | 'Contrib Get<T>' | Post | 107.35 us | 9.207 us | 13.920 us | 4.6000 | - | 14.45 KB | -| Susanoo | 'Mapping Static' | Post | 111.39 us | 7.716 us | 11.666 us | 4.8000 | - | 14.99 KB | -| Dapper | QueryFirstOrDefault<T> | Post | 112.32 us | 5.053 us | 7.639 us | 4.2000 | - | 13.47 KB | -| PetaPoco | Fetch<Post> | Post | 114.62 us | 3.273 us | 4.948 us | 4.6000 | - | 14.59 KB | -| Susanoo | 'Mapping Cache (dynamic)' | dynamic | 124.43 us | 3.182 us | 4.811 us | 6.6000 | - | 20.41 KB | -| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 124.43 us | 4.195 us | 6.342 us | 4.4000 | - | 13.87 KB | -| Linq2Sql | Compiled | Post | 125.92 us | 6.187 us | 9.354 us | 3.0000 | - | 9.82 KB | -| Susanoo | 'Mapping Cache' | Post | 128.99 us | 10.511 us | 15.891 us | 6.8000 | - | 20.9 KB | -| ServiceStack | SingleById | Post | 130.70 us | 5.525 us | 8.354 us | 5.6000 | - | 17.53 KB | -| Dapper | 'Query<T> (unbuffered)' | Post | 146.41 us | 12.281 us | 18.568 us | 4.4000 | - | 13.84 KB | -| EF6 | SqlQuery | Post | 197.36 us | 139.733 us | 211.257 us | 9.0000 | - | 27.86 KB | -| NHibernate | Get<T> | Post | 201.49 us | 7.650 us | 11.565 us | 10.4000 | - | 32.5 KB | -| NHibernate | HQL | Post | 231.44 us | 31.127 us | 47.060 us | 11.2000 | - | 35 KB | -| EFCore | Normal | Post | 244.87 us | 69.894 us | 105.670 us | 6.4000 | - | 20.25 KB | -| EFCore | 'No Tracking' | Post | 253.52 us | 61.048 us | 92.296 us | 6.8000 | - | 21.36 KB | -| Linq2Sql | ExecuteQuery | Post | 264.58 us | 4.516 us | 6.828 us | 13.6000 | - | 42.34 KB | -| EFCore | SqlQuery | Post | 273.44 us | 71.265 us | 107.742 us | 6.6000 | - | 20.75 KB | -| NHibernate | Criteria | Post | 274.41 us | 11.087 us | 16.762 us | 21.2000 | - | 65.37 KB | -| EF6 | Normal | Post | 317.09 us | 155.828 us | 235.589 us | 15.6000 | - | 48.29 KB | -| EF6 | 'No Tracking' | Post | 343.42 us | 150.279 us | 227.201 us | 17.8000 | - | 55.1 KB | -| NHibernate | SQL | Post | 355.03 us | 17.416 us | 26.330 us | 32.8000 | - | 101.06 KB | -| Linq2Sql | Normal | Post | 388.27 us | 260.226 us | 393.424 us | 4.6000 | 1.4000 | 14.68 KB | -| NHibernate | LINQ | Post | 1,156.97 us | 35.166 us | 53.167 us | 20.2000 | - | 62.13 KB | +| HandCoded | SqlCommand | Post | 86.76 us | 1.573 us | 2.379 us | 3.8000 | - | 12.24 KB | +| PetaPoco | 'Fetch<Post> (Fast)' | Post | 95.43 us | 2.434 us | 3.681 us | 4.4000 | - | 13.65 KB | +| HandCoded | DataTable | dynamic | 98.18 us | 5.242 us | 7.925 us | 2.2000 | 0.6000 | 12.45 KB | +| PetaPoco | Fetch<Post> | Post | 98.18 us | 2.252 us | 3.404 us | 4.6000 | - | 14.59 KB | +| Massive | 'Query (dynamic)' | dynamic | 98.47 us | 3.102 us | 4.690 us | 4.6000 | - | 14.21 KB | +| Belgrade | ExecuteReader | Post | 99.14 us | 15.731 us | 23.783 us | 3.6000 | 1.0000 | 11.37 KB | +| Dapper | 'Query<dynamic> (buffered)' | dynamic | 100.42 us | 5.311 us | 8.030 us | 4.4000 | - | 13.88 KB | +| Dapper | 'Contrib Get<T>' | Post | 102.55 us | 9.289 us | 14.044 us | 4.6000 | - | 14.45 KB | +| Dapper | 'Query<T> (buffered)' | Post | 104.85 us | 10.278 us | 15.539 us | 4.4000 | - | 13.79 KB | +| ServiceStack | SingleById | Post | 107.83 us | 5.144 us | 7.778 us | 5.6000 | - | 17.53 KB | +| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 109.78 us | 7.689 us | 11.624 us | 4.2000 | - | 13.51 KB | +| Dapper | QueryFirstOrDefault<T> | Post | 112.97 us | 4.081 us | 6.170 us | 4.2000 | - | 13.47 KB | +| Susanoo | 'Mapping Static' | Post | 118.91 us | 8.786 us | 13.282 us | 4.8000 | - | 14.99 KB | +| Susanoo | 'Mapping Static (dynamic)' | dynamic | 119.57 us | 6.985 us | 10.560 us | 4.8000 | - | 14.97 KB | +| Susanoo | 'Mapping Cache' | Post | 123.68 us | 6.693 us | 10.118 us | 6.8000 | - | 20.9 KB | +| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 127.06 us | 8.748 us | 13.225 us | 4.4000 | - | 13.87 KB | +| Dapper | 'Query<T> (unbuffered)' | Post | 132.28 us | 9.350 us | 14.136 us | 4.4000 | - | 13.84 KB | +| Susanoo | 'Mapping Cache (dynamic)' | dynamic | 133.45 us | 4.179 us | 6.318 us | 6.6000 | - | 20.41 KB | +| Linq2Sql | Compiled | Post | 133.46 us | 6.137 us | 9.278 us | 3.0000 | - | 9.82 KB | +| EFCore | Compiled | Post | 180.70 us | 65.498 us | 99.024 us | 5.2000 | - | 16.08 KB | +| EF6 | SqlQuery | Post | 187.09 us | 136.314 us | 206.088 us | 9.0000 | - | 27.86 KB | +| NHibernate | Get<T> | Post | 202.34 us | 10.271 us | 15.528 us | 10.4000 | - | 32.5 KB | +| NHibernate | HQL | Post | 216.95 us | 26.889 us | 40.653 us | 11.2000 | - | 35 KB | +| EFCore | 'No Tracking' | Post | 243.18 us | 59.359 us | 89.742 us | 6.8000 | - | 21.36 KB | +| EFCore | Normal | Post | 245.60 us | 72.618 us | 109.788 us | 6.4000 | - | 20.25 KB | +| Linq2Sql | ExecuteQuery | Post | 246.66 us | 6.215 us | 9.396 us | 13.6000 | - | 42.34 KB | +| NHibernate | Criteria | Post | 252.51 us | 18.303 us | 27.672 us | 21.2000 | - | 65.37 KB | +| EFCore | SqlQuery | Post | 263.34 us | 73.919 us | 111.755 us | 6.6000 | - | 20.75 KB | +| EF6 | Normal | Post | 308.46 us | 154.203 us | 233.133 us | 15.6000 | - | 48.29 KB | +| NHibernate | SQL | Post | 322.13 us | 9.530 us | 14.408 us | 32.8000 | - | 101.06 KB | +| EF6 | 'No Tracking' | Post | 324.12 us | 134.870 us | 203.905 us | 17.8000 | - | 55.1 KB | +| Linq2Sql | Normal | Post | 370.55 us | 238.033 us | 359.871 us | 4.6000 | 1.4000 | 14.68 KB | +| NHibernate | LINQ | Post | 1,110.87 us | 36.863 us | 55.731 us | 20.2000 | - | 62.13 KB | + Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (Ctrl+F5). From 9313e9f1f897d764645e4a2e31f3e2c34f1f9282 Mon Sep 17 00:00:00 2001 From: ifle <1140474+ifle@users.noreply.github.com> Date: Wed, 12 Sep 2018 00:39:12 +0300 Subject: [PATCH 039/312] Added Linq2DB benchmarks (#1125) Added Linq2DB benchmarks --- .../Benchmarks.Linq2DB.cs | 46 +++++++++++++++++++ .../Dapper.Tests.Performance.csproj | 3 +- .../Linq2DB/ConnectionStringSettings.cs | 12 +++++ .../Linq2DB/Linq2DBContext.cs | 10 ++++ .../Linq2DB/Linq2DbSettings.cs | 34 ++++++++++++++ Dapper.Tests.Performance/Post.cs | 2 + 6 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 Dapper.Tests.Performance/Benchmarks.Linq2DB.cs create mode 100644 Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs create mode 100644 Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs create mode 100644 Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs b/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs new file mode 100644 index 000000000..9a7e2bc1a --- /dev/null +++ b/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs @@ -0,0 +1,46 @@ +using BenchmarkDotNet.Attributes; + +using System; +using System.Linq; +using Dapper.Tests.Performance.Linq2Db; +using LinqToDB; +using LinqToDB.Data; + +namespace Dapper.Tests.Performance +{ + public class Liq2DbBenchmarks : BenchmarkBase + { + private Linq2DBContext _dbContext; + + private static Func compiledQuery = CompiledQuery.Compile((Linq2DBContext db, int id) => + db.Posts.First(c => c.Id == id)); + [GlobalSetup] + public void Setup() + { + BaseSetup(); + DataConnection.DefaultSettings = new Linq2DBSettings(_connection.ConnectionString); + _dbContext = new Linq2DBContext(); + } + + [Benchmark(Description = "Normal")] + public Post Normal() + { + Step(); + return _dbContext.Posts.First(p => p.Id == i); + } + + [Benchmark(Description = "Compiled")] + public Post Compiled() + { + Step(); + return compiledQuery(_dbContext, i); + } + + [Benchmark(Description = "SqlQuery")] + public Post SqlQuery() + { + Step(); + return _dbContext.Query("select * from Posts where Id = @id", new { id = i }).First(); + } + } +} diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index d5522fe9f..83e1a3d7e 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -16,6 +16,7 @@ + @@ -40,7 +41,7 @@ - + diff --git a/Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs b/Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs new file mode 100644 index 000000000..cf2ebc062 --- /dev/null +++ b/Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs @@ -0,0 +1,12 @@ +using LinqToDB.Configuration; + +namespace Dapper.Tests.Performance.Linq2Db +{ + public class ConnectionStringSettings : IConnectionStringSettings + { + public string ConnectionString { get; set; } + public string Name { get; set; } + public string ProviderName { get; set; } + public bool IsGlobal => false; + } +} \ No newline at end of file diff --git a/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs b/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs new file mode 100644 index 000000000..0922cd72c --- /dev/null +++ b/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs @@ -0,0 +1,10 @@ +using LinqToDB; +using Microsoft.EntityFrameworkCore; + +namespace Dapper.Tests.Performance.Linq2Db +{ + public class Linq2DBContext : LinqToDB.Data.DataConnection + { + public ITable Posts => GetTable(); + } +} diff --git a/Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs b/Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs new file mode 100644 index 000000000..4c8103b76 --- /dev/null +++ b/Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using System.Linq; +using LinqToDB.Configuration; + +namespace Dapper.Tests.Performance.Linq2Db +{ + public class Linq2DBSettings : ILinqToDBSettings + { + private readonly string _connectionString; + public IEnumerable DataProviders => Enumerable.Empty(); + + public string DefaultConfiguration => "SqlServer"; + public string DefaultDataProvider => "SqlServer"; + + public Linq2DBSettings(string connectionString) + { + _connectionString = connectionString; + } + + public IEnumerable ConnectionStrings + { + get + { + yield return + new ConnectionStringSettings + { + Name = "SqlServer", + ProviderName = "SqlServer", + ConnectionString = _connectionString + }; + } + } + } +} diff --git a/Dapper.Tests.Performance/Post.cs b/Dapper.Tests.Performance/Post.cs index 78c71ccd7..c3fd3b3a7 100644 --- a/Dapper.Tests.Performance/Post.cs +++ b/Dapper.Tests.Performance/Post.cs @@ -5,9 +5,11 @@ namespace Dapper.Tests.Performance { [ServiceStack.DataAnnotations.Alias("Posts")] [Table(Name = "Posts")] + [LinqToDB.Mapping.Table(Name = "Posts")] public class Post { [Id(IdKind.Identity)] + [LinqToDB.Mapping.PrimaryKey, LinqToDB.Mapping.Identity] public int Id { get; set; } public string Text { get; set; } public DateTime CreationDate { get; set; } From 5256f2ea0b19826cbe173a9c7ccc02ae8ba0cdf4 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 11 Sep 2018 17:41:58 -0400 Subject: [PATCH 040/312] Benchmarks: Linq2DB tidy --- Dapper.Tests.Performance/Benchmarks.Linq2DB.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs b/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs index 9a7e2bc1a..5f6b1db58 100644 --- a/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs +++ b/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs @@ -8,12 +8,12 @@ namespace Dapper.Tests.Performance { - public class Liq2DbBenchmarks : BenchmarkBase + public class Linq2DBBenchmarks : BenchmarkBase { private Linq2DBContext _dbContext; - - private static Func compiledQuery = CompiledQuery.Compile((Linq2DBContext db, int id) => - db.Posts.First(c => c.Id == id)); + + private static readonly Func compiledQuery = CompiledQuery.Compile((Linq2DBContext db, int id) => db.Posts.First(c => c.Id == id)); + [GlobalSetup] public void Setup() { From b262cb33dd50102ae5e58f9fc487558771f77212 Mon Sep 17 00:00:00 2001 From: markjerz Date: Tue, 11 Sep 2018 22:44:34 +0100 Subject: [PATCH 041/312] Added Dashing Benchmarks (#1126) Adds Dashing benchmarks to Dapper.Tests.Performance --- .../Benchmarks.Dashing.cs | 27 +++++++++++++++++++ .../Dapper.Tests.Performance.csproj | 9 ++++++- .../Dashing/DashingConfiguration.cs | 12 +++++++++ Dapper.Tests.Performance/Dashing/Post.cs | 21 +++++++++++++++ Dapper.Tests.Performance/LegacyTests.cs | 11 ++++++++ 5 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 Dapper.Tests.Performance/Benchmarks.Dashing.cs create mode 100644 Dapper.Tests.Performance/Dashing/DashingConfiguration.cs create mode 100644 Dapper.Tests.Performance/Dashing/Post.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Dashing.cs b/Dapper.Tests.Performance/Benchmarks.Dashing.cs new file mode 100644 index 000000000..1c9be9d14 --- /dev/null +++ b/Dapper.Tests.Performance/Benchmarks.Dashing.cs @@ -0,0 +1,27 @@ +using BenchmarkDotNet.Attributes; +using Dapper.Tests.Performance.Dashing; +using Dashing; + +namespace Dapper.Tests.Performance +{ + public class DashingBenchmarks : BenchmarkBase + { + private ISession session; + + [GlobalSetup] + public void Setup() + { + BaseSetup(); + var configuration = new DashingConfiguration(); + var database = new SqlDatabase(configuration, ConnectionString); + this.session = database.BeginTransactionLessSession(_connection); + } + + [Benchmark(Description = "Get By Id")] + public Dashing.Post QueryBuffered() + { + Step(); + return session.Get(i); + } + } +} diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 83e1a3d7e..80ba2b12a 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -1,4 +1,5 @@ - + + Dapper.Tests.Performance Dapper.Tests.Performance @@ -11,6 +12,8 @@ + + @@ -45,4 +48,8 @@ + + + -p "$(MSBuildThisFileDirectory)$(OutputPath)$(AssemblyName).exe" -t "Dapper.Tests.Performance.Dashing.DashingConfiguration" + diff --git a/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs b/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs new file mode 100644 index 000000000..29bc63242 --- /dev/null +++ b/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs @@ -0,0 +1,12 @@ +using Dashing.Configuration; + +namespace Dapper.Tests.Performance.Dashing +{ + public class DashingConfiguration : BaseConfiguration + { + public DashingConfiguration() + { + this.Add(); + } + } +} \ No newline at end of file diff --git a/Dapper.Tests.Performance/Dashing/Post.cs b/Dapper.Tests.Performance/Dashing/Post.cs new file mode 100644 index 000000000..e6a253ea5 --- /dev/null +++ b/Dapper.Tests.Performance/Dashing/Post.cs @@ -0,0 +1,21 @@ +using System; + +namespace Dapper.Tests.Performance.Dashing +{ + public class Post + { + public int Id { get; set; } + public string Text { get; set; } + public DateTime CreationDate { get; set; } + public DateTime LastChangeDate { get; set; } + public int? Counter1 { get; set; } + public int? Counter2 { get; set; } + public int? Counter3 { get; set; } + public int? Counter4 { get; set; } + public int? Counter5 { get; set; } + public int? Counter6 { get; set; } + public int? Counter7 { get; set; } + public int? Counter8 { get; set; } + public int? Counter9 { get; set; } + } +} diff --git a/Dapper.Tests.Performance/LegacyTests.cs b/Dapper.Tests.Performance/LegacyTests.cs index a25aefac2..5ca323ea4 100644 --- a/Dapper.Tests.Performance/LegacyTests.cs +++ b/Dapper.Tests.Performance/LegacyTests.cs @@ -19,7 +19,9 @@ using Susanoo; using System.Configuration; using System.Threading.Tasks; +using Dapper.Tests.Performance.Dashing; using Dapper.Tests.Performance.EntityFrameworkCore; +using Dashing; using Microsoft.EntityFrameworkCore; using Belgrade.SqlClient; @@ -186,6 +188,15 @@ public async Task RunAsync(int iterations) tests.Add(id => mapperConnection3.Get(id), "Dapper.Contrib"); }, "Dapper"); + // Dashing + Try(() => + { + var config = new DashingConfiguration(); + var database = new SqlDatabase(config, ConnectionString); + var session = database.BeginTransactionLessSession(GetOpenConnection()); + tests.Add(id => session.Get(id), "Dashing Get"); + }, "Dashing"); + // Massive Try(() => { From 0ec089b7d2373d744a0c17c9a78a86179ab6432b Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 11 Sep 2018 18:04:35 -0400 Subject: [PATCH 042/312] Benchmarks: normalize names and give ORMs better names via [Description] --- Dapper.Tests.Performance/Benchmarks.Belgrade.cs | 3 ++- Dapper.Tests.Performance/Benchmarks.Dapper.cs | 2 ++ Dapper.Tests.Performance/Benchmarks.Dashing.cs | 14 ++++++++------ .../Benchmarks.EntityFramework.cs | 12 +++++++----- .../Benchmarks.EntityFrameworkCore.cs | 11 +++++++---- Dapper.Tests.Performance/Benchmarks.HandCoded.cs | 5 +++-- Dapper.Tests.Performance/Benchmarks.Linq2DB.cs | 12 +++++++----- Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs | 13 ++++++++----- Dapper.Tests.Performance/Benchmarks.Massive.cs | 6 ++++-- Dapper.Tests.Performance/Benchmarks.PetaPoco.cs | 10 ++++++---- .../Benchmarks.ServiceStack.cs | 8 +++++--- Dapper.Tests.Performance/Benchmarks.Susanoo.cs | 16 ++++++++++------ Dapper.Tests.Performance/Helpers/ORMColum.cs | 11 +++++++++-- 13 files changed, 78 insertions(+), 45 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.Belgrade.cs b/Dapper.Tests.Performance/Benchmarks.Belgrade.cs index 5b9e3904a..db2272a8a 100644 --- a/Dapper.Tests.Performance/Benchmarks.Belgrade.cs +++ b/Dapper.Tests.Performance/Benchmarks.Belgrade.cs @@ -1,10 +1,11 @@ using BenchmarkDotNet.Attributes; using Belgrade.SqlClient.SqlDb; -using System.Threading.Tasks; using Belgrade.SqlClient; +using System.ComponentModel; namespace Dapper.Tests.Performance { + [Description("Belgrade")] public class BelgradeBenchmarks : BenchmarkBase { private QueryMapper _mapper; diff --git a/Dapper.Tests.Performance/Benchmarks.Dapper.cs b/Dapper.Tests.Performance/Benchmarks.Dapper.cs index a8593b931..40f3eb8d7 100644 --- a/Dapper.Tests.Performance/Benchmarks.Dapper.cs +++ b/Dapper.Tests.Performance/Benchmarks.Dapper.cs @@ -1,9 +1,11 @@ using BenchmarkDotNet.Attributes; using Dapper.Contrib.Extensions; +using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { + [Description("Dapper")] public class DapperBenchmarks : BenchmarkBase { [GlobalSetup] diff --git a/Dapper.Tests.Performance/Benchmarks.Dashing.cs b/Dapper.Tests.Performance/Benchmarks.Dashing.cs index 1c9be9d14..15913377d 100644 --- a/Dapper.Tests.Performance/Benchmarks.Dashing.cs +++ b/Dapper.Tests.Performance/Benchmarks.Dashing.cs @@ -1,12 +1,14 @@ -using BenchmarkDotNet.Attributes; +using System.ComponentModel; +using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.Dashing; using Dashing; namespace Dapper.Tests.Performance { + [Description("Dashing")] public class DashingBenchmarks : BenchmarkBase { - private ISession session; + private ISession Session; [GlobalSetup] public void Setup() @@ -14,14 +16,14 @@ public void Setup() BaseSetup(); var configuration = new DashingConfiguration(); var database = new SqlDatabase(configuration, ConnectionString); - this.session = database.BeginTransactionLessSession(_connection); + Session = database.BeginTransactionLessSession(_connection); } - [Benchmark(Description = "Get By Id")] - public Dashing.Post QueryBuffered() + [Benchmark(Description = "Get")] + public Dashing.Post Get() { Step(); - return session.Get(i); + return Session.Get(i); } } } diff --git a/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs b/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs index f145a24d1..6660782d3 100644 --- a/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs +++ b/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs @@ -1,8 +1,10 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; +using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { + [Description("EF 6")] public class EF6Benchmarks : BenchmarkBase { private EntityFramework.EFContext Context; @@ -14,8 +16,8 @@ public void Setup() Context = new EntityFramework.EFContext(_connection); } - [Benchmark(Description = "Normal")] - public Post Normal() + [Benchmark(Description = "First")] + public Post First() { Step(); return Context.Posts.First(p => p.Id == i); @@ -28,11 +30,11 @@ public Post SqlQuery() return Context.Database.SqlQuery("select * from Posts where Id = {0}", i).First(); } - [Benchmark(Description = "No Tracking")] + [Benchmark(Description = "First (No Tracking)")] public Post NoTracking() { Step(); return Context.Posts.AsNoTracking().First(p => p.Id == i); } } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs b/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs index ef9a5a596..bd79a8d79 100644 --- a/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs +++ b/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs @@ -2,13 +2,16 @@ using Dapper.Tests.Performance.EntityFrameworkCore; using Microsoft.EntityFrameworkCore; using System; +using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { + [Description("EF Core")] public class EFCoreBenchmarks : BenchmarkBase { private EFCoreContext Context; + private static readonly Func compiledQuery = EF.CompileQuery((EFCoreContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); @@ -19,14 +22,14 @@ public void Setup() Context = new EFCoreContext(_connection.ConnectionString); } - [Benchmark(Description = "Normal")] - public Post Normal() + [Benchmark(Description = "First")] + public Post First() { Step(); return Context.Posts.First(p => p.Id == i); } - [Benchmark(Description = "Compiled")] + [Benchmark(Description = "First (Compiled)")] public Post Compiled() { Step(); @@ -40,7 +43,7 @@ public Post SqlQuery() return Context.Posts.FromSql("select * from Posts where Id = {0}", i).First(); } - [Benchmark(Description = "No Tracking")] + [Benchmark(Description = "First (No Tracking)")] public Post NoTracking() { Step(); diff --git a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs index 17fd1057b..86ddb4d05 100644 --- a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs +++ b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs @@ -1,10 +1,12 @@ using BenchmarkDotNet.Attributes; using System; +using System.ComponentModel; using System.Data; using System.Data.SqlClient; namespace Dapper.Tests.Performance { + [Description("Hand Coded")] public class HandCodedBenchmarks : BenchmarkBase { private SqlCommand _postCommand; @@ -56,7 +58,7 @@ public Post SqlCommand() using (var reader = _postCommand.ExecuteReader()) { reader.Read(); - var post = new Post + return new Post { Id = reader.GetInt32(0), Text = reader.GetNullableString(1), @@ -73,7 +75,6 @@ public Post SqlCommand() Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11), Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12) }; - return post; } } diff --git a/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs b/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs index 5f6b1db58..ef2610183 100644 --- a/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs +++ b/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs @@ -5,9 +5,11 @@ using Dapper.Tests.Performance.Linq2Db; using LinqToDB; using LinqToDB.Data; +using System.ComponentModel; namespace Dapper.Tests.Performance { + [Description("LINQ to DB")] public class Linq2DBBenchmarks : BenchmarkBase { private Linq2DBContext _dbContext; @@ -22,22 +24,22 @@ public void Setup() _dbContext = new Linq2DBContext(); } - [Benchmark(Description = "Normal")] - public Post Normal() + [Benchmark(Description = "First")] + public Post First() { Step(); return _dbContext.Posts.First(p => p.Id == i); } - [Benchmark(Description = "Compiled")] + [Benchmark(Description = "First (Compiled)")] public Post Compiled() { Step(); return compiledQuery(_dbContext, i); } - [Benchmark(Description = "SqlQuery")] - public Post SqlQuery() + [Benchmark(Description = "Query")] + public Post Query() { Step(); return _dbContext.Query("select * from Posts where Id = @id", new { id = i }).First(); diff --git a/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs b/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs index 64889f1e1..9880dfe59 100644 --- a/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs +++ b/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs @@ -1,14 +1,17 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.Linq2Sql; using System; +using System.ComponentModel; using System.Data.Linq; using System.Linq; namespace Dapper.Tests.Performance { + [Description("LINQ to SQL")] public class Linq2SqlBenchmarks : BenchmarkBase { private DataClassesDataContext Linq2SqlContext; + private static readonly Func compiledQuery = CompiledQuery.Compile((DataClassesDataContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); @@ -19,14 +22,14 @@ public void Setup() Linq2SqlContext = new DataClassesDataContext(_connection); } - [Benchmark(Description = "Normal")] - public Linq2Sql.Post Normal() + [Benchmark(Description = "First")] + public Linq2Sql.Post First() { Step(); return Linq2SqlContext.Posts.First(p => p.Id == i); } - [Benchmark(Description = "Compiled")] + [Benchmark(Description = "First (Compiled)")] public Linq2Sql.Post Compiled() { Step(); @@ -40,4 +43,4 @@ public Post ExecuteQuery() return Linq2SqlContext.ExecuteQuery("select * from Posts where Id = {0}", i).First(); } } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/Benchmarks.Massive.cs b/Dapper.Tests.Performance/Benchmarks.Massive.cs index ac680ed90..98a63d953 100644 --- a/Dapper.Tests.Performance/Benchmarks.Massive.cs +++ b/Dapper.Tests.Performance/Benchmarks.Massive.cs @@ -1,9 +1,11 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using Massive; +using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { + [Description("Massive")] public class MassiveBenchmarks : BenchmarkBase { private DynamicModel _model; @@ -22,4 +24,4 @@ public dynamic QueryDynamic() return _model.Query("select * from Posts where Id = @0", _connection, i).First(); } } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs b/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs index 1a86b67ed..74fe362ef 100644 --- a/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs +++ b/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs @@ -1,9 +1,11 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using PetaPoco; +using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { + [Description("PetaPoco")] public class PetaPocoBenchmarks : BenchmarkBase { private Database _db, _dbFast; @@ -21,18 +23,18 @@ public void Setup() _dbFast.ForceDateTimesToUtc = false; } - [Benchmark(Description = "Fetch")] + [Benchmark(Description = "Fetch")] public Post Fetch() { Step(); return _db.Fetch("SELECT * from Posts where Id=@0", i).First(); } - [Benchmark(Description = "Fetch (Fast)")] + [Benchmark(Description = "Fetch (Fast)")] public Post FetchFast() { Step(); return _dbFast.Fetch("SELECT * from Posts where Id=@0", i).First(); } } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs b/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs index 41426e64b..d3bd0e024 100644 --- a/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs +++ b/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs @@ -1,9 +1,11 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using ServiceStack.OrmLite; +using System.ComponentModel; using System.Data; namespace Dapper.Tests.Performance { + [Description("ServiceStack")] public class ServiceStackBenchmarks : BenchmarkBase { private IDbConnection _db; @@ -16,11 +18,11 @@ public void Setup() _db = dbFactory.Open(); } - [Benchmark(Description = "SingleById")] + [Benchmark(Description = "SingleById")] public Post Query() { Step(); return _db.SingleById(i); } } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/Benchmarks.Susanoo.cs b/Dapper.Tests.Performance/Benchmarks.Susanoo.cs index 934d3b644..421d66e2c 100644 --- a/Dapper.Tests.Performance/Benchmarks.Susanoo.cs +++ b/Dapper.Tests.Performance/Benchmarks.Susanoo.cs @@ -1,18 +1,22 @@ -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using Susanoo; using Susanoo.Processing; +using System.ComponentModel; using System.Data; using System.Linq; namespace Dapper.Tests.Performance { + [Description("Susanoo")] public class SusanooBenchmarks : BenchmarkBase { private DatabaseManager _db; + private static readonly ISingleResultSetCommandProcessor _cmd = CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() .Realize(); + private static readonly ISingleResultSetCommandProcessor _cmdDynamic = CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) .DefineResults() @@ -25,7 +29,7 @@ public void Setup() _db = new DatabaseManager(_connection); } - [Benchmark(Description = "Mapping Cache")] + [Benchmark(Description = "Execute (Cache)")] public Post MappingCache() { Step(); @@ -35,7 +39,7 @@ public Post MappingCache() .Execute(_db, new { Id = i }).First(); } - [Benchmark(Description = "Mapping Cache (dynamic)")] + [Benchmark(Description = "Execute (Cache)")] public dynamic MappingCacheDynamic() { Step(); @@ -45,18 +49,18 @@ public dynamic MappingCacheDynamic() .Execute(_db, new { Id = i }).First(); } - [Benchmark(Description = "Mapping Static")] + [Benchmark(Description = "Execute (Static)")] public Post MappingStatic() { Step(); return _cmd.Execute(_db, new { Id = i }).First(); } - [Benchmark(Description = "Mapping Static (dynamic)")] + [Benchmark(Description = "Execut (Static)")] public dynamic MappingStaticDynamic() { Step(); return _cmdDynamic.Execute(_db, new { Id = i }).First(); } } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/Helpers/ORMColum.cs b/Dapper.Tests.Performance/Helpers/ORMColum.cs index a56f1b6e3..4f3fd88da 100644 --- a/Dapper.Tests.Performance/Helpers/ORMColum.cs +++ b/Dapper.Tests.Performance/Helpers/ORMColum.cs @@ -1,4 +1,6 @@ -using BenchmarkDotNet.Columns; +using System.ComponentModel; +using System.Reflection; +using BenchmarkDotNet.Columns; using BenchmarkDotNet.Reports; using BenchmarkDotNet.Running; @@ -11,7 +13,12 @@ public class ORMColum : IColumn public string Legend => "The object/relational mapper being tested"; public bool IsDefault(Summary summary, BenchmarkCase benchmarkCase) => false; - public string GetValue(Summary summary, BenchmarkCase benchmarkCase) => benchmarkCase.Descriptor.WorkloadMethod.DeclaringType.Name.Replace("Benchmarks", string.Empty); + public string GetValue(Summary summary, BenchmarkCase benchmarkCase) + { + var type = benchmarkCase.Descriptor.WorkloadMethod.DeclaringType; + return type.GetCustomAttribute()?.Description ?? type.Name.Replace("Benchmarks", string.Empty); + } + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, ISummaryStyle style) => GetValue(summary, benchmarkCase); public bool IsAvailable(Summary summary) => true; From 2a2ff8656196a4a302e628f6fea409cc4af705e4 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 11 Sep 2018 20:59:15 -0400 Subject: [PATCH 043/312] Fix Dapper.Contrib to prefer its attribute but still fall back to others --- Dapper.Contrib/SqlMapperExtensions.cs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 289620df9..7aac1004c 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -290,15 +290,19 @@ private static string GetTableName(Type type) } else { - //NOTE: This as dynamic trick should be able to handle both our own Table-attribute as well as the one in EntityFramework - var tableAttr = type #if NETSTANDARD1_3 - .GetTypeInfo() + var info = type.GetTypeInfo(); +#else + var info = type; #endif - .GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic; - if (tableAttr != null) + //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework + var tableAttrName = + info.GetCustomAttribute(false)?.Name + ?? (info.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name; + + if (tableAttrName != null) { - name = tableAttr.Name; + name = tableAttrName; } else { From c5f884de9b10d19f3b581e4a7f18413191710be1 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 11 Sep 2018 21:49:42 -0400 Subject: [PATCH 044/312] Fix benchmark stability Lots of runs is good, but changed behavior and greatly affects stability (due to GC). --- Dapper.Tests.Performance/Config.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/Dapper.Tests.Performance/Config.cs b/Dapper.Tests.Performance/Config.cs index 6a4fc045b..2aa638044 100644 --- a/Dapper.Tests.Performance/Config.cs +++ b/Dapper.Tests.Performance/Config.cs @@ -12,7 +12,7 @@ namespace Dapper.Tests.Performance { public class Config : ManualConfig { - public const int Iterations = 5000; + public const int Iterations = 500; public Config() { @@ -28,16 +28,16 @@ public Config() Add(TargetMethodColumn.Method); Add(new ReturnColum()); Add(StatisticColumn.Mean); - Add(StatisticColumn.StdDev); - Add(StatisticColumn.Error); + //Add(StatisticColumn.StdDev); + //Add(StatisticColumn.Error); Add(BaselineScaledColumn.Scaled); Add(md.GetColumnProvider()); - Add(Job.Dry - .WithLaunchCount(1) - .WithWarmupCount(1) - .WithInvocationCount(Iterations) - .WithIterationCount(10) + Add(Job.ShortRun + .WithLaunchCount(1) + .WithWarmupCount(2) + .WithUnrollFactor(Iterations) + .WithIterationCount(1) ); Set(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest)); SummaryPerType = false; From d702fb82ebecdba1bd20f228fabf36d392606425 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 11 Sep 2018 21:54:40 -0400 Subject: [PATCH 045/312] Update benchmarks to latest run --- Readme.md | 79 +++++++++++++++++++++++++++++-------------------------- 1 file changed, 42 insertions(+), 37 deletions(-) diff --git a/Readme.md b/Readme.md index 83feea30e..cd8f9b86d 100644 --- a/Readme.md +++ b/Readme.md @@ -123,44 +123,49 @@ Output from the latest run is: BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.254 (1803/April2018Update/Redstone4) Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores Frequency=2742188 Hz, Resolution=364.6723 ns, Timer=TSC - [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 - Dry : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 + [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 + ShortRun : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 + ``` -| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Allocated | -|------------- |------------------------------ |-------- |------------:|-----------:|-----------:|--------:|-------:|----------:| -| HandCoded | SqlCommand | Post | 86.76 us | 1.573 us | 2.379 us | 3.8000 | - | 12.24 KB | -| PetaPoco | 'Fetch<Post> (Fast)' | Post | 95.43 us | 2.434 us | 3.681 us | 4.4000 | - | 13.65 KB | -| HandCoded | DataTable | dynamic | 98.18 us | 5.242 us | 7.925 us | 2.2000 | 0.6000 | 12.45 KB | -| PetaPoco | Fetch<Post> | Post | 98.18 us | 2.252 us | 3.404 us | 4.6000 | - | 14.59 KB | -| Massive | 'Query (dynamic)' | dynamic | 98.47 us | 3.102 us | 4.690 us | 4.6000 | - | 14.21 KB | -| Belgrade | ExecuteReader | Post | 99.14 us | 15.731 us | 23.783 us | 3.6000 | 1.0000 | 11.37 KB | -| Dapper | 'Query<dynamic> (buffered)' | dynamic | 100.42 us | 5.311 us | 8.030 us | 4.4000 | - | 13.88 KB | -| Dapper | 'Contrib Get<T>' | Post | 102.55 us | 9.289 us | 14.044 us | 4.6000 | - | 14.45 KB | -| Dapper | 'Query<T> (buffered)' | Post | 104.85 us | 10.278 us | 15.539 us | 4.4000 | - | 13.79 KB | -| ServiceStack | SingleById | Post | 107.83 us | 5.144 us | 7.778 us | 5.6000 | - | 17.53 KB | -| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 109.78 us | 7.689 us | 11.624 us | 4.2000 | - | 13.51 KB | -| Dapper | QueryFirstOrDefault<T> | Post | 112.97 us | 4.081 us | 6.170 us | 4.2000 | - | 13.47 KB | -| Susanoo | 'Mapping Static' | Post | 118.91 us | 8.786 us | 13.282 us | 4.8000 | - | 14.99 KB | -| Susanoo | 'Mapping Static (dynamic)' | dynamic | 119.57 us | 6.985 us | 10.560 us | 4.8000 | - | 14.97 KB | -| Susanoo | 'Mapping Cache' | Post | 123.68 us | 6.693 us | 10.118 us | 6.8000 | - | 20.9 KB | -| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 127.06 us | 8.748 us | 13.225 us | 4.4000 | - | 13.87 KB | -| Dapper | 'Query<T> (unbuffered)' | Post | 132.28 us | 9.350 us | 14.136 us | 4.4000 | - | 13.84 KB | -| Susanoo | 'Mapping Cache (dynamic)' | dynamic | 133.45 us | 4.179 us | 6.318 us | 6.6000 | - | 20.41 KB | -| Linq2Sql | Compiled | Post | 133.46 us | 6.137 us | 9.278 us | 3.0000 | - | 9.82 KB | -| EFCore | Compiled | Post | 180.70 us | 65.498 us | 99.024 us | 5.2000 | - | 16.08 KB | -| EF6 | SqlQuery | Post | 187.09 us | 136.314 us | 206.088 us | 9.0000 | - | 27.86 KB | -| NHibernate | Get<T> | Post | 202.34 us | 10.271 us | 15.528 us | 10.4000 | - | 32.5 KB | -| NHibernate | HQL | Post | 216.95 us | 26.889 us | 40.653 us | 11.2000 | - | 35 KB | -| EFCore | 'No Tracking' | Post | 243.18 us | 59.359 us | 89.742 us | 6.8000 | - | 21.36 KB | -| EFCore | Normal | Post | 245.60 us | 72.618 us | 109.788 us | 6.4000 | - | 20.25 KB | -| Linq2Sql | ExecuteQuery | Post | 246.66 us | 6.215 us | 9.396 us | 13.6000 | - | 42.34 KB | -| NHibernate | Criteria | Post | 252.51 us | 18.303 us | 27.672 us | 21.2000 | - | 65.37 KB | -| EFCore | SqlQuery | Post | 263.34 us | 73.919 us | 111.755 us | 6.6000 | - | 20.75 KB | -| EF6 | Normal | Post | 308.46 us | 154.203 us | 233.133 us | 15.6000 | - | 48.29 KB | -| NHibernate | SQL | Post | 322.13 us | 9.530 us | 14.408 us | 32.8000 | - | 101.06 KB | -| EF6 | 'No Tracking' | Post | 324.12 us | 134.870 us | 203.905 us | 17.8000 | - | 55.1 KB | -| Linq2Sql | Normal | Post | 370.55 us | 238.033 us | 359.871 us | 4.6000 | 1.4000 | 14.68 KB | -| NHibernate | LINQ | Post | 1,110.87 us | 36.863 us | 55.731 us | 20.2000 | - | 62.13 KB | +| ORM | Method | Return | Mean | Gen 0 | Gen 1 | Gen 2 | Allocated | +|------------- |------------------------------ |-------- |------------:|--------:|-------:|-------:|----------:| +| LINQ to DB | 'First (Compiled)' | Post | 78.75 us | 0.7500 | - | - | 2.66 KB | +| LINQ to DB | Query<T> | Post | 80.38 us | 2.1250 | - | - | 6.87 KB | +| Hand Coded | SqlCommand | Post | 87.16 us | 2.5000 | 1.0000 | 0.2500 | 12.24 KB | +| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 87.80 us | 4.3750 | - | - | 13.5 KB | +| Belgrade | ExecuteReader | Post | 87.85 us | 3.6250 | 0.7500 | - | 11.27 KB | +| Dapper | QueryFirstOrDefault<T> | Post | 91.51 us | 2.8750 | 0.8750 | 0.2500 | 13.46 KB | +| Hand Coded | DataTable | dynamic | 91.74 us | 2.2500 | 0.6250 | - | 12.45 KB | +| Dapper | 'Query<T> (buffered)' | Post | 94.05 us | 2.8750 | 0.8750 | 0.2500 | 13.79 KB | +| Dapper | 'Query<dynamic> (buffered)' | dynamic | 95.25 us | 2.5000 | 1.0000 | 0.2500 | 13.87 KB | +| Massive | 'Query (dynamic)' | dynamic | 96.18 us | 3.2500 | 0.8750 | 0.3750 | 14.19 KB | +| PetaPoco | 'Fetch<T> (Fast)' | Post | 96.57 us | 2.7500 | 0.8750 | 0.2500 | 13.65 KB | +| PetaPoco | Fetch<T> | Post | 97.62 us | 2.8750 | 0.8750 | 0.2500 | 14.59 KB | +| Dapper | 'Contrib Get<T>' | Post | 98.85 us | 2.8750 | 1.0000 | 0.2500 | 14.45 KB | +| ServiceStack | SingleById<T> | Post | 102.39 us | 3.1250 | 0.8750 | 0.3750 | 17.52 KB | +| LINQ to DB | First | Post | 103.54 us | 1.7500 | - | - | 5.51 KB | +| Susanoo | 'Execute<T> (Static)' | Post | 105.07 us | 2.8750 | 0.8750 | 0.2500 | 14.98 KB | +| Dashing | Get | Post | 105.80 us | 3.1250 | 0.8750 | 0.3750 | 14.82 KB | +| Susanoo | 'Execut<dynamic> (Static)' | dynamic | 109.26 us | 3.1250 | 0.8750 | 0.2500 | 14.97 KB | +| LINQ to SQL | 'First (Compiled)' | Post | 114.62 us | 3.1250 | - | - | 9.82 KB | +| Dapper | 'Query<T> (unbuffered)' | Post | 119.72 us | 3.1250 | 0.8750 | 0.2500 | 13.83 KB | +| Susanoo | 'Execute<dynamic> (Cache)' | dynamic | 124.02 us | 3.6250 | 1.0000 | 0.5000 | 20.4 KB | +| Susanoo | 'Execute<T> (Cache)' | Post | 126.92 us | 4.2500 | 1.0000 | 0.5000 | 20.88 KB | +| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 139.89 us | 2.5000 | 1.0000 | 0.2500 | 13.87 KB | +| EF 6 | SqlQuery | Post | 143.86 us | 5.2500 | 0.7500 | - | 27.86 KB | +| EF Core | 'First (Compiled)' | Post | 148.42 us | 5.0000 | - | - | 16.08 KB | +| NHibernate | Get<T> | Post | 196.88 us | 5.7500 | 1.0000 | - | 32.5 KB | +| EF Core | First | Post | 197.91 us | 6.5000 | - | - | 20.25 KB | +| NHibernate | HQL | Post | 207.84 us | 6.0000 | 0.7500 | - | 35 KB | +| EF Core | 'First (No Tracking)' | Post | 213.58 us | 4.2500 | 0.7500 | 0.2500 | 21.36 KB | +| EF Core | SqlQuery | Post | 247.25 us | 6.5000 | - | - | 20.56 KB | +| EF 6 | First | Post | 247.53 us | 15.5000 | - | - | 48.29 KB | +| NHibernate | Criteria | Post | 253.30 us | 13.2500 | 1.2500 | 0.2500 | 65.32 KB | +| EF 6 | 'First (No Tracking)' | Post | 265.80 us | 10.5000 | 1.0000 | - | 55.09 KB | +| LINQ to SQL | ExecuteQuery | Post | 284.74 us | 7.0000 | 1.0000 | 0.5000 | 42.33 KB | +| NHibernate | SQL | Post | 313.85 us | 26.5000 | 1.0000 | - | 101.01 KB | +| LINQ to SQL | First | Post | 968.14 us | 4.0000 | 1.0000 | - | 14.68 KB | +| NHibernate | LINQ | Post | 1,062.16 us | 11.0000 | 2.0000 | - | 62.37 KB | Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (Ctrl+F5). From 48f6754ad69c5bc196612ec23f4887062fa87e17 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 11 Sep 2018 22:28:11 -0400 Subject: [PATCH 046/312] Updte literal example to be a bit better --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index cd8f9b86d..577cfae65 100644 --- a/Readme.md +++ b/Readme.md @@ -202,7 +202,7 @@ Literal replacements Dapper supports literal replacements for bool and numeric types. ```csharp -connection.Query("select * from User where UserId = {=Id}", new {Id = 1})); +connection.Query("select * from User where UserTypeId = {=Admin}", new { UserTypeId.Admin })); ``` The literal replacement is not sent as a parameter; this allows better plans and filtered index usage but should usually be used sparingly and after testing. This feature is particularly useful when the value being injected From ec194429cd26fff1b4fd2eb21a1d3ac5b287093f Mon Sep 17 00:00:00 2001 From: Andrew Sumido Date: Thu, 1 Nov 2018 19:56:06 -0500 Subject: [PATCH 047/312] Corrected class name from PostcresqlTests to PostgresqlTests (#1144) --- Dapper.Tests/Providers/PostgresqlTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dapper.Tests/Providers/PostgresqlTests.cs b/Dapper.Tests/Providers/PostgresqlTests.cs index 6ec20c3db..dee918e7f 100644 --- a/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/Dapper.Tests/Providers/PostgresqlTests.cs @@ -5,7 +5,7 @@ namespace Dapper.Tests { - public class PostcresqlTests : TestBase + public class PostgresqlTests : TestBase { private static Npgsql.NpgsqlConnection GetOpenNpgsqlConnection() { @@ -80,4 +80,4 @@ static FactPostgresqlAttribute() } } } -} \ No newline at end of file +} From 82a005953573b00b1d4c39e43568fb45449dd8e4 Mon Sep 17 00:00:00 2001 From: Maksim Karpenko <38804406+maksimkarpenko@users.noreply.github.com> Date: Fri, 2 Nov 2018 03:58:49 +0300 Subject: [PATCH 048/312] Add the eXpress Persistent Objects (XPO) benchmark (#1131) --- Dapper.Tests.Performance/Benchmarks.XPO.cs | 60 +++++++++++++++++++ .../Dapper.Tests.Performance.csproj | 1 + Dapper.Tests.Performance/LegacyTests.cs | 27 +++++++++ Dapper.Tests.Performance/XPO/Post.cs | 25 ++++++++ 4 files changed, 113 insertions(+) create mode 100644 Dapper.Tests.Performance/Benchmarks.XPO.cs create mode 100644 Dapper.Tests.Performance/XPO/Post.cs diff --git a/Dapper.Tests.Performance/Benchmarks.XPO.cs b/Dapper.Tests.Performance/Benchmarks.XPO.cs new file mode 100644 index 000000000..62a5fe295 --- /dev/null +++ b/Dapper.Tests.Performance/Benchmarks.XPO.cs @@ -0,0 +1,60 @@ +using BenchmarkDotNet.Attributes; + +using System; +using System.Linq; +using System.ComponentModel; +using DevExpress.Xpo; +using DevExpress.Data.Filtering; + +namespace Dapper.Tests.Performance +{ + [Description("DevExpress.XPO")] + public class XpoBenchmarks : BenchmarkBase + { + public UnitOfWork _session; + + [GlobalSetup] + public void Setup() + { + BaseSetup(); + IDataLayer dataLayer = XpoDefault.GetDataLayer(_connection, DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists); + dataLayer.Dictionary.GetDataStoreSchema(typeof(Xpo.Post)); + _session = new UnitOfWork(dataLayer, dataLayer); + _session.IdentityMapBehavior = IdentityMapBehavior.Strong; + _session.TypesManager.EnsureIsTypedObjectValid(); + } + + [GlobalCleanup] + public void Cleanup() + { + _session.Dispose(); + } + + [Benchmark(Description = "GetObjectByKey")] + public Xpo.Post GetObjectByKey() + { + Step(); + return _session.GetObjectByKey(i, true); + } + + [Benchmark(Description = "FindObject")] + public Xpo.Post FindObject() + { + Step(); + CriteriaOperator _findCriteria = new BinaryOperator() + { + OperatorType = BinaryOperatorType.Equal, + LeftOperand = new OperandProperty("Id"), + RightOperand = new ConstantValue(i) + }; + return _session.FindObject(_findCriteria); + } + + [Benchmark(Description = "Query")] + public Xpo.Post Query() + { + Step(); + return _session.Query().First(p => p.Id == i); + } + } +} diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 80ba2b12a..255616bf2 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -16,6 +16,7 @@ + diff --git a/Dapper.Tests.Performance/LegacyTests.cs b/Dapper.Tests.Performance/LegacyTests.cs index 5ca323ea4..ec15274c7 100644 --- a/Dapper.Tests.Performance/LegacyTests.cs +++ b/Dapper.Tests.Performance/LegacyTests.cs @@ -24,6 +24,9 @@ using Dashing; using Microsoft.EntityFrameworkCore; using Belgrade.SqlClient; +using DevExpress.Xpo; +using Dapper.Tests.Performance.Xpo; +using DevExpress.Data.Filtering; namespace Dapper.Tests.Performance { @@ -386,6 +389,30 @@ public async Task RunAsync(int iterations) #endif }, "Hand Coded"); + // DevExpress.XPO + Try(() => + { + IDataLayer dataLayer = XpoDefault.GetDataLayer(connection, DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists); + dataLayer.Dictionary.GetDataStoreSchema(typeof(Xpo.Post)); + UnitOfWork session = new UnitOfWork(dataLayer, dataLayer); + session.IdentityMapBehavior = IdentityMapBehavior.Strong; + session.TypesManager.EnsureIsTypedObjectValid(); + + tests.Add(id => session.Query().First(p => p.Id == id), "DevExpress.XPO: Query"); + tests.Add(id => session.GetObjectByKey(id, true), "DevExpress.XPO: GetObjectByKey"); + tests.Add(id => + { + CriteriaOperator findCriteria = new BinaryOperator() + { + OperatorType = BinaryOperatorType.Equal, + LeftOperand = new OperandProperty("Id"), + RightOperand = new ConstantValue(id) + }; + session.FindObject(findCriteria); + }, "DevExpress.XPO: FindObject"); + + }, "DevExpress.XPO"); + // Subsonic isn't maintained anymore - doesn't import correctly //Try(() => // { diff --git a/Dapper.Tests.Performance/XPO/Post.cs b/Dapper.Tests.Performance/XPO/Post.cs new file mode 100644 index 000000000..84fde9331 --- /dev/null +++ b/Dapper.Tests.Performance/XPO/Post.cs @@ -0,0 +1,25 @@ +using System; +using DevExpress.Xpo; + +namespace Dapper.Tests.Performance.Xpo +{ + [Persistent("Posts")] + public class Post : XPLiteObject + { + [Key(false)] + public int Id { get; set; } + public string Text { get; set; } + public DateTime CreationDate { get; set; } + public DateTime LastChangeDate { get; set; } + public int? Counter1 { get; set; } + public int? Counter2 { get; set; } + public int? Counter3 { get; set; } + public int? Counter4 { get; set; } + public int? Counter5 { get; set; } + public int? Counter6 { get; set; } + public int? Counter7 { get; set; } + public int? Counter8 { get; set; } + public int? Counter9 { get; set; } + public Post(Session session) : base(session) { } + } +} From b4c6d5eff438de92f8c08e09cc1f2402bd544bb4 Mon Sep 17 00:00:00 2001 From: James Kitamirike Date: Sun, 27 Jan 2019 16:45:27 +0300 Subject: [PATCH 049/312] Fix typo (#1155) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 577cfae65..f1fb34c00 100644 --- a/Readme.md +++ b/Readme.md @@ -240,7 +240,7 @@ class User Now let us say that we want to map a query that joins both the posts and the users table. Until now if we needed to combine the result of 2 queries, we'd need a new object to express it but it makes more sense in this case to put the `User` object inside the `Post` object. -This is the user case for multi mapping. You tell dapper that the query returns a `Post` and a `User` object and then give it a function describing what you want to do with each of the rows containing both a `Post` and a `User` object. In our case, we want to take the user object and put it inside the post object. So we write the function: +This is the use case for multi mapping. You tell dapper that the query returns a `Post` and a `User` object and then give it a function describing what you want to do with each of the rows containing both a `Post` and a `User` object. In our case, we want to take the user object and put it inside the post object. So we write the function: ```csharp (post, user) => { post.Owner = user; return post; } From 5927c66776c1df0605e253ddc2116bf87e3ed05a Mon Sep 17 00:00:00 2001 From: Yves Mancera Date: Sun, 27 Jan 2019 05:50:08 -0800 Subject: [PATCH 050/312] Mention how to use user defined variables (#1149) Fixes #620 Added a section to indicate Dapper works well with User Defined Variables. --- Readme.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Readme.md b/Readme.md index f1fb34c00..32ead193a 100644 --- a/Readme.md +++ b/Readme.md @@ -370,6 +370,14 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) } ``` +User Defined Variables in MySQL +--------------------- +In order to use Non-parameter SQL variables with MySql Connector, you have to add the following option to your connection string: + +`Allow User Variables=True` + +Make sure you don't provide Dapper with a property to map. + Limitations and caveats --------------------- Dapper caches information about every query it runs, this allows it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. Statements that are only used once are routinely flushed from this cache. Still, if you are generating SQL strings on the fly without using parameters it is possible you may hit memory issues. From b961089eff365e48a071108e283345216b85b2ef Mon Sep 17 00:00:00 2001 From: Wixely Date: Sat, 9 Feb 2019 12:19:33 +0000 Subject: [PATCH 051/312] Improve Exception message (#1154) In cases where the object has a property named ID, the property will automatically be assigned as [Key], even if [Computed] is added manually in an attempt to override it. This can cause confusion, this exception will be thrown despite the code looking fine and there only being one [ExplicitKey] defined. I've added this extra line of information to help diagnosis. --- Dapper.Contrib/SqlMapperExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 7aac1004c..f8a8f2160 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -153,7 +153,7 @@ private static PropertyInfo GetSingleKey(string method) var explicitKeys = ExplicitKeyPropertiesCache(type); var keyCount = keys.Count + explicitKeys.Count; if (keyCount > 1) - throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property"); + throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property. [Key] Count: {keys.Count}, [ExplicitKey] Count: {explicitKeys.Count}"); if (keyCount == 0) throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); From 68eea4ff2629e9cc9e36ed6a996b4eebc5a4f87a Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sat, 9 Feb 2019 12:21:42 +0000 Subject: [PATCH 052/312] fix #1190 --- Dapper/SqlMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index c2e41441d..4bce92ea1 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2416,7 +2416,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] if (isStruct) { - il.DeclareLocal(type.MakePointerType()); + il.DeclareLocal(type.MakeByRefType()); // note: ref-local il.Emit(OpCodes.Unbox, type); // stack is now [typed-param] } else From bed688bbe8f143675d46db6b2a54ba86ede5ae78 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 11 Feb 2019 08:44:46 +0000 Subject: [PATCH 053/312] version for public drop --- semver.txt | 2 +- version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/semver.txt b/semver.txt index 5a0a9b9ee..624afd388 100644 --- a/semver.txt +++ b/semver.txt @@ -1 +1 @@ -1.50.6-alpha \ No newline at end of file +1.50.7 \ No newline at end of file diff --git a/version.json b/version.json index 3288253cd..ec5309a58 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "1.50.7-alpha.{height}", + "version": "1.50.7", "assemblyVersion": "1.50.0.0", "publicReleaseRefSpec": [ "^refs/heads/master$", From e7020b2bde577f00333b36cdda847ece131f3ad3 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 11 Feb 2019 12:26:07 +0000 Subject: [PATCH 054/312] release notes for 1.50.7; set future versions to 1.50.8-alpha --- Dapper.sln | 1 + docs/index.md | 8 ++++++++ version.json | 2 +- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/Dapper.sln b/Dapper.sln index df80e9a5a..91bd7a2f0 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution appveyor.yml = appveyor.yml build.ps1 = build.ps1 Directory.build.props = Directory.build.props + docs\index.md = docs\index.md License.txt = License.txt nuget.config = nuget.config Readme.md = Readme.md diff --git a/docs/index.md b/docs/index.md index bc59f4f7d..e11af6a65 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,14 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes +### 1.50.7 + +- Fix #1190 - incorrect unmanaged pointer when processing parameters that are a boxed struct (rare error relating to GC) +- Fix #1111 - make `SqlMapper.Parse` consistent with `QueryImpl` +- Fix #111- - improve error message for invalid literal types +- Fix #1149 - improve error messages in "contrib" +- Improved detection of empty table-valued-parameters + ### 1.50.5 - Fixes empty result set hanging with `QueryAsync` diff --git a/version.json b/version.json index ec5309a58..5befcfcbb 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "1.50.7", + "version": "1.50.8-alpha.{height}", "assemblyVersion": "1.50.0.0", "publicReleaseRefSpec": [ "^refs/heads/master$", From 200f031ce4534c2b18e14cdb864affe3418078de Mon Sep 17 00:00:00 2001 From: Nick Kirby Date: Wed, 20 Feb 2019 14:29:18 +0000 Subject: [PATCH 055/312] Release notes issue links (#1195) This PR is for the issue I raised earlier #1194 - I've added links for all every issue - Tidied up capitalization Let me know if you need me to make any changes --- docs/index.md | 106 +++++++++++++++++++++++++------------------------- 1 file changed, 53 insertions(+), 53 deletions(-) diff --git a/docs/index.md b/docs/index.md index e11af6a65..67f735ca4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,10 +22,10 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### 1.50.7 -- Fix #1190 - incorrect unmanaged pointer when processing parameters that are a boxed struct (rare error relating to GC) -- Fix #1111 - make `SqlMapper.Parse` consistent with `QueryImpl` +- Fix [#1190](https://github.com/StackExchange/Dapper/issues/1190) - incorrect unmanaged pointer when processing parameters that are a boxed struct (rare error relating to GC) +- Fix [#1111](https://github.com/StackExchange/Dapper/issues/1111) - make `SqlMapper.Parse` consistent with `QueryImpl` - Fix #111- - improve error message for invalid literal types -- Fix #1149 - improve error messages in "contrib" +- Fix [#1149](https://github.com/StackExchange/Dapper/pull/1149) - improve error messages in "contrib" - Improved detection of empty table-valued-parameters ### 1.50.5 @@ -42,15 +42,15 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### 1.50.2 -- fix issue 569 (`in` expansions using ODBC pseudo-positional arguments) +- Fix issue [#569](https://github.com/StackExchange/Dapper/issues/569) (`in` expansions using ODBC pseudo-positional arguments) ### 1.50.1 -- change to how `string_split` is used for `InListStringSplitCount` +- Change to how `string_split` is used for `InListStringSplitCount` ### 1.50.0 -- no changes; stable release +- No changes; stable release ### 1.50.0-rc3 @@ -58,37 +58,37 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### 1.50.0-rc2b -- new `InListStringSplitCount` global setting; if set (non-negative), `in @foo` expansions (of at least the specified size) of primitive types (`int`, `tinyint`, `smallint`, `bigint`) are implemented via the SQL Server 2016 (compat level 130) `STRING_SPLIT` function -- fix for incorrect conversions in `GridReader` (#254) +- New `InListStringSplitCount` global setting; if set (non-negative), `in @foo` expansions (of at least the specified size) of primitive types (`int`, `tinyint`, `smallint`, `bigint`) are implemented via the SQL Server 2016 (compat level 130) `STRING_SPLIT` function +- Fix for incorrect conversions in `GridReader` ([#254](https://github.com/StackExchange/Dapper/issues/254)) ### 1.50.0-rc2 / 1.50.0-rc2a -- packaging for .NET Core rc2 +- Packaging for .NET Core rc2 ### 1.50-beta9 -- fix for `PadListExpansions` to work correctly with `not in` scenarios; now uses last non-null value instead of `null`; if none available, don't pad -- fix problems with single-result/single-row not being supported by all providers (basically: sqlite, #466) -- fix problems with enums - nulls (#467) and primitive values (#468) -- add support for C# 6 get-only properties (#473) -- add support for various xml types (#427) +- Fix for `PadListExpansions` to work correctly with `not in` scenarios; now uses last non-null value instead of `null`; if none available, don't pad +- Fix problems with single-result/single-row not being supported by all providers (basically: sqlite, [#466](https://github.com/StackExchange/Dapper/issues/466)) +- Fix problems with enums - nulls ([#467](https://github.com/StackExchange/Dapper/issues/467)) and primitive values ([#468](https://github.com/StackExchange/Dapper/issues/468)) +- Add support for C# 6 get-only properties ([#473](https://github.com/StackExchange/Dapper/issues/473)) +- Add support for various xml types ([#427](https://github.com/StackExchange/Dapper/issues/427)) ### 1.50-beta8 -- addition of `GetRowParser` extension method on `IDataReader` API - allows manual construction of discriminated unions, etc -- addition of `Settings.PadListExpansions` - reduces query-plan saturation by padding list expansions with `null` values (opt-in, because on some DB configurations this could change the meaning) *(note: bad choice of `null` revised in 1.50-beta9)* -- addition of `Settings.ApplyNullValues` - assigns (rather than ignores) `null` values when possible -- fix for #461 - ensure type-handlers work for constructor-based initialization -- fix for #455 - make the `LookupDbType` method available again +- Addition of `GetRowParser` extension method on `IDataReader` API - allows manual construction of discriminated unions, etc +- Addition of `Settings.PadListExpansions` - reduces query-plan saturation by padding list expansions with `null` values (opt-in, because on some DB configurations this could change the meaning) *(note: bad choice of `null` revised in 1.50-beta9)* +- Addition of `Settings.ApplyNullValues` - assigns (rather than ignores) `null` values when possible +- Fix for [#461](https://github.com/StackExchange/Dapper/issues/461) - ensure type-handlers work for constructor-based initialization +- Fix for [#455](https://github.com/StackExchange/Dapper/issues/455) - make the `LookupDbType` method available again ### 1.50-beta7 -- addition of `GetRowParser(Type)` (and refactor the backing store for readers to suit) -- column hash should consider type, not just name +- Addition of `GetRowParser(Type)` (and refactor the backing store for readers to suit) +- Column hash should consider type, not just name ### 1.50-beta6 -- fix for issue #424 - defensive `SqlDataRecord` handling +- Fix for issue [#424](https://github.com/StackExchange/Dapper/issues/424) - defensive `SqlDataRecord` handling ### 1.50-beta5 @@ -99,9 +99,9 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### 1.50-beta4 - Add `QueryFirstOrDefault` / `ReadFirstOrDefault` methods that optimize the single-row scenario -- remove some legacy `dynamic` usage from the async API -- make `DynamicTypeMap` public again (error during core-clr migration) -- use `Hashtable` again on core-clr +- Remove some legacy `dynamic` usage from the async API +- Make `DynamicTypeMap` public again (error during core-clr migration) +- Use `Hashtable` again on core-clr ### 1.50-beta3 @@ -110,61 +110,61 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### 1.50-beta2 - Core CLR now targets rc1 / 23516 -- various Core CLR fixes -- code cleanup and C# 6 usage (assorted) +- Various Core CLR fixes +- Code cleanup and C# 6 usage (assorted) ### 1.50-beta1 -- split `SqlMapper.cs` as it was becoming too unmaintainable; NuGet is now the only supported deployment channel -- remove down-level C# requirements, as "drop in the file" is no longer the expected usage +- Split `SqlMapper.cs` as it was becoming too unmaintainable; NuGet is now the only supported deployment channel +- Remove down-level C# requirements, as "drop in the file" is no longer the expected usage - `SqlMapper.Settings` added; provides high-level global configuration; initially `CommandTimeout` (@Irrational86) - improve error message if an array is used as a parameter in an invalid context -- add `Type[]` support for `GridReader.Read` scenarios (@NikolayGlynchak) -- support for custom type-maps in collection parameters (@gjsduarte) -- fix incorrect cast in `QueryAsync` (@phnx47, #346) -- fix incorrect null handling re `UdtTypeName` (@perliedman) -- support for `SqlDataRecord` (@sqmgh) -- allow `DbString` default for `IsAnsi` to be specified (@kppullin) +- Add `Type[]` support for `GridReader.Read` scenarios (@NikolayGlynchak) +- Support for custom type-maps in collection parameters (@gjsduarte) +- Fix incorrect cast in `QueryAsync` (@phnx47, [#346](https://github.com/StackExchange/Dapper/issues/346)) +- Fix incorrect null handling re `UdtTypeName` (@perliedman) +- Support for `SqlDataRecord` (@sqmgh) +- Allow `DbString` default for `IsAnsi` to be specified (@kppullin) - provide `TypeMapProvider` with lazy func-based initialization (@garyhuntddn) -- core-clr updated to beta-8 and various cleanups/fixes -- built using core-clr build tools +- Core-clr updated to beta-8 and various cleanups/fixes +- Built using core-clr build tools ### 1.42 -- fix bug with dynamic parameters where `.Get` is called before the command is executed +- Fix bug with dynamic parameters where `.Get` is called before the command is executed ### 1.41-beta5 -- core-clr packaging build and workarounds -- fix bug with literal `{=val}` boolean replacements +- Core-clr packaging build and workarounds +- Fix bug with literal `{=val}` boolean replacements ### 1.41-beta4 -- core-clr packaging build -- improve mapping to enum members (@BrianJolly) +- Core-clr packaging build +- Improve mapping to enum members (@BrianJolly) ### 1.41-beta -- core-clr packaging build +- Core-clr packaging build ### 1.41-alpha -- introduces dnx (core-clr) experimental changes -- adds `SqlBuilder` project -- improve error message when incorrectly accessing parameter values +- Introduces dnx (core-clr) experimental changes +- Adds `SqlBuilder` project +- Improve error message when incorrectly accessing parameter values ### 1.40 -- workaround for broken `GetValues()` on Mono; add `AsList()` +- Workaround for broken `GetValues()` on Mono; add `AsList()` ### 1.39 -- fix case on SQL CLR types; grid-reader should respect no-cache flags; make parameter inclusion case-insensitive +- Fix case on SQL CLR types; grid-reader should respect no-cache flags; make parameter inclusion case-insensitive ### 1.38 -- specify constructor explicitly; allow value-type parameters (albeit: boxed) +- Specify constructor explicitly; allow value-type parameters (albeit: boxed) ### 1.37 @@ -172,13 +172,13 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### 1.36 -- Fix Issue #192 (expanded parameter naming glitch) and Issue #178 (execute reader now wraps the command/reader pair, to extend the command lifetime; note that the underlying command/reader are available by casting to `IWrappedDataReader`) +- Fix Issue [#192](https://github.com/StackExchange/Dapper/issues/192) (expanded parameter naming glitch) and Issue [#178](https://github.com/StackExchange/Dapper/issues/178) (execute reader now wraps the command/reader pair, to extend the command lifetime; note that the underlying command/reader are available by casting to `IWrappedDataReader`) ### 1.35 -- Fix Issue #151 (Execute should work with `ExpandoObject` etc); Fix Issue #182 (better support for db-type when using `object` values); -- output expressions / callbacks in dynamic args (via Derek); arbitrary number of types in multi-mapping (via James Holwell); -- fix `DbString`/Oracle bug (via Mauro Cerutti); new support for **named positional arguments** +- Fix Issue [#151](https://github.com/StackExchange/Dapper/issues/151) (Execute should work with `ExpandoObject` etc); Fix Issue #182 (better support for db-type when using `object` values); +- Output expressions / callbacks in dynamic args (via Derek); arbitrary number of types in multi-mapping (via James Holwell); +- Fix `DbString`/Oracle bug (via Mauro Cerutti); new support for **named positional arguments** ### 1.34 From b520e26a255af13f2192b84f5ba0565302628300 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 25 Feb 2019 10:05:21 -0500 Subject: [PATCH 056/312] Bump version to 1.60.0 (#1198) This bumps and locks at 1.60.0 to cope with whatever binding policies .NET Core is enforcing - then we can stick at this until 2.x I guess. --- version.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/version.json b/version.json index 5befcfcbb..f49a47a32 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { - "version": "1.50.8-alpha.{height}", - "assemblyVersion": "1.50.0.0", + "version": "1.60", + "assemblyVersion": "1.60.0.0", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/tags/v\\d+\\.\\d+" From 684d7d9895e8b9eff4ab898528d69bd60979c098 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 3 Mar 2019 08:57:20 -0500 Subject: [PATCH 057/312] Add release notes for 1.60.1 --- docs/index.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/index.md b/docs/index.md index 67f735ca4..4c2ec94f5 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,10 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes +### 1.60.1 + +- Fix [#1196](https://github.com/StackExchange/Dapper/issues/1196) - versioning fix only ([#1198](https://github.com/StackExchange/Dapper/pull/1198)) - assembly version is now locked at 1.60.0 to resolve some mismatch issues with .NET Core assebly loading/binding. + ### 1.50.7 - Fix [#1190](https://github.com/StackExchange/Dapper/issues/1190) - incorrect unmanaged pointer when processing parameters that are a boxed struct (rare error relating to GC) From f0da900ee620918b0955d3661c208542a2d3321b Mon Sep 17 00:00:00 2001 From: Steven Yeh Date: Tue, 26 Mar 2019 04:16:20 -0500 Subject: [PATCH 058/312] Fix Typo (#1215) thanks, appreciated --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 4c2ec94f5..9c9b1d898 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### 1.60.1 -- Fix [#1196](https://github.com/StackExchange/Dapper/issues/1196) - versioning fix only ([#1198](https://github.com/StackExchange/Dapper/pull/1198)) - assembly version is now locked at 1.60.0 to resolve some mismatch issues with .NET Core assebly loading/binding. +- Fix [#1196](https://github.com/StackExchange/Dapper/issues/1196) - versioning fix only ([#1198](https://github.com/StackExchange/Dapper/pull/1198)) - assembly version is now locked at 1.60.0 to resolve some mismatch issues with .NET Core assembly loading/binding. ### 1.50.7 From 3bf2b4f703fde8ee150b8ecf5ea32c14c9ce78da Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 27 Mar 2019 09:12:06 +0000 Subject: [PATCH 059/312] support the descriptor (component-model) API (#1216) --- Dapper.sln | 7 + Dapper/SqlMapper.DapperRow.Descriptor.cs | 105 +++++++++++++++ Dapper/SqlMapper.DapperRow.cs | 11 +- Dapper/SqlMapper.DapperRowMetaObject.cs | 16 +++ UIBindingTest/App.config | 6 + UIBindingTest/BindingForm.Designer.cs | 63 +++++++++ UIBindingTest/BindingForm.cs | 19 +++ UIBindingTest/BindingForm.resx | 120 ++++++++++++++++++ UIBindingTest/Program.cs | 22 ++++ UIBindingTest/Properties/AssemblyInfo.cs | 22 ++++ .../Properties/Resources.Designer.cs | 71 +++++++++++ UIBindingTest/Properties/Resources.resx | 117 +++++++++++++++++ UIBindingTest/Properties/Settings.Designer.cs | 30 +++++ UIBindingTest/Properties/Settings.settings | 7 + UIBindingTest/UIBindingTest.csproj | 89 +++++++++++++ 15 files changed, 696 insertions(+), 9 deletions(-) create mode 100644 Dapper/SqlMapper.DapperRow.Descriptor.cs create mode 100644 UIBindingTest/App.config create mode 100644 UIBindingTest/BindingForm.Designer.cs create mode 100644 UIBindingTest/BindingForm.cs create mode 100644 UIBindingTest/BindingForm.resx create mode 100644 UIBindingTest/Program.cs create mode 100644 UIBindingTest/Properties/AssemblyInfo.cs create mode 100644 UIBindingTest/Properties/Resources.Designer.cs create mode 100644 UIBindingTest/Properties/Resources.resx create mode 100644 UIBindingTest/Properties/Settings.Designer.cs create mode 100644 UIBindingTest/Properties/Settings.settings create mode 100644 UIBindingTest/UIBindingTest.csproj diff --git a/Dapper.sln b/Dapper.sln index 91bd7a2f0..6f1d148c7 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -44,6 +44,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.Stro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIBindingTest", "UIBindingTest\UIBindingTest.csproj", "{FC8502EB-DF49-469B-B752-466EE61CC5C4}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -90,6 +92,10 @@ Global {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU + {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -105,6 +111,7 @@ Global {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} + {FC8502EB-DF49-469B-B752-466EE61CC5C4} = {568BD46C-1C65-4D44-870C-12CD72563262} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} diff --git a/Dapper/SqlMapper.DapperRow.Descriptor.cs b/Dapper/SqlMapper.DapperRow.Descriptor.cs new file mode 100644 index 000000000..7ab68f5dc --- /dev/null +++ b/Dapper/SqlMapper.DapperRow.Descriptor.cs @@ -0,0 +1,105 @@ +#if !NETSTANDARD1_3 // needs the component-model API +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Dapper +{ + public static partial class SqlMapper + { + [TypeDescriptionProvider(typeof(DapperRowTypeDescriptionProvider))] + private sealed partial class DapperRow + { + private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider + { + public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance) + => new DapperRowTypeDescriptor(instance); + public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) + => new DapperRowTypeDescriptor(instance); + } + + //// in theory we could implement this for zero-length results to bind; would require + //// additional changes, though, to capture a table even when no rows - so not currently provided + //internal sealed class DapperRowList : List, ITypedList + //{ + // private readonly DapperTable _table; + // public DapperRowList(DapperTable table) { _table = table; } + // PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors) + // { + // if (listAccessors != null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty; + + // return DapperRowTypeDescriptor.GetProperties(_table); + // } + + // string ITypedList.GetListName(PropertyDescriptor[] listAccessors) => null; + //} + + private sealed class DapperRowTypeDescriptor : ICustomTypeDescriptor + { + private readonly DapperRow _row; + public DapperRowTypeDescriptor(object instance) + => _row = (DapperRow)instance; + + AttributeCollection ICustomTypeDescriptor.GetAttributes() + => AttributeCollection.Empty; + + string ICustomTypeDescriptor.GetClassName() => typeof(DapperRow).FullName; + + string ICustomTypeDescriptor.GetComponentName() => null; + + private static readonly TypeConverter s_converter = new ExpandableObjectConverter(); + TypeConverter ICustomTypeDescriptor.GetConverter() => s_converter; + + EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => null; + + PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => null; + + object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null; + + EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty; + + EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty; + + internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row); + internal static PropertyDescriptorCollection GetProperties(DapperTable table, IDictionary row = null) + { + string[] names = table?.FieldNames; + if (names == null || names.Length == 0) return PropertyDescriptorCollection.Empty; + var arr = new PropertyDescriptor[names.Length]; + for (int i = 0; i < arr.Length; i++) + { + var type = row != null && row.TryGetValue(names[i], out var value) && value != null + ? value.GetType() : typeof(object); + arr[i] = new RowBoundPropertyDescriptor(type, names[i]); + } + return new PropertyDescriptorCollection(arr, true); + } + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row); + + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => GetProperties(_row); + + object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => _row; + } + + private sealed class RowBoundPropertyDescriptor : PropertyDescriptor + { + private readonly Type _type; + public RowBoundPropertyDescriptor(Type type, string name) : base(name, null) + => _type = type; + + public override bool CanResetValue(object component) => false; + public override void ResetValue(object component) => throw new NotSupportedException(); + + public override bool IsReadOnly => false; + public override bool ShouldSerializeValue(object component) => true; + public override Type ComponentType => typeof(DapperRow); + public override Type PropertyType => _type; + public override object GetValue(object component) + => ((IDictionary)component).TryGetValue(Name, out var val) ? val : null; + public override void SetValue(object component, object value) + => ((IDictionary)component)[Name] = value; + } + } + } +} +#endif diff --git a/Dapper/SqlMapper.DapperRow.cs b/Dapper/SqlMapper.DapperRow.cs index f4ebaedc5..b81affab5 100644 --- a/Dapper/SqlMapper.DapperRow.cs +++ b/Dapper/SqlMapper.DapperRow.cs @@ -7,9 +7,8 @@ namespace Dapper { public static partial class SqlMapper { - private sealed class DapperRow - : System.Dynamic.IDynamicMetaObjectProvider - , IDictionary + private sealed partial class DapperRow + : IDictionary , IReadOnlyDictionary { private readonly DapperTable table; @@ -78,12 +77,6 @@ public override string ToString() return sb.Append('}').__ToStringRecycle(); } - System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject( - System.Linq.Expressions.Expression parameter) - { - return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this); - } - public IEnumerator> GetEnumerator() { var names = table.FieldNames; diff --git a/Dapper/SqlMapper.DapperRowMetaObject.cs b/Dapper/SqlMapper.DapperRowMetaObject.cs index 789ec680d..3a71b81ab 100644 --- a/Dapper/SqlMapper.DapperRowMetaObject.cs +++ b/Dapper/SqlMapper.DapperRowMetaObject.cs @@ -5,6 +5,15 @@ namespace Dapper { public static partial class SqlMapper { + private sealed partial class DapperRow : System.Dynamic.IDynamicMetaObjectProvider + { + System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMetaObject( + System.Linq.Expressions.Expression parameter) + { + return new DapperRowMetaObject(parameter, System.Dynamic.BindingRestrictions.Empty, this); + } + } + private sealed class DapperRowMetaObject : System.Dynamic.DynamicMetaObject { private static readonly MethodInfo getValueMethod = typeof(IDictionary).GetProperty("Item").GetGetMethod(); @@ -79,6 +88,13 @@ public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.Se return callMethod; } + + static readonly string[] s_nixKeys = new string[0]; + public override IEnumerable GetDynamicMemberNames() + { + if(HasValue && Value is IDictionary lookup) return lookup.Keys; + return s_nixKeys; + } } } } diff --git a/UIBindingTest/App.config b/UIBindingTest/App.config new file mode 100644 index 000000000..b50c74f35 --- /dev/null +++ b/UIBindingTest/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/UIBindingTest/BindingForm.Designer.cs b/UIBindingTest/BindingForm.Designer.cs new file mode 100644 index 000000000..2f88c4059 --- /dev/null +++ b/UIBindingTest/BindingForm.Designer.cs @@ -0,0 +1,63 @@ +namespace UIBindingTest +{ + partial class BindingForm + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.mainGrid = new System.Windows.Forms.DataGridView(); + ((System.ComponentModel.ISupportInitialize)(this.mainGrid)).BeginInit(); + this.SuspendLayout(); + // + // mainGrid + // + this.mainGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; + this.mainGrid.Dock = System.Windows.Forms.DockStyle.Fill; + this.mainGrid.Location = new System.Drawing.Point(0, 0); + this.mainGrid.Name = "mainGrid"; + this.mainGrid.RowTemplate.Height = 28; + this.mainGrid.Size = new System.Drawing.Size(1235, 855); + this.mainGrid.TabIndex = 0; + // + // BindingForm + // + this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1235, 855); + this.Controls.Add(this.mainGrid); + this.Name = "BindingForm"; + this.Text = "Binding Test"; + ((System.ComponentModel.ISupportInitialize)(this.mainGrid)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.DataGridView mainGrid; + } +} + diff --git a/UIBindingTest/BindingForm.cs b/UIBindingTest/BindingForm.cs new file mode 100644 index 000000000..1a665711b --- /dev/null +++ b/UIBindingTest/BindingForm.cs @@ -0,0 +1,19 @@ +using System.Data.SqlClient; +using System.Windows.Forms; +using Dapper; + +namespace UIBindingTest +{ + public partial class BindingForm : Form + { + public BindingForm() + { + InitializeComponent(); + + using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated Security=SSPI")) + { + mainGrid.DataSource = conn.Query("select * from sys.objects").AsList(); + } + } + } +} diff --git a/UIBindingTest/BindingForm.resx b/UIBindingTest/BindingForm.resx new file mode 100644 index 000000000..1af7de150 --- /dev/null +++ b/UIBindingTest/BindingForm.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UIBindingTest/Program.cs b/UIBindingTest/Program.cs new file mode 100644 index 000000000..8800279eb --- /dev/null +++ b/UIBindingTest/Program.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace UIBindingTest +{ + static class Program + { + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new BindingForm()); + } + } +} diff --git a/UIBindingTest/Properties/AssemblyInfo.cs b/UIBindingTest/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..0b416fc55 --- /dev/null +++ b/UIBindingTest/Properties/AssemblyInfo.cs @@ -0,0 +1,22 @@ +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("UIBindingTest")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("UIBindingTest")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("fc8502eb-df49-469b-b752-466ee61cc5c4")] diff --git a/UIBindingTest/Properties/Resources.Designer.cs b/UIBindingTest/Properties/Resources.Designer.cs new file mode 100644 index 000000000..cf72b1e5c --- /dev/null +++ b/UIBindingTest/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UIBindingTest.Properties +{ + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UIBindingTest.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/UIBindingTest/Properties/Resources.resx b/UIBindingTest/Properties/Resources.resx new file mode 100644 index 000000000..af7dbebba --- /dev/null +++ b/UIBindingTest/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/UIBindingTest/Properties/Settings.Designer.cs b/UIBindingTest/Properties/Settings.Designer.cs new file mode 100644 index 000000000..1911d50fd --- /dev/null +++ b/UIBindingTest/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace UIBindingTest.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/UIBindingTest/Properties/Settings.settings b/UIBindingTest/Properties/Settings.settings new file mode 100644 index 000000000..39645652a --- /dev/null +++ b/UIBindingTest/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/UIBindingTest/UIBindingTest.csproj b/UIBindingTest/UIBindingTest.csproj new file mode 100644 index 000000000..68f5bb022 --- /dev/null +++ b/UIBindingTest/UIBindingTest.csproj @@ -0,0 +1,89 @@ + + + + + Debug + AnyCPU + {FC8502EB-DF49-469B-B752-466EE61CC5C4} + WinExe + UIBindingTest + UIBindingTest + v4.6.2 + 512 + true + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + Form + + + BindingForm.cs + + + + + BindingForm.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + + + + {fac24c3f-68f9-4247-a4b9-21d487e99275} + Dapper + + + + \ No newline at end of file From 733d1734cabe527ef78906071981b7ec1ac53378 Mon Sep 17 00:00:00 2001 From: Nigel <40146284+Nij4t2@users.noreply.github.com> Date: Wed, 27 Mar 2019 09:18:05 +0000 Subject: [PATCH 060/312] When reading Geometry and Geography values from the database, we need to specify the SRID explicitly to retain it in the DbGeometry / DbGeography type. (#1038) merging, thanks, and sorry for delay --- Dapper.EntityFramework/DbGeographyHandler.cs | 2 +- Dapper.EntityFramework/DbGeometryHandler.cs | 2 +- .../Providers/EntityFrameworkTests.cs | 27 ++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/Dapper.EntityFramework/DbGeographyHandler.cs b/Dapper.EntityFramework/DbGeographyHandler.cs index c0355dc91..3722c6f72 100644 --- a/Dapper.EntityFramework/DbGeographyHandler.cs +++ b/Dapper.EntityFramework/DbGeographyHandler.cs @@ -51,7 +51,7 @@ public override DbGeography Parse(object value) if (value == null || value is DBNull) return null; if (value is SqlGeography geo) { - return DbGeography.FromBinary(geo.STAsBinary().Value); + return DbGeography.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value); } return DbGeography.FromText(value.ToString()); } diff --git a/Dapper.EntityFramework/DbGeometryHandler.cs b/Dapper.EntityFramework/DbGeometryHandler.cs index bd8582529..f4e518f1c 100644 --- a/Dapper.EntityFramework/DbGeometryHandler.cs +++ b/Dapper.EntityFramework/DbGeometryHandler.cs @@ -51,7 +51,7 @@ public override DbGeometry Parse(object value) if (value == null || value is DBNull) return null; if (value is SqlGeometry geo) { - return DbGeometry.FromBinary(geo.STAsBinary().Value); + return DbGeometry.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value); } return DbGeometry.FromText(value.ToString()); } diff --git a/Dapper.Tests/Providers/EntityFrameworkTests.cs b/Dapper.Tests/Providers/EntityFrameworkTests.cs index 1f991b01c..4dd01b062 100644 --- a/Dapper.Tests/Providers/EntityFrameworkTests.cs +++ b/Dapper.Tests/Providers/EntityFrameworkTests.cs @@ -1,5 +1,7 @@ #if ENTITY_FRAMEWORK +using System; using System.Data.Entity.Spatial; +using System.Linq; using Xunit; namespace Dapper.Tests.Providers @@ -16,7 +18,7 @@ public EntityFrameworkTests() public void Issue570_DbGeo_HasValues() { EntityFramework.Handlers.Register(); - const string redmond = "POINT (122.1215 47.6740)"; + const string redmond = "POINT (-122.1215 47.6740)"; DbGeography point = DbGeography.PointFromText(redmond, DbGeography.DefaultCoordinateSystemId); DbGeography orig = point.Buffer(20); @@ -34,6 +36,29 @@ public void Issue22_ExecuteScalar_EntityFramework() var geo2 = connection.ExecuteScalar("select @geo", new { geo }); Assert.NotNull(geo2); } + + [Fact] + public void TestGeometryParsingRetainsSrid() + { + const int srid = 27700; + var s = $@"DECLARE @EdinburghPoint GEOMETRY = geometry::STPointFromText('POINT(258647 665289)', {srid}); +SELECT @EdinburghPoint"; + var edinPoint = connection.Query(s).Single(); + Assert.NotNull(edinPoint); + Assert.Equal(srid, edinPoint.CoordinateSystemId); + } + + [Fact] + public void TestGeographyParsingRetainsSrid() + { + const int srid = 4324; + var s = $@"DECLARE @EdinburghPoint GEOGRAPHY = geography::STPointFromText('POINT(-3.19 55.95)', {srid}); +SELECT @EdinburghPoint"; + var edinPoint = connection.Query(s).Single(); + Assert.NotNull(edinPoint); + Assert.Equal(srid, edinPoint.CoordinateSystemId); + } + } } #endif From d669d141f3e8c2ee45abbde0c518ffaedbfc4d8f Mon Sep 17 00:00:00 2001 From: mgravell Date: Sat, 30 Mar 2019 17:20:28 +0000 Subject: [PATCH 061/312] descriptor API: use index rather than constantly looking up the name --- Dapper/SqlMapper.DapperRow.Descriptor.cs | 22 ++++++++++++---------- Dapper/SqlMapper.DapperRow.cs | 12 ++++++++++-- UIBindingTest/BindingForm.cs | 2 ++ 3 files changed, 24 insertions(+), 12 deletions(-) diff --git a/Dapper/SqlMapper.DapperRow.Descriptor.cs b/Dapper/SqlMapper.DapperRow.Descriptor.cs index 7ab68f5dc..909cbd9f4 100644 --- a/Dapper/SqlMapper.DapperRow.Descriptor.cs +++ b/Dapper/SqlMapper.DapperRow.Descriptor.cs @@ -70,7 +70,7 @@ internal static PropertyDescriptorCollection GetProperties(DapperTable table, ID { var type = row != null && row.TryGetValue(names[i], out var value) && value != null ? value.GetType() : typeof(object); - arr[i] = new RowBoundPropertyDescriptor(type, names[i]); + arr[i] = new RowBoundPropertyDescriptor(type, names[i], i); } return new PropertyDescriptorCollection(arr, true); } @@ -84,20 +84,22 @@ internal static PropertyDescriptorCollection GetProperties(DapperTable table, ID private sealed class RowBoundPropertyDescriptor : PropertyDescriptor { private readonly Type _type; - public RowBoundPropertyDescriptor(Type type, string name) : base(name, null) - => _type = type; - - public override bool CanResetValue(object component) => false; - public override void ResetValue(object component) => throw new NotSupportedException(); - + private readonly int _index; + public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name, null) + { + _type = type; + _index = index; + } + public override bool CanResetValue(object component) => true; + public override void ResetValue(object component) => ((DapperRow)component).Remove(_index); public override bool IsReadOnly => false; - public override bool ShouldSerializeValue(object component) => true; + public override bool ShouldSerializeValue(object component) => ((DapperRow)component).TryGetValue(_index, out _); public override Type ComponentType => typeof(DapperRow); public override Type PropertyType => _type; public override object GetValue(object component) - => ((IDictionary)component).TryGetValue(Name, out var val) ? val : null; + => ((DapperRow)component).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value; public override void SetValue(object component, object value) - => ((IDictionary)component)[Name] = value; + => ((DapperRow)component).SetValue(_index, value is DBNull ? null : value); } } } diff --git a/Dapper/SqlMapper.DapperRow.cs b/Dapper/SqlMapper.DapperRow.cs index b81affab5..bb7afb0a9 100644 --- a/Dapper/SqlMapper.DapperRow.cs +++ b/Dapper/SqlMapper.DapperRow.cs @@ -40,8 +40,10 @@ int ICollection>.Count } public bool TryGetValue(string key, out object value) + => TryGetValue(table.IndexOfName(key), out value); + + internal bool TryGetValue(int index, out object value) { - var index = table.IndexOfName(key); if (index < 0) { // doesn't exist value = null; @@ -146,8 +148,10 @@ void IDictionary.Add(string key, object value) } bool IDictionary.Remove(string key) + => Remove(table.IndexOfName(key)); + + internal bool Remove(int index) { - int index = table.IndexOfName(key); if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; values[index] = DeadValue.Default; return true; @@ -177,6 +181,10 @@ private object SetValue(string key, object value, bool isAdd) // then semantically, this value already exists throw new ArgumentException("An item with the same key has already been added", nameof(key)); } + return SetValue(index, value); + } + internal object SetValue(int index, object value) + { int oldLength = values.Length; if (oldLength <= index) { diff --git a/UIBindingTest/BindingForm.cs b/UIBindingTest/BindingForm.cs index 1a665711b..77b8d802b 100644 --- a/UIBindingTest/BindingForm.cs +++ b/UIBindingTest/BindingForm.cs @@ -10,10 +10,12 @@ public BindingForm() { InitializeComponent(); + SuspendLayout(); using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated Security=SSPI")) { mainGrid.DataSource = conn.Query("select * from sys.objects").AsList(); } + ResumeLayout(); } } } From 23ff459f9d24c6295053e81a2409dc09d4414929 Mon Sep 17 00:00:00 2001 From: mgravell Date: Sat, 30 Mar 2019 17:22:50 +0000 Subject: [PATCH 062/312] release notes --- docs/index.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/index.md b/docs/index.md index 9c9b1d898..ba91abbd2 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,14 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes +### 1.60.7 + +- improve performance of descriptor API + +### 1.60.5 + +- add descriptor API to `DapperRow` (enables UI binding with non-generic `Query()` API) + ### 1.60.1 - Fix [#1196](https://github.com/StackExchange/Dapper/issues/1196) - versioning fix only ([#1198](https://github.com/StackExchange/Dapper/pull/1198)) - assembly version is now locked at 1.60.0 to resolve some mismatch issues with .NET Core assembly loading/binding. From c1d0fd870d82d40cab2ca38be210e56245407e2f Mon Sep 17 00:00:00 2001 From: mgravell Date: Sat, 30 Mar 2019 17:30:33 +0000 Subject: [PATCH 063/312] typo on release note version --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index ba91abbd2..2c03bf691 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes -### 1.60.7 +### 1.60.6 - improve performance of descriptor API From d19d6012e75feebf24d591ad21b16f13becc95f2 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 5 Apr 2019 11:39:04 +0100 Subject: [PATCH 064/312] fix #1228 - rev sqlclient to 4.6.0 (#1229) * fix #1228 - rev sqlclient to 4.6.0 * use transitive deps for SqlClient --- Dapper.StrongName/Dapper.StrongName.csproj | 2 +- Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj | 1 - Dapper.Tests.Performance/Dapper.Tests.Performance.csproj | 1 - Dapper.Tests/Dapper.Tests.csproj | 1 - Dapper/Dapper.csproj | 2 +- 5 files changed, 2 insertions(+), 5 deletions(-) diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 22d0a419e..011b906eb 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -20,7 +20,7 @@ - + diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 5c5d6ce6f..5fe94fcb4 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -19,7 +19,6 @@ - diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 255616bf2..75c161196 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -33,7 +33,6 @@ - diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 13aea73f6..b1301b2ee 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -23,7 +23,6 @@ - diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index c62cb33ad..a4f85ad37 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -15,7 +15,7 @@ - + From dac717d3ae2e0e0906b1683a9822e00643d7b54e Mon Sep 17 00:00:00 2001 From: Dmytro Gokun Date: Mon, 6 May 2019 13:58:17 +0300 Subject: [PATCH 065/312] Fix Rainbow Database.Dispose() so that it never throws and not overrides the active exception (if any) --- Dapper.Rainbow/Database.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index 7bd0f91c4..f478f16d7 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -466,13 +466,10 @@ public SqlMapper.GridReader QueryMultiple(string sql, dynamic param = null, IDbT /// public void Dispose() { - if (_connection.State != ConnectionState.Closed) - { - _transaction?.Rollback(); - - _connection.Close(); - _connection = null; - } + var connection = _connection; + _connection = null; + _transaction = null; + connection?.Close(); } } } From a5256b7d94a4da411d499b31d6cd9a3b966d2b49 Mon Sep 17 00:00:00 2001 From: Jonathan Channon Date: Mon, 3 Jun 2019 14:05:12 +0100 Subject: [PATCH 066/312] Remove .NET 45 in XML docs (#1271) --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 10 ++-- Dapper.Rainbow/Database.Async.cs | 2 +- Dapper/SqlMapper.Async.cs | 64 ++++++++++----------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index dc6e0b9b3..d7f975c5c 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -12,7 +12,7 @@ namespace Dapper.Contrib.Extensions public static partial class SqlMapperExtensions { /// - /// Returns a single entity by a single id from table "Ts" asynchronously using .NET 4.5 Task. T must be of interface type. + /// Returns a single entity by a single id from table "Ts" asynchronously using Task. T must be of interface type. /// Id must be marked with [Key] attribute. /// Created entity is tracked/intercepted for changes and used by the Update() extension. /// @@ -127,7 +127,7 @@ private static async Task> GetAllAsyncImpl(IDbConnection conne } /// - /// Inserts an entity into table "Ts" asynchronously using .NET 4.5 Task and returns identity id. + /// Inserts an entity into table "Ts" asynchronously using Task and returns identity id. /// /// The type being inserted. /// Open SqlConnection @@ -198,7 +198,7 @@ public static Task InsertAsync(this IDbConnection connection, T entityTo } /// - /// Updates entity in table "Ts" asynchronously using .NET 4.5 Task, checks if the entity is modified if the entity is tracked by the Get() extension. + /// Updates entity in table "Ts" asynchronously using Task, checks if the entity is modified if the entity is tracked by the Get() extension. /// /// Type to be updated /// Open SqlConnection @@ -269,7 +269,7 @@ public static async Task UpdateAsync(this IDbConnection connection, T e } /// - /// Delete entity in table "Ts" asynchronously using .NET 4.5 Task. + /// Delete entity in table "Ts" asynchronously using Task. /// /// Type of entity /// Open SqlConnection @@ -326,7 +326,7 @@ public static async Task DeleteAsync(this IDbConnection connection, T e } /// - /// Delete all entities in the table related to the type T asynchronously using .NET 4.5 Task. + /// Delete all entities in the table related to the type T asynchronously using Task. /// /// Type of entity /// Open SqlConnection diff --git a/Dapper.Rainbow/Database.Async.cs b/Dapper.Rainbow/Database.Async.cs index d247e9e18..ab42864d4 100644 --- a/Dapper.Rainbow/Database.Async.cs +++ b/Dapper.Rainbow/Database.Async.cs @@ -189,7 +189,7 @@ public Task> QueryAsync - /// Execute a query asynchronously using .NET 4.5 Task. + /// Execute a query asynchronously using Task. /// /// The SQL to execute. /// The parameters to use. diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index c25f06cfe..c923dd0a0 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -13,7 +13,7 @@ namespace Dapper public static partial class SqlMapper { /// - /// Execute a query asynchronously using .NET 4.5 Task. + /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. @@ -26,7 +26,7 @@ public static Task> QueryAsync(this IDbConnection cnn, stri QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); /// - /// Execute a query asynchronously using .NET 4.5 Task. + /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. @@ -35,7 +35,7 @@ public static Task> QueryAsync(this IDbConnection cnn, Comm QueryAsync(cnn, typeof(DapperRow), command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. @@ -44,7 +44,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefin QueryRowAsync(cnn, Row.First, typeof(DapperRow), command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. @@ -53,7 +53,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Com QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. @@ -62,7 +62,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefi QueryRowAsync(cnn, Row.Single, typeof(DapperRow), command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The command used to query on this connection. @@ -71,7 +71,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Co QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), command); /// - /// Execute a query asynchronously using .NET 4.5 Task. + /// Execute a query asynchronously using Task. /// /// The type of results to return. /// The connection to query on. @@ -88,7 +88,7 @@ public static Task> QueryAsync(this IDbConnection cnn, string QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type of result to return. /// The connection to query on. @@ -101,7 +101,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, obj QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type of result to return. /// The connection to query on. @@ -114,7 +114,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type of result to return. /// The connection to query on. @@ -127,7 +127,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, ob QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. @@ -140,7 +140,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, strin QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. @@ -152,7 +152,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. @@ -164,7 +164,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, str QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. @@ -176,7 +176,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for the query. @@ -188,7 +188,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, st QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); /// - /// Execute a query asynchronously using .NET 4.5 Task. + /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -205,7 +205,7 @@ public static Task> QueryAsync(this IDbConnection cnn, Type } /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -221,7 +221,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, Type type, st return QueryRowAsync(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); } /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -237,7 +237,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type return QueryRowAsync(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); } /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -253,7 +253,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, Type type, s return QueryRowAsync(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); } /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -270,7 +270,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Typ } /// - /// Execute a query asynchronously using .NET 4.5 Task. + /// Execute a query asynchronously using Task. /// /// The type to return. /// The connection to query on. @@ -283,7 +283,7 @@ public static Task> QueryAsync(this IDbConnection cnn, Command QueryAsync(cnn, typeof(T), command); /// - /// Execute a query asynchronously using .NET 4.5 Task. + /// Execute a query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -292,7 +292,7 @@ public static Task> QueryAsync(this IDbConnection cnn, Type QueryAsync(cnn, type, command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -301,7 +301,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, Type type, Co QueryRowAsync(cnn, Row.First, type, command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. @@ -310,7 +310,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefiniti QueryRowAsync(cnn, Row.First, typeof(T), command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -319,7 +319,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type QueryRowAsync(cnn, Row.FirstOrDefault, type, command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. @@ -328,7 +328,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Comman QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -337,7 +337,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, Type type, C QueryRowAsync(cnn, Row.Single, type, command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. @@ -346,7 +346,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinit QueryRowAsync(cnn, Row.Single, typeof(T), command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The connection to query on. /// The type to return. @@ -355,7 +355,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Typ QueryRowAsync(cnn, Row.SingleOrDefault, type, command); /// - /// Execute a single-row query asynchronously using .NET 4.5 Task. + /// Execute a single-row query asynchronously using Task. /// /// The type to return. /// The connection to query on. @@ -526,7 +526,7 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T } /// - /// Execute a command asynchronously using .NET 4.5 Task. + /// Execute a command asynchronously using Task. /// /// The connection to query on. /// The SQL to execute for this query. @@ -539,7 +539,7 @@ public static Task ExecuteAsync(this IDbConnection cnn, string sql, object ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); /// - /// Execute a command asynchronously using .NET 4.5 Task. + /// Execute a command asynchronously using Task. /// /// The connection to execute on. /// The command to execute on this connection. From 33d974a5c021f92e98f67b625d5902e088fc5c4d Mon Sep 17 00:00:00 2001 From: Joseph Musser Date: Wed, 12 Jun 2019 11:16:45 -0400 Subject: [PATCH 067/312] Fixes mapping for C# tuples with more than seven elements (#1242) * Failing tests for tuples larger than 7 * Special-case IL generation for ValueTuple * Make value-processing logic available to ValueTuple generation --- Dapper.Tests/TupleTests.cs | 43 +++++ Dapper/SqlMapper.cs | 369 +++++++++++++++++++++++-------------- 2 files changed, 276 insertions(+), 136 deletions(-) diff --git a/Dapper.Tests/TupleTests.cs b/Dapper.Tests/TupleTests.cs index 562397b7e..271e39200 100644 --- a/Dapper.Tests/TupleTests.cs +++ b/Dapper.Tests/TupleTests.cs @@ -51,5 +51,48 @@ public void TupleReturnValue_Works_NamesIgnored() Assert.Equal(42, val.id); Assert.Equal("Fred", val.name); } + + [Fact] + public void TupleReturnValue_Works_With8Elements() + { + // C# encodes an 8-tuple as ValueTuple> + + var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8)>( + "select 1, 2, 3, 4, 5, 6, 7, 8"); + + Assert.Equal(1, val.e1); + Assert.Equal(2, val.e2); + Assert.Equal(3, val.e3); + Assert.Equal(4, val.e4); + Assert.Equal(5, val.e5); + Assert.Equal(6, val.e6); + Assert.Equal(7, val.e7); + Assert.Equal(8, val.e8); + } + + [Fact] + public void TupleReturnValue_Works_With15Elements() + { + // C# encodes a 15-tuple as ValueTuple>> + + var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8, int e9, int e10, int e11, int e12, int e13, int e14, int e15)>( + "select 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"); + + Assert.Equal(1, val.e1); + Assert.Equal(2, val.e2); + Assert.Equal(3, val.e3); + Assert.Equal(4, val.e4); + Assert.Equal(5, val.e5); + Assert.Equal(6, val.e6); + Assert.Equal(7, val.e7); + Assert.Equal(8, val.e8); + Assert.Equal(9, val.e9); + Assert.Equal(10, val.e10); + Assert.Equal(11, val.e11); + Assert.Equal(12, val.e12); + Assert.Equal(13, val.e13); + Assert.Equal(14, val.e14); + Assert.Equal(15, val.e15); + } } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 4bce92ea1..306a114ed 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1239,7 +1239,7 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti } /// - /// Perform a multi-mapping query with 2 input types. + /// Perform a multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -1259,7 +1259,7 @@ public static IEnumerable Query(this IDbConne MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// - /// Perform a multi-mapping query with 3 input types. + /// Perform a multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -1280,7 +1280,7 @@ public static IEnumerable Query(this MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// - /// Perform a multi-mapping query with 4 input types. + /// Perform a multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -1302,7 +1302,7 @@ public static IEnumerable Query(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// - /// Perform a multi-mapping query with 5 input types. + /// Perform a multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -1325,7 +1325,7 @@ public static IEnumerable Query(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// - /// Perform a multi-mapping query with 6 input types. + /// Perform a multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -1374,7 +1374,7 @@ public static IEnumerable Query(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// - /// Perform a multi-mapping query with an arbitrary number of input types. + /// Perform a multi-mapping query with an arbitrary number of input types. /// This returns a single type, combined from the raw types via . /// /// The combined type to return. @@ -2372,27 +2372,6 @@ public static Action CreateParamInfoGenerator(Identity ident private static bool IsValueTuple(Type type) => type?.IsValueType() == true && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal); - private static List GetValueTupleMembers(Type type, string[] names) - { - var fields = type.GetFields(BindingFlags.Public | BindingFlags.Instance); - var result = new List(names.Length); - for (int i = 0; i < names.Length; i++) - { - FieldInfo field = null; - string name = "Item" + (i + 1).ToString(CultureInfo.InvariantCulture); - foreach (var test in fields) - { - if (test.Name == name) - { - field = test; - break; - } - } - result.Add(field == null ? null : new SimpleMemberMap(string.IsNullOrWhiteSpace(names[i]) ? name : names[i], field)); - } - return result; - } - internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { Type type = identity.parametersType; @@ -3079,14 +3058,6 @@ private static Func GetTypeDeserializerImpl( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { - var returnType = type.IsValueType() ? typeof(object) : type; - var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, true); - var il = dm.GetILGenerator(); - il.DeclareLocal(typeof(int)); - il.DeclareLocal(type); - il.Emit(OpCodes.Ldc_I4_0); - il.Emit(OpCodes.Stloc_0); - if (length == -1) { length = reader.FieldCount - startBound; @@ -3097,6 +3068,121 @@ private static Func GetTypeDeserializerImpl( throw MultiMapException(reader); } + var returnType = type.IsValueType() ? typeof(object) : type; + var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, true); + var il = dm.GetILGenerator(); + + if (IsValueTuple(type)) + { + GenerateValueTupleDeserializer(type, reader, startBound, length, il); + } + else + { + GenerateDeserializerFromMap(type, reader, startBound, length, returnNullIfFirstMissing, il); + } + + var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(IDataReader), returnType); + return (Func)dm.CreateDelegate(funcType); + } + + private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataReader reader, int startBound, int length, ILGenerator il) + { + var currentValueTupleType = valueTupleType; + + var constructors = new List(); + var languageTupleElementTypes = new List(); + + while (true) + { + var arity = int.Parse(currentValueTupleType.Name.Substring("ValueTuple`".Length), CultureInfo.InvariantCulture); + var constructorParameterTypes = new Type[arity]; + var restField = (FieldInfo)null; + + foreach (var field in currentValueTupleType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) + { + if (field.Name == "Rest") + { + restField = field; + } + else if (field.Name.StartsWith("Item", StringComparison.Ordinal)) + { + var elementNumber = int.Parse(field.Name.Substring("Item".Length), CultureInfo.InvariantCulture); + constructorParameterTypes[elementNumber - 1] = field.FieldType; + } + } + + var itemFieldCount = constructorParameterTypes.Length; + if (restField != null) itemFieldCount--; + + for (var i = 0; i < itemFieldCount; i++) + { + languageTupleElementTypes.Add(constructorParameterTypes[i]); + } + + if (restField != null) + { + constructorParameterTypes[constructorParameterTypes.Length - 1] = restField.FieldType; + } + + constructors.Add(currentValueTupleType.GetConstructor(constructorParameterTypes)); + + if (restField is null) break; + + currentValueTupleType = restField.FieldType; + if (!IsValueTuple(currentValueTupleType)) + { + throw new InvalidOperationException("The Rest field of a ValueTuple must contain a nested ValueTuple of arity 1 or greater."); + } + } + + var enumDeclareLocal = -1; + + for (var i = 0; i < languageTupleElementTypes.Count; i++) + { + var targetType = languageTupleElementTypes[i]; + + if (i < length) + { + LoadReaderValueOrBranchToDBNullLabel( + il, + startBound + i, + ref enumDeclareLocal, + valueCopyLocal: null, + reader.GetFieldType(startBound + i), + targetType, + out var isDbNullLabel); + + var finishLabel = il.DefineLabel(); + il.Emit(OpCodes.Br_S, finishLabel); + il.MarkLabel(isDbNullLabel); + il.Emit(OpCodes.Pop); + + LoadDefaultValue(il, targetType); + + il.MarkLabel(finishLabel); + } + else + { + LoadDefaultValue(il, targetType); + } + } + + for (var i = constructors.Count - 1; i >= 0; i--) + { + il.Emit(OpCodes.Newobj, constructors[i]); + } + + il.Emit(OpCodes.Box, valueTupleType); + il.Emit(OpCodes.Ret); + } + + private static void GenerateDeserializerFromMap(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing, ILGenerator il) + { + il.DeclareLocal(typeof(int)); + il.DeclareLocal(type); + il.Emit(OpCodes.Ldc_I4_0); + il.Emit(OpCodes.Stloc_0); + var names = Enumerable.Range(startBound, length).Select(i => reader.GetName(i)).ToArray(); ITypeMap typeMap = GetTypeMap(type); @@ -3187,9 +3273,9 @@ private static Func GetTypeDeserializerImpl( il.Emit(OpCodes.Ldloc_1);// [target] } - var members = IsValueTuple(type) ? GetValueTupleMembers(type, names) : ((specializedConstructor != null + var members = (specializedConstructor != null ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) - : names.Select(n => typeMap.GetMember(n))).ToList()); + : names.Select(n => typeMap.GetMember(n))).ToList(); // stack is now [target] @@ -3203,96 +3289,11 @@ private static Func GetTypeDeserializerImpl( { if (specializedConstructor == null) il.Emit(OpCodes.Dup); // stack is now [target][target] - Label isDbNullLabel = il.DefineLabel(); Label finishLabel = il.DefineLabel(); - - il.Emit(OpCodes.Ldarg_0); // stack is now [target][target][reader] - EmitInt32(il, index); // stack is now [target][target][reader][index] - il.Emit(OpCodes.Dup);// stack is now [target][target][reader][index][index] - il.Emit(OpCodes.Stloc_0);// stack is now [target][target][reader][index] - il.Emit(OpCodes.Callvirt, getItem); // stack is now [target][target][value-as-object] - il.Emit(OpCodes.Dup); // stack is now [target][target][value-as-object][value-as-object] - StoreLocal(il, valueCopyLocal); - Type colType = reader.GetFieldType(index); Type memberType = item.MemberType; - if (memberType == typeof(char) || memberType == typeof(char?)) - { - il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( - memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.ReadNullableChar), BindingFlags.Static | BindingFlags.Public), null); // stack is now [target][target][typed-value] - } - else - { - il.Emit(OpCodes.Dup); // stack is now [target][target][value][value] - il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [target][target][value-as-object][DBNull or null] - il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [target][target][value-as-object] - - // unbox nullable enums as the primitive, i.e. byte etc - - var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); - var unboxType = nullUnderlyingType?.IsEnum() == true ? nullUnderlyingType : memberType; + LoadReaderValueOrBranchToDBNullLabel(il, index, ref enumDeclareLocal, valueCopyLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel); - if (unboxType.IsEnum()) - { - Type numericType = Enum.GetUnderlyingType(unboxType); - if (colType == typeof(string)) - { - if (enumDeclareLocal == -1) - { - enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; - } - il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [target][target][string] - StoreLocal(il, enumDeclareLocal); // stack is now [target][target] - il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [target][target][enum-type-token] - il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null);// stack is now [target][target][enum-type] - LoadLocal(il, enumDeclareLocal); // stack is now [target][target][enum-type][string] - il.Emit(OpCodes.Ldc_I4_1); // stack is now [target][target][enum-type][string][true] - il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [target][target][enum-as-object] - il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] - } - else - { - FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); - } - - if (nullUnderlyingType != null) - { - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value] - } - } - else if (memberType.FullName == LinqBinary) - { - il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [target][target][byte-array] - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [target][target][binary] - } - else - { - TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType); - bool hasTypeHandler; - if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) - { - if (hasTypeHandler) - { -#pragma warning disable 618 - il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse)), null); // stack is now [target][target][typed-value] -#pragma warning restore 618 - } - else - { - il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [target][target][typed-value] - } - } - else - { - // not a direct match; need to tweak the unbox - FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null); - if (nullUnderlyingType != null) - { - il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [target][target][typed-value] - } - } - } - } if (specializedConstructor == null) { // Store the value in the property/field @@ -3312,17 +3313,7 @@ private static Func GetTypeDeserializerImpl( if (specializedConstructor != null) { il.Emit(OpCodes.Pop); - if (item.MemberType.IsValueType()) - { - int localIndex = il.DeclareLocal(item.MemberType).LocalIndex; - LoadLocalAddress(il, localIndex); - il.Emit(OpCodes.Initobj, item.MemberType); - LoadLocal(il, localIndex); - } - else - { - il.Emit(OpCodes.Ldnull); - } + LoadDefaultValue(il, item.MemberType); } else if (applyNullSetting && (!memberType.IsValueType() || Nullable.GetUnderlyingType(memberType) != null)) { @@ -3400,9 +3391,115 @@ private static Func GetTypeDeserializerImpl( il.Emit(OpCodes.Box, type); } il.Emit(OpCodes.Ret); + } - var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(IDataReader), returnType); - return (Func)dm.CreateDelegate(funcType); + private static void LoadDefaultValue(ILGenerator il, Type type) + { + if (type.IsValueType()) + { + int localIndex = il.DeclareLocal(type).LocalIndex; + LoadLocalAddress(il, localIndex); + il.Emit(OpCodes.Initobj, type); + LoadLocal(il, localIndex); + } + else + { + il.Emit(OpCodes.Ldnull); + } + } + + private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref int enumDeclareLocal, int? valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel) + { + isDbNullLabel = il.DefineLabel(); + il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] + EmitInt32(il, index); // stack is now [...][reader][index] + il.Emit(OpCodes.Dup);// stack is now [...][reader][index][index] + il.Emit(OpCodes.Stloc_0);// stack is now [...][reader][index] + il.Emit(OpCodes.Callvirt, getItem); // stack is now [...][value-as-object] + + if (valueCopyLocal != null) + { + il.Emit(OpCodes.Dup); // stack is now [...][value-as-object][value-as-object] + StoreLocal(il, valueCopyLocal.Value); // stack is now [...][value-as-object] + } + + if (memberType == typeof(char) || memberType == typeof(char?)) + { + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( + memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.ReadNullableChar), BindingFlags.Static | BindingFlags.Public), null); // stack is now [...][typed-value] + } + else + { + il.Emit(OpCodes.Dup); // stack is now [...][value-as-object][value-as-object] + il.Emit(OpCodes.Isinst, typeof(DBNull)); // stack is now [...][value-as-object][DBNull or null] + il.Emit(OpCodes.Brtrue_S, isDbNullLabel); // stack is now [...][value-as-object] + + // unbox nullable enums as the primitive, i.e. byte etc + + var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); + var unboxType = nullUnderlyingType?.IsEnum() == true ? nullUnderlyingType : memberType; + + if (unboxType.IsEnum()) + { + Type numericType = Enum.GetUnderlyingType(unboxType); + if (colType == typeof(string)) + { + if (enumDeclareLocal == -1) + { + enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; + } + il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] + StoreLocal(il, enumDeclareLocal); // stack is now [...] + il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] + il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null);// stack is now [...][enum-type] + LoadLocal(il, enumDeclareLocal); // stack is now [...][enum-type][string] + il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] + il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] + il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] + } + else + { + FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); + } + + if (nullUnderlyingType != null) + { + il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [...][typed-value] + } + } + else if (memberType.FullName == LinqBinary) + { + il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [...][byte-array] + il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [...][binary] + } + else + { + TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType); + bool hasTypeHandler; + if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) + { + if (hasTypeHandler) + { +#pragma warning disable 618 + il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse)), null); // stack is now [...][typed-value] +#pragma warning restore 618 + } + else + { + il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] + } + } + else + { + // not a direct match; need to tweak the unbox + FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null); + if (nullUnderlyingType != null) + { + il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [...][typed-value] + } + } + } + } } private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type via) From 9fbba78da0fe09d8829c8bc4ebd0eb45aa57be32 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 12 Jun 2019 16:24:38 +0100 Subject: [PATCH 068/312] Make Dapper happy with Microsoft.Data.SqlClient (#1262) * prep work for adding Microsoft.Data.SqlClient support/tests; SqlClient dependency is not yet removed --- Dapper.StrongName/Dapper.StrongName.csproj | 10 +- .../Dapper.Tests.Contrib.csproj | 1 + Dapper.Tests/AsyncTests.cs | 99 +++--- Dapper.Tests/ConstructorTests.cs | 7 +- Dapper.Tests/Dapper.Tests.csproj | 18 +- Dapper.Tests/DataReaderTests.cs | 7 +- Dapper.Tests/DecimalTests.cs | 6 +- Dapper.Tests/EnumTests.cs | 6 +- Dapper.Tests/Helpers/Attributes.cs | 10 +- Dapper.Tests/LiteralTests.cs | 6 +- Dapper.Tests/MiscTests.cs | 22 +- Dapper.Tests/MultiMapTests.cs | 6 +- Dapper.Tests/NullTests.cs | 8 +- Dapper.Tests/ParameterTests.cs | 41 ++- Dapper.Tests/ProcedureTests.cs | 6 +- .../Providers/EntityFrameworkTests.cs | 7 +- Dapper.Tests/Providers/FirebirdTests.cs | 15 +- Dapper.Tests/Providers/Linq2SqlTests.cs | 6 +- Dapper.Tests/Providers/MySQLTests.cs | 96 ++++-- Dapper.Tests/Providers/OLDEBTests.cs | 16 +- Dapper.Tests/Providers/PostgresqlTests.cs | 18 +- Dapper.Tests/Providers/SqliteTests.cs | 144 +++++---- Dapper.Tests/QueryMultipleTests.cs | 6 +- Dapper.Tests/TestBase.cs | 106 +++++-- Dapper.Tests/TransactionTests.cs | 6 +- Dapper.Tests/TupleTests.cs | 6 +- Dapper.Tests/TypeHandlerTests.cs | 8 +- Dapper.Tests/XmlTests.cs | 6 +- Dapper/Dapper.csproj | 10 +- Dapper/SqlDataRecordHandler.cs | 7 +- Dapper/SqlDataRecordListTVPParameter.cs | 83 ++++- Dapper/SqlMapper.Async.cs | 15 +- Dapper/SqlMapper.cs | 21 +- Dapper/TableValuedParameter.cs | 23 +- Dapper/UdtTypeHandler.cs | 6 +- Dapper/WrappedReader.cs | 300 ++++++++++++++---- global.json | 6 + 37 files changed, 836 insertions(+), 328 deletions(-) create mode 100644 global.json diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 011b906eb..fc1ec3a56 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -5,7 +5,7 @@ Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net451;netstandard1.3;netstandard2.0 + net451;netstandard1.3;netstandard2.0;netcoreapp2.1 true true @@ -20,9 +20,15 @@ - + + + + + + + diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 5fe94fcb4..884b30e11 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -21,5 +21,6 @@ + diff --git a/Dapper.Tests/AsyncTests.cs b/Dapper.Tests/AsyncTests.cs index 186a6cf6e..535badc26 100644 --- a/Dapper.Tests/AsyncTests.cs +++ b/Dapper.Tests/AsyncTests.cs @@ -4,15 +4,31 @@ using System; using System.Threading.Tasks; using System.Threading; -using System.Data.SqlClient; using Xunit; +using System.Data.Common; namespace Dapper.Tests { - public class Tests : TestBase + [Collection(NonParallelDefinition.Name)] + public sealed class SystemSqlClientAsyncTests : AsyncTests { } +#if MSSQLCLIENT + [Collection(NonParallelDefinition.Name)] + public sealed class MicrosoftSqlClientAsyncTests : AsyncTests { } +#endif + + [Collection(NonParallelDefinition.Name)] + public sealed class SystemSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { } +#if MSSQLCLIENT + [Collection(NonParallelDefinition.Name)] + public sealed class MicrosoftSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { } +#endif + + + public abstract class AsyncTests : TestBase where TProvider : SqlServerDatabaseProvider { - private SqlConnection _marsConnection; - private SqlConnection MarsConnection => _marsConnection ?? (_marsConnection = GetOpenConnection(true)); + private DbConnection _marsConnection; + + private DbConnection MarsConnection => _marsConnection ?? (_marsConnection = Provider.GetOpenConnection(true)); [Fact] public async Task TestBasicStringUsageAsync() @@ -100,7 +116,7 @@ public void TestLongOperationWithCancellation() } catch (AggregateException agg) { - Assert.True(agg.InnerException is SqlException); + Assert.True(agg.InnerException.GetType().Name == "SqlException"); } } @@ -382,38 +398,6 @@ public void RunSequentialVersusParallelSync() Console.WriteLine("Pipeline: {0}ms", watch.ElapsedMilliseconds); } - [Collection(NonParallelDefinition.Name)] - public class AsyncQueryCacheTests : TestBase - { - private SqlConnection _marsConnection; - private SqlConnection MarsConnection => _marsConnection ?? (_marsConnection = GetOpenConnection(true)); - - [Fact] - public void AssertNoCacheWorksForQueryMultiple() - { - 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(); - Assert.Equal(0, before); - Assert.Equal(0, after); - Assert.Equal(123, c); - Assert.Equal(456, d); - } - } - private class BasicType { public string Value { get; set; } @@ -827,7 +811,46 @@ public async Task Issue563_QueryAsyncShouldThrowException() var data = (await connection.QueryAsync("select 1 union all select 2; RAISERROR('after select', 16, 1);").ConfigureAwait(false)).ToList(); Assert.True(false, "Expected Exception"); } - catch (SqlException ex) when (ex.Message == "after select") { /* swallow only this */ } + catch (Exception ex) when (ex.GetType().Name == "SqlException" && ex.Message == "after select") { /* swallow only this */ } + } + } + + [Collection(NonParallelDefinition.Name)] + public abstract class AsyncQueryCacheTests : TestBase where TProvider : SqlServerDatabaseProvider + { + private DbConnection _marsConnection; + private DbConnection MarsConnection => _marsConnection ?? (_marsConnection = Provider.GetOpenConnection(true)); + + public override void Dispose() + { + _marsConnection?.Dispose(); + _marsConnection = null; + base.Dispose(); + } + + [Fact] + public void AssertNoCacheWorksForQueryMultiple() + { + 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(); + Assert.Equal(0, before); + Assert.Equal(0, after); + Assert.Equal(123, c); + Assert.Equal(456, d); } } } diff --git a/Dapper.Tests/ConstructorTests.cs b/Dapper.Tests/ConstructorTests.cs index baac5f6ab..1acd1e6b4 100644 --- a/Dapper.Tests/ConstructorTests.cs +++ b/Dapper.Tests/ConstructorTests.cs @@ -5,7 +5,12 @@ namespace Dapper.Tests { - public class ConstructorTests : TestBase + public sealed class SystemSqlClientConstructorTests : ConstructorTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientConstructorTests : ConstructorTests { } +#endif + + public abstract class ConstructorTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestAbstractInheritance() diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index b1301b2ee..9b9ecb1c5 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -6,16 +6,24 @@ false true true - net452;netcoreapp1.0;netcoreapp2.0 + netcoreapp2.1;net46;netcoreapp2.0;net472 false - + $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB + @@ -26,9 +34,10 @@ + - + @@ -48,6 +57,9 @@ + + + diff --git a/Dapper.Tests/DataReaderTests.cs b/Dapper.Tests/DataReaderTests.cs index 8946d4551..10d11e4e9 100644 --- a/Dapper.Tests/DataReaderTests.cs +++ b/Dapper.Tests/DataReaderTests.cs @@ -4,7 +4,12 @@ namespace Dapper.Tests { - public class DataReaderTests : TestBase + public sealed class SystemSqlClientDataReaderTests : DataReaderTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientDataReaderTests : DataReaderTests { } +#endif + + public abstract class DataReaderTests : TestBase where TProvider : DatabaseProvider { [Fact] public void GetSameReaderForSameShape() diff --git a/Dapper.Tests/DecimalTests.cs b/Dapper.Tests/DecimalTests.cs index c26ef5f12..f4f6cb1cd 100644 --- a/Dapper.Tests/DecimalTests.cs +++ b/Dapper.Tests/DecimalTests.cs @@ -5,7 +5,11 @@ namespace Dapper.Tests { - public class DecimalTests : TestBase + public sealed class SystemSqlClientDecimalTests : DecimalTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientDecimalTests : DecimalTests { } +#endif + public abstract class DecimalTests : TestBase where TProvider : DatabaseProvider { [Fact] public void Issue261_Decimals() diff --git a/Dapper.Tests/EnumTests.cs b/Dapper.Tests/EnumTests.cs index e74e4522d..d830605ff 100644 --- a/Dapper.Tests/EnumTests.cs +++ b/Dapper.Tests/EnumTests.cs @@ -4,7 +4,11 @@ namespace Dapper.Tests { - public class EnumTests : TestBase + public sealed class SystemSqlClientEnumTests : EnumTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientEnumTests : EnumTests { } +#endif + public abstract class EnumTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestEnumWeirdness() diff --git a/Dapper.Tests/Helpers/Attributes.cs b/Dapper.Tests/Helpers/Attributes.cs index d71d932b8..83bcdca31 100644 --- a/Dapper.Tests/Helpers/Attributes.cs +++ b/Dapper.Tests/Helpers/Attributes.cs @@ -1,5 +1,4 @@ using System; -using System.Data.SqlClient; using Xunit; namespace Dapper.Tests @@ -32,7 +31,7 @@ public FactRequiredCompatibilityLevelAttribute(int level) : base() public static readonly int DetectedLevel; static FactRequiredCompatibilityLevelAttribute() { - using (var conn = TestBase.GetOpenConnection()) + using (var conn = DatabaseProvider.Instance.GetOpenConnection()) { try { @@ -57,15 +56,16 @@ public FactUnlessCaseSensitiveDatabaseAttribute() : base() public static readonly bool IsCaseSensitive; static FactUnlessCaseSensitiveDatabaseAttribute() { - using (var conn = TestBase.GetOpenConnection()) + using (var conn = DatabaseProvider.Instance.GetOpenConnection()) { try { conn.Execute("declare @i int; set @I = 1;"); } - catch (SqlException s) + catch (Exception ex) when (ex.GetType().Name == "SqlException") { - if (s.Number == 137) + int err = ((dynamic)ex).Number; + if (err == 137) IsCaseSensitive = true; else throw; diff --git a/Dapper.Tests/LiteralTests.cs b/Dapper.Tests/LiteralTests.cs index f176fa34a..9108d93f8 100644 --- a/Dapper.Tests/LiteralTests.cs +++ b/Dapper.Tests/LiteralTests.cs @@ -3,7 +3,11 @@ namespace Dapper.Tests { - public class LiteralTests : TestBase + public sealed class SystemSqlClientLiteralTests : LiteralTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientLiteralTests : LiteralTests { } +#endif + public abstract class LiteralTests : TestBase where TProvider : DatabaseProvider { [Fact] public void LiteralReplacementEnumAndString() diff --git a/Dapper.Tests/MiscTests.cs b/Dapper.Tests/MiscTests.cs index b118eabee..276606121 100644 --- a/Dapper.Tests/MiscTests.cs +++ b/Dapper.Tests/MiscTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Data.SqlClient; using System.Diagnostics; using System.Linq; using Xunit; @@ -40,7 +39,11 @@ public GenericUriParser(GenericUriParserOptions options) namespace Dapper.Tests { - public class MiscTests : TestBase + public sealed class SystemSqlClientMiscTests : MiscTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientMiscTests : MiscTests { } +#endif + public abstract class MiscTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestNullableGuidSupport() @@ -1026,13 +1029,16 @@ public void Issue178_SqlServer() try { connection.Execute("create table Issue178(id int not null)"); } catch { /* don't care */ } // raw ADO.net - var sqlCmd = new SqlCommand(sql, connection); - using (IDataReader reader1 = sqlCmd.ExecuteReader()) + using (var sqlCmd = connection.CreateCommand()) { - Assert.True(reader1.Read()); - Assert.Equal(0, reader1.GetInt32(0)); - Assert.False(reader1.Read()); - Assert.False(reader1.NextResult()); + 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 diff --git a/Dapper.Tests/MultiMapTests.cs b/Dapper.Tests/MultiMapTests.cs index fbec87bcb..195b981ba 100644 --- a/Dapper.Tests/MultiMapTests.cs +++ b/Dapper.Tests/MultiMapTests.cs @@ -6,7 +6,11 @@ namespace Dapper.Tests { - public class MultiMapTests : TestBase + public sealed class SystemSqlClientMultiMapTests : MultiMapTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientMultiMapTests : MultiMapTests { } +#endif + public abstract class MultiMapTests : TestBase where TProvider : DatabaseProvider { [Fact] public void ParentChildIdentityAssociations() diff --git a/Dapper.Tests/NullTests.cs b/Dapper.Tests/NullTests.cs index 5bce7b586..6e24e4e16 100644 --- a/Dapper.Tests/NullTests.cs +++ b/Dapper.Tests/NullTests.cs @@ -3,7 +3,13 @@ namespace Dapper.Tests { [Collection(NonParallelDefinition.Name)] - public class NullTests : TestBase + public sealed class SystemSqlClientNullTests : NullTests { } +#if MSSQLCLIENT + [Collection(NonParallelDefinition.Name)] + public sealed class MicrosoftSqlClientNullTests : NullTests { } +#endif + + public abstract class NullTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestNullableDefault() diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index 2844fddc5..e499d5846 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; -using System.Data.SqlClient; using System.Data.SqlTypes; using System.Dynamic; using System.Linq; @@ -19,7 +18,13 @@ namespace Dapper.Tests { - public class ParameterTests : TestBase + [Collection(NonParallelDefinition.Name)] // because it creates SQL types that compete between the two providers + public sealed class SystemSqlClientParameterTests : ParameterTests { } +#if MSSQLCLIENT + [Collection(NonParallelDefinition.Name)] // because it creates SQL types that compete between the two providers + public sealed class MicrosoftSqlClientParameterTests : ParameterTests { } +#endif + public abstract class ParameterTests : TestBase where TProvider : DatabaseProvider { public class DbParams : SqlMapper.IDynamicParameters, IEnumerable { @@ -37,7 +42,7 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id command.Parameters.Add(parameter); } } - + /* problems with conflicting type private static List CreateSqlDataRecordList(IEnumerable numbers) { var number_list = new List(); @@ -55,6 +60,7 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id return number_list; } + private class IntDynamicParam : SqlMapper.IDynamicParameters { @@ -66,7 +72,7 @@ public IntDynamicParam(IEnumerable numbers) public void AddParameters(IDbCommand command, SqlMapper.Identity identity) { - var sqlCommand = (SqlCommand)command; + var sqlCommand = (System.Data.SqlClient.SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; var number_list = CreateSqlDataRecordList(numbers); @@ -78,7 +84,7 @@ public void AddParameters(IDbCommand command, SqlMapper.Identity identity) p.Value = number_list; } } - + private class IntCustomParam : SqlMapper.ICustomQueryParameter { private readonly IEnumerable numbers; @@ -89,7 +95,7 @@ public IntCustomParam(IEnumerable numbers) public void AddParameter(IDbCommand command, string name) { - var sqlCommand = (SqlCommand)command; + var sqlCommand = (System.Data.SqlClient.SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; var number_list = CreateSqlDataRecordList(numbers); @@ -101,6 +107,7 @@ public void AddParameter(IDbCommand command, string name) p.Value = number_list; } } + */ /* TODO: * @@ -214,6 +221,9 @@ public void TestMassiveStrings() Assert.Equal(connection.Query("select @a", new { a = str }).First(), str); } + + /* problems with conflicting type + * [Fact] public void TestTVPWithAnonymousObject() { @@ -312,7 +322,7 @@ public DynamicParameterWithIntTVP(IEnumerable numbers) { base.AddParameters(command, identity); - var sqlCommand = (SqlCommand)command; + var sqlCommand = (System.Data.SqlClient.SqlCommand)command; sqlCommand.CommandType = CommandType.StoredProcedure; var number_list = CreateSqlDataRecordList(numbers); @@ -462,6 +472,8 @@ public void TestSqlDataRecordListParametersWithTypeHandlers() } } + */ + #if !NETCOREAPP1_0 [Fact] public void DataTableParameters() @@ -612,14 +624,19 @@ public SO29596645_RuleTableValuedParameters(string parameterName) public void AddParameters(IDbCommand command, SqlMapper.Identity identity) { Debug.WriteLine("> AddParameters"); - var lazy = (SqlCommand)command; - lazy.Parameters.AddWithValue("Id", 7); + var p = command.CreateParameter(); + p.ParameterName = "Id"; + p.Value = 7; + command.Parameters.Add(p); var table = new DataTable { Columns = { { "Id", typeof(int) } }, Rows = { { 4 }, { 9 } } }; - lazy.Parameters.AddWithValue("Rules", table); + p = command.CreateParameter(); + p.ParameterName = "Rules"; + p.Value = table; + command.Parameters.Add(p); Debug.WriteLine("< AddParameters"); } } @@ -733,8 +750,8 @@ public class HazSqlHierarchy public void TestCustomParameters() { var args = new DbParams { - new SqlParameter("foo", 123), - new SqlParameter("bar", "abc") + Provider.CreateRawParameter("foo", 123), + Provider.CreateRawParameter("bar", "abc") }; var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); int foo = result.Foo; diff --git a/Dapper.Tests/ProcedureTests.cs b/Dapper.Tests/ProcedureTests.cs index a560c680c..2ecaad884 100644 --- a/Dapper.Tests/ProcedureTests.cs +++ b/Dapper.Tests/ProcedureTests.cs @@ -6,7 +6,11 @@ namespace Dapper.Tests { - public class ProcedureTests : TestBase + public sealed class SystemSqlClientProcedureTests : ProcedureTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientProcedureTests : ProcedureTests { } +#endif + public abstract class ProcedureTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestProcWithOutParameter() diff --git a/Dapper.Tests/Providers/EntityFrameworkTests.cs b/Dapper.Tests/Providers/EntityFrameworkTests.cs index 4dd01b062..8827024f3 100644 --- a/Dapper.Tests/Providers/EntityFrameworkTests.cs +++ b/Dapper.Tests/Providers/EntityFrameworkTests.cs @@ -6,8 +6,13 @@ namespace Dapper.Tests.Providers { + public sealed class SystemSqlClientEntityFrameworkTests : EntityFrameworkTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientEntityFrameworkTests : EntityFrameworkTests { } +#endif + [Collection("TypeHandlerTests")] - public class EntityFrameworkTests : TestBase + public abstract class EntityFrameworkTests : TestBase where TProvider : DatabaseProvider { public EntityFrameworkTests() { diff --git a/Dapper.Tests/Providers/FirebirdTests.cs b/Dapper.Tests/Providers/FirebirdTests.cs index f6974e8b8..ae383b7fb 100644 --- a/Dapper.Tests/Providers/FirebirdTests.cs +++ b/Dapper.Tests/Providers/FirebirdTests.cs @@ -1,20 +1,25 @@ using FirebirdSql.Data.FirebirdClient; using System.Data; +using System.Data.Common; using System.Linq; using Xunit; namespace Dapper.Tests.Providers { - public class FirebirdTests : TestBase + public class FirebirdProvider : DatabaseProvider { + public override DbProviderFactory Factory => FirebirdClientFactory.Instance; + public override string GetConnectionString() => "initial catalog=localhost:database;user id=SYSDBA;password=masterkey"; + } + public class FirebirdTests : TestBase + { + private FbConnection GetOpenFirebirdConnection() => (FbConnection)Provider.GetOpenConnection(); + [Fact(Skip = "Bug in Firebird; a PR to fix it has been submitted")] public void Issue178_Firebird() { - const string cs = "initial catalog=localhost:database;user id=SYSDBA;password=masterkey"; - - using (var connection = new FbConnection(cs)) + using (var connection = GetOpenFirebirdConnection()) { - connection.Open(); const string sql = "select count(*) from Issue178"; try { connection.Execute("drop table Issue178"); } catch { /* don't care */ } diff --git a/Dapper.Tests/Providers/Linq2SqlTests.cs b/Dapper.Tests/Providers/Linq2SqlTests.cs index 4e0691b7c..42127d82b 100644 --- a/Dapper.Tests/Providers/Linq2SqlTests.cs +++ b/Dapper.Tests/Providers/Linq2SqlTests.cs @@ -8,7 +8,11 @@ namespace Dapper.Tests { - public class Linq2SqlTests : TestBase + public sealed class SystemSqlClientLinq2SqlTests : Linq2SqlTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientLinq2SqlTests : Linq2SqlTests { } +#endif + public abstract class Linq2SqlTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestLinqBinaryToClass() diff --git a/Dapper.Tests/Providers/MySQLTests.cs b/Dapper.Tests/Providers/MySQLTests.cs index aa39a89ad..8052c0c4f 100644 --- a/Dapper.Tests/Providers/MySQLTests.cs +++ b/Dapper.Tests/Providers/MySQLTests.cs @@ -1,31 +1,39 @@ using System; +using System.Data; +using System.Data.Common; using System.Linq; +using System.Threading.Tasks; using Xunit; namespace Dapper.Tests { - public class MySQLTests : TestBase + public sealed class MySqlProvider : DatabaseProvider { - private static MySql.Data.MySqlClient.MySqlConnection GetMySqlConnection(bool open = true, - bool convertZeroDatetime = false, bool allowZeroDatetime = false) - { - string cs = IsAppVeyor + public override DbProviderFactory Factory => MySql.Data.MySqlClient.MySqlClientFactory.Instance; + public override string GetConnectionString() => IsAppVeyor ? "Server=localhost;Database=test;Uid=root;Pwd=Password12!;" : "Server=localhost;Database=tests;Uid=test;Pwd=pass;"; - var csb = new MySql.Data.MySqlClient.MySqlConnectionStringBuilder(cs) - { - AllowZeroDateTime = allowZeroDatetime, - ConvertZeroDateTime = convertZeroDatetime - }; - var conn = new MySql.Data.MySqlClient.MySqlConnection(csb.ConnectionString); + + public DbConnection GetMySqlConnection(bool open = true, + bool convertZeroDatetime = false, bool allowZeroDatetime = false) + { + string cs = GetConnectionString(); + var csb = Factory.CreateConnectionStringBuilder(); + csb.ConnectionString = cs; + ((dynamic)csb).AllowZeroDateTime = allowZeroDatetime; + ((dynamic)csb).ConvertZeroDateTime = convertZeroDatetime; + var conn = Factory.CreateConnection(); + conn.ConnectionString = csb.ConnectionString; if (open) conn.Open(); return conn; } - + } + public class MySQLTests : TestBase + { [FactMySql] public void DapperEnumValue_Mysql() { - using (var conn = GetMySqlConnection()) + using (var conn = Provider.GetMySqlConnection()) { Common.DapperEnumValue(conn); } @@ -34,7 +42,7 @@ public void DapperEnumValue_Mysql() [FactMySql(Skip = "See https://github.com/StackExchange/Dapper/issues/552, not resolved on the MySQL end.")] public void Issue552_SignedUnsignedBooleans() { - using (var conn = GetMySqlConnection(true, false, false)) + using (var conn = Provider.GetMySqlConnection(true, false, false)) { conn.Execute(@" CREATE TEMPORARY TABLE IF NOT EXISTS `bar` ( @@ -74,7 +82,7 @@ private class MySqlHasBool [FactMySql] public void Issue295_NullableDateTime_MySql_Default() { - using (var conn = GetMySqlConnection(true, false, false)) + using (var conn = Provider.GetMySqlConnection(true, false, false)) { Common.TestDateTime(conn); } @@ -83,7 +91,7 @@ public void Issue295_NullableDateTime_MySql_Default() [FactMySql] public void Issue295_NullableDateTime_MySql_ConvertZeroDatetime() { - using (var conn = GetMySqlConnection(true, true, false)) + using (var conn = Provider.GetMySqlConnection(true, true, false)) { Common.TestDateTime(conn); } @@ -92,7 +100,7 @@ public void Issue295_NullableDateTime_MySql_ConvertZeroDatetime() [FactMySql(Skip = "See https://github.com/StackExchange/Dapper/issues/295, AllowZeroDateTime=True is not supported")] public void Issue295_NullableDateTime_MySql_AllowZeroDatetime() { - using (var conn = GetMySqlConnection(true, false, true)) + using (var conn = Provider.GetMySqlConnection(true, false, true)) { Common.TestDateTime(conn); } @@ -101,7 +109,7 @@ public void Issue295_NullableDateTime_MySql_AllowZeroDatetime() [FactMySql(Skip = "See https://github.com/StackExchange/Dapper/issues/295, AllowZeroDateTime=True is not supported")] public void Issue295_NullableDateTime_MySql_ConvertAllowZeroDatetime() { - using (var conn = GetMySqlConnection(true, true, true)) + using (var conn = Provider.GetMySqlConnection(true, true, true)) { Common.TestDateTime(conn); } @@ -110,7 +118,7 @@ public void Issue295_NullableDateTime_MySql_ConvertAllowZeroDatetime() [FactMySql] public void Issue426_SO34439033_DateTimeGainsTicks() { - using (var conn = GetMySqlConnection(true, true, true)) + using (var conn = Provider.GetMySqlConnection(true, true, true)) { try { conn.Execute("drop table Issue426_Test"); } catch { /* don't care */ } try { conn.Execute("create table Issue426_Test (Id int not null, Time time not null)"); } catch { /* don't care */ } @@ -133,7 +141,7 @@ public void Issue426_SO34439033_DateTimeGainsTicks() [FactMySql] public void SO36303462_Tinyint_Bools() { - using (var conn = GetMySqlConnection(true, true, true)) + using (var conn = Provider.GetMySqlConnection(true, true, true)) { try { conn.Execute("drop table SO36303462_Test"); } catch { /* don't care */ } conn.Execute("create table SO36303462_Test (Id int not null, IsBold tinyint not null);"); @@ -149,6 +157,52 @@ public void SO36303462_Tinyint_Bools() } } + [FactMySql] + public void Issue1277_ReaderSync() + { + using (var conn = Provider.GetMySqlConnection()) + { + try { conn.Execute("drop table Issue1277_Test"); } catch { /* don't care */ } + conn.Execute("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); + conn.Execute("insert Issue1277_Test (Id, IsBold) values (1,1);"); + conn.Execute("insert Issue1277_Test (Id, IsBold) values (2,0);"); + conn.Execute("insert Issue1277_Test (Id, IsBold) values (3,1);"); + + using (var reader = conn.ExecuteReader( + "select * from Issue1277_Test where Id < @id", + new { id = 42 })) + { + var table = new DataTable(); + table.Load(reader); + Assert.Equal(2, table.Columns.Count); + Assert.Equal(3, table.Rows.Count); + } + } + } + + [FactMySql] + public async Task Issue1277_ReaderAsync() + { + using (var conn = Provider.GetMySqlConnection()) + { + try { await conn.ExecuteAsync("drop table Issue1277_Test"); } catch { /* don't care */ } + await conn.ExecuteAsync("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); + await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (1,1);"); + await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (2,0);"); + await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (3,1);"); + + using (var reader = await conn.ExecuteReaderAsync( + "select * from Issue1277_Test where Id < @id", + new { id = 42 })) + { + var table = new DataTable(); + table.Load(reader); + Assert.Equal(2, table.Columns.Count); + Assert.Equal(3, table.Rows.Count); + } + } + } + private class SO36303462 { public int Id { get; set; } @@ -176,7 +230,7 @@ static FactMySqlAttribute() { try { - using (GetMySqlConnection(true)) { /* just trying to see if it works */ } + using (DatabaseProvider.Instance.GetMySqlConnection(true)) { /* just trying to see if it works */ } } catch (Exception ex) { diff --git a/Dapper.Tests/Providers/OLDEBTests.cs b/Dapper.Tests/Providers/OLDEBTests.cs index 932b5bfbf..227e7c63d 100644 --- a/Dapper.Tests/Providers/OLDEBTests.cs +++ b/Dapper.Tests/Providers/OLDEBTests.cs @@ -1,24 +1,24 @@ #if OLEDB using System; +using System.Data.Common; using System.Data.OleDb; using System.Linq; using Xunit; namespace Dapper.Tests { - public class OLDEBTests : TestBase + public class OLEDBProvider : DatabaseProvider { - public static string OleDbConnectionString => + public override DbProviderFactory Factory => OleDbFactory.Instance; + public override string GetConnectionString() => IsAppVeyor ? @"Provider=SQLOLEDB;Data Source=(local)\SQL2016;Initial Catalog=tempdb;User Id=sa;Password=Password12!" : "Provider=SQLOLEDB;Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI"; + } - public OleDbConnection GetOleDbConnection() - { - var conn = new OleDbConnection(OleDbConnectionString); - conn.Open(); - return conn; - } + public class OLDEBTests : TestBase + { + public OleDbConnection GetOleDbConnection() => (OleDbConnection) Provider.GetOpenConnection(); // see https://stackoverflow.com/q/18847510/23354 [Fact] diff --git a/Dapper.Tests/Providers/PostgresqlTests.cs b/Dapper.Tests/Providers/PostgresqlTests.cs index dee918e7f..8b6c13624 100644 --- a/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/Dapper.Tests/Providers/PostgresqlTests.cs @@ -1,21 +1,21 @@ using System; using System.Data; +using System.Data.Common; using System.Linq; using Xunit; namespace Dapper.Tests { - public class PostgresqlTests : TestBase + public class PostgresProvider : DatabaseProvider { - private static Npgsql.NpgsqlConnection GetOpenNpgsqlConnection() - { - string cs = IsAppVeyor + public override DbProviderFactory Factory => Npgsql.NpgsqlFactory.Instance; + public override string GetConnectionString() => IsAppVeyor ? "Server=localhost;Port=5432;User Id=postgres;Password=Password12!;Database=test" : "Server=localhost;Port=5432;User Id=dappertest;Password=dapperpass;Database=dappertest"; // ;Encoding = UNICODE - var conn = new Npgsql.NpgsqlConnection(cs); - conn.Open(); - return conn; - } + } + public class PostgresqlTests : TestBase + { + private Npgsql.NpgsqlConnection GetOpenNpgsqlConnection() => (Npgsql.NpgsqlConnection)Provider.GetOpenConnection(); private class Cat { @@ -71,7 +71,7 @@ static FactPostgresqlAttribute() { try { - using (GetOpenNpgsqlConnection()) { /* just trying to see if it works */ } + using (DatabaseProvider.Instance.GetOpenConnection()) { /* just trying to see if it works */ } } catch (Exception ex) { diff --git a/Dapper.Tests/Providers/SqliteTests.cs b/Dapper.Tests/Providers/SqliteTests.cs index 8aa750791..5609a5eaf 100644 --- a/Dapper.Tests/Providers/SqliteTests.cs +++ b/Dapper.Tests/Providers/SqliteTests.cs @@ -1,71 +1,105 @@ using Microsoft.Data.Sqlite; using System; +using System.Data.Common; using System.Linq; using System.Threading.Tasks; using Xunit; namespace Dapper.Tests { - public class SqliteTests : TestBase + public class SqliteProvider : DatabaseProvider { - protected static SqliteConnection GetSQLiteConnection(bool open = true) + public override DbProviderFactory Factory => SqliteFactory.Instance; + public override string GetConnectionString() => "Data Source=:memory:"; + } + + public abstract class SqliteTypeTestBase : TestBase + { + protected SqliteConnection GetSQLiteConnection(bool open = true) + => (SqliteConnection)(open ? Provider.GetOpenConnection() : Provider.GetClosedConnection()); + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class FactSqliteAttribute : FactAttribute { - var connection = new SqliteConnection("Data Source=:memory:"); - if (open) connection.Open(); - return connection; + public override string Skip + { + get { return unavailable ?? base.Skip; } + set { base.Skip = value; } + } + + private static readonly string unavailable; + + static FactSqliteAttribute() + { + try + { + using (DatabaseProvider.Instance.GetOpenConnection()) + { + } + } + catch (Exception ex) + { + unavailable = $"Sqlite is unavailable: {ex.Message}"; + } + } } + } + [Collection(NonParallelDefinition.Name)] + public class SqliteTypeHandlerTests : SqliteTypeTestBase + { [FactSqlite] - public void DapperEnumValue_Sqlite() + public void Issue466_SqliteHatesOptimizations() { using (var connection = GetSQLiteConnection()) { - Common.DapperEnumValue(connection); + SqlMapper.ResetTypeHandlers(); + var row = connection.Query("select 42 as Id").First(); + Assert.Equal(42, row.Id); + row = connection.Query("select 42 as Id").First(); + Assert.Equal(42, row.Id); + + SqlMapper.ResetTypeHandlers(); + row = connection.QueryFirst("select 42 as Id"); + Assert.Equal(42, row.Id); + row = connection.QueryFirst("select 42 as Id"); + Assert.Equal(42, row.Id); } } - [Collection(NonParallelDefinition.Name)] - public class SqliteTypeHandlerTests : TestBase + [FactSqlite] + public async Task Issue466_SqliteHatesOptimizations_Async() { - [FactSqlite] - public void Issue466_SqliteHatesOptimizations() + using (var connection = GetSQLiteConnection()) { - using (var connection = GetSQLiteConnection()) - { - SqlMapper.ResetTypeHandlers(); - var row = connection.Query("select 42 as Id").First(); - Assert.Equal(42, row.Id); - row = connection.Query("select 42 as Id").First(); - Assert.Equal(42, row.Id); + SqlMapper.ResetTypeHandlers(); + var row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); + Assert.Equal(42, row.Id); + row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); + Assert.Equal(42, row.Id); - SqlMapper.ResetTypeHandlers(); - row = connection.QueryFirst("select 42 as Id"); - Assert.Equal(42, row.Id); - row = connection.QueryFirst("select 42 as Id"); - Assert.Equal(42, row.Id); - } + SqlMapper.ResetTypeHandlers(); + row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); + Assert.Equal(42, row.Id); + row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); + Assert.Equal(42, row.Id); } + } + } - [FactSqlite] - public async Task Issue466_SqliteHatesOptimizations_Async() + public class SqliteTests : SqliteTypeTestBase + { + [FactSqlite] + public void DapperEnumValue_Sqlite() + { + using (var connection = GetSQLiteConnection()) { - using (var connection = GetSQLiteConnection()) - { - SqlMapper.ResetTypeHandlers(); - var row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); - Assert.Equal(42, row.Id); - row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); - Assert.Equal(42, row.Id); - - SqlMapper.ResetTypeHandlers(); - row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); - Assert.Equal(42, row.Id); - row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); - Assert.Equal(42, row.Id); - } + Common.DapperEnumValue(connection); } } + + [FactSqlite] public void Isse467_SqliteLikesParametersWithPrefix() { @@ -89,32 +123,6 @@ private void Isse467_SqliteParameterNaming(bool prefix) var i = Convert.ToInt32(cmd.ExecuteScalar()); Assert.Equal(42, i); } - } - - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class FactSqliteAttribute : FactAttribute - { - public override string Skip - { - get { return unavailable ?? base.Skip; } - set { base.Skip = value; } - } - - private static readonly string unavailable; - - static FactSqliteAttribute() - { - try - { - using (GetSQLiteConnection()) - { - } - } - catch (Exception ex) - { - unavailable = $"Sqlite is unavailable: {ex.Message}"; - } - } - } + } } -} \ No newline at end of file +} diff --git a/Dapper.Tests/QueryMultipleTests.cs b/Dapper.Tests/QueryMultipleTests.cs index 19dcb557f..b48f91d23 100644 --- a/Dapper.Tests/QueryMultipleTests.cs +++ b/Dapper.Tests/QueryMultipleTests.cs @@ -6,7 +6,11 @@ namespace Dapper.Tests { - public class QueryMultipleTests : TestBase + public sealed class SystemSqlClientQueryMultipleTests : QueryMultipleTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientQueryMultipleTests : QueryMultipleTests { } +#endif + public abstract class QueryMultipleTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestQueryMultipleBuffered() diff --git a/Dapper.Tests/TestBase.cs b/Dapper.Tests/TestBase.cs index 312356206..14320d376 100644 --- a/Dapper.Tests/TestBase.cs +++ b/Dapper.Tests/TestBase.cs @@ -1,49 +1,93 @@ using System; using System.Data; -using System.Data.SqlClient; using System.Globalization; using Xunit; +using System.Data.Common; #if !NETCOREAPP1_0 using System.Threading; #endif namespace Dapper.Tests { - public abstract class TestBase : IDisposable + public static class DatabaseProvider where TProvider : DatabaseProvider { - protected static readonly bool IsAppVeyor = Environment.GetEnvironmentVariable("Appveyor")?.ToUpperInvariant() == "TRUE"; - - public static string ConnectionString => - IsAppVeyor - ? @"Server=(local)\SQL2016;Database=tempdb;User ID=sa;Password=Password12!" - : "Data Source=.;Initial Catalog=tempdb;Integrated Security=True"; + public static TProvider Instance { get; } = Activator.CreateInstance(); + } + public abstract class DatabaseProvider + { + public abstract DbProviderFactory Factory { get; } - protected SqlConnection _connection; - protected SqlConnection connection => _connection ?? (_connection = GetOpenConnection()); + public static bool IsAppVeyor { get; } = Environment.GetEnvironmentVariable("Appveyor")?.ToUpperInvariant() == "TRUE"; + public virtual void Dispose() { } + public abstract string GetConnectionString(); - public static SqlConnection GetOpenConnection(bool mars = false) + public DbConnection GetOpenConnection() { - var cs = ConnectionString; - if (mars) - { - var scsb = new SqlConnectionStringBuilder(cs) - { - MultipleActiveResultSets = true - }; - cs = scsb.ConnectionString; - } - var connection = new SqlConnection(cs); - connection.Open(); - return connection; + var conn = Factory.CreateConnection(); + conn.ConnectionString = GetConnectionString(); + conn.Open(); + if (conn.State != ConnectionState.Open) throw new InvalidOperationException("should be open!"); + return conn; } - public SqlConnection GetClosedConnection() + public DbConnection GetClosedConnection() { - var conn = new SqlConnection(ConnectionString); + var conn = Factory.CreateConnection(); + conn.ConnectionString = GetConnectionString(); if (conn.State != ConnectionState.Closed) throw new InvalidOperationException("should be closed!"); return conn; } + public DbParameter CreateRawParameter(string name, object value) + { + var p = Factory.CreateParameter(); + p.ParameterName = name; + p.Value = value ?? DBNull.Value; + return p; + } + } + + public abstract class SqlServerDatabaseProvider : DatabaseProvider + { + public override string GetConnectionString() => + IsAppVeyor + ? @"Server=(local)\SQL2016;Database=tempdb;User ID=sa;Password=Password12!" + : "Data Source=.;Initial Catalog=tempdb;Integrated Security=True"; + + public DbConnection GetOpenConnection(bool mars) + { + if (!mars) return GetOpenConnection(); + + var scsb = Factory.CreateConnectionStringBuilder(); + scsb.ConnectionString = GetConnectionString(); + ((dynamic)scsb).MultipleActiveResultSets = true; + var conn = Factory.CreateConnection(); + conn.ConnectionString = scsb.ConnectionString; + conn.Open(); + if (conn.State != ConnectionState.Open) throw new InvalidOperationException("should be open!"); + return conn; + } + } + public sealed class SystemSqlClientProvider : SqlServerDatabaseProvider + { + public override DbProviderFactory Factory => System.Data.SqlClient.SqlClientFactory.Instance; + } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientProvider : SqlServerDatabaseProvider + { + public override DbProviderFactory Factory => Microsoft.Data.SqlClient.SqlClientFactory.Instance; + } +#endif + + public abstract class TestBase : IDisposable where TProvider : DatabaseProvider + { + protected DbConnection GetOpenConnection() => Provider.GetOpenConnection(); + protected DbConnection GetClosedConnection() => Provider.GetClosedConnection(); + protected DbConnection _connection; + protected DbConnection connection => _connection ?? (_connection = Provider.GetOpenConnection()); + + public TProvider Provider { get; } = DatabaseProvider.Instance; + protected static CultureInfo ActiveCulture { #if NETCOREAPP1_0 @@ -58,7 +102,10 @@ protected static CultureInfo ActiveCulture static TestBase() { Console.WriteLine("Dapper: " + typeof(SqlMapper).AssemblyQualifiedName); - Console.WriteLine("Using Connectionstring: {0}", ConnectionString); + var provider = DatabaseProvider.Instance; + Console.WriteLine("Using Connectionstring: {0}", provider.GetConnectionString()); + var factory = provider.Factory; + Console.WriteLine("Using Provider: {0}", factory.GetType().FullName); #if NETCOREAPP1_0 Console.WriteLine("CoreCLR (netcoreapp1.0)"); #else @@ -77,14 +124,15 @@ static TestBase() #endif } - public void Dispose() + public virtual void Dispose() { _connection?.Dispose(); + _connection = null; + Provider?.Dispose(); } } - [CollectionDefinition(Name, DisableParallelization = true)] - public class NonParallelDefinition : TestBase + public static class NonParallelDefinition { public const string Name = "NonParallel"; } diff --git a/Dapper.Tests/TransactionTests.cs b/Dapper.Tests/TransactionTests.cs index 84519feee..36e28dfb3 100644 --- a/Dapper.Tests/TransactionTests.cs +++ b/Dapper.Tests/TransactionTests.cs @@ -7,7 +7,11 @@ namespace Dapper.Tests { - public class TransactionTests : TestBase + public sealed class SystemSqlClientTransactionTests : TransactionTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientTransactionTests : TransactionTests { } +#endif + public abstract class TransactionTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestTransactionCommit() diff --git a/Dapper.Tests/TupleTests.cs b/Dapper.Tests/TupleTests.cs index 271e39200..92aa087f5 100644 --- a/Dapper.Tests/TupleTests.cs +++ b/Dapper.Tests/TupleTests.cs @@ -3,7 +3,11 @@ namespace Dapper.Tests { - public class TupleTests : TestBase + public sealed class SystemSqlClientTupleTests : TupleTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientTupleTests : TupleTests { } +#endif + public abstract class TupleTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TupleStructParameter_Fails_HelpfulMessage() diff --git a/Dapper.Tests/TypeHandlerTests.cs b/Dapper.Tests/TypeHandlerTests.cs index c766190b4..b10b0b092 100644 --- a/Dapper.Tests/TypeHandlerTests.cs +++ b/Dapper.Tests/TypeHandlerTests.cs @@ -9,7 +9,13 @@ namespace Dapper.Tests { [Collection(NonParallelDefinition.Name)] - public class TypeHandlerTests : TestBase + public sealed class SystemSqlClientTypeHandlerTests : TypeHandlerTests { } +#if MSSQLCLIENT + [Collection(NonParallelDefinition.Name)] + public sealed class MicrosoftSqlClientTypeHandlerTests : TypeHandlerTests { } +#endif + + public abstract class TypeHandlerTests : TestBase where TProvider : DatabaseProvider { [Fact] public void TestChangingDefaultStringTypeMappingToAnsiString() diff --git a/Dapper.Tests/XmlTests.cs b/Dapper.Tests/XmlTests.cs index 50aa322fb..addf90e61 100644 --- a/Dapper.Tests/XmlTests.cs +++ b/Dapper.Tests/XmlTests.cs @@ -4,7 +4,11 @@ namespace Dapper.Tests { - public class XmlTests : TestBase + public sealed class SystemSqlClientXmlTests : XmlTests { } +#if MSSQLCLIENT + public sealed class MicrosoftSqlClientXmlTests : XmlTests { } +#endif + public abstract class XmlTests : TestBase where TProvider : DatabaseProvider { [Fact] public void CommonXmlTypesSupported() diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index a4f85ad37..fcb5fdbf1 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,7 +5,7 @@ Dapper A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net451;netstandard1.3;netstandard2.0 + net451;netstandard1.3;netstandard2.0;netcoreapp2.1 @@ -15,9 +15,15 @@ - + + + + + + + diff --git a/Dapper/SqlDataRecordHandler.cs b/Dapper/SqlDataRecordHandler.cs index 301aa36ab..946652ac6 100644 --- a/Dapper/SqlDataRecordHandler.cs +++ b/Dapper/SqlDataRecordHandler.cs @@ -4,7 +4,10 @@ namespace Dapper { - internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler + internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler +#if !NETSTANDARD1_3 + where T : IDataRecord +#endif { public object Parse(Type destinationType, object value) { @@ -13,7 +16,7 @@ public object Parse(Type destinationType, object value) public void SetValue(IDbDataParameter parameter, object value) { - SqlDataRecordListTVPParameter.Set(parameter, value as IEnumerable, null); + SqlDataRecordListTVPParameter.Set(parameter, value as IEnumerable, null); } } } diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index 3d3dc9bea..c236fd3df 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -1,22 +1,29 @@ -using System.Collections.Generic; +using System; +using System.Collections; +using System.Collections.Generic; using System.Data; using System.Linq; +using System.Reflection; +using System.Reflection.Emit; namespace Dapper { /// /// Used to pass a IEnumerable<SqlDataRecord> as a SqlDataRecordListTVPParameter /// - internal sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter + internal sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter +#if !NETSTANDARD1_3 + where T : IDataRecord +#endif { - private readonly IEnumerable data; + private readonly IEnumerable data; private readonly string typeName; /// - /// Create a new instance of . + /// Create a new instance of . /// /// The data records to convert into TVPs. /// The parameter type name. - public SqlDataRecordListTVPParameter(IEnumerable data, string typeName) + public SqlDataRecordListTVPParameter(IEnumerable data, string typeName) { this.data = data; this.typeName = typeName; @@ -30,14 +37,72 @@ void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string nam command.Parameters.Add(param); } - internal static void Set(IDbDataParameter parameter, IEnumerable data, string typeName) + internal static void Set(IDbDataParameter parameter, IEnumerable data, string typeName) { parameter.Value = data != null && data.Any() ? data : null; - if (parameter is System.Data.SqlClient.SqlParameter sqlParam) + StructuredHelper.ConfigureTVP(parameter, typeName); + } + } + static class StructuredHelper + { + private static readonly Hashtable s_udt = new Hashtable(), s_tvp = new Hashtable(); + + private static Action GetUDT(Type type) + => (Action)s_udt[type] ?? SlowGetHelper(type, s_udt, "UdtTypeName", 29); // 29 = SqlDbType.Udt (avoiding ref) + private static Action GetTVP(Type type) + => (Action)s_tvp[type] ?? SlowGetHelper(type, s_tvp, "TypeName", 30); // 30 = SqlDbType.Structured (avoiding ref) + + static Action SlowGetHelper(Type type, Hashtable hashtable, string nameProperty, int sqlDbType) + { + lock (hashtable) { - sqlParam.SqlDbType = SqlDbType.Structured; - sqlParam.TypeName = typeName; + var helper = (Action)hashtable[type]; + if (helper == null) + { + helper = CreateFor(type, nameProperty, sqlDbType); + hashtable.Add(type, helper); + } + return helper; } } + + static Action CreateFor(Type type, string nameProperty, int sqlDbType) + { + var name = type.GetProperty(nameProperty, BindingFlags.Public | BindingFlags.Instance); + if (name == null || !name.CanWrite) + { + return (p, n) => { }; + } + + var dbType = type.GetProperty("SqlDbType", BindingFlags.Public | BindingFlags.Instance); + if (dbType != null && !dbType.CanWrite) dbType = null; + + var dm = new DynamicMethod(nameof(CreateFor) + "_" + type.Name, null, + new[] { typeof(IDbDataParameter), typeof(string) }, true); + var il = dm.GetILGenerator(); + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, type); + il.Emit(OpCodes.Ldarg_1); + il.EmitCall(OpCodes.Callvirt, name.GetSetMethod(), null); + + if (dbType != null) + { + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, type); + il.Emit(OpCodes.Ldc_I4, sqlDbType); + il.EmitCall(OpCodes.Callvirt, dbType.GetSetMethod(), null); + } + + il.Emit(OpCodes.Ret); + return (Action)dm.CreateDelegate(typeof(Action)); + + } + + // this needs to be done per-provider; "dynamic" doesn't work well on all runtimes, although that + // would be a fair option otherwise + internal static void ConfigureUDT(IDbDataParameter parameter, string typeName) + => GetUDT(parameter.GetType())(parameter, typeName); + internal static void ConfigureTVP(IDbDataParameter parameter, string typeName) + => GetTVP(parameter.GetType())(parameter, typeName); } } diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index c923dd0a0..b5177275b 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -1101,7 +1101,7 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, /// /// public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - ExecuteReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); + ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); /// /// Execute parameterized SQL and return an . @@ -1114,7 +1114,7 @@ public static Task ExecuteReaderAsync(this IDbConnection cnn, strin /// or . /// public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) => - ExecuteReaderImplAsync(cnn, command, CommandBehavior.Default); + ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default); /// /// Execute parameterized SQL and return an . @@ -1128,26 +1128,27 @@ public static Task ExecuteReaderAsync(this IDbConnection cnn, Comma /// or . /// public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) => - ExecuteReaderImplAsync(cnn, command, commandBehavior); + ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior); - private static async Task ExecuteReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) + private static async Task ExecuteWrappedReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { Action paramReader = GetParameterReader(cnn, ref command); DbCommand cmd = null; - bool wasClosed = cnn.State == ConnectionState.Closed; + bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true; try { cmd = command.TrySetupAsyncCommand(cnn, paramReader); if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false); wasClosed = false; - return reader; + disposeCommand = false; + return WrappedReader.Create(cmd, reader); } finally { if (wasClosed) cnn.Close(); - cmd?.Dispose(); + if (cmd != null && disposeCommand) cmd.Dispose(); } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 306a114ed..82c4ab3a5 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -237,7 +237,7 @@ private static void ResetTypeHandlers(bool clone) [MethodImpl(MethodImplOptions.NoInlining)] private static void AddSqlDataRecordsTypeHandler(bool clone) { - AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone); + AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone); } /// @@ -599,7 +599,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, obje { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); - return new WrappedReader(dbcmd, reader); + return WrappedReader.Create(dbcmd, reader); } /// @@ -615,7 +615,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, obje public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command) { var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); - return new WrappedReader(dbcmd, reader); + return WrappedReader.Create(dbcmd, reader); } /// @@ -632,7 +632,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand dbcmd); - return new WrappedReader(dbcmd, reader); + return WrappedReader.Create(dbcmd, reader); } /// @@ -3785,13 +3785,24 @@ public static string GetTypeName(this DataTable table) => table?.ExtendedProperties[DataTableTypeNameKey] as string; #endif + /// + /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. + /// + /// The list of records to convert to TVPs. + /// The sql parameter type name. + public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) where T : IDataRecord => + new SqlDataRecordListTVPParameter(list, typeName); + + /* /// /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. /// /// 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); + new SqlDataRecordListTVPParameter(list, typeName); + // ^^^ retained to avoid missing-method-exception; can presumably drop in a "major" + */ // one per thread [ThreadStatic] diff --git a/Dapper/TableValuedParameter.cs b/Dapper/TableValuedParameter.cs index 3d307e2df..a0ae7e62c 100644 --- a/Dapper/TableValuedParameter.cs +++ b/Dapper/TableValuedParameter.cs @@ -1,6 +1,4 @@ -using System; -using System.Data; -using System.Reflection; +using System.Data; #if !NETSTANDARD1_3 namespace Dapper @@ -30,17 +28,6 @@ public TableValuedParameter(DataTable table, string typeName) this.typeName = typeName; } - private static readonly Action setTypeName; - static TableValuedParameter() - { - var prop = typeof(System.Data.SqlClient.SqlParameter).GetProperty("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(); @@ -58,12 +45,8 @@ internal static void Set(IDbDataParameter parameter, DataTable table, string typ { typeName = table.GetTypeName(); } - if (!string.IsNullOrEmpty(typeName) && (parameter is System.Data.SqlClient.SqlParameter sqlParam)) - { - setTypeName?.Invoke(sqlParam, typeName); - sqlParam.SqlDbType = SqlDbType.Structured; - } + if (!string.IsNullOrEmpty(typeName)) StructuredHelper.ConfigureTVP(parameter, typeName); } } } -#endif \ No newline at end of file +#endif diff --git a/Dapper/UdtTypeHandler.cs b/Dapper/UdtTypeHandler.cs index c0a3a1872..a2341b05e 100644 --- a/Dapper/UdtTypeHandler.cs +++ b/Dapper/UdtTypeHandler.cs @@ -33,11 +33,7 @@ void ITypeHandler.SetValue(IDbDataParameter parameter, object value) #pragma warning disable 0618 parameter.Value = SanitizeParameterValue(value); #pragma warning restore 0618 - if (parameter is System.Data.SqlClient.SqlParameter && !(value is DBNull)) - { - ((System.Data.SqlClient.SqlParameter)parameter).SqlDbType = SqlDbType.Udt; - ((System.Data.SqlClient.SqlParameter)parameter).UdtTypeName = udtTypeName; - } + if(!(value is DBNull)) StructuredHelper.ConfigureUDT(parameter, udtTypeName); } } #endif diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index a17d783af..8e2ab2c8c 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -1,112 +1,302 @@ using System; +using System.Collections; using System.Data; +using System.Data.Common; +using System.IO; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; namespace Dapper { - internal class WrappedReader : IWrappedDataReader + internal sealed class DisposedReader : DbDataReader { - private IDataReader reader; - private IDbCommand cmd; + internal static readonly DisposedReader Instance = new DisposedReader(); + private DisposedReader() { } + public override int Depth => 0; + public override int FieldCount => 0; + public override bool IsClosed => true; + public override bool HasRows => false; + public override int RecordsAffected => -1; + public override int VisibleFieldCount => 0; + + [MethodImpl(MethodImplOptions.NoInlining)] + private static T ThrowDisposed() => throw new ObjectDisposedException(nameof(DbDataReader)); + [MethodImpl(MethodImplOptions.NoInlining)] + private async static Task ThrowDisposedAsync() + { + var result = ThrowDisposed(); + await Task.Yield(); // will never hit this - already thrown and handled + return result; + } +#if !NETSTANDARD1_3 + public override void Close() { } + public override DataTable GetSchemaTable() => ThrowDisposed(); + public override object InitializeLifetimeService() => ThrowDisposed(); +#endif + protected override void Dispose(bool disposing) { } +#if NET451 + public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType) => ThrowDisposed(); +#endif + public override bool GetBoolean(int ordinal) => ThrowDisposed(); + public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => ThrowDisposed(); + public override float GetFloat(int ordinal) => ThrowDisposed(); + public override short GetInt16(int ordinal) => ThrowDisposed(); + public override byte GetByte(int ordinal) => ThrowDisposed(); + public override char GetChar(int ordinal) => ThrowDisposed(); + public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => ThrowDisposed(); + public override string GetDataTypeName(int ordinal) => ThrowDisposed(); + public override DateTime GetDateTime(int ordinal) => ThrowDisposed(); + protected override DbDataReader GetDbDataReader(int ordinal) => ThrowDisposed(); + public override decimal GetDecimal(int ordinal) => ThrowDisposed(); + public override double GetDouble(int ordinal) => ThrowDisposed(); + public override IEnumerator GetEnumerator() => ThrowDisposed(); + public override Type GetFieldType(int ordinal) => ThrowDisposed(); + public override T GetFieldValue(int ordinal) => ThrowDisposed(); + public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) => ThrowDisposedAsync(); + public override Guid GetGuid(int ordinal) => ThrowDisposed(); + public override int GetInt32(int ordinal) => ThrowDisposed(); + public override long GetInt64(int ordinal) => ThrowDisposed(); + public override string GetName(int ordinal) => ThrowDisposed(); + public override int GetOrdinal(string name) => ThrowDisposed(); + public override Type GetProviderSpecificFieldType(int ordinal) => ThrowDisposed(); + public override object GetProviderSpecificValue(int ordinal) => ThrowDisposed(); + public override int GetProviderSpecificValues(object[] values) => ThrowDisposed(); + public override Stream GetStream(int ordinal) => ThrowDisposed(); + public override string GetString(int ordinal) => ThrowDisposed(); + public override TextReader GetTextReader(int ordinal) => ThrowDisposed(); + public override object GetValue(int ordinal) => ThrowDisposed(); + public override int GetValues(object[] values) => ThrowDisposed(); + public override bool IsDBNull(int ordinal) => ThrowDisposed(); + public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) => ThrowDisposedAsync(); + public override bool NextResult() => ThrowDisposed(); + public override bool Read() => ThrowDisposed(); + public override Task NextResultAsync(CancellationToken cancellationToken) => ThrowDisposedAsync(); + public override Task ReadAsync(CancellationToken cancellationToken) => ThrowDisposedAsync(); + public override object this[int ordinal] => ThrowDisposed(); + public override object this[string name] => ThrowDisposed(); + } - public IDataReader Reader + internal static class WrappedReader + { + // the purpose of wrapping here is to allow closing a reader to *also* close + // the command, without having to explicitly hand the command back to the + // caller; what that actually looks like depends on what we get: if we are + // given a DbDataReader, we will surface a DbDataReader; if we are given + // a raw IDataReader, we will surface that; and if null: null + public static IDataReader Create(IDbCommand cmd, IDataReader reader) { - get - { - var tmp = reader; - if (tmp == null) throw new ObjectDisposedException(GetType().Name); - return tmp; - } + if (cmd == null) return reader; // no need to wrap if no command + + if (reader is DbDataReader dbr) return new DbWrappedReader(cmd, dbr); + if (reader != null) return new BasicWrappedReader(cmd, reader); + cmd.Dispose(); + return null; // GIGO } + } + internal sealed class DbWrappedReader : DbDataReader, IWrappedDataReader + { + private DbDataReader _reader; + private IDbCommand _cmd; + + IDataReader IWrappedDataReader.Reader => _reader; - IDbCommand IWrappedDataReader.Command + IDbCommand IWrappedDataReader.Command => _cmd; + + public DbWrappedReader(IDbCommand cmd, DbDataReader reader) { - get + _cmd = cmd; + _reader = reader; + } + + public override bool HasRows => _reader.HasRows; + +#if !NETSTANDARD1_3 + public override void Close() => _reader.Close(); + public override DataTable GetSchemaTable() => _reader.GetSchemaTable(); + public override object InitializeLifetimeService() => _reader.InitializeLifetimeService(); +#endif +#if NET451 + public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType) => _reader.CreateObjRef(requestedType); +#endif + + public override int Depth => _reader.Depth; + + public override bool IsClosed => _reader.IsClosed; + + public override bool NextResult() => _reader.NextResult(); + + public override bool Read() => _reader.Read(); + + public override int RecordsAffected => _reader.RecordsAffected; + + protected override void Dispose(bool disposing) + { + if (disposing) { - var tmp = cmd; - if (tmp == null) throw new ObjectDisposedException(GetType().Name); - return tmp; +#if !NETSTANDARD1_3 + _reader.Close(); +#endif + _reader.Dispose(); + _reader = DisposedReader.Instance; // all future ops are no-ops + _cmd?.Dispose(); + _cmd = null; } } + + public override int FieldCount => _reader.FieldCount; + + public override bool GetBoolean(int i) => _reader.GetBoolean(i); + + public override byte GetByte(int i) => _reader.GetByte(i); + + public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => + _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); + + public override char GetChar(int i) => _reader.GetChar(i); + + public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => + _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); + + public override string GetDataTypeName(int i) => _reader.GetDataTypeName(i); + + public override DateTime GetDateTime(int i) => _reader.GetDateTime(i); + + public override decimal GetDecimal(int i) => _reader.GetDecimal(i); + + public override double GetDouble(int i) => _reader.GetDouble(i); + + public override Type GetFieldType(int i) => _reader.GetFieldType(i); + + public override float GetFloat(int i) => _reader.GetFloat(i); + + public override Guid GetGuid(int i) => _reader.GetGuid(i); + + public override short GetInt16(int i) => _reader.GetInt16(i); + + public override int GetInt32(int i) => _reader.GetInt32(i); + + public override long GetInt64(int i) => _reader.GetInt64(i); + + public override string GetName(int i) => _reader.GetName(i); + + public override int GetOrdinal(string name) => _reader.GetOrdinal(name); + + public override string GetString(int i) => _reader.GetString(i); + + public override object GetValue(int i) => _reader.GetValue(i); + + public override int GetValues(object[] values) => _reader.GetValues(values); + + public override bool IsDBNull(int i) => _reader.IsDBNull(i); + + public override object this[string name] => _reader[name]; + + public override object this[int i] => _reader[i]; + + public override T GetFieldValue(int ordinal) => _reader.GetFieldValue(ordinal); + public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) => _reader.GetFieldValueAsync(ordinal, cancellationToken); + public override IEnumerator GetEnumerator() => _reader.GetEnumerator(); + public override Type GetProviderSpecificFieldType(int ordinal) => _reader.GetProviderSpecificFieldType(ordinal); + public override object GetProviderSpecificValue(int ordinal) => _reader.GetProviderSpecificValue(ordinal); + public override int GetProviderSpecificValues(object[] values) => _reader.GetProviderSpecificValues(values); + public override Stream GetStream(int ordinal) => _reader.GetStream(ordinal); + public override TextReader GetTextReader(int ordinal) => _reader.GetTextReader(ordinal); + public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) => _reader.IsDBNullAsync(ordinal, cancellationToken); + public override Task NextResultAsync(CancellationToken cancellationToken) => _reader.NextResultAsync(cancellationToken); + public override Task ReadAsync(CancellationToken cancellationToken) => _reader.ReadAsync(cancellationToken); + public override int VisibleFieldCount => _reader.VisibleFieldCount; + protected override DbDataReader GetDbDataReader(int ordinal) => (((IDataReader)_reader).GetData(ordinal) as DbDataReader) ?? throw new NotSupportedException(); + } + + internal class BasicWrappedReader : IWrappedDataReader + { + private IDataReader _reader; + private IDbCommand _cmd; + + IDataReader IWrappedDataReader.Reader => _reader; + + IDbCommand IWrappedDataReader.Command => _cmd; - public WrappedReader(IDbCommand cmd, IDataReader reader) + public BasicWrappedReader(IDbCommand cmd, IDataReader reader) { - this.cmd = cmd; - this.reader = reader; + _cmd = cmd; + _reader = reader; } - void IDataReader.Close() => reader?.Close(); + void IDataReader.Close() => _reader.Close(); - int IDataReader.Depth => Reader.Depth; + int IDataReader.Depth => _reader.Depth; - DataTable IDataReader.GetSchemaTable() => Reader.GetSchemaTable(); + DataTable IDataReader.GetSchemaTable() => _reader.GetSchemaTable(); - bool IDataReader.IsClosed => reader?.IsClosed ?? true; + bool IDataReader.IsClosed => _reader.IsClosed; - bool IDataReader.NextResult() => Reader.NextResult(); + bool IDataReader.NextResult() => _reader.NextResult(); - bool IDataReader.Read() => Reader.Read(); + bool IDataReader.Read() => _reader.Read(); - int IDataReader.RecordsAffected => Reader.RecordsAffected; + int IDataReader.RecordsAffected => _reader.RecordsAffected; void IDisposable.Dispose() { - reader?.Close(); - reader?.Dispose(); - reader = null; - cmd?.Dispose(); - cmd = null; + _reader.Close(); + _reader.Dispose(); + _reader = DisposedReader.Instance; + _cmd?.Dispose(); + _cmd = null; } - int IDataRecord.FieldCount => Reader.FieldCount; + int IDataRecord.FieldCount => _reader.FieldCount; - bool IDataRecord.GetBoolean(int i) => Reader.GetBoolean(i); + bool IDataRecord.GetBoolean(int i) => _reader.GetBoolean(i); - byte IDataRecord.GetByte(int i) => Reader.GetByte(i); + byte IDataRecord.GetByte(int i) => _reader.GetByte(i); long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => - Reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); + _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); - char IDataRecord.GetChar(int i) => Reader.GetChar(i); + char IDataRecord.GetChar(int i) => _reader.GetChar(i); long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => - Reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); + _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); - IDataReader IDataRecord.GetData(int i) => Reader.GetData(i); + IDataReader IDataRecord.GetData(int i) => _reader.GetData(i); - string IDataRecord.GetDataTypeName(int i) => Reader.GetDataTypeName(i); + string IDataRecord.GetDataTypeName(int i) => _reader.GetDataTypeName(i); - DateTime IDataRecord.GetDateTime(int i) => Reader.GetDateTime(i); + DateTime IDataRecord.GetDateTime(int i) => _reader.GetDateTime(i); - decimal IDataRecord.GetDecimal(int i) => Reader.GetDecimal(i); + decimal IDataRecord.GetDecimal(int i) => _reader.GetDecimal(i); - double IDataRecord.GetDouble(int i) => Reader.GetDouble(i); + double IDataRecord.GetDouble(int i) => _reader.GetDouble(i); - Type IDataRecord.GetFieldType(int i) => Reader.GetFieldType(i); + Type IDataRecord.GetFieldType(int i) => _reader.GetFieldType(i); - float IDataRecord.GetFloat(int i) => Reader.GetFloat(i); + float IDataRecord.GetFloat(int i) => _reader.GetFloat(i); - Guid IDataRecord.GetGuid(int i) => Reader.GetGuid(i); + Guid IDataRecord.GetGuid(int i) => _reader.GetGuid(i); - short IDataRecord.GetInt16(int i) => Reader.GetInt16(i); + short IDataRecord.GetInt16(int i) => _reader.GetInt16(i); - int IDataRecord.GetInt32(int i) => Reader.GetInt32(i); + int IDataRecord.GetInt32(int i) => _reader.GetInt32(i); - long IDataRecord.GetInt64(int i) => Reader.GetInt64(i); + long IDataRecord.GetInt64(int i) => _reader.GetInt64(i); - string IDataRecord.GetName(int i) => Reader.GetName(i); + string IDataRecord.GetName(int i) => _reader.GetName(i); - int IDataRecord.GetOrdinal(string name) => Reader.GetOrdinal(name); + int IDataRecord.GetOrdinal(string name) => _reader.GetOrdinal(name); - string IDataRecord.GetString(int i) => Reader.GetString(i); + string IDataRecord.GetString(int i) => _reader.GetString(i); - object IDataRecord.GetValue(int i) => Reader.GetValue(i); + object IDataRecord.GetValue(int i) => _reader.GetValue(i); - int IDataRecord.GetValues(object[] values) => Reader.GetValues(values); + int IDataRecord.GetValues(object[] values) => _reader.GetValues(values); - bool IDataRecord.IsDBNull(int i) => Reader.IsDBNull(i); + bool IDataRecord.IsDBNull(int i) => _reader.IsDBNull(i); - object IDataRecord.this[string name] => Reader[name]; + object IDataRecord.this[string name] => _reader[name]; - object IDataRecord.this[int i] => Reader[i]; + object IDataRecord.this[int i] => _reader[i]; } } diff --git a/global.json b/global.json new file mode 100644 index 000000000..c562f03c5 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ + +{ + "sdk": { + "version": "2.2.203" + } +} \ No newline at end of file From 28de4690d8dd5263a3d5ebbaf821b3813973fa8f Mon Sep 17 00:00:00 2001 From: mgravell Date: Wed, 12 Jun 2019 16:26:27 +0100 Subject: [PATCH 069/312] UI binding test: making build painful - kill it --- UIBindingTest/App.config | 6 - UIBindingTest/BindingForm.Designer.cs | 63 --------- UIBindingTest/BindingForm.cs | 21 --- UIBindingTest/BindingForm.resx | 120 ------------------ UIBindingTest/Program.cs | 22 ---- UIBindingTest/Properties/AssemblyInfo.cs | 22 ---- .../Properties/Resources.Designer.cs | 71 ----------- UIBindingTest/Properties/Resources.resx | 117 ----------------- UIBindingTest/Properties/Settings.Designer.cs | 30 ----- UIBindingTest/Properties/Settings.settings | 7 - UIBindingTest/UIBindingTest.csproj | 89 ------------- 11 files changed, 568 deletions(-) delete mode 100644 UIBindingTest/App.config delete mode 100644 UIBindingTest/BindingForm.Designer.cs delete mode 100644 UIBindingTest/BindingForm.cs delete mode 100644 UIBindingTest/BindingForm.resx delete mode 100644 UIBindingTest/Program.cs delete mode 100644 UIBindingTest/Properties/AssemblyInfo.cs delete mode 100644 UIBindingTest/Properties/Resources.Designer.cs delete mode 100644 UIBindingTest/Properties/Resources.resx delete mode 100644 UIBindingTest/Properties/Settings.Designer.cs delete mode 100644 UIBindingTest/Properties/Settings.settings delete mode 100644 UIBindingTest/UIBindingTest.csproj diff --git a/UIBindingTest/App.config b/UIBindingTest/App.config deleted file mode 100644 index b50c74f35..000000000 --- a/UIBindingTest/App.config +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/UIBindingTest/BindingForm.Designer.cs b/UIBindingTest/BindingForm.Designer.cs deleted file mode 100644 index 2f88c4059..000000000 --- a/UIBindingTest/BindingForm.Designer.cs +++ /dev/null @@ -1,63 +0,0 @@ -namespace UIBindingTest -{ - partial class BindingForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if (disposing && (components != null)) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - this.mainGrid = new System.Windows.Forms.DataGridView(); - ((System.ComponentModel.ISupportInitialize)(this.mainGrid)).BeginInit(); - this.SuspendLayout(); - // - // mainGrid - // - this.mainGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize; - this.mainGrid.Dock = System.Windows.Forms.DockStyle.Fill; - this.mainGrid.Location = new System.Drawing.Point(0, 0); - this.mainGrid.Name = "mainGrid"; - this.mainGrid.RowTemplate.Height = 28; - this.mainGrid.Size = new System.Drawing.Size(1235, 855); - this.mainGrid.TabIndex = 0; - // - // BindingForm - // - this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 20F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; - this.ClientSize = new System.Drawing.Size(1235, 855); - this.Controls.Add(this.mainGrid); - this.Name = "BindingForm"; - this.Text = "Binding Test"; - ((System.ComponentModel.ISupportInitialize)(this.mainGrid)).EndInit(); - this.ResumeLayout(false); - - } - - #endregion - - private System.Windows.Forms.DataGridView mainGrid; - } -} - diff --git a/UIBindingTest/BindingForm.cs b/UIBindingTest/BindingForm.cs deleted file mode 100644 index 77b8d802b..000000000 --- a/UIBindingTest/BindingForm.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.Data.SqlClient; -using System.Windows.Forms; -using Dapper; - -namespace UIBindingTest -{ - public partial class BindingForm : Form - { - public BindingForm() - { - InitializeComponent(); - - SuspendLayout(); - using (var conn = new SqlConnection("Data Source=.;Initial Catalog=master;Integrated Security=SSPI")) - { - mainGrid.DataSource = conn.Query("select * from sys.objects").AsList(); - } - ResumeLayout(); - } - } -} diff --git a/UIBindingTest/BindingForm.resx b/UIBindingTest/BindingForm.resx deleted file mode 100644 index 1af7de150..000000000 --- a/UIBindingTest/BindingForm.resx +++ /dev/null @@ -1,120 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/UIBindingTest/Program.cs b/UIBindingTest/Program.cs deleted file mode 100644 index 8800279eb..000000000 --- a/UIBindingTest/Program.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using System.Windows.Forms; - -namespace UIBindingTest -{ - static class Program - { - /// - /// The main entry point for the application. - /// - [STAThread] - static void Main() - { - Application.EnableVisualStyles(); - Application.SetCompatibleTextRenderingDefault(false); - Application.Run(new BindingForm()); - } - } -} diff --git a/UIBindingTest/Properties/AssemblyInfo.cs b/UIBindingTest/Properties/AssemblyInfo.cs deleted file mode 100644 index 0b416fc55..000000000 --- a/UIBindingTest/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Reflection; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("UIBindingTest")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("")] -[assembly: AssemblyProduct("UIBindingTest")] -[assembly: AssemblyCopyright("Copyright © 2019")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("fc8502eb-df49-469b-b752-466ee61cc5c4")] diff --git a/UIBindingTest/Properties/Resources.Designer.cs b/UIBindingTest/Properties/Resources.Designer.cs deleted file mode 100644 index cf72b1e5c..000000000 --- a/UIBindingTest/Properties/Resources.Designer.cs +++ /dev/null @@ -1,71 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace UIBindingTest.Properties -{ - - - /// - /// A strongly-typed resource class, for looking up localized strings, etc. - /// - // This class was auto-generated by the StronglyTypedResourceBuilder - // class via a tool like ResGen or Visual Studio. - // To add or remove a member, edit your .ResX file then rerun ResGen - // with the /str option, or rebuild your VS project. - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] - [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - internal class Resources - { - - private static global::System.Resources.ResourceManager resourceMan; - - private static global::System.Globalization.CultureInfo resourceCulture; - - [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] - internal Resources() - { - } - - /// - /// Returns the cached ResourceManager instance used by this class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Resources.ResourceManager ResourceManager - { - get - { - if ((resourceMan == null)) - { - global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("UIBindingTest.Properties.Resources", typeof(Resources).Assembly); - resourceMan = temp; - } - return resourceMan; - } - } - - /// - /// Overrides the current thread's CurrentUICulture property for all - /// resource lookups using this strongly typed resource class. - /// - [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] - internal static global::System.Globalization.CultureInfo Culture - { - get - { - return resourceCulture; - } - set - { - resourceCulture = value; - } - } - } -} diff --git a/UIBindingTest/Properties/Resources.resx b/UIBindingTest/Properties/Resources.resx deleted file mode 100644 index af7dbebba..000000000 --- a/UIBindingTest/Properties/Resources.resx +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - text/microsoft-resx - - - 2.0 - - - System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - - System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - - \ No newline at end of file diff --git a/UIBindingTest/Properties/Settings.Designer.cs b/UIBindingTest/Properties/Settings.Designer.cs deleted file mode 100644 index 1911d50fd..000000000 --- a/UIBindingTest/Properties/Settings.Designer.cs +++ /dev/null @@ -1,30 +0,0 @@ -//------------------------------------------------------------------------------ -// -// This code was generated by a tool. -// Runtime Version:4.0.30319.42000 -// -// Changes to this file may cause incorrect behavior and will be lost if -// the code is regenerated. -// -//------------------------------------------------------------------------------ - -namespace UIBindingTest.Properties -{ - - - [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] - internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase - { - - private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); - - public static Settings Default - { - get - { - return defaultInstance; - } - } - } -} diff --git a/UIBindingTest/Properties/Settings.settings b/UIBindingTest/Properties/Settings.settings deleted file mode 100644 index 39645652a..000000000 --- a/UIBindingTest/Properties/Settings.settings +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/UIBindingTest/UIBindingTest.csproj b/UIBindingTest/UIBindingTest.csproj deleted file mode 100644 index 68f5bb022..000000000 --- a/UIBindingTest/UIBindingTest.csproj +++ /dev/null @@ -1,89 +0,0 @@ - - - - - Debug - AnyCPU - {FC8502EB-DF49-469B-B752-466EE61CC5C4} - WinExe - UIBindingTest - UIBindingTest - v4.6.2 - 512 - true - true - - - AnyCPU - true - full - false - bin\Debug\ - DEBUG;TRACE - prompt - 4 - - - AnyCPU - pdbonly - true - bin\Release\ - TRACE - prompt - 4 - - - - - - - - - - - - - - - - - Form - - - BindingForm.cs - - - - - BindingForm.cs - - - ResXFileCodeGenerator - Resources.Designer.cs - Designer - - - True - Resources.resx - - - SettingsSingleFileGenerator - Settings.Designer.cs - - - True - Settings.settings - True - - - - - - - - {fac24c3f-68f9-4247-a4b9-21d487e99275} - Dapper - - - - \ No newline at end of file From 67e3b338965726d75268b74eb6e4f215543ddb1c Mon Sep 17 00:00:00 2001 From: mgravell Date: Wed, 12 Jun 2019 16:27:27 +0100 Subject: [PATCH 070/312] remove ui test from sln --- Dapper.sln | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/Dapper.sln b/Dapper.sln index 6f1d148c7..d7245e6da 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 15 -VisualStudioVersion = 15.0.26730.8 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.28917.182 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" ProjectSection(SolutionItems) = preProject @@ -44,8 +44,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.Stro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UIBindingTest", "UIBindingTest\UIBindingTest.csproj", "{FC8502EB-DF49-469B-B752-466EE61CC5C4}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -92,10 +90,6 @@ Global {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU - {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FC8502EB-DF49-469B-B752-466EE61CC5C4}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -111,7 +105,6 @@ Global {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} - {FC8502EB-DF49-469B-B752-466EE61CC5C4} = {568BD46C-1C65-4D44-870C-12CD72563262} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} From fcd8a70279fbe19e2aa0dbc81f7625a0ddae5e2f Mon Sep 17 00:00:00 2001 From: mgravell Date: Wed, 12 Jun 2019 19:39:12 +0100 Subject: [PATCH 071/312] reinstate SqlDataRecord types (major conflict with MSSQLCLIENT, but that is removed for now) --- Dapper.Tests/ParameterTests.cs | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index e499d5846..e3567a85f 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -42,7 +42,7 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id command.Parameters.Add(parameter); } } - /* problems with conflicting type + private static List CreateSqlDataRecordList(IEnumerable numbers) { var number_list = new List(); @@ -107,7 +107,6 @@ public void AddParameter(IDbCommand command, string name) p.Value = number_list; } } - */ /* TODO: * @@ -222,8 +221,6 @@ public void TestMassiveStrings() } - /* problems with conflicting type - * [Fact] public void TestTVPWithAnonymousObject() { @@ -472,8 +469,6 @@ public void TestSqlDataRecordListParametersWithTypeHandlers() } } - */ - #if !NETCOREAPP1_0 [Fact] public void DataTableParameters() From 8b49eb691dd4eaca6d80993130ca8a3d88455111 Mon Sep 17 00:00:00 2001 From: Joseph Musser Date: Thu, 13 Jun 2019 02:37:41 -0400 Subject: [PATCH 072/312] Stop setting current index local which is nonexistent in ValueTuple deserializer (#1280) * String and byte[] tuple elements cause InvalidProgramException * Stop setting current index local which is nonexistent in ValueTuple deserializer --- Dapper.Tests/TupleTests.cs | 14 ++++++++++++++ Dapper/SqlMapper.cs | 10 ++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/Dapper.Tests/TupleTests.cs b/Dapper.Tests/TupleTests.cs index 92aa087f5..cfc1b05b6 100644 --- a/Dapper.Tests/TupleTests.cs +++ b/Dapper.Tests/TupleTests.cs @@ -98,5 +98,19 @@ public void TupleReturnValue_Works_With15Elements() Assert.Equal(14, val.e14); Assert.Equal(15, val.e15); } + + [Fact] + public void TupleReturnValue_Works_WithStringField() + { + var val = connection.QuerySingle>("select '42'"); + Assert.Equal("42", val.Item1); + } + + [Fact] + public void TupleReturnValue_Works_WithByteField() + { + var val = connection.QuerySingle>("select 0xDEADBEEF"); + Assert.Equal(new byte[] { 0xDE, 0xAD, 0xBE, 0xEF }, val.Item1); + } } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 82c4ab3a5..eb5f8c1be 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -3178,7 +3178,7 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataRea private static void GenerateDeserializerFromMap(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing, ILGenerator il) { - il.DeclareLocal(typeof(int)); + var currentIndexDiagnosticLocal = il.DeclareLocal(typeof(int)); il.DeclareLocal(type); il.Emit(OpCodes.Ldc_I4_0); il.Emit(OpCodes.Stloc_0); @@ -3292,6 +3292,10 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i Label finishLabel = il.DefineLabel(); Type memberType = item.MemberType; + // Save off the current index for access if an exception is thrown + EmitInt32(il, index); + il.Emit(OpCodes.Stloc, currentIndexDiagnosticLocal); + LoadReaderValueOrBranchToDBNullLabel(il, index, ref enumDeclareLocal, valueCopyLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel); if (specializedConstructor == null) @@ -3379,7 +3383,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i } il.MarkLabel(allDone); il.BeginCatchBlock(typeof(Exception)); // stack is Exception - il.Emit(OpCodes.Ldloc_0); // stack is Exception, index + il.Emit(OpCodes.Ldloc, currentIndexDiagnosticLocal); // stack is Exception, index il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.ThrowDataException)), null); @@ -3413,8 +3417,6 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind isDbNullLabel = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] EmitInt32(il, index); // stack is now [...][reader][index] - il.Emit(OpCodes.Dup);// stack is now [...][reader][index][index] - il.Emit(OpCodes.Stloc_0);// stack is now [...][reader][index] il.Emit(OpCodes.Callvirt, getItem); // stack is now [...][value-as-object] if (valueCopyLocal != null) From 6754fe2795fef79a604f368dd4cb23ffae255414 Mon Sep 17 00:00:00 2001 From: Joseph Musser Date: Tue, 27 Aug 2019 11:52:43 -0400 Subject: [PATCH 073/312] Add ExecuteReaderAsync overloads that return Task (#1295) --- Dapper/Extensions.cs | 44 ++++++++++++++++++++++++++++++ Dapper/SqlMapper.Async.cs | 57 +++++++++++++++++++++++++++++---------- Dapper/WrappedReader.cs | 10 ++++++- 3 files changed, 96 insertions(+), 15 deletions(-) create mode 100644 Dapper/Extensions.cs diff --git a/Dapper/Extensions.cs b/Dapper/Extensions.cs new file mode 100644 index 000000000..6bd91529e --- /dev/null +++ b/Dapper/Extensions.cs @@ -0,0 +1,44 @@ +using System; +using System.Threading.Tasks; + +namespace Dapper +{ + internal static class Extensions + { + /// + /// Creates a with a less specific generic parameter that perfectly mirrors the + /// state of the specified . + /// + internal static Task CastResult(this Task task) + where TFrom : TTo + { + if (task is null) throw new ArgumentNullException(nameof(task)); + + if (task.Status == TaskStatus.RanToCompletion) + return Task.FromResult((TTo)task.Result); + + var source = new TaskCompletionSource(); + task.ContinueWith(OnTaskCompleted, state: source, TaskContinuationOptions.ExecuteSynchronously); + return source.Task; + } + + private static void OnTaskCompleted(Task completedTask, object state) + where TFrom : TTo + { + var source = (TaskCompletionSource)state; + + switch (completedTask.Status) + { + case TaskStatus.RanToCompletion: + source.SetResult(completedTask.Result); + break; + case TaskStatus.Canceled: + source.SetCanceled(); + break; + case TaskStatus.Faulted: + source.SetException(completedTask.Exception.InnerExceptions); + break; + } + } + } +} diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index b5177275b..b3aeec3f9 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -687,7 +687,7 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini } /// - /// Perform a asynchronous multi-mapping query with 2 input types. + /// Perform a asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -708,7 +708,7 @@ public static Task> QueryAsync(th new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 2 input types. + /// Perform a asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -723,7 +723,7 @@ public static Task> QueryAsync(th MultiMapAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 3 input types. + /// Perform a asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -745,7 +745,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 3 input types. + /// Perform a asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -761,7 +761,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 4 input types. + /// Perform a asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -784,7 +784,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 4 input types. + /// Perform a asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -801,7 +801,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 5 input types. + /// Perform a asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -825,7 +825,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 5 input types. + /// Perform a asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -843,7 +843,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 6 input types. + /// Perform a asynchronous multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -868,7 +868,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 6 input types. + /// Perform a asynchronous multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -887,7 +887,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 7 input types. + /// Perform a asynchronous multi-mapping query with 7 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -913,7 +913,7 @@ public static Task> QueryAsync - /// Perform an asynchronous multi-mapping query with 7 input types. + /// Perform an asynchronous multi-mapping query with 7 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -956,7 +956,7 @@ private static async Task> MultiMapAsync - /// Perform a asynchronous multi-mapping query with an arbitrary number of input types. + /// Perform a asynchronous multi-mapping query with an arbitrary number of input types. /// This returns a single type, combined from the raw types via . /// /// The combined type to return. @@ -1101,6 +1101,18 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, /// /// public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default).CastResult(); + + /// + /// Execute parameterized SQL and return a . + /// + /// The connection to execute on. + /// The SQL to execute. + /// The parameters to use for this command. + /// The transaction to use for this command. + /// Number of seconds before command execution timeout. + /// Is it a stored proc or a batch? + public static Task ExecuteReaderAsync(this DbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); /// @@ -1114,6 +1126,14 @@ public static Task ExecuteReaderAsync(this IDbConnection cnn, strin /// or . /// public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command) => + ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default).CastResult(); + + /// + /// Execute parameterized SQL and return a . + /// + /// The connection to execute on. + /// The command to execute. + public static Task ExecuteReaderAsync(this DbConnection cnn, CommandDefinition command) => ExecuteWrappedReaderImplAsync(cnn, command, CommandBehavior.Default); /// @@ -1128,9 +1148,18 @@ public static Task ExecuteReaderAsync(this IDbConnection cnn, Comma /// or . /// public static Task ExecuteReaderAsync(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) => + ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior).CastResult(); + + /// + /// Execute parameterized SQL and return a . + /// + /// The connection to execute on. + /// The command to execute. + /// The flags for this reader. + public static Task ExecuteReaderAsync(this DbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) => ExecuteWrappedReaderImplAsync(cnn, command, commandBehavior); - private static async Task ExecuteWrappedReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) + private static async Task ExecuteWrappedReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { Action paramReader = GetParameterReader(cnn, ref command); diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index 8e2ab2c8c..1956948cc 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -93,6 +93,14 @@ public static IDataReader Create(IDbCommand cmd, IDataReader reader) cmd.Dispose(); return null; // GIGO } + public static DbDataReader Create(IDbCommand cmd, DbDataReader reader) + { + if (cmd == null) return reader; // no need to wrap if no command + + if (reader != null) return new DbWrappedReader(cmd, reader); + cmd.Dispose(); + return null; // GIGO + } } internal sealed class DbWrappedReader : DbDataReader, IWrappedDataReader { @@ -143,7 +151,7 @@ protected override void Dispose(bool disposing) _cmd = null; } } - + public override int FieldCount => _reader.FieldCount; public override bool GetBoolean(int i) => _reader.GetBoolean(i); From f1ad61c711596a3985713dceaeb21a2f05387052 Mon Sep 17 00:00:00 2001 From: Joseph Musser Date: Tue, 27 Aug 2019 12:01:34 -0400 Subject: [PATCH 074/312] Use LocalBuilders rather than hardcoding or passing local indexes (#1282) * Pass LocalBuilders instead of indexes since ILGenerator already optimizes the opcode used * Eradicate magic local indexes to make refactoring safer --- Dapper/SqlMapper.cs | 152 ++++++++++++++------------------------------ 1 file changed, 48 insertions(+), 104 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index eb5f8c1be..3d65be65f 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2391,19 +2391,21 @@ internal static Action CreateParamInfoGenerator(Identity ide var il = dm.GetILGenerator(); bool isStruct = type.IsValueType(); - bool haveInt32Arg1 = false; + var sizeLocal = (LocalBuilder)null; il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] + + LocalBuilder typedParameterLocal; if (isStruct) { - il.DeclareLocal(type.MakeByRefType()); // note: ref-local + typedParameterLocal = il.DeclareLocal(type.MakeByRefType()); // note: ref-local il.Emit(OpCodes.Unbox, type); // stack is now [typed-param] } else { - il.DeclareLocal(type); // 0 + typedParameterLocal = il.DeclareLocal(type); il.Emit(OpCodes.Castclass, type); // stack is now [typed-param] } - il.Emit(OpCodes.Stloc_0);// stack is now empty + il.Emit(OpCodes.Stloc, typedParameterLocal); // stack is now empty il.Emit(OpCodes.Ldarg_0); // stack is now [command] il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty(nameof(IDbCommand.Parameters)).GetGetMethod(), null); // stack is now [parameters] @@ -2482,7 +2484,7 @@ internal static Action CreateParamInfoGenerator(Identity ide { if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType)) { - il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [typed-param] + il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [custom] il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name] @@ -2497,7 +2499,7 @@ internal static Action CreateParamInfoGenerator(Identity ide // this actually represents special handling for list types; il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] - il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [command] [name] [typed-param] + il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [command] [name] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value] if (prop.PropertyType.IsValueType()) { @@ -2531,7 +2533,7 @@ internal static Action CreateParamInfoGenerator(Identity ide if (dbType == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic { // look it up from the param value - il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] + il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value] il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.GetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] } @@ -2548,7 +2550,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Direction)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] - il.Emit(OpCodes.Ldloc_0); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] + il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] bool checkForNull; if (prop.PropertyType.IsValueType()) @@ -2600,10 +2602,9 @@ internal static Action CreateParamInfoGenerator(Identity ide } if (checkForNull) { - if ((dbType == DbType.String || dbType == DbType.AnsiString) && !haveInt32Arg1) + if ((dbType == DbType.String || dbType == DbType.AnsiString) && sizeLocal == null) { - il.DeclareLocal(typeof(int)); - haveInt32Arg1 = true; + sizeLocal = il.DeclareLocal(typeof(int)); } // relative stack: [boxed value] il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value] @@ -2616,7 +2617,7 @@ internal static Action CreateParamInfoGenerator(Identity ide if (dbType == DbType.String || dbType == DbType.AnsiString) { EmitInt32(il, 0); - il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Stloc, sizeLocal); } if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value); il.MarkLabel(notNull); @@ -2633,7 +2634,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.MarkLabel(isLong); EmitInt32(il, -1); // [string] [-1] il.MarkLabel(lenDone); - il.Emit(OpCodes.Stloc_1); // [string] + il.Emit(OpCodes.Stloc, sizeLocal); // [string] } if (prop.PropertyType.FullName == LinqBinary) { @@ -2658,11 +2659,11 @@ internal static Action CreateParamInfoGenerator(Identity ide { var endOfSize = il.DefineLabel(); // don't set if 0 - il.Emit(OpCodes.Ldloc_1); // [parameters] [[parameters]] [parameter] [size] + il.Emit(OpCodes.Ldloc, sizeLocal); // [parameters] [[parameters]] [parameter] [size] il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] - il.Emit(OpCodes.Ldloc_1); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size] + il.Emit(OpCodes.Ldloc, sizeLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size] il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty(nameof(IDbDataParameter.Size)).GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter] il.MarkLabel(endOfSize); @@ -2715,7 +2716,7 @@ internal static Action CreateParamInfoGenerator(Identity ide if (prop != null) { il.Emit(OpCodes.Ldstr, literal.Token); - il.Emit(OpCodes.Ldloc_0); // command, sql, typed parameter + il.Emit(OpCodes.Ldloc, typedParameterLocal); // command, sql, typed parameter il.EmitCall(callOpCode, prop.GetGetMethod(), null); // command, sql, typed value Type propType = prop.PropertyType; var typeCode = TypeExtensions.GetTypeCode(propType); @@ -3046,9 +3047,9 @@ private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary reader.GetName(i)).ToArray(); @@ -3196,7 +3197,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i Dictionary structLocals = null; if (type.IsValueType()) { - il.Emit(OpCodes.Ldloca_S, (byte)1); + il.Emit(OpCodes.Ldloca, returnValueLocal); il.Emit(OpCodes.Initobj, type); } else @@ -3224,12 +3225,12 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i } il.Emit(OpCodes.Newobj, explicitConstr); - il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Stloc, returnValueLocal); #if !NETSTANDARD1_3 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { - il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); } #endif @@ -3246,12 +3247,12 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i if (ctor.GetParameters().Length == 0) { il.Emit(OpCodes.Newobj, ctor); - il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Stloc, returnValueLocal); #if !NETSTANDARD1_3 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { - il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); } #endif @@ -3266,11 +3267,11 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i il.BeginExceptionBlock(); if (type.IsValueType()) { - il.Emit(OpCodes.Ldloca_S, (byte)1);// [target] + il.Emit(OpCodes.Ldloca, returnValueLocal); // [target] } else if (specializedConstructor == null) { - il.Emit(OpCodes.Ldloc_1);// [target] + il.Emit(OpCodes.Ldloc, returnValueLocal); // [target] } var members = (specializedConstructor != null @@ -3281,7 +3282,8 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i bool first = true; var allDone = il.DefineLabel(); - int enumDeclareLocal = -1, valueCopyLocal = il.DeclareLocal(typeof(object)).LocalIndex; + var stringEnumLocal = (LocalBuilder)null; + var valueCopyDiagnosticLocal = il.DeclareLocal(typeof(object)); bool applyNullSetting = Settings.ApplyNullValues; foreach (var item in members) { @@ -3296,7 +3298,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i EmitInt32(il, index); il.Emit(OpCodes.Stloc, currentIndexDiagnosticLocal); - LoadReaderValueOrBranchToDBNullLabel(il, index, ref enumDeclareLocal, valueCopyLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel); + LoadReaderValueOrBranchToDBNullLabel(il, index, ref stringEnumLocal, valueCopyDiagnosticLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel); if (specializedConstructor == null) { @@ -3353,7 +3355,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i { il.Emit(OpCodes.Pop); il.Emit(OpCodes.Ldnull); // stack is now [null] - il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Stloc, returnValueLocal); il.Emit(OpCodes.Br, allDone); } @@ -3372,11 +3374,11 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i { il.Emit(OpCodes.Newobj, specializedConstructor); } - il.Emit(OpCodes.Stloc_1); // stack is empty + il.Emit(OpCodes.Stloc, returnValueLocal); // stack is empty #if !NETSTANDARD1_3 if (supportInitialize) { - il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.EndInit)), null); } #endif @@ -3385,11 +3387,11 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i il.BeginCatchBlock(typeof(Exception)); // stack is Exception il.Emit(OpCodes.Ldloc, currentIndexDiagnosticLocal); // stack is Exception, index il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader - LoadLocal(il, valueCopyLocal); // stack is Exception, index, reader, value + il.Emit(OpCodes.Ldloc, valueCopyDiagnosticLocal); // stack is Exception, index, reader, value il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.ThrowDataException)), null); il.EndExceptionBlock(); - il.Emit(OpCodes.Ldloc_1); // stack is [rval] + il.Emit(OpCodes.Ldloc, returnValueLocal); // stack is [rval] if (type.IsValueType()) { il.Emit(OpCodes.Box, type); @@ -3401,10 +3403,10 @@ private static void LoadDefaultValue(ILGenerator il, Type type) { if (type.IsValueType()) { - int localIndex = il.DeclareLocal(type).LocalIndex; - LoadLocalAddress(il, localIndex); + var local = il.DeclareLocal(type); + il.Emit(OpCodes.Ldloca, local); il.Emit(OpCodes.Initobj, type); - LoadLocal(il, localIndex); + il.Emit(OpCodes.Ldloc, local); } else { @@ -3412,7 +3414,7 @@ private static void LoadDefaultValue(ILGenerator il, Type type) } } - private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref int enumDeclareLocal, int? valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel) + private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref LocalBuilder stringEnumLocal, LocalBuilder valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel) { isDbNullLabel = il.DefineLabel(); il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] @@ -3422,7 +3424,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind if (valueCopyLocal != null) { il.Emit(OpCodes.Dup); // stack is now [...][value-as-object][value-as-object] - StoreLocal(il, valueCopyLocal.Value); // stack is now [...][value-as-object] + il.Emit(OpCodes.Stloc, valueCopyLocal); // stack is now [...][value-as-object] } if (memberType == typeof(char) || memberType == typeof(char?)) @@ -3446,15 +3448,15 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind Type numericType = Enum.GetUnderlyingType(unboxType); if (colType == typeof(string)) { - if (enumDeclareLocal == -1) + if (stringEnumLocal == null) { - enumDeclareLocal = il.DeclareLocal(typeof(string)).LocalIndex; + stringEnumLocal = il.DeclareLocal(typeof(string)); } il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] - StoreLocal(il, enumDeclareLocal); // stack is now [...] + il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null);// stack is now [...][enum-type] - LoadLocal(il, enumDeclareLocal); // stack is now [...][enum-type][string] + il.Emit(OpCodes.Ldloc, stringEnumLocal); // stack is now [...][enum-type][string] il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] @@ -3608,64 +3610,6 @@ private static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type return null; } - private static void LoadLocal(ILGenerator il, int index) - { - if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException(nameof(index)); - switch (index) - { - case 0: il.Emit(OpCodes.Ldloc_0); break; - case 1: il.Emit(OpCodes.Ldloc_1); break; - case 2: il.Emit(OpCodes.Ldloc_2); break; - case 3: il.Emit(OpCodes.Ldloc_3); break; - default: - if (index <= 255) - { - il.Emit(OpCodes.Ldloc_S, (byte)index); - } - else - { - il.Emit(OpCodes.Ldloc, (short)index); - } - break; - } - } - - private static void StoreLocal(ILGenerator il, int index) - { - if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException(nameof(index)); - switch (index) - { - case 0: il.Emit(OpCodes.Stloc_0); break; - case 1: il.Emit(OpCodes.Stloc_1); break; - case 2: il.Emit(OpCodes.Stloc_2); break; - case 3: il.Emit(OpCodes.Stloc_3); break; - default: - if (index <= 255) - { - il.Emit(OpCodes.Stloc_S, (byte)index); - } - else - { - il.Emit(OpCodes.Stloc, (short)index); - } - break; - } - } - - private static void LoadLocalAddress(ILGenerator il, int index) - { - if (index < 0 || index >= short.MaxValue) throw new ArgumentNullException(nameof(index)); - - if (index <= 255) - { - il.Emit(OpCodes.Ldloca_S, (byte)index); - } - else - { - il.Emit(OpCodes.Ldloca, (short)index); - } - } - /// /// Throws a data exception, only used internally /// @@ -3795,7 +3739,7 @@ public static string GetTypeName(this DataTable table) => public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) where T : IDataRecord => new SqlDataRecordListTVPParameter(list, typeName); - /* + /* /// /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. /// From 4eeb632fd56e665468a4f2aa6830e6d66727e766 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Tue, 27 Aug 2019 18:03:49 +0200 Subject: [PATCH 075/312] Fixed issue #569 with IN and other condition. (#1146) --- Dapper.Tests/Providers/OLDEBTests.cs | 16 ++++++++++++++++ Dapper/SqlMapper.cs | 4 ++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/Dapper.Tests/Providers/OLDEBTests.cs b/Dapper.Tests/Providers/OLDEBTests.cs index 227e7c63d..bfdffb3de 100644 --- a/Dapper.Tests/Providers/OLDEBTests.cs +++ b/Dapper.Tests/Providers/OLDEBTests.cs @@ -81,6 +81,22 @@ public void PseudoPositionalParameters_ReusedParameter() } } + [Fact] + public void Issue569_SO38527197_PseudoPositionalParameters_In_And_Other_Condition() + { + const string sql = @"select s1.value as id, s2.value as score + from string_split('1,2,3,4,5',',') s1, string_split('1,2,3,4,5',',') s2 + where s1.value in ?ids? and s2.value = ?score?"; + using (var connection = GetOleDbConnection()) + { + const int score = 2; + int[] ids = { 1, 2, 5, 7 }; + var list = connection.Query(sql, new { ids, score }).AsList(); + list.Sort(); + Assert.Equal("1,2,5", string.Join(",", list)); + } + } + [Fact] public void Issue569_SO38527197_PseudoPositionalParameters_In() { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 3d65be65f..f5a07792b 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2101,11 +2101,11 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj else { var sb = GetStringBuilder().Append('(').Append(variableName); - if (!byPosition) sb.Append(1); + if (!byPosition) sb.Append(1); else sb.Append(namePrefix).Append(1).Append(variableName); for (int i = 2; i <= count; i++) { sb.Append(',').Append(variableName); - if (!byPosition) sb.Append(i); + if (!byPosition) sb.Append(i); else sb.Append(namePrefix).Append(i).Append(variableName); } return sb.Append(')').__ToStringRecycle(); } From 012e9b6f08d60df73d0f1bdc2249d2143aab8174 Mon Sep 17 00:00:00 2001 From: Dmytro Gokun Date: Tue, 27 Aug 2019 19:32:51 +0300 Subject: [PATCH 076/312] Add ability to override Dispose() in the derived class (#1256) --- Dapper.Rainbow/Database.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index 7bd0f91c4..b155baf42 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -464,7 +464,7 @@ public SqlMapper.GridReader QueryMultiple(string sql, dynamic param = null, IDbT /// /// Disposes the current database, rolling back current transactions. /// - public void Dispose() + public virtual void Dispose() { if (_connection.State != ConnectionState.Closed) { From 6df95cbdbdd90d81323aaf19904b4d331e643b0f Mon Sep 17 00:00:00 2001 From: Dmytro Gokun Date: Tue, 27 Aug 2019 19:33:14 +0300 Subject: [PATCH 077/312] Expose the underlying connection from Rainbow Database so it's easier to use Dapper alongside other DB frameworks (#1257) --- Dapper.Rainbow/Database.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index b155baf42..a408d8708 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -175,6 +175,11 @@ public Table(Database database, string likelyTableName) private int _commandTimeout; private DbTransaction _transaction; + /// + /// Get underlying database connection. + /// + public DbConnection Connection => _connection; + /// /// Initializes the database. /// From 8152f2c71823b4834c04afaa7eaa479b1a4fdb64 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 28 Aug 2019 15:07:32 +0100 Subject: [PATCH 078/312] WIP: Lib updates and prepare for Microsoft.Data.SqlClient (#1313) v2 work; primarily prep for split SqlClient --- Dapper.Contrib/Dapper.Contrib.csproj | 13 +- Dapper.Contrib/SqlMapperExtensions.Async.cs | 20 +- Dapper.Contrib/SqlMapperExtensions.cs | 22 +- .../Dapper.EntityFramework.StrongName.csproj | 11 +- .../Dapper.EntityFramework.csproj | 11 +- Dapper.Rainbow/Dapper.Rainbow.csproj | 14 +- Dapper.Rainbow/Database.cs | 2 +- Dapper.Rainbow/Snapshotter.cs | 34 +-- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 14 +- Dapper.StrongName/Dapper.StrongName.csproj | 28 +-- .../Dapper.Tests.Contrib.csproj | 16 +- Dapper.Tests.Contrib/TestSuite.cs | 17 +- Dapper.Tests.Contrib/TestSuites.cs | 9 +- Dapper.Tests.Performance/Config.cs | 10 +- .../Dapper.Tests.Performance.csproj | 24 +-- Dapper.Tests.Performance/Helpers/ORMColum.cs | 2 +- .../Helpers/ReturnColum.cs | 2 +- Dapper.Tests/App.config | 11 + Dapper.Tests/AsyncTests.cs | 17 +- Dapper.Tests/ConstructorTests.cs | 2 + Dapper.Tests/Dapper.Tests.csproj | 68 +++--- Dapper.Tests/DataReaderTests.cs | 2 + Dapper.Tests/DecimalTests.cs | 2 + Dapper.Tests/EnumTests.cs | 2 + Dapper.Tests/Helpers/Attributes.cs | 30 ++- Dapper.Tests/Helpers/XunitSkippable.cs | 136 ++++++++---- Dapper.Tests/LiteralTests.cs | 2 + Dapper.Tests/MiscTests.cs | 2 + Dapper.Tests/MultiMapTests.cs | 2 + Dapper.Tests/ParameterTests.cs | 121 ++++++++--- Dapper.Tests/ProcedureTests.cs | 2 + .../Providers/EntityFrameworkTests.cs | 4 + Dapper.Tests/QueryMultipleTests.cs | 2 + Dapper.Tests/TestBase.cs | 3 + Dapper.Tests/TransactionTests.cs | 2 + Dapper.Tests/TupleTests.cs | 2 + Dapper.Tests/XmlTests.cs | 2 + Dapper.sln | 1 + Dapper/Dapper.csproj | 28 +-- Dapper/DataTableHandler.cs | 2 - Dapper/DefaultTypeMap.cs | 29 +-- Dapper/DynamicParameters.cs | 4 - Dapper/SqlDataRecordHandler.cs | 2 - Dapper/SqlDataRecordListTVPParameter.cs | 2 - Dapper/SqlMapper.Async.cs | 18 +- Dapper/SqlMapper.DapperRow.Descriptor.cs | 4 +- Dapper/SqlMapper.GridReader.cs | 10 +- Dapper/SqlMapper.IDataReader.cs | 2 +- Dapper/SqlMapper.Identity.cs | 139 +++++++++++-- Dapper/SqlMapper.TypeHandlerCache.cs | 2 - Dapper/SqlMapper.cs | 196 ++++++++---------- Dapper/TableValuedParameter.cs | 2 - Dapper/TypeExtensions.cs | 87 +------- Dapper/UdtTypeHandler.cs | 2 - Directory.build.props | 4 +- version.json | 4 +- 56 files changed, 633 insertions(+), 568 deletions(-) create mode 100644 Dapper.Tests/App.config diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj index add00a547..8ee574dfd 100644 --- a/Dapper.Contrib/Dapper.Contrib.csproj +++ b/Dapper.Contrib/Dapper.Contrib.csproj @@ -5,8 +5,7 @@ Dapper.Contrib The official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities. Sam Saffron;Johan Danforth - net451;netstandard1.3;netstandard2.0 - + netstandard2.0 false @@ -15,16 +14,8 @@ - - - - - - - - - + \ No newline at end of file diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index d7f975c5c..e08a015dc 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -37,7 +37,7 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i var dynParms = new DynamicParameters(); dynParms.Add("@id", id); - if (!type.IsInterface()) + if (!type.IsInterface) return (await connection.QueryAsync(sql, dynParms, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); var res = (await connection.QueryAsync(sql, dynParms).ConfigureAwait(false)).FirstOrDefault() as IDictionary; @@ -51,7 +51,7 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i { var val = res[property.Name]; if (val == null) continue; - if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); @@ -92,7 +92,7 @@ public static Task> GetAllAsync(this IDbConnection connection, GetQueries[cacheType.TypeHandle] = sql; } - if (!type.IsInterface()) + if (!type.IsInterface) { return connection.QueryAsync(sql, null, transaction, commandTimeout); } @@ -110,7 +110,7 @@ private static async Task> GetAllAsyncImpl(IDbConnection conne { var val = res[property.Name]; if (val == null) continue; - if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); @@ -148,11 +148,11 @@ public static Task InsertAsync(this IDbConnection connection, T entityTo isList = true; type = type.GetElementType(); } - else if (type.IsGenericType()) + else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) @@ -219,11 +219,11 @@ public static async Task UpdateAsync(this IDbConnection connection, T e { type = type.GetElementType(); } - else if (type.IsGenericType()) + else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) @@ -288,11 +288,11 @@ public static async Task DeleteAsync(this IDbConnection connection, T e { type = type.GetElementType(); } - else if (type.IsGenericType()) + else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index f8a8f2160..a41cfeaa1 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -190,7 +190,7 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction T obj; - if (type.IsInterface()) + if (type.IsInterface) { var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary; @@ -203,7 +203,7 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction { var val = res[property.Name]; if (val == null) continue; - if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); @@ -248,7 +248,7 @@ public static IEnumerable GetAll(this IDbConnection connection, IDbTransac GetQueries[cacheType.TypeHandle] = sql; } - if (!type.IsInterface()) return connection.Query(sql, null, transaction, commandTimeout: commandTimeout); + if (!type.IsInterface) return connection.Query(sql, null, transaction, commandTimeout: commandTimeout); var result = connection.Query(sql); var list = new List(); @@ -259,7 +259,7 @@ public static IEnumerable GetAll(this IDbConnection connection, IDbTransac { var val = res[property.Name]; if (val == null) continue; - if (property.PropertyType.IsGenericType() && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) + if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) { var genericType = Nullable.GetUnderlyingType(property.PropertyType); if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); @@ -307,7 +307,7 @@ private static string GetTableName(Type type) else { name = type.Name + "s"; - if (type.IsInterface() && name.StartsWith("I")) + if (type.IsInterface && name.StartsWith("I")) name = name.Substring(1); } } @@ -336,11 +336,11 @@ public static long Insert(this IDbConnection connection, T entityToInsert, ID isList = true; type = type.GetElementType(); } - else if (type.IsGenericType()) + else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) @@ -417,11 +417,11 @@ public static bool Update(this IDbConnection connection, T entityToUpdate, ID { type = type.GetElementType(); } - else if (type.IsGenericType()) + else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) @@ -486,11 +486,11 @@ public static bool Delete(this IDbConnection connection, T entityToDelete, ID { type = type.GetElementType(); } - else if (type.IsGenericType()) + else if (type.IsGenericType) { var typeInfo = type.GetTypeInfo(); bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType() && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || + typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); if (implementsGenericIEnumerableOrIsGenericIEnumerable) diff --git a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj index fdc7382c0..87e09a1bb 100644 --- a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj +++ b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj @@ -4,7 +4,7 @@ Dapper: Entity Framework type handlers (with a strong name) Extension handlers for entity framework Marc Gravell;Nick Craver - net451 + net462 ../Dapper.snk true true @@ -16,13 +16,8 @@ + - - - - - - - + diff --git a/Dapper.EntityFramework/Dapper.EntityFramework.csproj b/Dapper.EntityFramework/Dapper.EntityFramework.csproj index 8d2976e78..598dcc66c 100644 --- a/Dapper.EntityFramework/Dapper.EntityFramework.csproj +++ b/Dapper.EntityFramework/Dapper.EntityFramework.csproj @@ -5,18 +5,13 @@ Dapper entity framework type handlers 1.50.2 Marc Gravell;Nick Craver - net451 + net462 orm;sql;micro-orm + - - - - - - - + \ No newline at end of file diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj index 5712fff9a..fc30f7650 100644 --- a/Dapper.Rainbow/Dapper.Rainbow.csproj +++ b/Dapper.Rainbow/Dapper.Rainbow.csproj @@ -6,7 +6,7 @@ Trivial micro-orm implemented on Dapper, provides with CRUD helpers. Sam Saffron 2017 Sam Saffron - net451;netstandard1.3 + netstandard2.0 false @@ -16,14 +16,8 @@ - - - - - - - - - + + + \ No newline at end of file diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index a408d8708..d9ab4cb6f 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -257,7 +257,7 @@ protected Action CreateTableConstructor(params Type[] tableTypes) var il = dm.GetILGenerator(); var setters = GetType().GetProperties() - .Where(p => p.PropertyType.IsGenericType() && tableTypes.Contains(p.PropertyType.GetGenericTypeDefinition())) + .Where(p => p.PropertyType.IsGenericType && tableTypes.Contains(p.PropertyType.GetGenericTypeDefinition())) .Select(p => Tuple.Create( p.GetSetMethod(true), p.PropertyType.GetConstructor(new[] { typeof(TDatabase), typeof(string) }), diff --git a/Dapper.Rainbow/Snapshotter.cs b/Dapper.Rainbow/Snapshotter.cs index ef6f8b0d5..d267d60cb 100644 --- a/Dapper.Rainbow/Snapshotter.cs +++ b/Dapper.Rainbow/Snapshotter.cs @@ -91,8 +91,8 @@ private static List RelevantProperties() p.GetSetMethod(true) != null && p.GetGetMethod(true) != null && (p.PropertyType == typeof(string) - || p.PropertyType.IsValueType() - || (p.PropertyType.IsGenericType() && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))) + || p.PropertyType.IsValueType + || (p.PropertyType.IsGenericType && p.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>))) ).ToList(); } @@ -109,13 +109,13 @@ private static Func> GenerateDiffer() var il = dm.GetILGenerator(); // change list - il.DeclareLocal(typeof(List)); - il.DeclareLocal(typeof(Change)); - il.DeclareLocal(typeof(object)); // boxed change + var list = il.DeclareLocal(typeof(List)); + var change = il.DeclareLocal(typeof(Change)); + var boxed = il.DeclareLocal(typeof(object)); // boxed change il.Emit(OpCodes.Newobj, typeof(List).GetConstructor(Type.EmptyTypes)); // [list] - il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Stloc, list); foreach (var prop in RelevantProperties()) { @@ -138,7 +138,7 @@ private static Func> GenerateDiffer() // [original prop val, current prop val, current prop val boxed] } - il.Emit(OpCodes.Stloc_2); + il.Emit(OpCodes.Stloc, boxed); // [original prop val, current prop val] il.EmitCall(OpCodes.Call, typeof(Snapshot).GetMethod(nameof(AreEqual), BindingFlags.NonPublic | BindingFlags.Static).MakeGenericMethod(new Type[] { prop.PropertyType }), null); @@ -153,7 +153,7 @@ private static Func> GenerateDiffer() il.Emit(OpCodes.Dup); // [change,change] - il.Emit(OpCodes.Stloc_1); + il.Emit(OpCodes.Stloc, change); // [change] il.Emit(OpCodes.Ldstr, prop.Name); @@ -161,18 +161,18 @@ private static Func> GenerateDiffer() il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_Name")); // [] - il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldloc, change); // [change] - il.Emit(OpCodes.Ldloc_2); + il.Emit(OpCodes.Ldloc, boxed); // [change, boxed] il.Emit(OpCodes.Callvirt, typeof(Change).GetMethod("set_NewValue")); // [] - il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldloc, list); // [change list] - il.Emit(OpCodes.Ldloc_1); + il.Emit(OpCodes.Ldloc, change); // [change list, change] il.Emit(OpCodes.Callvirt, typeof(List).GetMethod("Add")); // [] @@ -180,7 +180,7 @@ private static Func> GenerateDiffer() il.MarkLabel(skip); } - il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldloc, list); // [change list] il.Emit(OpCodes.Ret); @@ -195,14 +195,14 @@ private static Func GenerateCloner() var il = dm.GetILGenerator(); - il.DeclareLocal(typeof(T)); + var typed = il.DeclareLocal(typeof(T)); il.Emit(OpCodes.Newobj, ctor); - il.Emit(OpCodes.Stloc_0); + il.Emit(OpCodes.Stloc, typed); foreach (var prop in RelevantProperties()) { - il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldloc, typed); // [clone] il.Emit(OpCodes.Ldarg_0); // [clone, source] @@ -213,7 +213,7 @@ private static Func GenerateCloner() } // Load new constructed obj on eval stack -> 1 item on stack - il.Emit(OpCodes.Ldloc_0); + il.Emit(OpCodes.Ldloc, typed); // Return constructed object. --> 0 items on stack il.Emit(OpCodes.Ret); diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index 173c24d0c..bb6fb786a 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -5,21 +5,15 @@ Dapper SqlBuilder component The Dapper SqlBuilder component, for building SQL queries dynamically. Sam Saffron, Johan Danforth - net451;netstandard1.3 - + netstandard2.0 false false - - - - - - - - + + + \ No newline at end of file diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index fc1ec3a56..d745d37cf 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -5,37 +5,15 @@ Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net451;netstandard1.3;netstandard2.0;netcoreapp2.1 + netstandard2.0 true true - - - - - - - - - - - - - - - - - - - - - - - - + + diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 884b30e11..0b4571d48 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -6,7 +6,7 @@ portable Exe false - netcoreapp1.0;netcoreapp2.0 + netcoreapp2.0 @@ -16,11 +16,15 @@ - - - + + + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index fb457baa6..0e7defac0 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -2,14 +2,10 @@ using System.Collections.Generic; using System.Data; using System.Linq; - +using System.Transactions; using Dapper.Contrib.Extensions; using Xunit; -#if !NETCOREAPP1_0 && !NETCOREAPP2_0 -using System.Transactions; -using System.Data.SqlServerCe; -#endif using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute; namespace Dapper.Tests.Contrib @@ -525,7 +521,7 @@ public void InsertGetUpdate() } } -#if !NETCOREAPP1_0 && !NETCOREAPP2_0 +#if SQLCE [Fact(Skip = "Not parallel friendly - thinking about how to test this")] public void InsertWithCustomDbType() { @@ -563,7 +559,7 @@ public void InsertWithCustomTableNameMapper() { SqlMapperExtensions.TableNameMapper = type => { - switch (type.Name()) + switch (type.Name) { case "Person": return "People"; @@ -573,7 +569,7 @@ public void InsertWithCustomTableNameMapper() return tableattr.Name; var name = type.Name + "s"; - if (type.IsInterface() && name.StartsWith("I")) + if (type.IsInterface && name.StartsWith("I")) return name.Substring(1); return name; } @@ -652,8 +648,7 @@ public void Transactions() Assert.Equal(car.Name, orgName); } } - -#if !NETCOREAPP1_0 && !NETCOREAPP2_0 +#if TRANSCOPE [Fact] public void TransactionScope() { @@ -665,7 +660,7 @@ public void TransactionScope() txscope.Dispose(); //rollback - Assert.IsNull(connection.Get(id)); //returns null - car with that id should not exist + Assert.Null(connection.Get(id)); //returns null - car with that id should not exist } } } diff --git a/Dapper.Tests.Contrib/TestSuites.cs b/Dapper.Tests.Contrib/TestSuites.cs index 31ef7f37e..ed84508c9 100644 --- a/Dapper.Tests.Contrib/TestSuites.cs +++ b/Dapper.Tests.Contrib/TestSuites.cs @@ -7,10 +7,6 @@ using Xunit; using Xunit.Sdk; -#if !NETCOREAPP1_0 && !NETCOREAPP2_0 -using System.Data.SqlServerCe; -#endif - namespace Dapper.Tests.Contrib { // The test suites here implement TestSuiteBase so that each provider runs @@ -72,7 +68,7 @@ public class MySqlServerTestSuite : TestSuite public override IDbConnection GetConnection() { - if (_skip) throw new SkipTestException("Skipping MySQL Tests - no server."); + if (_skip) Skip.Inconclusive("Skipping MySQL Tests - no server."); return new MySqlConnection(ConnectionString); } @@ -148,7 +144,8 @@ static SQLiteTestSuite() } } -#if !NETCOREAPP1_0 && !NETCOREAPP2_0 + +#if SQLCE public class SqlCETestSuite : TestSuite { const string FileName = "Test.DB.sdf"; diff --git a/Dapper.Tests.Performance/Config.cs b/Dapper.Tests.Performance/Config.cs index 2aa638044..b8e7798aa 100644 --- a/Dapper.Tests.Performance/Config.cs +++ b/Dapper.Tests.Performance/Config.cs @@ -22,7 +22,7 @@ public Config() Add(MarkdownExporter.GitHub); Add(HtmlExporter.Default); - var md = new MemoryDiagnoser(); + var md = MemoryDiagnoser.Default; Add(md); Add(new ORMColum()); Add(TargetMethodColumn.Method); @@ -30,8 +30,8 @@ public Config() Add(StatisticColumn.Mean); //Add(StatisticColumn.StdDev); //Add(StatisticColumn.Error); - Add(BaselineScaledColumn.Scaled); - Add(md.GetColumnProvider()); + Add(BaselineRatioColumn.RatioMean); + //Add(md.GetColumnProvider()); Add(Job.ShortRun .WithLaunchCount(1) @@ -39,8 +39,8 @@ public Config() .WithUnrollFactor(Iterations) .WithIterationCount(1) ); - Set(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest)); - SummaryPerType = false; + Orderer = new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest); + Options |= ConfigOptions.JoinSummary; } } } diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 75c161196..7f4193502 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -12,27 +12,27 @@ - - + + - - + + - - - - - - + + + + + + - + - + diff --git a/Dapper.Tests.Performance/Helpers/ORMColum.cs b/Dapper.Tests.Performance/Helpers/ORMColum.cs index 4f3fd88da..574ba7894 100644 --- a/Dapper.Tests.Performance/Helpers/ORMColum.cs +++ b/Dapper.Tests.Performance/Helpers/ORMColum.cs @@ -19,7 +19,7 @@ public string GetValue(Summary summary, BenchmarkCase benchmarkCase) return type.GetCustomAttribute()?.Description ?? type.Name.Replace("Benchmarks", string.Empty); } - public string GetValue(Summary summary, BenchmarkCase benchmarkCase, ISummaryStyle style) => GetValue(summary, benchmarkCase); + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) => GetValue(summary, benchmarkCase); public bool IsAvailable(Summary summary) => true; public bool AlwaysShow => true; diff --git a/Dapper.Tests.Performance/Helpers/ReturnColum.cs b/Dapper.Tests.Performance/Helpers/ReturnColum.cs index 47e13f09d..b646770c9 100644 --- a/Dapper.Tests.Performance/Helpers/ReturnColum.cs +++ b/Dapper.Tests.Performance/Helpers/ReturnColum.cs @@ -17,7 +17,7 @@ public string GetValue(Summary summary, BenchmarkCase benchmarkCase) return type == typeof(object) ? "dynamic" : type.Name; } - public string GetValue(Summary summary, BenchmarkCase benchmarkCase, ISummaryStyle style) => GetValue(summary, benchmarkCase); + public string GetValue(Summary summary, BenchmarkCase benchmarkCase, SummaryStyle style) => GetValue(summary, benchmarkCase); public bool IsAvailable(Summary summary) => true; public bool AlwaysShow => true; diff --git a/Dapper.Tests/App.config b/Dapper.Tests/App.config new file mode 100644 index 000000000..bf437bd25 --- /dev/null +++ b/Dapper.Tests/App.config @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/Dapper.Tests/AsyncTests.cs b/Dapper.Tests/AsyncTests.cs index 535badc26..5b64cd419 100644 --- a/Dapper.Tests/AsyncTests.cs +++ b/Dapper.Tests/AsyncTests.cs @@ -6,6 +6,7 @@ using System.Threading; using Xunit; using System.Data.Common; +using Xunit.Abstractions; namespace Dapper.Tests { @@ -17,10 +18,14 @@ public sealed class MicrosoftSqlClientAsyncTests : AsyncTests { } + public sealed class SystemSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { + public SystemSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) { } + } #if MSSQLCLIENT [Collection(NonParallelDefinition.Name)] - public sealed class MicrosoftSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { } + public sealed class MicrosoftSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { + public MicrosoftSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) { } + } #endif @@ -818,6 +823,8 @@ public async Task Issue563_QueryAsyncShouldThrowException() [Collection(NonParallelDefinition.Name)] public abstract class AsyncQueryCacheTests : TestBase where TProvider : SqlServerDatabaseProvider { + private readonly ITestOutputHelper _log; + public AsyncQueryCacheTests(ITestOutputHelper log) => _log = log; private DbConnection _marsConnection; private DbConnection MarsConnection => _marsConnection ?? (_marsConnection = Provider.GetOpenConnection(true)); @@ -847,8 +854,10 @@ public void AssertNoCacheWorksForQueryMultiple() d = multi.Read().Single(); } int after = SqlMapper.GetCachedSQLCount(); - Assert.Equal(0, before); - Assert.Equal(0, after); + _log?.WriteLine($"before: {before}; after: {after}"); + // too brittle in concurrent tests to assert + // Assert.Equal(0, before); + // Assert.Equal(0, after); Assert.Equal(123, c); Assert.Equal(456, d); } diff --git a/Dapper.Tests/ConstructorTests.cs b/Dapper.Tests/ConstructorTests.cs index 1acd1e6b4..4c72894ee 100644 --- a/Dapper.Tests/ConstructorTests.cs +++ b/Dapper.Tests/ConstructorTests.cs @@ -5,8 +5,10 @@ namespace Dapper.Tests { + [Collection("ConstructorTests")] public sealed class SystemSqlClientConstructorTests : ConstructorTests { } #if MSSQLCLIENT + [Collection("ConstructorTests")] public sealed class MicrosoftSqlClientConstructorTests : ConstructorTests { } #endif diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 9b9ecb1c5..68cad0ffa 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -6,41 +6,60 @@ false true true - netcoreapp2.1;net46;netcoreapp2.0;net472 + netcoreapp2.1;net462;net472 false - + $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB - + - - - - - + + + + + - - + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + - + - + @@ -50,23 +69,10 @@ - - - - - - - - + + + + - - - - if not exist "$(TargetDir)x86" md "$(TargetDir)x86" - xcopy /s /y /q "$(NuGetPackageRoot)\Microsoft.SqlServer.Types\14.0.314.76\NativeBinaries\x86\*.*" "$(TargetDir)x86" - if not exist "$(TargetDir)x64" md "$(TargetDir)x64" - xcopy /s /y /q "$(NuGetPackageRoot)\Microsoft.SqlServer.Types\14.0.314.76\NativeBinaries\x64\*.*" "$(TargetDir)x64" - - diff --git a/Dapper.Tests/DataReaderTests.cs b/Dapper.Tests/DataReaderTests.cs index 10d11e4e9..ab28619bc 100644 --- a/Dapper.Tests/DataReaderTests.cs +++ b/Dapper.Tests/DataReaderTests.cs @@ -4,8 +4,10 @@ namespace Dapper.Tests { + [Collection("DataReaderTests")] public sealed class SystemSqlClientDataReaderTests : DataReaderTests { } #if MSSQLCLIENT + [Collection("DataReaderTests")] public sealed class MicrosoftSqlClientDataReaderTests : DataReaderTests { } #endif diff --git a/Dapper.Tests/DecimalTests.cs b/Dapper.Tests/DecimalTests.cs index f4f6cb1cd..05b0e4f91 100644 --- a/Dapper.Tests/DecimalTests.cs +++ b/Dapper.Tests/DecimalTests.cs @@ -5,8 +5,10 @@ namespace Dapper.Tests { + [Collection("DecimalTests")] public sealed class SystemSqlClientDecimalTests : DecimalTests { } #if MSSQLCLIENT + [Collection("DecimalTests")] public sealed class MicrosoftSqlClientDecimalTests : DecimalTests { } #endif public abstract class DecimalTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/EnumTests.cs b/Dapper.Tests/EnumTests.cs index d830605ff..1698e98d0 100644 --- a/Dapper.Tests/EnumTests.cs +++ b/Dapper.Tests/EnumTests.cs @@ -4,8 +4,10 @@ namespace Dapper.Tests { + [Collection("EnumTests")] public sealed class SystemSqlClientEnumTests : EnumTests { } #if MSSQLCLIENT + [Collection("EnumTests")] public sealed class MicrosoftSqlClientEnumTests : EnumTests { } #endif public abstract class EnumTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/Helpers/Attributes.cs b/Dapper.Tests/Helpers/Attributes.cs index 83bcdca31..94a23dc78 100644 --- a/Dapper.Tests/Helpers/Attributes.cs +++ b/Dapper.Tests/Helpers/Attributes.cs @@ -1,8 +1,36 @@ using System; -using Xunit; +using Xunit.Sdk; namespace Dapper.Tests { + /// + /// Override for that truncates our DisplayName down. + /// + /// Attribute that is applied to a method to indicate that it is a fact that should + /// be run by the test runner. It can also be extended to support a customized definition + /// of a test method. + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Dapper.Tests.FactDiscoverer", "Dapper.Tests")] + public class FactAttribute : Xunit.FactAttribute + { + } + + /// + /// Override for that truncates our DisplayName down. + /// + /// Marks a test method as being a data theory. Data theories are tests which are + /// fed various bits of data from a data source, mapping to parameters on the test + /// method. If the data source contains multiple rows, then the test method is executed + /// multiple times (once with each data row). Data is provided by attributes which + /// derive from Xunit.Sdk.DataAttribute (notably, Xunit.InlineDataAttribute and Xunit.MemberDataAttribute). + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Dapper.Tests.TheoryDiscoverer", "Dapper.Tests")] + public class TheoryAttribute : Xunit.TheoryAttribute { } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public sealed class FactLongRunningAttribute : FactAttribute { diff --git a/Dapper.Tests/Helpers/XunitSkippable.cs b/Dapper.Tests/Helpers/XunitSkippable.cs index aa0111ee1..260ba0251 100644 --- a/Dapper.Tests/Helpers/XunitSkippable.cs +++ b/Dapper.Tests/Helpers/XunitSkippable.cs @@ -9,6 +9,17 @@ namespace Dapper.Tests { + public static class Skip + { + public static void Inconclusive(string reason = "inconclusive") + => throw new SkipTestException(reason); + + public static void If(object obj, string reason = null) + where T : class + { + if (obj is T) Skip.Inconclusive(reason ?? $"not valid for {typeof(T).FullName}"); + } + } public class SkipTestException : Exception { public SkipTestException(string reason) : base(reason) @@ -16,31 +27,40 @@ public SkipTestException(string reason) : base(reason) } } - // Most of the below is a direct copy & port from the wonderful examples by Brad Wilson at - // https://github.com/xunit/samples.xunit/tree/master/DynamicSkipExample - public class SkippableFactDiscoverer : IXunitTestCaseDiscoverer + public class FactDiscoverer : Xunit.Sdk.FactDiscoverer { - private readonly IMessageSink _diagnosticMessageSink; + public FactDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } - public SkippableFactDiscoverer(IMessageSink diagnosticMessageSink) - { - _diagnosticMessageSink = diagnosticMessageSink; - } + protected override IXunitTestCase CreateTestCase(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) + => new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); + } - public IEnumerable Discover(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo factAttribute) - { - yield return new SkippableFactTestCase(_diagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod); - } + public class TheoryDiscoverer : Xunit.Sdk.TheoryDiscoverer + { + public TheoryDiscoverer(IMessageSink diagnosticMessageSink) : base(diagnosticMessageSink) { } + + protected override IEnumerable CreateTestCasesForDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow) + => new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, dataRow) }; + + protected override IEnumerable CreateTestCasesForSkip(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, string skipReason) + => new[] { new SkippableTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) }; + + protected override IEnumerable CreateTestCasesForTheory(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute) + => new[] { new SkippableTheoryTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod) }; + + protected override IEnumerable CreateTestCasesForSkippedDataRow(ITestFrameworkDiscoveryOptions discoveryOptions, ITestMethod testMethod, IAttributeInfo theoryAttribute, object[] dataRow, string skipReason) + => new[] { new NamedSkippedDataRowTestCase(DiagnosticMessageSink, discoveryOptions.MethodDisplayOrDefault(), discoveryOptions.MethodDisplayOptionsOrDefault(), testMethod, skipReason, dataRow) }; } - public class SkippableFactTestCase : XunitTestCase + public class SkippableTestCase : XunitTestCase { + protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => + base.GetDisplayName(factAttribute, displayName).StripName(); + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] - public SkippableFactTestCase() - { - } + public SkippableTestCase() { } - public SkippableFactTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[] testMethodArguments = null) + public SkippableTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[] testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) { } @@ -52,36 +72,56 @@ public override async Task RunAsync( ExceptionAggregator aggregator, CancellationTokenSource cancellationTokenSource) { - var skipMessageBus = new SkippableFactMessageBus(messageBus); - var result = await base.RunAsync( - diagnosticMessageSink, - skipMessageBus, - constructorArguments, - aggregator, - cancellationTokenSource).ConfigureAwait(false); - if (skipMessageBus.DynamicallySkippedTestCount > 0) - { - result.Failed -= skipMessageBus.DynamicallySkippedTestCount; - result.Skipped += skipMessageBus.DynamicallySkippedTestCount; - } - - return result; + var skipMessageBus = new SkippableMessageBus(messageBus); + var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource).ConfigureAwait(false); + return result.Update(skipMessageBus); } } - public class SkippableFactMessageBus : IMessageBus + public class SkippableTheoryTestCase : XunitTheoryTestCase { - private readonly IMessageBus _innerBus; - public SkippableFactMessageBus(IMessageBus innerBus) + protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => + base.GetDisplayName(factAttribute, displayName).StripName(); + + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public SkippableTheoryTestCase() { } + + public SkippableTheoryTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod) { } + + public override async Task RunAsync( + IMessageSink diagnosticMessageSink, + IMessageBus messageBus, + object[] constructorArguments, + ExceptionAggregator aggregator, + CancellationTokenSource cancellationTokenSource) { - _innerBus = innerBus; + var skipMessageBus = new SkippableMessageBus(messageBus); + var result = await base.RunAsync(diagnosticMessageSink, skipMessageBus, constructorArguments, aggregator, cancellationTokenSource).ConfigureAwait(false); + return result.Update(skipMessageBus); } + } + + public class NamedSkippedDataRowTestCase : XunitSkippedDataRowTestCase + { + protected override string GetDisplayName(IAttributeInfo factAttribute, string displayName) => + base.GetDisplayName(factAttribute, displayName).StripName(); + + [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] + public NamedSkippedDataRowTestCase() { } + + public NamedSkippedDataRowTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, string skipReason, object[] testMethodArguments = null) + : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, skipReason, testMethodArguments) { } + } + + public class SkippableMessageBus : IMessageBus + { + private readonly IMessageBus InnerBus; + public SkippableMessageBus(IMessageBus innerBus) => InnerBus = innerBus; public int DynamicallySkippedTestCount { get; private set; } - public void Dispose() - { - } + public void Dispose() { } public bool QueueMessage(IMessageSinkMessage message) { @@ -91,10 +131,26 @@ public bool QueueMessage(IMessageSinkMessage message) if (exceptionType == typeof(SkipTestException).FullName) { DynamicallySkippedTestCount++; - return _innerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); + return InnerBus.QueueMessage(new TestSkipped(testFailed.Test, testFailed.Messages.FirstOrDefault())); } } - return _innerBus.QueueMessage(message); + return InnerBus.QueueMessage(message); + } + } + + internal static class XUnitExtensions + { + internal static string StripName(this string name) => + name.Replace("Dapper.Tests.", ""); + + public static RunSummary Update(this RunSummary summary, SkippableMessageBus bus) + { + if (bus.DynamicallySkippedTestCount > 0) + { + summary.Failed -= bus.DynamicallySkippedTestCount; + summary.Skipped += bus.DynamicallySkippedTestCount; + } + return summary; } } } diff --git a/Dapper.Tests/LiteralTests.cs b/Dapper.Tests/LiteralTests.cs index 9108d93f8..445fda96a 100644 --- a/Dapper.Tests/LiteralTests.cs +++ b/Dapper.Tests/LiteralTests.cs @@ -3,8 +3,10 @@ namespace Dapper.Tests { + [Collection("LiteralTests")] public sealed class SystemSqlClientLiteralTests : LiteralTests { } #if MSSQLCLIENT + [Collection("LiteralTests")] public sealed class MicrosoftSqlClientLiteralTests : LiteralTests { } #endif public abstract class LiteralTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/MiscTests.cs b/Dapper.Tests/MiscTests.cs index 276606121..54c28f60c 100644 --- a/Dapper.Tests/MiscTests.cs +++ b/Dapper.Tests/MiscTests.cs @@ -39,8 +39,10 @@ public GenericUriParser(GenericUriParserOptions options) 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 diff --git a/Dapper.Tests/MultiMapTests.cs b/Dapper.Tests/MultiMapTests.cs index 195b981ba..9c072a340 100644 --- a/Dapper.Tests/MultiMapTests.cs +++ b/Dapper.Tests/MultiMapTests.cs @@ -6,8 +6,10 @@ namespace Dapper.Tests { + [Collection("MultiMapTests")] public sealed class SystemSqlClientMultiMapTests : MultiMapTests { } #if MSSQLCLIENT + [Collection("MultiMapTests")] public sealed class MicrosoftSqlClientMultiMapTests : MultiMapTests { } #endif public abstract class MultiMapTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index e3567a85f..b075fcbef 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -43,7 +43,19 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id } } - private static List CreateSqlDataRecordList(IEnumerable numbers) + private static IEnumerable CreateSqlDataRecordList(IDbCommand command, IEnumerable numbers) + { + if (command is System.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_SD(numbers); + if (command is Microsoft.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_MD(numbers); + throw new ArgumentException(nameof(command)); + } + private static IEnumerable CreateSqlDataRecordList(IDbConnection connection, IEnumerable numbers) + { + if (connection is System.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_SD(numbers); + if (connection is Microsoft.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_MD(numbers); + throw new ArgumentException(nameof(connection)); + } + private static List CreateSqlDataRecordList_SD(IEnumerable numbers) { var number_list = new List(); @@ -60,7 +72,25 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id return number_list; } - + + private static List CreateSqlDataRecordList_MD(IEnumerable numbers) + { + var number_list = new List(); + + // Create an SqlMetaData object that describes our table type. + Microsoft.Data.SqlClient.Server.SqlMetaData[] tvp_definition = { new Microsoft.Data.SqlClient.Server.SqlMetaData("n", SqlDbType.Int) }; + + foreach (int n in numbers) + { + // Create a new record, using the metadata array above. + var rec = new Microsoft.Data.SqlClient.Server.SqlDataRecord(tvp_definition); + rec.SetInt32(0, n); // Set the value. + number_list.Add(rec); // Add it to the list. + } + + return number_list; + } + private class IntDynamicParam : SqlMapper.IDynamicParameters { @@ -72,16 +102,11 @@ public IntDynamicParam(IEnumerable numbers) public void AddParameters(IDbCommand command, SqlMapper.Identity identity) { - var sqlCommand = (System.Data.SqlClient.SqlCommand)command; - sqlCommand.CommandType = CommandType.StoredProcedure; + command.CommandType = CommandType.StoredProcedure; - var number_list = CreateSqlDataRecordList(numbers); + var number_list = CreateSqlDataRecordList(command, numbers); - // Add the table parameter. - var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); - p.Direction = ParameterDirection.Input; - p.TypeName = "int_list_type"; - p.Value = number_list; + AddStructured(command, number_list); } } @@ -95,17 +120,35 @@ public IntCustomParam(IEnumerable numbers) public void AddParameter(IDbCommand command, string name) { - var sqlCommand = (System.Data.SqlClient.SqlCommand)command; - sqlCommand.CommandType = CommandType.StoredProcedure; + command.CommandType = CommandType.StoredProcedure; - var number_list = CreateSqlDataRecordList(numbers); + var number_list = CreateSqlDataRecordList(command, numbers); // Add the table parameter. - var p = sqlCommand.Parameters.Add(name, SqlDbType.Structured); + AddStructured(command, number_list); + } + } + + private static IDbDataParameter AddStructured(IDbCommand command, object value) + { + if (command is System.Data.SqlClient.SqlCommand sdcmd) + { + var p = sdcmd.Parameters.Add("integers", SqlDbType.Structured); p.Direction = ParameterDirection.Input; p.TypeName = "int_list_type"; - p.Value = number_list; + p.Value = value; + return p; } + else if (command is Microsoft.Data.SqlClient.SqlCommand mdcmd) + { + var p = mdcmd.Parameters.Add("integers", SqlDbType.Structured); + p.Direction = ParameterDirection.Input; + p.TypeName = "int_list_type"; + p.Value = value; + return p; + } + else + throw new ArgumentException(nameof(command)); } /* TODO: @@ -286,7 +329,7 @@ public void TestTVP() try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); - connection.Execute("CREATE PROC get_ints @ints int_list_type READONLY AS select * from @ints"); + connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); var nums = connection.Query("get_ints", new IntDynamicParam(new int[] { 1, 2, 3 })).ToList(); Assert.Equal(1, nums[0]); @@ -298,11 +341,11 @@ public void TestTVP() { try { - connection.Execute("DROP PROC get_ints"); + try { connection.Execute("DROP PROC get_ints"); } catch { } } finally { - connection.Execute("DROP TYPE int_list_type"); + try { connection.Execute("DROP TYPE int_list_type"); } catch { } } } } @@ -319,16 +362,12 @@ public DynamicParameterWithIntTVP(IEnumerable numbers) { base.AddParameters(command, identity); - var sqlCommand = (System.Data.SqlClient.SqlCommand)command; - sqlCommand.CommandType = CommandType.StoredProcedure; + command.CommandType = CommandType.StoredProcedure; - var number_list = CreateSqlDataRecordList(numbers); + var number_list = CreateSqlDataRecordList(command, numbers); // Add the table parameter. - var p = sqlCommand.Parameters.Add("ints", SqlDbType.Structured); - p.Direction = ParameterDirection.Input; - p.TypeName = "int_list_type"; - p.Value = number_list; + AddStructured(command, number_list); } } @@ -338,7 +377,7 @@ public void TestTVPWithAdditionalParams() try { connection.Execute("CREATE TYPE int_list_type AS TABLE (n int NOT NULL PRIMARY KEY)"); - connection.Execute("CREATE PROC get_values @ints int_list_type READONLY, @stringParam varchar(20), @dateParam datetime AS select i.*, @stringParam as stringParam, @dateParam as dateParam from @ints i"); + connection.Execute("CREATE PROC get_values @integers int_list_type READONLY, @stringParam varchar(20), @dateParam datetime AS select i.*, @stringParam as stringParam, @dateParam as dateParam from @integers i"); var dynamicParameters = new DynamicParameterWithIntTVP(new int[] { 1, 2, 3 }); dynamicParameters.AddDynamicParams(new { stringParam = "stringParam", dateParam = new DateTime(2012, 1, 1) }); @@ -374,7 +413,7 @@ public void TestSqlDataRecordListParametersWithAsTableValuedParameter() 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 records = CreateSqlDataRecordList(connection, new int[] { 1, 2, 3 }); var nums = connection.Query("get_ints", new { integers = records.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList(); Assert.Equal(new int[] { 1, 2, 3 }, nums); @@ -414,7 +453,7 @@ public void TestEmptySqlDataRecordListParametersWithAsTableValuedParameter() connection.Execute("CREATE PROC get_ints @integers int_list_type READONLY AS select * from @integers"); - var emptyRecord = CreateSqlDataRecordList(Enumerable.Empty()); + var emptyRecord = CreateSqlDataRecordList(connection, Enumerable.Empty()); var nums = connection.Query("get_ints", new { integers = emptyRecord.AsTableValuedParameter() }, commandType: CommandType.StoredProcedure).ToList(); Assert.True(nums.Count == 0); @@ -441,14 +480,28 @@ public void TestSqlDataRecordListParametersWithTypeHandlers() 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 }); + object args; + if (connection is System.Data.SqlClient.SqlConnection) + { + IEnumerable records = CreateSqlDataRecordList_SD(new int[] { 1, 2, 3 }); + args = new { integers = records }; + } + else if (connection is Microsoft.Data.SqlClient.SqlConnection) + { + IEnumerable records = CreateSqlDataRecordList_MD(new int[] { 1, 2, 3 }); + args = new { integers = records }; + } + else + { + throw new ArgumentException(nameof(connection)); + } - var nums = connection.Query("get_ints", new { integers = records }, commandType: CommandType.StoredProcedure).ToList(); + var nums = connection.Query("get_ints", args, commandType: CommandType.StoredProcedure).ToList(); Assert.Equal(new int[] { 1, 2, 3 }, nums); try { - connection.Query("select * from @integers", new { integers = records }).First(); + connection.Query("select * from @integers", args).First(); throw new InvalidOperationException(); } catch (Exception ex) @@ -665,6 +718,8 @@ private class HazSqlGeo [Fact] public void DBGeography_SO24405645_SO24402424() { + SkipIfMsDataClient(); + EntityFramework.Handlers.Register(); connection.Execute("create table #Geo (id int, geo geography, geometry geometry)"); @@ -686,6 +741,8 @@ public void DBGeography_SO24405645_SO24402424() [Fact] public void SqlGeography_SO25538154() { + SkipIfMsDataClient(); + SqlMapper.ResetTypeHandlers(); connection.Execute("create table #SqlGeo (id int, geo geography, geometry geometry)"); @@ -724,6 +781,8 @@ public void NullableSqlGeometry() [Fact] public void SqlHierarchyId_SO18888911() { + SkipIfMsDataClient(); + SqlMapper.ResetTypeHandlers(); var row = connection.Query("select 3 as [Id], hierarchyid::Parse('/1/2/3/') as [Path]").Single(); Assert.Equal(3, row.Id); diff --git a/Dapper.Tests/ProcedureTests.cs b/Dapper.Tests/ProcedureTests.cs index 2ecaad884..35fa9b2a7 100644 --- a/Dapper.Tests/ProcedureTests.cs +++ b/Dapper.Tests/ProcedureTests.cs @@ -6,8 +6,10 @@ namespace Dapper.Tests { + [Collection("ProcedureTests")] public sealed class SystemSqlClientProcedureTests : ProcedureTests { } #if MSSQLCLIENT + [Collection("ProcedureTests")] public sealed class MicrosoftSqlClientProcedureTests : ProcedureTests { } #endif public abstract class ProcedureTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/Providers/EntityFrameworkTests.cs b/Dapper.Tests/Providers/EntityFrameworkTests.cs index 8827024f3..2bd4a77bc 100644 --- a/Dapper.Tests/Providers/EntityFrameworkTests.cs +++ b/Dapper.Tests/Providers/EntityFrameworkTests.cs @@ -22,6 +22,8 @@ public EntityFrameworkTests() [Fact] public void Issue570_DbGeo_HasValues() { + SkipIfMsDataClient(); + EntityFramework.Handlers.Register(); const string redmond = "POINT (-122.1215 47.6740)"; DbGeography point = DbGeography.PointFromText(redmond, DbGeography.DefaultCoordinateSystemId); @@ -37,6 +39,8 @@ public void Issue570_DbGeo_HasValues() [Fact] public void Issue22_ExecuteScalar_EntityFramework() { + SkipIfMsDataClient(); + var geo = DbGeography.LineFromText("LINESTRING(-122.360 47.656, -122.343 47.656 )", 4326); var geo2 = connection.ExecuteScalar("select @geo", new { geo }); Assert.NotNull(geo2); diff --git a/Dapper.Tests/QueryMultipleTests.cs b/Dapper.Tests/QueryMultipleTests.cs index b48f91d23..bb311dcb0 100644 --- a/Dapper.Tests/QueryMultipleTests.cs +++ b/Dapper.Tests/QueryMultipleTests.cs @@ -6,8 +6,10 @@ namespace Dapper.Tests { + [Collection("QueryMultipleTests")] public sealed class SystemSqlClientQueryMultipleTests : QueryMultipleTests { } #if MSSQLCLIENT + [Collection("QueryMultipleTests")] public sealed class MicrosoftSqlClientQueryMultipleTests : QueryMultipleTests { } #endif public abstract class QueryMultipleTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/TestBase.cs b/Dapper.Tests/TestBase.cs index 14320d376..05f242319 100644 --- a/Dapper.Tests/TestBase.cs +++ b/Dapper.Tests/TestBase.cs @@ -81,6 +81,9 @@ public sealed class MicrosoftSqlClientProvider : SqlServerDatabaseProvider public abstract class TestBase : IDisposable where TProvider : DatabaseProvider { + protected void SkipIfMsDataClient() + => Skip.If(connection); + protected DbConnection GetOpenConnection() => Provider.GetOpenConnection(); protected DbConnection GetClosedConnection() => Provider.GetClosedConnection(); protected DbConnection _connection; diff --git a/Dapper.Tests/TransactionTests.cs b/Dapper.Tests/TransactionTests.cs index 36e28dfb3..105813805 100644 --- a/Dapper.Tests/TransactionTests.cs +++ b/Dapper.Tests/TransactionTests.cs @@ -7,8 +7,10 @@ namespace Dapper.Tests { + [Collection("TransactionTests")] public sealed class SystemSqlClientTransactionTests : TransactionTests { } #if MSSQLCLIENT + [Collection("TransactionTests")] public sealed class MicrosoftSqlClientTransactionTests : TransactionTests { } #endif public abstract class TransactionTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/TupleTests.cs b/Dapper.Tests/TupleTests.cs index cfc1b05b6..bcf183db5 100644 --- a/Dapper.Tests/TupleTests.cs +++ b/Dapper.Tests/TupleTests.cs @@ -3,8 +3,10 @@ namespace Dapper.Tests { + [Collection("TupleTests")] public sealed class SystemSqlClientTupleTests : TupleTests { } #if MSSQLCLIENT + [Collection("TupleTests")] public sealed class MicrosoftSqlClientTupleTests : TupleTests { } #endif public abstract class TupleTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.Tests/XmlTests.cs b/Dapper.Tests/XmlTests.cs index addf90e61..e0b3cf289 100644 --- a/Dapper.Tests/XmlTests.cs +++ b/Dapper.Tests/XmlTests.cs @@ -4,8 +4,10 @@ namespace Dapper.Tests { + [Collection("XmlTests")] public sealed class SystemSqlClientXmlTests : XmlTests { } #if MSSQLCLIENT + [Collection("XmlTests")] public sealed class MicrosoftSqlClientXmlTests : XmlTests { } #endif public abstract class XmlTests : TestBase where TProvider : DatabaseProvider diff --git a/Dapper.sln b/Dapper.sln index d7245e6da..c46af6e92 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -9,6 +9,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution appveyor.yml = appveyor.yml build.ps1 = build.ps1 Directory.build.props = Directory.build.props + global.json = global.json docs\index.md = docs\index.md License.txt = License.txt nuget.config = nuget.config diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index fcb5fdbf1..5031f288b 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,32 +5,10 @@ Dapper A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net451;netstandard1.3;netstandard2.0;netcoreapp2.1 + netstandard2.0 - - - - - - - - + + - - - - - - - - - - - - - - - - diff --git a/Dapper/DataTableHandler.cs b/Dapper/DataTableHandler.cs index f9b2b5fe6..df4dc07a5 100644 --- a/Dapper/DataTableHandler.cs +++ b/Dapper/DataTableHandler.cs @@ -1,6 +1,5 @@ using System; using System.Data; -#if !NETSTANDARD1_3 namespace Dapper { internal sealed class DataTableHandler : SqlMapper.ITypeHandler @@ -16,4 +15,3 @@ public void SetValue(IDbDataParameter parameter, object value) } } } -#endif \ No newline at end of file diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index 88859a08b..8366ae5ee 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -26,27 +26,11 @@ public DefaultTypeMap(Type type) Properties = GetSettableProps(type); _type = type; } -#if NETSTANDARD1_3 - private static bool IsParameterMatch(ParameterInfo[] x, ParameterInfo[] y) - { - if (ReferenceEquals(x, y)) return true; - if (x == null || y == null) return false; - if (x.Length != y.Length) return false; - for (int i = 0; i < x.Length; i++) - if (x[i].ParameterType != y[i].ParameterType) return false; - return true; - } -#endif + internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type) { if (propertyInfo.DeclaringType == type) return propertyInfo.GetSetMethod(true); -#if NETSTANDARD1_3 - return propertyInfo.DeclaringType.GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Single(x => x.Name == propertyInfo.Name - && x.PropertyType == propertyInfo.PropertyType - && IsParameterMatch(x.GetIndexParameters(), propertyInfo.GetIndexParameters()) - ).GetSetMethod(true); -#else + return propertyInfo.DeclaringType.GetProperty( propertyInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, @@ -54,7 +38,6 @@ internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type typ propertyInfo.PropertyType, propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(), null).GetSetMethod(true); -#endif } internal static List GetSettableProps(Type t) @@ -97,9 +80,9 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) continue; var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType; if ((unboxedType != types[i] && !SqlMapper.HasTypeHandler(unboxedType)) - && !(unboxedType.IsEnum() && Enum.GetUnderlyingType(unboxedType) == types[i]) + && !(unboxedType.IsEnum && Enum.GetUnderlyingType(unboxedType) == types[i]) && !(unboxedType == typeof(char) && types[i] == typeof(string)) - && !(unboxedType.IsEnum() && types[i] == typeof(string))) + && !(unboxedType.IsEnum && types[i] == typeof(string))) { break; } @@ -118,11 +101,7 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) public ConstructorInfo FindExplicitConstructor() { var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); -#if NETSTANDARD1_3 - var withAttr = constructors.Where(c => c.CustomAttributes.Any(x => x.AttributeType == typeof(ExplicitConstructorAttribute))).ToList(); -#else var withAttr = constructors.Where(c => c.GetCustomAttributes(typeof(ExplicitConstructorAttribute), true).Length > 0).ToList(); -#endif if (withAttr.Count == 1) { diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index 83038656c..f37c159ad 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -6,10 +6,6 @@ using System.Reflection; using System.Reflection.Emit; -#if NETSTANDARD1_3 -using ApplicationException = System.InvalidOperationException; -#endif - namespace Dapper { /// diff --git a/Dapper/SqlDataRecordHandler.cs b/Dapper/SqlDataRecordHandler.cs index 946652ac6..2d9976338 100644 --- a/Dapper/SqlDataRecordHandler.cs +++ b/Dapper/SqlDataRecordHandler.cs @@ -5,9 +5,7 @@ namespace Dapper { internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler -#if !NETSTANDARD1_3 where T : IDataRecord -#endif { public object Parse(Type destinationType, object value) { diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index c236fd3df..c83542169 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -12,9 +12,7 @@ namespace Dapper /// Used to pass a IEnumerable<SqlDataRecord> as a SqlDataRecordListTVPParameter /// internal sealed class SqlDataRecordListTVPParameter : SqlMapper.ICustomQueryParameter -#if !NETSTANDARD1_3 where T : IDataRecord -#endif { private readonly IEnumerable data; private readonly string typeName; diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index b3aeec3f9..fe510ed9a 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -406,7 +406,7 @@ private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, ID private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; @@ -470,7 +470,7 @@ private static async Task> QueryAsync(this IDbConnection cnn, private static async Task QueryRowAsync(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) { object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; @@ -594,7 +594,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD isFirst = false; cmd = command.TrySetupAsyncCommand(cnn, null); masterSql = cmd.CommandText; - var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); + var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); info = GetCacheInfo(identity, obj, command.AddToCache); } else if (pending.Count >= MAX_PENDING) @@ -642,7 +642,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD { masterSql = cmd.CommandText; isFirst = false; - var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); + var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); info = GetCacheInfo(identity, obj, command.AddToCache); } else @@ -667,7 +667,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) @@ -935,7 +935,7 @@ public static Task> QueryAsync> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn) { object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -985,7 +985,7 @@ private static async Task> MultiMapAsync(this IDbC } object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); + var identity = new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -1037,7 +1037,7 @@ public static Task QueryMultipleAsync(this IDbConnection cnn, string public static async Task QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command) { object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); DbCommand cmd = null; @@ -1233,7 +1233,7 @@ private static async Task ExecuteScalarImplAsync(IDbConnection cnn, Comman object param = command.Parameters; if (param != null) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } diff --git a/Dapper/SqlMapper.DapperRow.Descriptor.cs b/Dapper/SqlMapper.DapperRow.Descriptor.cs index 909cbd9f4..3bb535dbe 100644 --- a/Dapper/SqlMapper.DapperRow.Descriptor.cs +++ b/Dapper/SqlMapper.DapperRow.Descriptor.cs @@ -1,5 +1,4 @@ -#if !NETSTANDARD1_3 // needs the component-model API -using System; +using System; using System.Collections.Generic; using System.ComponentModel; @@ -104,4 +103,3 @@ public override void SetValue(object component, object value) } } } -#endif diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index e4c697c1b..2a05198eb 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -204,15 +204,7 @@ private T ReadRow(Type type, Row row) private IEnumerable MultiReadInternal(Delegate func, string splitOn) { - var identity = this.identity.ForGrid(typeof(TReturn), new Type[] { - typeof(TFirst), - typeof(TSecond), - typeof(TThird), - typeof(TFourth), - typeof(TFifth), - typeof(TSixth), - typeof(TSeventh) - }, gridIndex); + var identity = this.identity.ForGrid(typeof(TReturn), gridIndex); IsConsumed = true; diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs index ac260172f..02000ca45 100644 --- a/Dapper/SqlMapper.IDataReader.cs +++ b/Dapper/SqlMapper.IDataReader.cs @@ -140,7 +140,7 @@ public static Func GetRowParser(this IDataReader reader, Type { concreteType = concreteType ?? typeof(T); var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing); - if (concreteType.IsValueType()) + if (concreteType.IsValueType) { return _ => (T)func(_); } diff --git a/Dapper/SqlMapper.Identity.cs b/Dapper/SqlMapper.Identity.cs index a02f20e62..3ae04993b 100644 --- a/Dapper/SqlMapper.Identity.cs +++ b/Dapper/SqlMapper.Identity.cs @@ -1,20 +1,111 @@ using System; using System.Data; +using System.Runtime.CompilerServices; namespace Dapper { public static partial class SqlMapper { + internal sealed class Identity : Identity + { + private static readonly int s_typeHash; + private static readonly int s_typeCount = CountNonTrivial(out s_typeHash); + + internal Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, int gridIndex = 0) + : base(sql, commandType, connectionString, type, parametersType, s_typeHash, gridIndex) + {} + internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, int gridIndex = 0) + : base(sql, commandType, connection.ConnectionString, type, parametersType, s_typeHash, gridIndex) + { } + + static int CountNonTrivial(out int hashCode) + { + int hashCodeLocal = 0; + int count = 0; + bool Map() + { + if(typeof(T) != typeof(DontMap)) + { + count++; + hashCodeLocal = (hashCodeLocal * 23) + (typeof(T).GetHashCode()); + return true; + } + return false; + } + _ = Map() && Map() && Map() + && Map() && Map() && Map() + && Map(); + hashCode = hashCodeLocal; + return count; + } + internal override int TypeCount => s_typeCount; + internal override Type GetType(int index) + { + switch (index) + { + case 0: return typeof(TFirst); + case 1: return typeof(TSecond); + case 2: return typeof(TThird); + case 3: return typeof(TFourth); + case 4: return typeof(TFifth); + case 5: return typeof(TSixth); + case 6: return typeof(TSeventh); + default: return base.GetType(index); + } + } + } + internal sealed class IdentityWithTypes : Identity + { + private readonly Type[] _types; + + internal IdentityWithTypes(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex = 0) + : base(sql, commandType, connectionString, type, parametersType, HashTypes(otherTypes), gridIndex) + { + _types = otherTypes ?? Type.EmptyTypes; + } + internal IdentityWithTypes(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes, int gridIndex = 0) + : base(sql, commandType, connection.ConnectionString, type, parametersType, HashTypes(otherTypes), gridIndex) + { + _types = otherTypes ?? Type.EmptyTypes; + } + + internal override int TypeCount => _types.Length; + + internal override Type GetType(int index) => _types[index]; + + static int HashTypes(Type[] types) + { + var hashCode = 0; + if (types != null) + { + foreach (var t in types) + { + hashCode = (hashCode * 23) + (t?.GetHashCode() ?? 0); + } + } + return hashCode; + } + } + /// /// Identity of a cached query in Dapper, used for extensibility. /// public class Identity : IEquatable { + internal virtual int TypeCount => 0; + + internal virtual Type GetType(int index) => throw new IndexOutOfRangeException(nameof(index)); + + internal Identity ForGrid(Type primaryType, int gridIndex) => + new Identity(sql, commandType, connectionString, primaryType, parametersType, gridIndex); + internal Identity ForGrid(Type primaryType, int gridIndex) => - new Identity(sql, commandType, connectionString, primaryType, parametersType, null, gridIndex); + new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex); internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => - new Identity(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); + (otherTypes == null || otherTypes.Length == 0) + ? new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex) + : new IdentityWithTypes(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); /// /// Create an identity for use with DynamicParameters, internal use only. @@ -22,12 +113,12 @@ internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => /// The parameters type to create an for. /// public Identity ForDynamicParameters(Type type) => - new Identity(sql, commandType, connectionString, this.type, type, null, -1); + new Identity(sql, commandType, connectionString, this.type, type, 0, -1); - internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType, Type[] otherTypes) - : this(sql, commandType, connection.ConnectionString, type, parametersType, otherTypes, 0) { /* base call */ } + internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType) + : this(sql, commandType, connection.ConnectionString, type, parametersType, 0, 0) { /* base call */ } - private Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, Type[] otherTypes, int gridIndex) + private protected Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, int otherTypesHash, int gridIndex) { this.sql = sql; this.commandType = commandType; @@ -42,13 +133,7 @@ private Identity(string sql, CommandType? commandType, string connectionString, hashCode = (hashCode * 23) + gridIndex.GetHashCode(); hashCode = (hashCode * 23) + (sql?.GetHashCode() ?? 0); hashCode = (hashCode * 23) + (type?.GetHashCode() ?? 0); - if (otherTypes != null) - { - foreach (var t in otherTypes) - { - hashCode = (hashCode * 23) + (t?.GetHashCode() ?? 0); - } - } + hashCode = (hashCode * 23) + otherTypesHash; hashCode = (hashCode * 23) + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString)); hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0); } @@ -101,6 +186,11 @@ private Identity(string sql, CommandType? commandType, string connectionString, /// public override int GetHashCode() => hashCode; + /// + /// See object.ToString() + /// + public override string ToString() => sql; + /// /// Compare 2 Identity objects /// @@ -108,13 +198,30 @@ private Identity(string sql, CommandType? commandType, string connectionString, /// Whether the two are equal public bool Equals(Identity other) { - return other != null - && gridIndex == other.gridIndex + if (ReferenceEquals(this, other)) return true; + if (ReferenceEquals(other, null)) return false; + + int typeCount; + return gridIndex == other.gridIndex && type == other.type && sql == other.sql && commandType == other.commandType && connectionStringComparer.Equals(connectionString, other.connectionString) - && parametersType == other.parametersType; + && parametersType == other.parametersType + && (typeCount = TypeCount) == other.TypeCount + && (typeCount == 0 || TypesEqual(this, other, typeCount)); + } + + [MethodImpl(MethodImplOptions.NoInlining)] + private static bool TypesEqual(Identity x, Identity y, int count) + { + if (y.TypeCount != count) return false; + for(int i = 0; i < count; i++) + { + if (x.GetType(i) != y.GetType(i)) + return false; + } + return true; } } } diff --git a/Dapper/SqlMapper.TypeHandlerCache.cs b/Dapper/SqlMapper.TypeHandlerCache.cs index 04d9baba8..106f2c3ce 100644 --- a/Dapper/SqlMapper.TypeHandlerCache.cs +++ b/Dapper/SqlMapper.TypeHandlerCache.cs @@ -11,9 +11,7 @@ public static partial class SqlMapper /// /// The type to have a cache for. [Obsolete(ObsoleteInternalUsageOnly, false)] -#if !NETSTANDARD1_3 [Browsable(false)] -#endif [EditorBrowsable(EditorBrowsableState.Never)] public static class TypeHandlerCache { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index f5a07792b..936e01a6b 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -12,17 +12,12 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; -using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; using System.Xml; using System.Xml.Linq; -#if NETSTANDARD1_3 -using DataException = System.InvalidOperationException; -#endif - namespace Dapper { /// @@ -221,25 +216,12 @@ static SqlMapper() private static void ResetTypeHandlers(bool clone) { typeHandlers = new Dictionary(); -#if !NETSTANDARD1_3 AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); -#endif - try - { - AddSqlDataRecordsTypeHandler(clone); - } - catch { /* https://github.com/StackExchange/dapper-dot-net/issues/424 */ } AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); } - [MethodImpl(MethodImplOptions.NoInlining)] - private static void AddSqlDataRecordsTypeHandler(bool clone) - { - AddTypeHandlerImpl(typeof(IEnumerable), new SqlDataRecordHandler(), clone); - } - /// /// Configure the specified type to be mapped to a given db-type. /// @@ -292,7 +274,7 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon if (type == null) throw new ArgumentNullException(nameof(type)); Type secondary = null; - if (type.IsValueType()) + if (type.IsValueType) { var underlying = Nullable.GetUnderlyingType(type); if (underlying == null) @@ -350,9 +332,7 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon /// /// The object to get a corresponding database type for. [Obsolete(ObsoleteInternalUsageOnly, false)] -#if !NETSTANDARD1_3 [Browsable(false)] -#endif [EditorBrowsable(EditorBrowsableState.Never)] public static DbType GetDbType(object value) { @@ -369,16 +349,14 @@ public static DbType GetDbType(object value) /// Whether to demand a value (throw if missing). /// The handler for . [Obsolete(ObsoleteInternalUsageOnly, false)] -#if !NETSTANDARD1_3 [Browsable(false)] -#endif [EditorBrowsable(EditorBrowsableState.Never)] public static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) { handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); if (nullUnderlyingType != null) type = nullUnderlyingType; - if (type.IsEnum() && !typeMap.ContainsKey(type)) + if (type.IsEnum && !typeMap.ContainsKey(type)) { type = Enum.GetUnderlyingType(type); } @@ -396,10 +374,30 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType } if (typeof(IEnumerable).IsAssignableFrom(type)) { + // auto-detect things like IEnumerable as a family + if (type.IsInterface && type.IsGenericType + && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) + && typeof(IEnumerable).IsAssignableFrom(type)) + { + var argTypes = type.GetGenericArguments(); + if(typeof(IDataRecord).IsAssignableFrom(argTypes[0])) + { + try + { + handler = (ITypeHandler)Activator.CreateInstance( + typeof(SqlDataRecordHandler<>).MakeGenericType(argTypes)); + AddTypeHandlerImpl(type, handler, true); + return DbType.Object; + } + catch + { + handler = null; + } + } + } return DynamicParameters.EnumerableMultiParameter; } -#if !NETSTANDARD1_3 && !NETSTANDARD2_0 switch (type.FullName) { case "Microsoft.SqlServer.Types.SqlGeography": @@ -412,7 +410,7 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType AddTypeHandler(type, handler = new UdtTypeHandler("hierarchyid")); return DbType.Object; } -#endif + if (demand) throw new NotSupportedException($"The member {name} of type {type.FullName} cannot be used as a parameter value"); return DbType.Object; @@ -540,7 +538,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com { masterSql = cmd.CommandText; isFirst = false; - identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType(), null); + identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); info = GetCacheInfo(identity, obj, command.AddToCache); } else @@ -564,7 +562,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com // nice and simple if (param != null) { - identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); + identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); } return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader); @@ -1009,7 +1007,7 @@ public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command) { object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; @@ -1066,7 +1064,7 @@ private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool w private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefinition command, Type effectiveType) { object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; @@ -1164,7 +1162,7 @@ private static void ThrowZeroRows(Row row) private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType) { object param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; @@ -1407,7 +1405,7 @@ private static IEnumerable MultiMap MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize) { object param = command.Parameters; - identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType(), new[] { typeof(TFirst), typeof(TSecond), typeof(TThird), typeof(TFourth), typeof(TFifth), typeof(TSixth), typeof(TSeventh) }); + identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null; @@ -1429,7 +1427,7 @@ private static IEnumerable MultiMapImpl MultiMapImpl(this IDbConnection cnn } object param = command.Parameters; - identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); + identity = identity ?? new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null; @@ -1499,7 +1497,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn int hash = GetColumnHash(reader); if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) { - var deserializers = GenerateDeserializers(types, splitOn, reader); + var deserializers = GenerateDeserializers(identity, splitOn, reader); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); SetQueryCache(identity, cinfo); @@ -1571,12 +1569,14 @@ private static Func GenerateMapper(int length, Fu }; } - private static Func[] GenerateDeserializers(Type[] types, string splitOn, IDataReader reader) + private static Func[] GenerateDeserializers(Identity identity, string splitOn, IDataReader reader) { var deserializers = new List>(); var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray(); bool isMultiSplit = splits.Length > 1; - if (types[0] == typeof(object)) + + int typeCount = identity.TypeCount; + if (identity.GetType(0) == typeof(object)) { // we go left to right for dynamic multi-mapping so that the madness of TestMultiMappingVariations // is supported @@ -1584,8 +1584,10 @@ private static Func[] GenerateDeserializers(Type[] types, s int currentPos = 0; int splitIdx = 0; string currentSplit = splits[splitIdx]; - foreach (var type in types) + + for (int i = 0; i < typeCount; i++) { + Type type = identity.GetType(i); if (type == typeof(DontMap)) { break; @@ -1608,9 +1610,9 @@ private static Func[] GenerateDeserializers(Type[] types, s int currentPos = reader.FieldCount; int splitIdx = splits.Length - 1; var currentSplit = splits[splitIdx]; - for (var typeIdx = types.Length - 1; typeIdx >= 0; --typeIdx) + for (var typeIdx = typeCount - 1; typeIdx >= 0; --typeIdx) { - var type = types[typeIdx]; + var type = identity.GetType(typeIdx); if (type == typeof(DontMap)) { continue; @@ -1775,8 +1777,8 @@ private static Func GetDeserializer(Type type, IDataReader return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } Type underlyingType = null; - if (!(typeMap.ContainsKey(type) || type.IsEnum() || type.FullName == LinqBinary - || (type.IsValueType() && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum()))) + if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary + || (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum))) { if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) { @@ -1868,9 +1870,7 @@ internal static Func GetDapperRowDeserializer(IDataRecord r /// Internal use only. /// /// The object to convert to a character. -#if !NETSTANDARD1_3 [Browsable(false)] -#endif [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, false)] public static char ReadChar(object value) @@ -1885,9 +1885,7 @@ public static char ReadChar(object value) /// Internal use only. /// /// The object to convert to a character. -#if !NETSTANDARD1_3 [Browsable(false)] -#endif [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, false)] public static char? ReadNullableChar(object value) @@ -1904,9 +1902,7 @@ public static char ReadChar(object value) /// The parameter collection to search in. /// The command for this fetch. /// The name of the parameter to get. -#if !NETSTANDARD1_3 [Browsable(false)] -#endif [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, true)] public static IDbDataParameter FindOrAddParameter(IDataParameterCollection parameters, IDbCommand command, string name) @@ -1962,9 +1958,7 @@ private static string GetInListRegex(string name, bool byPosition) => byPosition /// The command to pack parameters for. /// The name prefix for these parameters. /// The parameter value can be an -#if !NETSTANDARD1_3 [Browsable(false)] -#endif [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, false)] public static void PackListParameters(IDbCommand command, string namePrefix, object value) @@ -2206,7 +2200,7 @@ public static object SanitizeParameterValue(object value) } else { - typeCode = TypeExtensions.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); + typeCode = Type.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); } switch (typeCode) { @@ -2265,12 +2259,10 @@ public static string Format(object value) } else { - switch (TypeExtensions.GetTypeCode(value.GetType())) + switch (Type.GetTypeCode(value.GetType())) { -#if !NETSTANDARD1_3 case TypeCode.DBNull: return "null"; -#endif case TypeCode.Boolean: return ((bool)value) ? "1" : "0"; case TypeCode.Byte: @@ -2370,7 +2362,7 @@ internal static IList GetLiteralTokens(string sql) public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); - private static bool IsValueTuple(Type type) => type?.IsValueType() == true && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal); + private static bool IsValueTuple(Type type) => type?.IsValueType == true && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal); internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { @@ -2390,8 +2382,9 @@ internal static Action CreateParamInfoGenerator(Identity ide var il = dm.GetILGenerator(); - bool isStruct = type.IsValueType(); - var sizeLocal = (LocalBuilder)null; + bool isStruct = type.IsValueType; + var _sizeLocal = (LocalBuilder)null; + LocalBuilder GetSizeLocal() => _sizeLocal ?? (_sizeLocal = il.DeclareLocal(typeof(int))); il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] LocalBuilder typedParameterLocal; @@ -2501,7 +2494,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [command] [name] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value] - if (prop.PropertyType.IsValueType()) + if (prop.PropertyType.IsValueType) { il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value] } @@ -2553,13 +2546,13 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] bool checkForNull; - if (prop.PropertyType.IsValueType()) + if (prop.PropertyType.IsValueType) { var propType = prop.PropertyType; var nullType = Nullable.GetUnderlyingType(propType); bool callSanitize = false; - if ((nullType ?? propType).IsEnum()) + if ((nullType ?? propType).IsEnum) { if (nullType != null) { @@ -2571,7 +2564,7 @@ internal static Action CreateParamInfoGenerator(Identity ide { checkForNull = false; // non-nullable enum; we can do that! just box to the wrong type! (no, really) - switch (TypeExtensions.GetTypeCode(Enum.GetUnderlyingType(propType))) + switch (Type.GetTypeCode(Enum.GetUnderlyingType(propType))) { case TypeCode.Byte: propType = typeof(byte); break; case TypeCode.SByte: propType = typeof(sbyte); break; @@ -2602,10 +2595,6 @@ internal static Action CreateParamInfoGenerator(Identity ide } if (checkForNull) { - if ((dbType == DbType.String || dbType == DbType.AnsiString) && sizeLocal == null) - { - sizeLocal = il.DeclareLocal(typeof(int)); - } // relative stack: [boxed value] il.Emit(OpCodes.Dup);// relative stack: [boxed value] [boxed value] Label notNull = il.DefineLabel(); @@ -2617,7 +2606,7 @@ internal static Action CreateParamInfoGenerator(Identity ide if (dbType == DbType.String || dbType == DbType.AnsiString) { EmitInt32(il, 0); - il.Emit(OpCodes.Stloc, sizeLocal); + il.Emit(OpCodes.Stloc, GetSizeLocal()); } if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value); il.MarkLabel(notNull); @@ -2634,7 +2623,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.MarkLabel(isLong); EmitInt32(il, -1); // [string] [-1] il.MarkLabel(lenDone); - il.Emit(OpCodes.Stloc, sizeLocal); // [string] + il.Emit(OpCodes.Stloc, GetSizeLocal()); // [string] } if (prop.PropertyType.FullName == LinqBinary) { @@ -2658,6 +2647,7 @@ internal static Action CreateParamInfoGenerator(Identity ide if (prop.PropertyType == typeof(string)) { var endOfSize = il.DefineLabel(); + var sizeLocal = GetSizeLocal(); // don't set if 0 il.Emit(OpCodes.Ldloc, sizeLocal); // [parameters] [[parameters]] [parameter] [size] il.Emit(OpCodes.Brfalse_S, endOfSize); // [parameters] [[parameters]] [parameter] @@ -2719,7 +2709,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Ldloc, typedParameterLocal); // command, sql, typed parameter il.EmitCall(callOpCode, prop.GetGetMethod(), null); // command, sql, typed value Type propType = prop.PropertyType; - var typeCode = TypeExtensions.GetTypeCode(propType); + var typeCode = Type.GetTypeCode(propType); switch (typeCode) { case TypeCode.Boolean: @@ -2768,7 +2758,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.EmitCall(OpCodes.Call, convert, null); // command, sql, string value break; default: - if (propType.IsValueType()) il.Emit(OpCodes.Box, propType); // command, sql, object value + if (propType.IsValueType) il.Emit(OpCodes.Box, propType); // command, sql, object value il.EmitCall(OpCodes.Call, format, null); // command, sql, string value break; } @@ -2786,7 +2776,7 @@ internal static Action CreateParamInfoGenerator(Identity ide { typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short), typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal) - }.ToDictionary(x => TypeExtensions.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), new[] { typeof(IFormatProvider) })); + }.ToDictionary(x => Type.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), new[] { typeof(IFormatProvider) })); private static MethodInfo GetToString(TypeCode typeCode) { @@ -2821,7 +2811,7 @@ private static T ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition c object param = command.Parameters; if (param != null) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } @@ -2878,7 +2868,7 @@ private static Action GetParameterReader(IDbConnection cnn, // nice and simple if (param != null) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType(), null); + var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); } var paramReader = info?.ParamReader; @@ -2903,7 +2893,7 @@ private static Func GetStructDeserializer(Type type, Type e } #pragma warning restore 618 - if (effectiveType.IsEnum()) + if (effectiveType.IsEnum) { // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum return r => { @@ -2936,7 +2926,7 @@ private static T Parse(object value) if (value is T) return (T)value; var type = typeof(T); type = Nullable.GetUnderlyingType(type) ?? type; - if (type.IsEnum()) + if (type.IsEnum) { if (value is float || value is double || value is decimal) { @@ -3069,7 +3059,7 @@ private static Func GetTypeDeserializerImpl( throw MultiMapException(reader); } - var returnType = type.IsValueType() ? typeof(object) : type; + var returnType = type.IsValueType ? typeof(object) : type; var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, true); var il = dm.GetILGenerator(); @@ -3191,11 +3181,9 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i int index = startBound; ConstructorInfo specializedConstructor = null; -#if !NETSTANDARD1_3 bool supportInitialize = false; -#endif Dictionary structLocals = null; - if (type.IsValueType()) + if (type.IsValueType) { il.Emit(OpCodes.Ldloca, returnValueLocal); il.Emit(OpCodes.Initobj, type); @@ -3214,7 +3202,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i var consPs = explicitConstr.GetParameters(); foreach (var p in consPs) { - if (!p.ParameterType.IsValueType()) + if (!p.ParameterType.IsValueType) { il.Emit(OpCodes.Ldnull); } @@ -3226,14 +3214,12 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i il.Emit(OpCodes.Newobj, explicitConstr); il.Emit(OpCodes.Stloc, returnValueLocal); -#if !NETSTANDARD1_3 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); } -#endif } else { @@ -3248,14 +3234,12 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i { il.Emit(OpCodes.Newobj, ctor); il.Emit(OpCodes.Stloc, returnValueLocal); -#if !NETSTANDARD1_3 supportInitialize = typeof(ISupportInitialize).IsAssignableFrom(type); if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); } -#endif } else { @@ -3265,7 +3249,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i } il.BeginExceptionBlock(); - if (type.IsValueType()) + if (type.IsValueType) { il.Emit(OpCodes.Ldloca, returnValueLocal); // [target] } @@ -3279,7 +3263,6 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i : names.Select(n => typeMap.GetMember(n))).ToList(); // stack is now [target] - bool first = true; var allDone = il.DefineLabel(); var stringEnumLocal = (LocalBuilder)null; @@ -3305,7 +3288,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i // Store the value in the property/field if (item.Property != null) { - il.Emit(type.IsValueType() ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); + il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); } else { @@ -3321,11 +3304,11 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i il.Emit(OpCodes.Pop); LoadDefaultValue(il, item.MemberType); } - else if (applyNullSetting && (!memberType.IsValueType() || Nullable.GetUnderlyingType(memberType) != null)) + else if (applyNullSetting && (!memberType.IsValueType || Nullable.GetUnderlyingType(memberType) != null)) { il.Emit(OpCodes.Pop); // stack is now [target][target] // can load a null with this value - if (memberType.IsValueType()) + if (memberType.IsValueType) { // must be Nullable for some T GetTempLocal(il, ref structLocals, memberType, true); // stack is now [target][target][null] } @@ -3337,7 +3320,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i // Store the value in the property/field if (item.Property != null) { - il.Emit(type.IsValueType() ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); + il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); // stack is now [target] } else @@ -3364,7 +3347,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i first = false; index++; } - if (type.IsValueType()) + if (type.IsValueType) { il.Emit(OpCodes.Pop); } @@ -3375,13 +3358,11 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i il.Emit(OpCodes.Newobj, specializedConstructor); } il.Emit(OpCodes.Stloc, returnValueLocal); // stack is empty -#if !NETSTANDARD1_3 if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.EndInit)), null); } -#endif } il.MarkLabel(allDone); il.BeginCatchBlock(typeof(Exception)); // stack is Exception @@ -3392,7 +3373,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc, returnValueLocal); // stack is [rval] - if (type.IsValueType()) + if (type.IsValueType) { il.Emit(OpCodes.Box, type); } @@ -3401,7 +3382,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i private static void LoadDefaultValue(ILGenerator il, Type type) { - if (type.IsValueType()) + if (type.IsValueType) { var local = il.DeclareLocal(type); il.Emit(OpCodes.Ldloca, local); @@ -3441,9 +3422,9 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind // unbox nullable enums as the primitive, i.e. byte etc var nullUnderlyingType = Nullable.GetUnderlyingType(memberType); - var unboxType = nullUnderlyingType?.IsEnum() == true ? nullUnderlyingType : memberType; + var unboxType = nullUnderlyingType?.IsEnum == true ? nullUnderlyingType : memberType; - if (unboxType.IsEnum()) + if (unboxType.IsEnum) { Type numericType = Enum.GetUnderlyingType(unboxType); if (colType == typeof(string)) @@ -3478,9 +3459,9 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind } else { - TypeCode dataTypeCode = TypeExtensions.GetTypeCode(colType), unboxTypeCode = TypeExtensions.GetTypeCode(unboxType); + TypeCode dataTypeCode = Type.GetTypeCode(colType), unboxTypeCode = Type.GetTypeCode(unboxType); bool hasTypeHandler; - if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == TypeExtensions.GetTypeCode(nullUnderlyingType)) + if ((hasTypeHandler = typeHandlers.ContainsKey(unboxType)) || colType == unboxType || dataTypeCode == unboxTypeCode || dataTypeCode == Type.GetTypeCode(nullUnderlyingType)) { if (hasTypeHandler) { @@ -3523,7 +3504,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro { bool handled = false; OpCode opCode = default(OpCode); - switch (TypeExtensions.GetTypeCode(from)) + switch (Type.GetTypeCode(from)) { case TypeCode.Boolean: case TypeCode.Byte: @@ -3537,7 +3518,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro case TypeCode.Single: case TypeCode.Double: handled = true; - switch (TypeExtensions.GetTypeCode(via ?? to)) + switch (Type.GetTypeCode(via ?? to)) { case TypeCode.Byte: opCode = OpCodes.Conv_Ovf_I1_Un; break; @@ -3635,7 +3616,7 @@ public static void ThrowDataException(Exception ex, int index, IDataReader reade } else { - formattedValue = Convert.ToString(value) + " - " + TypeExtensions.GetTypeCode(value.GetType()); + formattedValue = Convert.ToString(value) + " - " + Type.GetTypeCode(value.GetType()); } } catch (Exception valEx) @@ -3693,7 +3674,6 @@ public static IEqualityComparer ConnectionStringComparer private static IEqualityComparer connectionStringComparer = StringComparer.Ordinal; -#if !NETSTANDARD1_3 /// /// Key used to indicate the type name associated with a DataTable. /// @@ -3729,7 +3709,6 @@ 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 TableValuedParameter. @@ -3739,17 +3718,6 @@ public static string GetTypeName(this DataTable table) => public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) where T : IDataRecord => new SqlDataRecordListTVPParameter(list, typeName); - /* - /// - /// Used to pass a IEnumerable<SqlDataRecord> as a TableValuedParameter. - /// - /// 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); - // ^^^ retained to avoid missing-method-exception; can presumably drop in a "major" - */ - // one per thread [ThreadStatic] private static StringBuilder perThreadStringBuilderCache; diff --git a/Dapper/TableValuedParameter.cs b/Dapper/TableValuedParameter.cs index a0ae7e62c..78409a40d 100644 --- a/Dapper/TableValuedParameter.cs +++ b/Dapper/TableValuedParameter.cs @@ -1,6 +1,5 @@ using System.Data; -#if !NETSTANDARD1_3 namespace Dapper { /// @@ -49,4 +48,3 @@ internal static void Set(IDbDataParameter parameter, DataTable table, string typ } } } -#endif diff --git a/Dapper/TypeExtensions.cs b/Dapper/TypeExtensions.cs index 81ac91f2f..6291fe46d 100644 --- a/Dapper/TypeExtensions.cs +++ b/Dapper/TypeExtensions.cs @@ -1,96 +1,11 @@ using System; using System.Reflection; -using System.Collections.Generic; namespace Dapper { internal static class TypeExtensions { - public static string Name(this Type type) => -#if NETSTANDARD1_3 || NETCOREAPP1_0 - type.GetTypeInfo().Name; -#else - type.Name; -#endif - - public static bool IsValueType(this Type type) => -#if NETSTANDARD1_3 || NETCOREAPP1_0 - type.GetTypeInfo().IsValueType; -#else - type.IsValueType; -#endif - - public static bool IsEnum(this Type type) => -#if NETSTANDARD1_3 || NETCOREAPP1_0 - type.GetTypeInfo().IsEnum; -#else - type.IsEnum; -#endif - - public static bool IsGenericType(this Type type) => -#if NETSTANDARD1_3 || NETCOREAPP1_0 - type.GetTypeInfo().IsGenericType; -#else - type.IsGenericType; -#endif - - public static bool IsInterface(this Type type) => -#if NETSTANDARD1_3 || NETCOREAPP1_0 - type.GetTypeInfo().IsInterface; -#else - type.IsInterface; -#endif - -#if NETSTANDARD1_3 || NETCOREAPP1_0 - public static IEnumerable GetCustomAttributes(this Type type, bool inherit) - { - return type.GetTypeInfo().GetCustomAttributes(inherit); - } - - public static TypeCode GetTypeCode(Type type) - { - if (type == null) return TypeCode.Empty; - if (typeCodeLookup.TryGetValue(type, out TypeCode result)) return result; - - if (type.IsEnum()) - { - type = Enum.GetUnderlyingType(type); - if (typeCodeLookup.TryGetValue(type, out result)) return result; - } - return TypeCode.Object; - } - - private static readonly Dictionary typeCodeLookup = new Dictionary - { - [typeof(bool)] = TypeCode.Boolean, - [typeof(byte)] = TypeCode.Byte, - [typeof(char)] = TypeCode.Char, - [typeof(DateTime)] = TypeCode.DateTime, - [typeof(decimal)] = TypeCode.Decimal, - [typeof(double)] = TypeCode.Double, - [typeof(short)] = TypeCode.Int16, - [typeof(int)] = TypeCode.Int32, - [typeof(long)] = TypeCode.Int64, - [typeof(object)] = TypeCode.Object, - [typeof(sbyte)] = TypeCode.SByte, - [typeof(float)] = TypeCode.Single, - [typeof(string)] = TypeCode.String, - [typeof(ushort)] = TypeCode.UInt16, - [typeof(uint)] = TypeCode.UInt32, - [typeof(ulong)] = TypeCode.UInt64, - }; -#else - public static TypeCode GetTypeCode(Type type) => Type.GetTypeCode(type); -#endif - public static MethodInfo GetPublicInstanceMethod(this Type type, string name, Type[] types) - { -#if NETSTANDARD1_3 || NETCOREAPP1_0 - var method = type.GetMethod(name, types); - return (method?.IsPublic == true && !method.IsStatic) ? method : null; -#else - return type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null); -#endif - } + => type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null); } } diff --git a/Dapper/UdtTypeHandler.cs b/Dapper/UdtTypeHandler.cs index a2341b05e..4662dfe3b 100644 --- a/Dapper/UdtTypeHandler.cs +++ b/Dapper/UdtTypeHandler.cs @@ -5,7 +5,6 @@ namespace Dapper { public static partial class SqlMapper { -#if !NETSTANDARD1_3 && !NETSTANDARD2_0 /// /// A type handler for data-types that are supported by the underlying provider, but which need /// a well-known UdtTypeName to be specified @@ -36,6 +35,5 @@ void ITypeHandler.SetValue(IDbDataParameter parameter, object value) if(!(value is DBNull)) StructuredHelper.ConfigureUDT(parameter, udtTypeName); } } -#endif } } diff --git a/Directory.build.props b/Directory.build.props index b2d84fe51..f478aa234 100644 --- a/Directory.build.props +++ b/Directory.build.props @@ -17,7 +17,7 @@ embedded en-US false - 2.4.1-pre.build.4059 + 2.4.1 @@ -25,7 +25,7 @@ - + diff --git a/version.json b/version.json index f49a47a32..75a19b97d 100644 --- a/version.json +++ b/version.json @@ -1,6 +1,6 @@ { - "version": "1.60", - "assemblyVersion": "1.60.0.0", + "version": "2.0-preview1", + "assemblyVersion": "2.0.0.0", "publicReleaseRefSpec": [ "^refs/heads/master$", "^refs/tags/v\\d+\\.\\d+" From f5ca275a299e70a622a4de2dde676b0009a6babf Mon Sep 17 00:00:00 2001 From: mgravell Date: Wed, 28 Aug 2019 15:26:35 +0100 Subject: [PATCH 079/312] release notes --- docs/index.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/index.md b/docs/index.md index 2c03bf691..f4565b25a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,24 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes +### 2.0-preview1 + +Primary changes: + +- remove the System.Data.SqlClient depenency, allowing consumers to use System.Data.SqlClient or Microsoft.Data.SqlClient (or neither, or both) as they choose + - this means that some users may need to *re-add* one of the above as a `` for their project to build, if they were previously relying on Dapper to provide System.Data.SqlClient + - the `AsTableValuedParameter(this IEnumerable)` extension method is now `AsTableValuedParameter(this IEnumerable) where T : IDataRecord`; this is a breaking change but should be code-compatible and just requires a rebuild +- unify the target platform at NetStandard2.0 (and .NET Framework 4.6.2 for the EF DB geometry/geography types) +- fix bug with `Identity` not enforcing type identity of multi-mapped types + +Other changes merged: + +- fix #1242, #1280, #1282 - fix value-tuple mapping +- fix #1295 - add `ExecuteReaderAsync` overload to expose `DbDataReader` +- fix #569 - handing of `IN` and similar clauses in some scenarios +- fix #1256 - make `Dispose()` polymorphic in "rainbow" +- fix #1257 - make the `.Connection` available in "rainbow" + ### 1.60.6 - improve performance of descriptor API From 522bc49a9996758477381a4e2d42a709a7f47cab Mon Sep 17 00:00:00 2001 From: mgravell Date: Wed, 28 Aug 2019 17:09:18 +0100 Subject: [PATCH 080/312] rev Microsoft.Data.SqlClient to GA (1.0.19239.1) --- Dapper.Tests/Dapper.Tests.csproj | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 68cad0ffa..626ff128e 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -22,9 +22,9 @@ - + + Condition="'$(TargetFramework)' == 'net462' OR '$(TargetFramework)' == 'net472'" /> Date: Thu, 29 Aug 2019 14:17:14 +0100 Subject: [PATCH 081/312] 2.0 RTM --- docs/index.md | 2 +- version.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index f4565b25a..5bc19e4ee 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes -### 2.0-preview1 +### 2.0 (and 2.0-preview1) Primary changes: diff --git a/version.json b/version.json index 75a19b97d..5b13ff57a 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "2.0-preview1", + "version": "2.0", "assemblyVersion": "2.0.0.0", "publicReleaseRefSpec": [ "^refs/heads/master$", From e9adb722f69fa61f0c8d86e6ca67ef37832c2703 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 29 Aug 2019 14:35:03 +0100 Subject: [PATCH 082/312] RTM v is 2.0.5 --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 5bc19e4ee..889871fdb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes -### 2.0 (and 2.0-preview1) +### 2.0.5 Primary changes: From b5352775992bbb0887982f1e4b340dc820f6447b Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 29 Aug 2019 14:47:59 +0100 Subject: [PATCH 083/312] oops, 2.0.4 --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 889871fdb..5bf101812 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes -### 2.0.5 +### 2.0.4 Primary changes: From 3fe88fc1e0b48e7dfa87ff433862e4adb96ec1b9 Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 13:33:04 +0100 Subject: [PATCH 084/312] add Dapper.ProviderTools lib --- Dapper.ProviderTools/BulkCopy.cs | 122 ++++++++++++++++++ .../Dapper.ProviderTools.csproj | 20 +++ Dapper.ProviderTools/DbExceptionExtensions.cs | 64 +++++++++ .../Internal/DynamicBulkCopy.cs | 56 ++++++++ Dapper.Tests/Dapper.Tests.csproj | 11 +- Dapper.Tests/ProviderTests.cs | 67 ++++++++++ Dapper.sln | 7 + 7 files changed, 341 insertions(+), 6 deletions(-) create mode 100644 Dapper.ProviderTools/BulkCopy.cs create mode 100644 Dapper.ProviderTools/Dapper.ProviderTools.csproj create mode 100644 Dapper.ProviderTools/DbExceptionExtensions.cs create mode 100644 Dapper.ProviderTools/Internal/DynamicBulkCopy.cs create mode 100644 Dapper.Tests/ProviderTests.cs diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs new file mode 100644 index 000000000..31bbc8d12 --- /dev/null +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -0,0 +1,122 @@ +using System; +using System.Collections.Concurrent; +using System.Data; +using System.Data.Common; +using System.Linq.Expressions; +using System.Text.RegularExpressions; +using System.Threading; +using System.Threading.Tasks; +using Dapper.ProviderTools.Internal; + +namespace Dapper.ProviderTools +{ + /// + /// Provides provider-agnostic access to bulk-copy services + /// + public abstract class BulkCopy : IDisposable + { + /// + /// Attempt to create a BulkCopy instance for the connection provided + /// + public static BulkCopy? TryCreate(DbConnection connection) + { + if (connection == null) return null; + var type = connection.GetType(); + if (!s_bcpFactory.TryGetValue(type, out var func)) + { + s_bcpFactory[type] = func = CreateBcpFactory(type); + } + var obj = func?.Invoke(connection); + return DynamicBulkCopy.Create(obj); + } + + /// + /// Provide an external registration for a given connection type + /// + public static void Register(Type type, Func? factory) + => s_bcpFactory[type] = factory; + + private static readonly ConcurrentDictionary?> s_bcpFactory + = new ConcurrentDictionary?>(); + + internal static Func? CreateBcpFactory(Type connectionType) + { + try + { + var match = Regex.Match(connectionType.Name, "^(.+)Connection$"); + if (match.Success) + { + var prefix = match.Groups[1].Value; + var bcpType = connectionType.Assembly.GetType($"{connectionType.Namespace}.{prefix}BulkCopy"); + if (bcpType != null) + { + var ctor = bcpType.GetConstructor(new[] { connectionType }); + if (ctor == null) return null; + + var p = Expression.Parameter(typeof(DbConnection), "conn"); + var body = Expression.New(ctor, Expression.Convert(p, connectionType)); + return Expression.Lambda>(body, p).Compile(); + } + } + } + catch { } + return null; + } + + /// + /// Name of the destination table on the server. + /// + public abstract string DestinationTableName { get; set; } + /// + /// Write a set of data to the server + /// + public abstract void WriteToServer(DataTable source); + /// + /// Write a set of data to the server + /// + public abstract void WriteToServer(IDataReader source); + /// + /// Write a set of data to the server + /// + public abstract Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken); + /// + /// Write a set of data to the server + /// + public abstract Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken); + /// + /// Add a mapping between two columns by name + /// + public abstract void AddColumnMapping(string sourceColumn, string destinationColumn); + /// + /// Add a mapping between two columns by position + /// + public abstract void AddColumnMapping(int sourceColumn, int destinationColumn); + /// + /// The underlying untyped object providing the bulk-copy service + /// + public abstract object Wrapped { get; } + + /// + /// Enables or disables streaming from a data-reader + /// + public bool EnableStreaming { get; set; } + /// + /// Number of rows in each batch + /// + public int BatchSize { get; set; } + /// + /// Number of seconds for the operation to complete before it times out. + /// + public int BulkCopyTimeout { get; set; } + + /// + /// Release any resources associated with this instance + /// + public void Dispose() => Dispose(true); + + /// + /// Release any resources associated with this instance + /// + protected abstract void Dispose(bool disposing); + } +} diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj new file mode 100644 index 000000000..57f36cf26 --- /dev/null +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -0,0 +1,20 @@ + + + + netstandard2.0 + + Dapper.ProviderTools + orm;sql;micro-orm + Dapper Provider Tools + Provider-agnostic ADO.NET helper utilities + Marc Gravell + netstandard2.0 + true + enable + 8.0 + + + + + + diff --git a/Dapper.ProviderTools/DbExceptionExtensions.cs b/Dapper.ProviderTools/DbExceptionExtensions.cs new file mode 100644 index 000000000..c14f1bc62 --- /dev/null +++ b/Dapper.ProviderTools/DbExceptionExtensions.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Common; +using System.Linq.Expressions; +using System.Reflection; + +namespace Dapper.ProviderTools +{ + /// + /// Helper utilties for working with database exceptions + /// + public static class DbExceptionExtensions + { + /// + /// Indicates whether the provided exception has an integer Number property with the supplied value + /// + public static bool IsNumber(this DbException exception, int number) + => exception != null && ByTypeHelpers.Get(exception.GetType()).IsNumber(exception, number); + + + private sealed class ByTypeHelpers + { + private static readonly ConcurrentDictionary s_byType + = new ConcurrentDictionary(); + private readonly Func? _getNumber; + + public bool IsNumber(DbException exception, int number) + => _getNumber != null && _getNumber(exception) == number; + + public static ByTypeHelpers Get(Type type) + { + if (!s_byType.TryGetValue(type, out var value)) + { + s_byType[type] = value = new ByTypeHelpers(type); + } + return value; + } + + private ByTypeHelpers(Type type) + { + _getNumber = TryGetInstanceProperty("Number", type); + } + + private static Func? TryGetInstanceProperty(string name, Type type) + { + try + { + var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); + if (prop == null || !prop.CanRead) return null; + if (prop.PropertyType != typeof(T)) return null; + + var p = Expression.Parameter(typeof(DbException), "exception"); + var body = Expression.Property(Expression.Convert(p, type), prop); + var lambda = Expression.Lambda>(body, p); + return lambda.Compile(); + } + catch + { + return null; + } + } + } + } +} diff --git a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs new file mode 100644 index 000000000..10a3624be --- /dev/null +++ b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs @@ -0,0 +1,56 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; + +namespace Dapper.ProviderTools.Internal +{ + internal sealed class DynamicBulkCopy : BulkCopy + { + internal static BulkCopy? Create(object? wrapped) + => wrapped == null ? null : new DynamicBulkCopy(wrapped); + + private DynamicBulkCopy(object wrapped) + => _wrapped = wrapped; + + private readonly dynamic _wrapped; + + public override string DestinationTableName + { + get => _wrapped.DestinationTableName; + set => _wrapped.DestinationTableName = value; + } + + public override object Wrapped => _wrapped; + + public override void AddColumnMapping(string sourceColumn, string destinationColumn) + => _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn); + + public override void AddColumnMapping(int sourceColumn, int destinationColumn) + => _wrapped.ColumnMappings.Add(sourceColumn, destinationColumn); + + public override void WriteToServer(DataTable source) + => _wrapped.WriteToServer(source); + + public override void WriteToServer(IDataReader source) + => _wrapped.WriteToServer(source); + + public override Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken) + => _wrapped.WriteToServer(source, cancellationToken); + + public override Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken) + => _wrapped.WriteToServer(source, cancellationToken); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + if (_wrapped is IDisposable d) + { + try { d.Dispose(); } catch { } + } + } + } + } +} diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 626ff128e..d028eb66f 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -23,25 +23,23 @@ - + - + %(Filename)%(Extension) PreserveNewest - + %(Filename)%(Extension) PreserveNewest + @@ -54,6 +52,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/Dapper.Tests/ProviderTests.cs b/Dapper.Tests/ProviderTests.cs new file mode 100644 index 000000000..bb3c0886d --- /dev/null +++ b/Dapper.Tests/ProviderTests.cs @@ -0,0 +1,67 @@ +using System.Data.Common; +using Dapper.ProviderTools; +using Xunit; + +namespace Dapper.Tests +{ + public class ProviderTests + { + [Fact] + public void BulkCopy_SystemDataSqlClient() + { + using (var conn = new System.Data.SqlClient.SqlConnection()) + { + Test(conn); + } + } + + [Fact] + public void BulkCopy_MicrosoftDataSqlClient() + { + using (var conn = new Microsoft.Data.SqlClient.SqlConnection()) + { + Test(conn); + } + } + + private static void Test(DbConnection connection) + { + using (var bcp = BulkCopy.TryCreate(connection)) + { + Assert.NotNull(bcp); + Assert.IsType(bcp.Wrapped); + bcp.EnableStreaming = true; + } + } + + [Theory] + [InlineData(51000, 51000, true)] + [InlineData(51000, 43, false)] + public void DbNumber_SystemData(int create, int test, bool result) + => Test(create, test, result); + + [Theory] + [InlineData(51000, 51000, true)] + [InlineData(51000, 43, false)] + public void DbNumber_MicrosoftData(int create, int test, bool result) + => Test(create, test, result); + + private void Test(int create, int test, bool result) + where T : SqlServerDatabaseProvider, new() + { + var provider = new T(); + using (var conn = provider.GetOpenConnection()) + { + try + { + conn.Execute("throw @create, 'boom', 1;", new { create }); + Assert.False(true); + } + catch(DbException err) + { + Assert.Equal(result, err.IsNumber(test)); + } + } + } + } +} diff --git a/Dapper.sln b/Dapper.sln index c46af6e92..d3c153bd4 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -45,6 +45,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.Stro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -91,6 +93,10 @@ Global {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -106,6 +112,7 @@ Global {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} From 822a9a33bee15573424e6f0d138aad26da7bbc8e Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 13:38:22 +0100 Subject: [PATCH 085/312] IDE and command-line disagree on #nullable --- Dapper.ProviderTools/BulkCopy.cs | 2 +- Dapper.ProviderTools/DbExceptionExtensions.cs | 2 +- Dapper.ProviderTools/Internal/DynamicBulkCopy.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs index 31bbc8d12..38e496b0a 100644 --- a/Dapper.ProviderTools/BulkCopy.cs +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Dapper.ProviderTools.Internal; - +#nullable enable namespace Dapper.ProviderTools { /// diff --git a/Dapper.ProviderTools/DbExceptionExtensions.cs b/Dapper.ProviderTools/DbExceptionExtensions.cs index c14f1bc62..1850f4e80 100644 --- a/Dapper.ProviderTools/DbExceptionExtensions.cs +++ b/Dapper.ProviderTools/DbExceptionExtensions.cs @@ -3,7 +3,7 @@ using System.Data.Common; using System.Linq.Expressions; using System.Reflection; - +#nullable enable namespace Dapper.ProviderTools { /// diff --git a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs index 10a3624be..36188b9f2 100644 --- a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs +++ b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs @@ -3,7 +3,7 @@ using System.Data.Common; using System.Threading; using System.Threading.Tasks; - +#nullable enable namespace Dapper.ProviderTools.Internal { internal sealed class DynamicBulkCopy : BulkCopy From b66391465030830d6e1fc1e982ef9d01a4eb8caf Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 13:52:20 +0100 Subject: [PATCH 086/312] use a license expression --- Directory.build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.build.props b/Directory.build.props index f478aa234..c1390ca5a 100644 --- a/Directory.build.props +++ b/Directory.build.props @@ -9,7 +9,7 @@ $(AssemblyName) https://stackexchange.github.io/Dapper/ https://github.com/StackExchange/Dapper - http://www.apache.org/licenses/LICENSE-2.0 + Apache-2.0 git https://github.com/StackExchange/Dapper From ed90d66e48b138bea38d04bd88f564daaeb8d9db Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 14:19:23 +0100 Subject: [PATCH 087/312] add missing APIs to BulkCopy --- Dapper.ProviderTools/BulkCopy.cs | 26 +++++++++++++++++-- .../Internal/DynamicBulkCopy.cs | 4 +++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs index 38e496b0a..7808f31c3 100644 --- a/Dapper.ProviderTools/BulkCopy.cs +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -30,6 +30,20 @@ public abstract class BulkCopy : IDisposable return DynamicBulkCopy.Create(obj); } + /// + /// Create a BulkCopy instance for the connection provided + /// + public static BulkCopy Create(DbConnection connection) + { + var bcp = TryCreate(connection); + if (bcp == null) + { + if (connection == null) throw new ArgumentNullException(nameof(connection)); + throw new NotSupportedException("Unable to create BulkCopy for " + connection.GetType().FullName); + } + return bcp; + } + /// /// Provide an external registration for a given connection type /// @@ -74,15 +88,23 @@ private static readonly ConcurrentDictionary?> /// /// Write a set of data to the server /// + public abstract void WriteToServer(DataRow[] source); + /// + /// Write a set of data to the server + /// public abstract void WriteToServer(IDataReader source); /// /// Write a set of data to the server /// - public abstract Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken); + public abstract Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken = default); + /// + /// Write a set of data to the server + /// + public abstract Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken = default); /// /// Write a set of data to the server /// - public abstract Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken); + public abstract Task WriteToServerAsync(DataRow[] source, CancellationToken cancellationToken = default); /// /// Add a mapping between two columns by name /// diff --git a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs index 36188b9f2..2505b4119 100644 --- a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs +++ b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs @@ -32,6 +32,8 @@ public override void AddColumnMapping(int sourceColumn, int destinationColumn) public override void WriteToServer(DataTable source) => _wrapped.WriteToServer(source); + public override void WriteToServer(DataRow[] source) + => _wrapped.WriteToServer(source); public override void WriteToServer(IDataReader source) => _wrapped.WriteToServer(source); @@ -41,6 +43,8 @@ public override Task WriteToServerAsync(DbDataReader source, CancellationToken c public override Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken) => _wrapped.WriteToServer(source, cancellationToken); + public override Task WriteToServerAsync(DataRow[] source, CancellationToken cancellationToken) + => _wrapped.WriteToServer(source, cancellationToken); protected override void Dispose(bool disposing) { From 16ed3fd985ce4e198c76c7ddc2bcdc77b7ca725f Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 14:33:46 +0100 Subject: [PATCH 088/312] remove unused oracle ref --- Dapper.Tests/Dapper.Tests.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index d028eb66f..5a0164992 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -52,13 +52,13 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + From 3b8b0cdc8ad0faf1f58f49585c1bd2a6d79bc32f Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 15:23:15 +0100 Subject: [PATCH 089/312] add APIs for working with the connection pools and client-id --- .../DbConnectionExtensions.cs | 129 ++++++++++++++++++ Dapper.Tests/ProviderTests.cs | 59 +++++++- 2 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 Dapper.ProviderTools/DbConnectionExtensions.cs diff --git a/Dapper.ProviderTools/DbConnectionExtensions.cs b/Dapper.ProviderTools/DbConnectionExtensions.cs new file mode 100644 index 000000000..05d21d5a7 --- /dev/null +++ b/Dapper.ProviderTools/DbConnectionExtensions.cs @@ -0,0 +1,129 @@ +using System; +using System.Collections.Concurrent; +using System.Data.Common; +using System.Linq.Expressions; +using System.Reflection; + +namespace Dapper.ProviderTools +{ + /// + /// Helper utilties for working with database connections + /// + public static class DbConnectionExtensions + { + /// + /// Attempt to get the client connection id for a given connection + /// + public static bool TryGetClientConnectionId(this DbConnection connection, out Guid clientConnectionId) + { + clientConnectionId = default; + return connection != null && ByTypeHelpers.Get(connection.GetType()).TryGetClientConnectionId( + connection, out clientConnectionId); + } + + /// + /// Clear all pools associated with the provided connection type + /// + public static bool TryClearAllPools(this DbConnection connection) + => connection != null && ByTypeHelpers.Get(connection.GetType()).TryClearAllPools(); + + /// + /// Clear the pools associated with the provided connection + /// + public static bool TryClearPool(this DbConnection connection) + => connection != null && ByTypeHelpers.Get(connection.GetType()).TryClearPool(connection); + + private sealed class ByTypeHelpers + { + private static readonly ConcurrentDictionary s_byType + = new ConcurrentDictionary(); + private readonly Func? _getClientConnectionId; + + private readonly Action? _clearPool; + private readonly Action? _clearAllPools; + + public bool TryGetClientConnectionId(DbConnection connection, out Guid clientConnectionId) + { + if (_getClientConnectionId == null) + { + clientConnectionId = default; + return false; + } + clientConnectionId = _getClientConnectionId(connection); + return true; + } + + public bool TryClearPool(DbConnection connection) + { + if (_clearPool == null) return false; + _clearPool(connection); + return true; + } + + public bool TryClearAllPools() + { + if (_clearAllPools == null) return false; + _clearAllPools(); + return true; + } + + public static ByTypeHelpers Get(Type type) + { + if (!s_byType.TryGetValue(type, out var value)) + { + s_byType[type] = value = new ByTypeHelpers(type); + } + return value; + } + + private ByTypeHelpers(Type type) + { + _getClientConnectionId = TryGetInstanceProperty("ClientConnectionId", type); + + try + { + var clearAllPools = type.GetMethod("ClearAllPools", BindingFlags.Public | BindingFlags.Static, + null, Type.EmptyTypes, null); + if (clearAllPools != null) + { + _clearAllPools = (Action)Delegate.CreateDelegate(typeof(Action), clearAllPools); + } + } + catch { } + + try + { + var clearPool = type.GetMethod("ClearPool", BindingFlags.Public | BindingFlags.Static, + null, new[] { type }, null); + if (clearPool != null) + { + var p = Expression.Parameter(typeof(DbConnection), "connection"); + var body = Expression.Call(clearPool, Expression.Convert(p, type)); + var lambda = Expression.Lambda>(body, p); + _clearPool = lambda.Compile(); + } + } + catch { } + } + + private static Func? TryGetInstanceProperty(string name, Type type) + { + try + { + var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); + if (prop == null || !prop.CanRead) return null; + if (prop.PropertyType != typeof(T)) return null; + + var p = Expression.Parameter(typeof(DbConnection), "connection"); + var body = Expression.Property(Expression.Convert(p, type), prop); + var lambda = Expression.Lambda>(body, p); + return lambda.Compile(); + } + catch + { + return null; + } + } + } + } +} diff --git a/Dapper.Tests/ProviderTests.cs b/Dapper.Tests/ProviderTests.cs index bb3c0886d..b51ed2d2e 100644 --- a/Dapper.Tests/ProviderTests.cs +++ b/Dapper.Tests/ProviderTests.cs @@ -1,4 +1,5 @@ -using System.Data.Common; +using System; +using System.Data.Common; using Dapper.ProviderTools; using Xunit; @@ -24,6 +25,62 @@ public void BulkCopy_MicrosoftDataSqlClient() } } + [Fact] + public void ClientId_SystemDataSqlClient() + => TestClientId(); + + [Fact] + public void ClientId_MicrosoftDataSqlClient() + => TestClientId(); + + + [Fact] + public void ClearPool_SystemDataSqlClient() + => ClearPool(); + + [Fact] + public void ClearPool_MicrosoftDataSqlClient() + => ClearPool(); + + [Fact] + public void ClearAllPools_SystemDataSqlClient() + => ClearAllPools(); + + [Fact] + public void ClearAllPools_MicrosoftDataSqlClient() + => ClearAllPools(); + + private static void TestClientId() + where T : SqlServerDatabaseProvider, new() + { + var provider = new T(); + using (var conn = provider.GetOpenConnection()) + { + Assert.True(conn.TryGetClientConnectionId(out var id)); + Assert.NotEqual(Guid.Empty, id); + } + } + + private static void ClearPool() + where T : SqlServerDatabaseProvider, new() + { + var provider = new T(); + using (var conn = provider.GetOpenConnection()) + { + Assert.True(conn.TryClearPool()); + } + } + + private static void ClearAllPools() + where T : SqlServerDatabaseProvider, new() + { + var provider = new T(); + using (var conn = provider.GetOpenConnection()) + { + Assert.True(conn.TryClearAllPools()); + } + } + private static void Test(DbConnection connection) { using (var bcp = BulkCopy.TryCreate(connection)) From 343d36b5c3303b8f698a30c27210601f89926bd6 Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 15:24:01 +0100 Subject: [PATCH 090/312] #nullable enable --- Dapper.ProviderTools/DbConnectionExtensions.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.ProviderTools/DbConnectionExtensions.cs b/Dapper.ProviderTools/DbConnectionExtensions.cs index 05d21d5a7..e7cce475f 100644 --- a/Dapper.ProviderTools/DbConnectionExtensions.cs +++ b/Dapper.ProviderTools/DbConnectionExtensions.cs @@ -3,7 +3,7 @@ using System.Data.Common; using System.Linq.Expressions; using System.Reflection; - +#nullable enable namespace Dapper.ProviderTools { /// From e6f63507bfdf0f0a607ff876bda4edcb51a04b2b Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 30 Aug 2019 16:24:18 +0100 Subject: [PATCH 091/312] remove the BulkCopy.Register API until I have chance to test it --- Dapper.ProviderTools/BulkCopy.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs index 7808f31c3..4cfafe329 100644 --- a/Dapper.ProviderTools/BulkCopy.cs +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -44,11 +44,13 @@ public static BulkCopy Create(DbConnection connection) return bcp; } - /// - /// Provide an external registration for a given connection type - /// - public static void Register(Type type, Func? factory) - => s_bcpFactory[type] = factory; + ///// + ///// Provide an external registration for a given connection type + ///// + //public static void Register(Type type, Func factory) + //{ + // throw new NotImplementedException(); + //} private static readonly ConcurrentDictionary?> s_bcpFactory = new ConcurrentDictionary?>(); From a47f23cc754bc5f6884195b17f675e3bc85ac451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Jone=C5=A1?= Date: Tue, 10 Sep 2019 10:21:50 +0200 Subject: [PATCH 092/312] Handle PostgreSQL chars correctly (#1326) * Handle char correctly * Add PostgreSQL char test * Test for string before testing for char --- Dapper.Tests/Providers/PostgresqlTests.cs | 22 ++++++++++++++++++++++ Dapper/SqlMapper.cs | 18 ++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/Dapper.Tests/Providers/PostgresqlTests.cs b/Dapper.Tests/Providers/PostgresqlTests.cs index 8b6c13624..416be50e2 100644 --- a/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/Dapper.Tests/Providers/PostgresqlTests.cs @@ -56,6 +56,28 @@ public void TestPostgresqlArrayParameters() } } + private class CharTable + { + public int Id { get; set; } + public char CharColumn { get; set; } + } + + [FactPostgresql] + public void TestPostgresqlChar() + { + using (var conn = GetOpenNpgsqlConnection()) + { + var transaction = conn.BeginTransaction(); + conn.Execute("create table chartable (id serial not null, charcolumn \"char\" not null);"); + conn.Execute("insert into chartable(charcolumn) values('a');"); + + var r = conn.Query("select * from chartable"); + Assert.Single(r); + Assert.Equal('a', r.Single().CharColumn); + transaction.Rollback(); + } + } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactPostgresqlAttribute : FactAttribute { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 936e01a6b..4b3dd1c73 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1877,8 +1877,13 @@ public static char ReadChar(object value) { if (value == null || value is DBNull) throw new ArgumentNullException(nameof(value)); var s = value as string; - if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", nameof(value)); - return s[0]; + if (s == null) + { + var c = value as char?; + if (c != null) return c.Value; + } + else if (s.Length == 1) return s[0]; + throw new ArgumentException("A single-character was expected", nameof(value)); } /// @@ -1892,8 +1897,13 @@ public static char ReadChar(object value) { if (value == null || value is DBNull) return null; var s = value as string; - if (s == null || s.Length != 1) throw new ArgumentException("A single-character was expected", nameof(value)); - return s[0]; + if (s == null) + { + var c = value as char?; + if (c != null) return c; + } + else if (s.Length == 1) return s[0]; + throw new ArgumentException("A single-character was expected", nameof(value)); } /// From 07bc83f2b780da79ea4b780fa27baa9cf87c608d Mon Sep 17 00:00:00 2001 From: mgravell Date: Tue, 10 Sep 2019 09:25:18 +0100 Subject: [PATCH 093/312] tidy ReadChar/ReadNullableChar with modern C# --- Dapper/SqlMapper.cs | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 4b3dd1c73..8a5771034 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1876,13 +1876,8 @@ internal static Func GetDapperRowDeserializer(IDataRecord r public static char ReadChar(object value) { if (value == null || value is DBNull) throw new ArgumentNullException(nameof(value)); - var s = value as string; - if (s == null) - { - var c = value as char?; - if (c != null) return c.Value; - } - else if (s.Length == 1) return s[0]; + if (value is string s && s.Length == 1) return s[0]; + if (value is char c) return c; throw new ArgumentException("A single-character was expected", nameof(value)); } @@ -1896,13 +1891,8 @@ public static char ReadChar(object value) public static char? ReadNullableChar(object value) { if (value == null || value is DBNull) return null; - var s = value as string; - if (s == null) - { - var c = value as char?; - if (c != null) return c; - } - else if (s.Length == 1) return s[0]; + if (value is string s && s.Length == 1) return s[0]; + if (value is char c) return c; throw new ArgumentException("A single-character was expected", nameof(value)); } From aefb2840bb4e2f0c048486c3bbd4b8c0254e003c Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 12 Sep 2019 08:30:35 +0100 Subject: [PATCH 094/312] reinstate net461 TFM; fix #1325 --- Dapper.Contrib/Dapper.Contrib.csproj | 5 ++++- .../Dapper.EntityFramework.StrongName.csproj | 2 +- Dapper.EntityFramework/Dapper.EntityFramework.csproj | 2 +- Dapper.ProviderTools/Dapper.ProviderTools.csproj | 2 +- Dapper.Rainbow/Dapper.Rainbow.csproj | 5 ++++- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 5 ++++- Dapper.StrongName/Dapper.StrongName.csproj | 2 +- Dapper.sln | 2 +- Dapper/Dapper.csproj | 2 +- 9 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj index 8ee574dfd..1d3532ddb 100644 --- a/Dapper.Contrib/Dapper.Contrib.csproj +++ b/Dapper.Contrib/Dapper.Contrib.csproj @@ -5,7 +5,7 @@ Dapper.Contrib The official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities. Sam Saffron;Johan Danforth - netstandard2.0 + net461;netstandard2.0 false @@ -18,4 +18,7 @@ + + + \ No newline at end of file diff --git a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj index 87e09a1bb..4207cd180 100644 --- a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj +++ b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj @@ -4,7 +4,7 @@ Dapper: Entity Framework type handlers (with a strong name) Extension handlers for entity framework Marc Gravell;Nick Craver - net462 + net461 ../Dapper.snk true true diff --git a/Dapper.EntityFramework/Dapper.EntityFramework.csproj b/Dapper.EntityFramework/Dapper.EntityFramework.csproj index 598dcc66c..13310a116 100644 --- a/Dapper.EntityFramework/Dapper.EntityFramework.csproj +++ b/Dapper.EntityFramework/Dapper.EntityFramework.csproj @@ -5,7 +5,7 @@ Dapper entity framework type handlers 1.50.2 Marc Gravell;Nick Craver - net462 + net461 orm;sql;micro-orm diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index 57f36cf26..002b5266b 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -8,7 +8,7 @@ Dapper Provider Tools Provider-agnostic ADO.NET helper utilities Marc Gravell - netstandard2.0 + net461;netstandard2.0 true enable 8.0 diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj index fc30f7650..208415287 100644 --- a/Dapper.Rainbow/Dapper.Rainbow.csproj +++ b/Dapper.Rainbow/Dapper.Rainbow.csproj @@ -6,7 +6,7 @@ Trivial micro-orm implemented on Dapper, provides with CRUD helpers. Sam Saffron 2017 Sam Saffron - netstandard2.0 + net461;netstandard2.0 false @@ -20,4 +20,7 @@ + + + \ No newline at end of file diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index bb6fb786a..ea474c6d5 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -5,7 +5,7 @@ Dapper SqlBuilder component The Dapper SqlBuilder component, for building SQL queries dynamically. Sam Saffron, Johan Danforth - netstandard2.0 + net461;netstandard2.0 false false @@ -16,4 +16,7 @@ + + + \ No newline at end of file diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index d745d37cf..2a429f291 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -5,7 +5,7 @@ Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - netstandard2.0 + net461;netstandard2.0 true true diff --git a/Dapper.sln b/Dapper.sln index d3c153bd4..a7ad2a7b8 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -45,7 +45,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.Stro EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 5031f288b..e62df4975 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,7 +5,7 @@ Dapper A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - netstandard2.0 + net461;netstandard2.0 From f12b73a66fa5af47b2b25dc8da2391a1381b7f42 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 12 Sep 2019 08:58:35 +0100 Subject: [PATCH 095/312] update global.json to preview9; /cc #1294 --- global.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/global.json b/global.json index c562f03c5..45f541bad 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "2.2.203" + "version": "3.0.100-preview9" } } \ No newline at end of file From 9282ef2872e1c82c68c86f31e93e8f1ceaacd409 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 12 Sep 2019 13:47:05 +0100 Subject: [PATCH 096/312] fix CI --- appveyor.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index 907205d0b..9009095e3 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,9 @@ skip_commits: files: - '**/*.md' +install: + - choco install dotnetcore-sdk --version 3.0.100-preview9-014004 + environment: Appveyor: true # Postgres From 61707905f681a4daa451a2a00a994a721293be1f Mon Sep 17 00:00:00 2001 From: james-hester-ah <49126981+james-hester-ah@users.noreply.github.com> Date: Sat, 21 Sep 2019 06:16:34 -0700 Subject: [PATCH 097/312] Fix issue #418 for DeleteAsync (#1309) Fix issue #418 for DeleteAsync (with tests) --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 8 ++++---- Dapper.Tests.Contrib/TestSuite.Async.cs | 11 +++++++++++ 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index e08a015dc..c454fce50 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -307,18 +307,18 @@ public static async Task DeleteAsync(this IDbConnection connection, T e throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); var name = GetTableName(type); - keyProperties.AddRange(explicitKeyProperties); + var allKeyProperties = keyProperties.Concat(explicitKeyProperties).ToList(); var sb = new StringBuilder(); sb.AppendFormat("DELETE FROM {0} WHERE ", name); var adapter = GetFormatter(connection); - for (var i = 0; i < keyProperties.Count; i++) + for (var i = 0; i < allKeyProperties.Count; i++) { - var property = keyProperties[i]; + var property = allKeyProperties[i]; adapter.AppendColumnNameEqualsValue(sb, property.Name); - if (i < keyProperties.Count - 1) + if (i < allKeyProperties.Count - 1) sb.Append(" AND "); } var deleted = await connection.ExecuteAsync(sb.ToString(), entityToDelete, transaction, commandTimeout).ConfigureAwait(false); diff --git a/Dapper.Tests.Contrib/TestSuite.Async.cs b/Dapper.Tests.Contrib/TestSuite.Async.cs index e9543e06e..96271647d 100644 --- a/Dapper.Tests.Contrib/TestSuite.Async.cs +++ b/Dapper.Tests.Contrib/TestSuite.Async.cs @@ -83,6 +83,17 @@ public async Task TypeWithGenericParameterCanBeDeletedAsync() } } + [Fact] + public async Task GetAsyncSucceedsAfterDeleteAsyncWhenExplicitKeyPresent() + { + using (var connection = GetOpenConnection()) + { + await connection.DeleteAsync(new ObjectX { ObjectXId = Guid.NewGuid().ToString() }).ConfigureAwait(false); + var retrieved = await connection.GetAsync(Guid.NewGuid().ToString()).ConfigureAwait(false); + Assert.Null(retrieved); + } + } + /// /// Tests for issue #351 /// From 4dbf74f4d11bb21de0be933fadfa2175cb8c37e4 Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Sun, 22 Sep 2019 14:34:11 +0300 Subject: [PATCH 098/312] Replaced WriteLine with WriteLineColor --- Dapper.Tests.Performance/Program.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/Dapper.Tests.Performance/Program.cs b/Dapper.Tests.Performance/Program.cs index 79032b602..669c9febc 100644 --- a/Dapper.Tests.Performance/Program.cs +++ b/Dapper.Tests.Performance/Program.cs @@ -1,6 +1,4 @@ -using BenchmarkDotNet.Configs; -using BenchmarkDotNet.Order; -using BenchmarkDotNet.Running; +using BenchmarkDotNet.Running; using System; using System.Data.SqlClient; using System.Linq; @@ -28,7 +26,7 @@ public static void Main(string[] args) WriteColor(" (no args)", ConsoleColor.Blue); WriteLine(": run all benchmarks"); WriteColor(" --legacy", ConsoleColor.Blue); - WriteLine(": run the legacy benchmark suite/format", ConsoleColor.Gray); + WriteLineColor(": run the legacy benchmark suite/format", ConsoleColor.Gray); WriteLine(); } WriteLine("Using ConnectionString: " + BenchmarkBase.ConnectionString); From 77cd19d0e908a78b4b47319ab8ca1d5600dfd822 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9s?= Date: Sun, 22 Sep 2019 14:12:22 +0200 Subject: [PATCH 099/312] Fix performance tests (#1331) Hi! This PR tries to fix Dapper.Performance.Tests, actually you can't get [MemoryDiagnoser](https://benchmarkdotnet.org/api/BenchmarkDotNet.Diagnosers.MemoryDiagnoser.html) stats because ```IDiagnoser.GetColumnProvider()``` method was gone on BenchmarkDotnet 0.11.2. I choose ```DefaultColumnProviders.Metrics``` because of [this](https://github.com/dotnet/BenchmarkDotNet/issues/1184#issuecomment-507377695), i think it's the most simple way to add stats now. **Before** ![image](https://user-images.githubusercontent.com/34173061/65264060-1a5f7780-db0e-11e9-9c8c-bb4c69a808df.png) **After** ![image](https://user-images.githubusercontent.com/34173061/65264158-4f6bca00-db0e-11e9-9313-597b09070ae6.png) On the other hand i choose removing ```[Ordered]``` as temporal solution of https://github.com/dotnet/BenchmarkDotNet/issues/1238, and yeah, the results are still ordered because of https://github.com/StackExchange/Dapper/blob/9282ef2872e1c82c68c86f31e93e8f1ceaacd409/Dapper.Tests.Performance/Config.cs#L42 **Before** ![image](https://user-images.githubusercontent.com/34173061/65264660-6232ce80-db0f-11e9-9046-9c2d8fc5e2d8.png) **After** (No errors) Thanks for all!! --- Dapper.Tests.Performance/Benchmarks.cs | 1 - Dapper.Tests.Performance/Config.cs | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.cs b/Dapper.Tests.Performance/Benchmarks.cs index b3575ca2f..4bab0411a 100644 --- a/Dapper.Tests.Performance/Benchmarks.cs +++ b/Dapper.Tests.Performance/Benchmarks.cs @@ -6,7 +6,6 @@ namespace Dapper.Tests.Performance { - [Orderer(SummaryOrderPolicy.FastestToSlowest)] [BenchmarkCategory("ORM")] public abstract class BenchmarkBase { diff --git a/Dapper.Tests.Performance/Config.cs b/Dapper.Tests.Performance/Config.cs index b8e7798aa..08e419e73 100644 --- a/Dapper.Tests.Performance/Config.cs +++ b/Dapper.Tests.Performance/Config.cs @@ -31,7 +31,7 @@ public Config() //Add(StatisticColumn.StdDev); //Add(StatisticColumn.Error); Add(BaselineRatioColumn.RatioMean); - //Add(md.GetColumnProvider()); + Add(DefaultColumnProviders.Metrics); Add(Job.ShortRun .WithLaunchCount(1) From 3b0cd9a9a1eef5a94dc00ac2f2f522ea1cb6226b Mon Sep 17 00:00:00 2001 From: Jesper Meyer Date: Sun, 22 Sep 2019 14:40:07 +0200 Subject: [PATCH 100/312] Updated Hand Coded benchmarks (#1206) Hand coded version did not operate under the same circumstances as others. - Add CommandBehavior.SingleResult and CommandBehavior.SingleRow. All other framworks does this under the hood. - Run SqlCommand.Prepare() (perhaps it should be benchmarked as "Compiled"?) - Use GetNullableValue because it's easier to read and slightly faster - _table.Rows was incrementally added, which made the benchmark run slower the longer it executed. Added _table.Rows.Clear() to prevent the growth. - Changed select to use * like other benchmarks --- .../Benchmarks.HandCoded.cs | 34 ++++++++----------- 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs index 86ddb4d05..9b72e9747 100644 --- a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs +++ b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs @@ -19,13 +19,9 @@ public class HandCodedBenchmarks : BenchmarkBase public void Setup() { BaseSetup(); - _postCommand = new SqlCommand() - { - Connection = _connection, - CommandText = @"select Id, [Text], [CreationDate], LastChangeDate, - Counter1,Counter2,Counter3,Counter4,Counter5,Counter6,Counter7,Counter8,Counter9 from Posts where Id = @Id" - }; + _postCommand = new SqlCommand("select * from Posts where Id = @Id", _connection); _idParam = _postCommand.Parameters.Add("@Id", SqlDbType.Int); + _postCommand.Prepare(); #if !NETCOREAPP1_0 _table = new DataTable { @@ -55,7 +51,7 @@ public Post SqlCommand() Step(); _idParam.Value = i; - using (var reader = _postCommand.ExecuteReader()) + using (var reader = _postCommand.ExecuteReader(CommandBehavior.SingleResult | CommandBehavior.SingleRow)) { reader.Read(); return new Post @@ -65,15 +61,15 @@ public Post SqlCommand() CreationDate = reader.GetDateTime(2), LastChangeDate = reader.GetDateTime(3), - Counter1 = reader.IsDBNull(4) ? null : (int?)reader.GetInt32(4), - Counter2 = reader.IsDBNull(5) ? null : (int?)reader.GetInt32(5), - Counter3 = reader.IsDBNull(6) ? null : (int?)reader.GetInt32(6), - Counter4 = reader.IsDBNull(7) ? null : (int?)reader.GetInt32(7), - Counter5 = reader.IsDBNull(8) ? null : (int?)reader.GetInt32(8), - Counter6 = reader.IsDBNull(9) ? null : (int?)reader.GetInt32(9), - Counter7 = reader.IsDBNull(10) ? null : (int?)reader.GetInt32(10), - Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11), - Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12) + Counter1 = reader.GetNullableValue(4), + Counter2 = reader.GetNullableValue(5), + Counter3 = reader.GetNullableValue(6), + Counter4 = reader.GetNullableValue(7), + Counter5 = reader.GetNullableValue(8), + Counter6 = reader.GetNullableValue(9), + Counter7 = reader.GetNullableValue(10), + Counter8 = reader.GetNullableValue(11), + Counter9 = reader.GetNullableValue(12) }; } } @@ -83,13 +79,13 @@ public dynamic DataTableDynamic() { Step(); _idParam.Value = i; + _table.Rows.Clear(); var values = new object[13]; - using (var reader = _postCommand.ExecuteReader()) + using (var reader = _postCommand.ExecuteReader(CommandBehavior.SingleResult | CommandBehavior.SingleRow)) { reader.Read(); reader.GetValues(values); - _table.Rows.Add(values); - return _table.Rows[_table.Rows.Count - 1]; + return _table.Rows.Add(values); } } } From 7cac38246adddf64c23072ffa06e7c9662588d4e Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Sun, 22 Sep 2019 13:33:59 +0000 Subject: [PATCH 101/312] =?UTF-8?q?Replaced=20Connection=20with=20GetClose?= =?UTF-8?q?dConnection=20in=20some=20unit=20tests=E2=80=A6=20(#1337)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit …cording to their names. --- Dapper.Tests/AsyncTests.cs | 50 ++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 18 deletions(-) diff --git a/Dapper.Tests/AsyncTests.cs b/Dapper.Tests/AsyncTests.cs index 5b64cd419..9e556edeb 100644 --- a/Dapper.Tests/AsyncTests.cs +++ b/Dapper.Tests/AsyncTests.cs @@ -18,12 +18,14 @@ public sealed class MicrosoftSqlClientAsyncTests : AsyncTests { + public sealed class SystemSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests + { public SystemSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) { } } #if MSSQLCLIENT [Collection(NonParallelDefinition.Name)] - public sealed class MicrosoftSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests { + public sealed class MicrosoftSqlClientAsyncQueryCacheTests : AsyncQueryCacheTests + { public MicrosoftSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) { } } #endif @@ -32,7 +34,7 @@ public MicrosoftSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) public abstract class AsyncTests : TestBase where TProvider : SqlServerDatabaseProvider { private DbConnection _marsConnection; - + private DbConnection MarsConnection => _marsConnection ?? (_marsConnection = Provider.GetOpenConnection(true)); [Fact] @@ -128,9 +130,12 @@ public void TestLongOperationWithCancellation() [Fact] public async Task TestBasicStringUsageClosedAsync() { - var query = await connection.QueryAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); - var arr = query.ToArray(); - Assert.Equal(new[] { "abc", "def" }, arr); + using (var conn = GetClosedConnection()) + { + var query = await conn.QueryAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); + var arr = query.ToArray(); + Assert.Equal(new[] { "abc", "def" }, arr); + } } [Fact] @@ -159,9 +164,12 @@ public async Task TestExecuteAsync() [Fact] public void TestExecuteClosedConnAsyncInner() { - var query = connection.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }); - var val = query.Result; - Assert.Equal(1, val); + using (var conn = GetClosedConnection()) + { + var query = conn.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }); + var val = query.Result; + Assert.Equal(1, val); + } } [Fact] @@ -248,23 +256,29 @@ public async Task TestMultiAsyncViaFirstOrDefault() [Fact] public async Task TestMultiClosedConnAsync() { - using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false)) + using (var conn = GetClosedConnection()) { - Assert.Equal(1, multi.ReadAsync().Result.Single()); - Assert.Equal(2, multi.ReadAsync().Result.Single()); + using (SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false)) + { + Assert.Equal(1, multi.ReadAsync().Result.Single()); + Assert.Equal(2, multi.ReadAsync().Result.Single()); + } } } [Fact] public async Task TestMultiClosedConnAsyncViaFirstOrDefault() { - using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5;").ConfigureAwait(false)) + using (var conn = GetClosedConnection()) { - Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); - Assert.Equal(2, multi.ReadAsync().Result.Single()); - Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); - Assert.Equal(4, multi.ReadAsync().Result.Single()); - Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); + using (SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5").ConfigureAwait(false)) + { + Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); + Assert.Equal(2, multi.ReadAsync().Result.Single()); + Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); + Assert.Equal(4, multi.ReadAsync().Result.Single()); + Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); + } } } From 1481c4d37974e14009767759fb5426f2bbda2503 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 26 Sep 2019 19:47:55 -0400 Subject: [PATCH 102/312] Tooling and project updates (#1333) This is a branch cleaning up old `#if` clauses, `.csproj` bits, Linux build compatibility, and a few other tid-bits (broken out by commit). In general: we're on 3.0 tooling and all previous cruft has been removed. --- Dapper.Contrib/Dapper.Contrib.csproj | 5 +- Dapper.Contrib/SqlMapperExtensions.cs | 20 +- .../Dapper.ProviderTools.csproj | 9 +- Dapper.Rainbow/Dapper.Rainbow.csproj | 4 - Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 1 - .../Dapper.Tests.Contrib.csproj | 6 +- .../Benchmarks.Dashing.cs | 9 +- .../Benchmarks.EntityFramework.cs | 4 +- .../Benchmarks.HandCoded.cs | 6 +- .../Benchmarks.Linq2Sql.cs | 4 +- .../Benchmarks.Susanoo.cs | 4 +- Dapper.Tests.Performance/Config.cs | 6 +- .../Dapper.Tests.Performance.csproj | 103 +++++----- .../EntityFramework/EFContext.cs | 4 +- Dapper.Tests.Performance/LegacyTests.cs | 178 +++++++++--------- .../Linq2Sql/DataClasses.designer.cs | 2 +- Dapper.Tests.Performance/Massive/Massive.cs | 6 +- .../NHibernate/NHibernateHelper.cs | 8 +- Dapper.Tests.Performance/PetaPoco/PetaPoco.cs | 6 +- Dapper.Tests.Performance/Post.cs | 18 ++ Dapper.Tests.Performance/Soma/SomaConfig.cs | 4 +- .../SqlDataReaderHelper.cs | 3 + Dapper.Tests/AsyncTests.cs | 2 - Dapper.Tests/Dapper.Tests.csproj | 52 +---- Dapper.Tests/Helpers/XunitSkippable.cs | 3 + Dapper.Tests/MiscTests.cs | 33 +--- Dapper.Tests/ParameterTests.cs | 2 - Dapper.Tests/ProcedureTests.cs | 2 - Dapper.Tests/ProviderTests.cs | 19 +- Dapper.Tests/TestBase.cs | 15 +- Dapper.Tests/TypeHandlerTests.cs | 5 - Dapper.sln | 4 +- Dapper/Dapper.csproj | 4 +- Dapper/WrappedReader.cs | 12 -- ...ctory.build.props => Directory.Build.props | 13 +- appveyor.yml | 2 +- global.json | 2 +- semver.txt | 1 - 38 files changed, 243 insertions(+), 338 deletions(-) rename Directory.build.props => Directory.Build.props (69%) delete mode 100644 semver.txt diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj index 1d3532ddb..d357df12e 100644 --- a/Dapper.Contrib/Dapper.Contrib.csproj +++ b/Dapper.Contrib/Dapper.Contrib.csproj @@ -1,16 +1,13 @@  Dapper.Contrib - orm;sql;micro-orm;dapper Dapper.Contrib + orm;sql;micro-orm;dapper The official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities. Sam Saffron;Johan Danforth net461;netstandard2.0 false - - - diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index a41cfeaa1..078f0c3ad 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -6,15 +6,10 @@ using System.Text; using System.Collections.Concurrent; using System.Reflection.Emit; +using System.Threading; using Dapper; -#if NETSTANDARD1_3 -using DataException = System.InvalidOperationException; -#else -using System.Threading; -#endif - namespace Dapper.Contrib.Extensions { /// @@ -290,15 +285,10 @@ private static string GetTableName(Type type) } else { -#if NETSTANDARD1_3 - var info = type.GetTypeInfo(); -#else - var info = type; -#endif //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework var tableAttrName = - info.GetCustomAttribute(false)?.Name - ?? (info.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name; + type.GetCustomAttribute(false)?.Name + ?? (type.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name; if (tableAttrName != null) { @@ -562,7 +552,7 @@ private static class ProxyGenerator private static AssemblyBuilder GetAsmBuilder(string name) { -#if NETSTANDARD1_3 || NETSTANDARD2_0 +#if NETSTANDARD2_0 return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #else return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); @@ -597,7 +587,7 @@ public static T GetInterfaceProxy() CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId); } -#if NETSTANDARD1_3 || NETSTANDARD2_0 +#if NETSTANDARD2_0 var generatedType = typeBuilder.CreateTypeInfo().AsType(); #else var generatedType = typeBuilder.CreateType(); diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index 002b5266b..518f99839 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -1,8 +1,5 @@  - - netstandard2.0 - Dapper.ProviderTools orm;sql;micro-orm Dapper Provider Tools @@ -13,8 +10,10 @@ enable 8.0 - + - + + + diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj index 208415287..0968f0fed 100644 --- a/Dapper.Rainbow/Dapper.Rainbow.csproj +++ b/Dapper.Rainbow/Dapper.Rainbow.csproj @@ -10,13 +10,9 @@ false - - - - diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index ea474c6d5..5e924cfea 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -12,7 +12,6 @@ - diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 0b4571d48..d681ce4e2 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -1,12 +1,10 @@  Dapper.Tests.Contrib - Dapper.Tests.Contrib Dapper Contrib Test Suite - portable - Exe false netcoreapp2.0 + false @@ -19,12 +17,12 @@ + all runtime; build; native; contentfiles; analyzers; buildtransitive - diff --git a/Dapper.Tests.Performance/Benchmarks.Dashing.cs b/Dapper.Tests.Performance/Benchmarks.Dashing.cs index 15913377d..ca89b6715 100644 --- a/Dapper.Tests.Performance/Benchmarks.Dashing.cs +++ b/Dapper.Tests.Performance/Benchmarks.Dashing.cs @@ -1,4 +1,5 @@ -using System.ComponentModel; +#if NET4X +using System.ComponentModel; using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.Dashing; using Dashing; @@ -19,7 +20,10 @@ public void Setup() Session = database.BeginTransactionLessSession(_connection); } - [Benchmark(Description = "Get")] + // This needs love to be compatible with current SDKs (weaving doesn't work and shouldn't be used here anyway (competition). + // I'll file an issue with Dashing to see if someone can help me out here since I can't figure out from the docs how to + // make it work correctly. + //[Benchmark(Description = "Get")] public Dashing.Post Get() { Step(); @@ -27,3 +31,4 @@ public Dashing.Post Get() } } } +#endif diff --git a/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs b/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs index 6660782d3..3d44d0021 100644 --- a/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs +++ b/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs @@ -1,4 +1,5 @@ -using BenchmarkDotNet.Attributes; +#if NET4X +using BenchmarkDotNet.Attributes; using System.ComponentModel; using System.Linq; @@ -38,3 +39,4 @@ public Post NoTracking() } } } +#endif diff --git a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs index 9b72e9747..05013e465 100644 --- a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs +++ b/Dapper.Tests.Performance/Benchmarks.HandCoded.cs @@ -11,18 +11,15 @@ public class HandCodedBenchmarks : BenchmarkBase { private SqlCommand _postCommand; private SqlParameter _idParam; -#if !NETCOREAPP1_0 private DataTable _table; -#endif [GlobalSetup] public void Setup() { BaseSetup(); - _postCommand = new SqlCommand("select * from Posts where Id = @Id", _connection); + _postCommand = new SqlCommand("select Top 1 * from Posts where Id = @Id", _connection); _idParam = _postCommand.Parameters.Add("@Id", SqlDbType.Int); _postCommand.Prepare(); -#if !NETCOREAPP1_0 _table = new DataTable { Columns = @@ -42,7 +39,6 @@ public void Setup() {"Counter9", typeof (int)}, } }; -#endif } [Benchmark(Description = "SqlCommand")] diff --git a/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs b/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs index 9880dfe59..dcee70577 100644 --- a/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs +++ b/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs @@ -1,4 +1,5 @@ -using BenchmarkDotNet.Attributes; +#if NET4X +using BenchmarkDotNet.Attributes; using Dapper.Tests.Performance.Linq2Sql; using System; using System.ComponentModel; @@ -44,3 +45,4 @@ public Post ExecuteQuery() } } } +#endif diff --git a/Dapper.Tests.Performance/Benchmarks.Susanoo.cs b/Dapper.Tests.Performance/Benchmarks.Susanoo.cs index 421d66e2c..81ebab56d 100644 --- a/Dapper.Tests.Performance/Benchmarks.Susanoo.cs +++ b/Dapper.Tests.Performance/Benchmarks.Susanoo.cs @@ -1,4 +1,5 @@ -using BenchmarkDotNet.Attributes; +#if NET4X +using BenchmarkDotNet.Attributes; using Susanoo; using Susanoo.Processing; using System.ComponentModel; @@ -64,3 +65,4 @@ public dynamic MappingStaticDynamic() } } } +#endif diff --git a/Dapper.Tests.Performance/Config.cs b/Dapper.Tests.Performance/Config.cs index 08e419e73..981b2c926 100644 --- a/Dapper.Tests.Performance/Config.cs +++ b/Dapper.Tests.Performance/Config.cs @@ -28,8 +28,8 @@ public Config() Add(TargetMethodColumn.Method); Add(new ReturnColum()); Add(StatisticColumn.Mean); - //Add(StatisticColumn.StdDev); - //Add(StatisticColumn.Error); + Add(StatisticColumn.StdDev); + Add(StatisticColumn.Error); Add(BaselineRatioColumn.RatioMean); Add(DefaultColumnProviders.Metrics); @@ -37,7 +37,7 @@ public Config() .WithLaunchCount(1) .WithWarmupCount(2) .WithUnrollFactor(Iterations) - .WithIterationCount(1) + .WithIterationCount(10) ); Orderer = new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest); Options |= ConfigOptions.JoinSummary; diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 7f4193502..5d8976546 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -1,55 +1,48 @@ - - - - Dapper.Tests.Performance - Dapper.Tests.Performance - Dapper Core Performance Suite - Exe - false - net462 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -p "$(MSBuildThisFileDirectory)$(OutputPath)$(AssemblyName).exe" -t "Dapper.Tests.Performance.Dashing.DashingConfiguration" - - + + + Dapper.Tests.Performance + Dapper.Tests.Performance + Dapper Core Performance Suite + Exe + false + net462;netcoreapp3.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + $(DefineConstants);NET4X + + + + + + + + + + + + + diff --git a/Dapper.Tests.Performance/EntityFramework/EFContext.cs b/Dapper.Tests.Performance/EntityFramework/EFContext.cs index a1c4aa8b0..5dd7053a2 100644 --- a/Dapper.Tests.Performance/EntityFramework/EFContext.cs +++ b/Dapper.Tests.Performance/EntityFramework/EFContext.cs @@ -1,4 +1,5 @@ -using System.Data.Common; +#if NET4X +using System.Data.Common; using System.Data.Entity; namespace Dapper.Tests.Performance.EntityFramework @@ -12,3 +13,4 @@ public EFContext(DbConnection connection, bool owned = false) : base(connection, public DbSet Posts { get; set; } } } +#endif diff --git a/Dapper.Tests.Performance/LegacyTests.cs b/Dapper.Tests.Performance/LegacyTests.cs index ec15274c7..60c18c90f 100644 --- a/Dapper.Tests.Performance/LegacyTests.cs +++ b/Dapper.Tests.Performance/LegacyTests.cs @@ -1,32 +1,33 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.Linq; using System.Data.SqlClient; using System.Diagnostics; using System.Linq; -//using BLToolkit.Data; // Note: this doesn't load in the new .csproj system...likely a bug -using Dapper.Tests.Performance.EntityFramework; -using Dapper.Tests.Performance.Linq2Sql; -using Dapper.Tests.Performance.NHibernate; +using Belgrade.SqlClient; using Dapper.Contrib.Extensions; +using Dapper.Tests.Performance.Dashing; +using Dapper.Tests.Performance.EntityFrameworkCore; +using Dapper.Tests.Performance.NHibernate; +using Dashing; +using DevExpress.Xpo; +using DevExpress.Data.Filtering; using Massive; +using Microsoft.EntityFrameworkCore; using NHibernate.Criterion; -using NHibernate.Linq; using ServiceStack.OrmLite; using ServiceStack.OrmLite.Dapper; -using Susanoo; using System.Configuration; using System.Threading.Tasks; -using Dapper.Tests.Performance.Dashing; -using Dapper.Tests.Performance.EntityFrameworkCore; -using Dashing; -using Microsoft.EntityFrameworkCore; -using Belgrade.SqlClient; -using DevExpress.Xpo; +#if NET4X +using System.Data.Linq; +using Dapper.Tests.Performance.EntityFramework; +using Dapper.Tests.Performance.Linq2Sql; using Dapper.Tests.Performance.Xpo; -using DevExpress.Data.Filtering; +using NHibernate.Linq; +using Susanoo; +#endif namespace Dapper.Tests.Performance { @@ -110,8 +111,10 @@ public static SqlConnection GetOpenConnection() return connection; } +#if NET4X private static DataClassesDataContext GetL2SContext(SqlConnection connection) => new DataClassesDataContext(connection); +#endif private static void Try(Action action, string blame) { @@ -133,33 +136,6 @@ public async Task RunAsync(int iterations) #pragma warning disable RCS1121 // Use [] instead of calling 'First'. var tests = new Tests(); - // Linq2SQL - Try(() => - { - var l2scontext1 = GetL2SContext(connection); - tests.Add(id => l2scontext1.Posts.First(p => p.Id == id), "Linq2Sql: Normal"); - - var l2scontext2 = GetL2SContext(connection); - var compiledGetPost = CompiledQuery.Compile((Linq2Sql.DataClassesDataContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); - tests.Add(id => compiledGetPost(l2scontext2, id), "Linq2Sql: Compiled"); - - var l2scontext3 = GetL2SContext(connection); - tests.Add(id => l2scontext3.ExecuteQuery("select * from Posts where Id = {0}", id).First(), "Linq2Sql: ExecuteQuery"); - }, "LINQ-to-SQL"); - - // Entity Framework - Try(() => - { - var entityContext = new EFContext(connection); - tests.Add(id => entityContext.Posts.First(p => p.Id == id), "Entity Framework"); - - var entityContext2 = new EFContext(connection); - tests.Add(id => entityContext2.Database.SqlQuery("select * from Posts where Id = {0}", id).First(), "Entity Framework: SqlQuery"); - - var entityContext3 = new EFContext(connection); - tests.Add(id => entityContext3.Posts.AsNoTracking().First(p => p.Id == id), "Entity Framework: No Tracking"); - }, "Entity Framework"); - // Entity Framework Core Try(() => { @@ -169,7 +145,7 @@ public async Task RunAsync(int iterations) var entityContext2 = new EFCoreContext(ConnectionString); tests.Add(id => entityContext2.Posts.FromSql("select * from Posts where Id = {0}", id).First(), "Entity Framework Core: FromSql"); - var entityContext3 = new EFContext(connection); + var entityContext3 = new EFCoreContext(ConnectionString); tests.Add(id => entityContext3.Posts.AsNoTracking().First(p => p.Id == id), "Entity Framework Core: No Tracking"); }, "Entity Framework Core"); @@ -191,15 +167,6 @@ public async Task RunAsync(int iterations) tests.Add(id => mapperConnection3.Get(id), "Dapper.Contrib"); }, "Dapper"); - // Dashing - Try(() => - { - var config = new DashingConfiguration(); - var database = new SqlDatabase(config, ConnectionString); - var session = database.BeginTransactionLessSession(GetOpenConnection()); - tests.Add(id => session.Get(id), "Dashing Get"); - }, "Dashing"); - // Massive Try(() => { @@ -277,40 +244,6 @@ public async Task RunAsync(int iterations) }), "Belgrade Sql Client"); }, "Belgrade Sql Client"); - //Susanoo - Try(() => - { - var susanooDb = new DatabaseManager(connection); - - var susanooPreDefinedCommand = - CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) - .DefineResults() - .Realize(); - - var susanooDynamicPreDefinedCommand = - CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) - .DefineResults() - .Realize(); - - tests.Add(Id => - CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) - .DefineResults() - .Realize() - .Execute(susanooDb, new { Id }).First(), "Susanoo: Mapping Cache Retrieval"); - - tests.Add(Id => - CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) - .DefineResults() - .Realize() - .Execute(susanooDb, new { Id }).First(), "Susanoo: Dynamic Mapping Cache Retrieval"); - - tests.Add(Id => susanooDynamicPreDefinedCommand - .Execute(susanooDb, new { Id }).First(), "Susanoo: Dynamic Mapping Static"); - - tests.Add(Id => susanooPreDefinedCommand - .Execute(susanooDb, new { Id }).First(), "Susanoo: Mapping Static"); - }, "Susanoo"); - //ServiceStack's OrmLite: Try(() => { @@ -355,7 +288,6 @@ public async Task RunAsync(int iterations) } }, "Hand Coded"); -#if !NETSTANDARD1_3 var table = new DataTable { Columns = @@ -386,7 +318,6 @@ public async Task RunAsync(int iterations) table.Rows.Add(values); } }, "DataTable via IDataReader.GetValues"); -#endif }, "Hand Coded"); // DevExpress.XPO @@ -410,9 +341,80 @@ public async Task RunAsync(int iterations) }; session.FindObject(findCriteria); }, "DevExpress.XPO: FindObject"); - }, "DevExpress.XPO"); +#if NET4X + // Entity Framework + Try(() => + { + var entityContext = new EFContext(connection); + tests.Add(id => entityContext.Posts.First(p => p.Id == id), "Entity Framework"); + + var entityContext2 = new EFContext(connection); + tests.Add(id => entityContext2.Database.SqlQuery("select * from Posts where Id = {0}", id).First(), "Entity Framework: SqlQuery"); + + var entityContext3 = new EFContext(connection); + tests.Add(id => entityContext3.Posts.AsNoTracking().First(p => p.Id == id), "Entity Framework: No Tracking"); + }, "Entity Framework"); + + // Linq2SQL + Try(() => + { + var l2scontext1 = GetL2SContext(connection); + tests.Add(id => l2scontext1.Posts.First(p => p.Id == id), "Linq2Sql: Normal"); + + var l2scontext2 = GetL2SContext(connection); + var compiledGetPost = CompiledQuery.Compile((Linq2Sql.DataClassesDataContext ctx, int id) => ctx.Posts.First(p => p.Id == id)); + tests.Add(id => compiledGetPost(l2scontext2, id), "Linq2Sql: Compiled"); + + var l2scontext3 = GetL2SContext(connection); + tests.Add(id => l2scontext3.ExecuteQuery("select * from Posts where Id = {0}", id).First(), "Linq2Sql: ExecuteQuery"); + }, "LINQ-to-SQL"); + + // Dashing + Try(() => + { + var config = new DashingConfiguration(); + var database = new SqlDatabase(config, ConnectionString); + var session = database.BeginTransactionLessSession(GetOpenConnection()); + tests.Add(id => session.Get(id), "Dashing Get"); + }, "Dashing"); + + //Susanoo + Try(() => + { + var susanooDb = new DatabaseManager(connection); + + var susanooPreDefinedCommand = + CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) + .DefineResults() + .Realize(); + + var susanooDynamicPreDefinedCommand = + CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) + .DefineResults() + .Realize(); + + tests.Add(Id => + CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) + .DefineResults() + .Realize() + .Execute(susanooDb, new { Id }).First(), "Susanoo: Mapping Cache Retrieval"); + + tests.Add(Id => + CommandManager.Instance.DefineCommand("SELECT * FROM Posts WHERE Id = @Id", CommandType.Text) + .DefineResults() + .Realize() + .Execute(susanooDb, new { Id }).First(), "Susanoo: Dynamic Mapping Cache Retrieval"); + + tests.Add(Id => susanooDynamicPreDefinedCommand + .Execute(susanooDb, new { Id }).First(), "Susanoo: Dynamic Mapping Static"); + + tests.Add(Id => susanooPreDefinedCommand + .Execute(susanooDb, new { Id }).First(), "Susanoo: Mapping Static"); + }, "Susanoo"); +#endif + // Subsonic isn't maintained anymore - doesn't import correctly //Try(() => // { diff --git a/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs b/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs index c8694e839..9067e3155 100644 --- a/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs +++ b/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs @@ -1,4 +1,4 @@ -#if !NETSTANDARD1_3 +#if NET4X #pragma warning disable 1591 //------------------------------------------------------------------------------ // diff --git a/Dapper.Tests.Performance/Massive/Massive.cs b/Dapper.Tests.Performance/Massive/Massive.cs index e564fe5d7..27fa4d727 100644 --- a/Dapper.Tests.Performance/Massive/Massive.cs +++ b/Dapper.Tests.Performance/Massive/Massive.cs @@ -1,5 +1,4 @@ -#if !NETSTANDARD1_3 -using System; +using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Data; @@ -453,5 +452,4 @@ public virtual dynamic Single(object key, string columns = "*") } } } -#pragma warning restore RCS1141 // Add parameter to documentation comment. -#endif +#pragma warning restore RCS1141 // Add parameter to documentation comment. \ No newline at end of file diff --git a/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs b/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs index 9c349211f..7da6eb12d 100644 --- a/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs +++ b/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs @@ -1,4 +1,5 @@ -using NHibernate; +using System.Reflection; +using NHibernate; using NHibernate.Cfg; namespace Dapper.Tests.Performance.NHibernate @@ -14,9 +15,8 @@ private static ISessionFactory SessionFactory if (_sessionFactory == null) { var configuration = new Configuration(); - configuration.Configure(@".\NHibernate\hibernate.cfg.xml"); + configuration.Configure(Assembly.GetExecutingAssembly(), "Dapper.Tests.Performance.NHibernate.hibernate.cfg.xml"); configuration.AddAssembly(typeof(Post).Assembly); - configuration.AddXmlFile(@".\NHibernate\Post.hbm.xml"); _sessionFactory = configuration.BuildSessionFactory(); } @@ -29,4 +29,4 @@ public static IStatelessSession OpenSession() return SessionFactory.OpenStatelessSession(); } } -} \ No newline at end of file +} diff --git a/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs b/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs index 9f13ea3f7..40690fd95 100644 --- a/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs +++ b/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs @@ -1,5 +1,4 @@ -#if !NETSTANDARD1_3 -using System; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -1442,5 +1441,4 @@ public void Build(StringBuilder sb, List args, Sql lhs) } } } -#pragma warning restore RCS1023 // Format empty block. -#endif +#pragma warning restore RCS1023 // Format empty block. \ No newline at end of file diff --git a/Dapper.Tests.Performance/Post.cs b/Dapper.Tests.Performance/Post.cs index c3fd3b3a7..245df5224 100644 --- a/Dapper.Tests.Performance/Post.cs +++ b/Dapper.Tests.Performance/Post.cs @@ -1,27 +1,45 @@ using System; +#if NET4X using Soma.Core; +#endif namespace Dapper.Tests.Performance { [ServiceStack.DataAnnotations.Alias("Posts")] +#if NET4X [Table(Name = "Posts")] +#endif [LinqToDB.Mapping.Table(Name = "Posts")] public class Post { +#if NET4X [Id(IdKind.Identity)] +#endif [LinqToDB.Mapping.PrimaryKey, LinqToDB.Mapping.Identity] public int Id { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public string Text { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.NotNull] public DateTime CreationDate { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.NotNull] public DateTime LastChangeDate { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter1 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter2 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter3 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter4 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter5 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter6 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter7 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter8 { get; set; } + [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] public int? Counter9 { get; set; } } } diff --git a/Dapper.Tests.Performance/Soma/SomaConfig.cs b/Dapper.Tests.Performance/Soma/SomaConfig.cs index aa2dbcdd6..6f81e0577 100644 --- a/Dapper.Tests.Performance/Soma/SomaConfig.cs +++ b/Dapper.Tests.Performance/Soma/SomaConfig.cs @@ -1,4 +1,5 @@ -using Soma.Core; +#if NET4X +using Soma.Core; using System; namespace Dapper.Tests.Performance.Soma @@ -12,3 +13,4 @@ internal class SomaConfig : MsSqlConfig private static readonly Action noOp = x => { /* nope */ }; } } +#endif diff --git a/Dapper.Tests.Performance/SqlDataReaderHelper.cs b/Dapper.Tests.Performance/SqlDataReaderHelper.cs index a71375826..d6d286e96 100644 --- a/Dapper.Tests.Performance/SqlDataReaderHelper.cs +++ b/Dapper.Tests.Performance/SqlDataReaderHelper.cs @@ -1,10 +1,12 @@ using System; using System.Data.SqlClient; +using System.Runtime.CompilerServices; namespace Dapper.Tests.Performance { public static class SqlDataReaderHelper { + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static string GetNullableString(this SqlDataReader reader, int index) { object tmp = reader.GetValue(index); @@ -15,6 +17,7 @@ public static string GetNullableString(this SqlDataReader reader, int index) return null; } + [MethodImpl(MethodImplOptions.AggressiveInlining)] public static T? GetNullableValue(this SqlDataReader reader, int index) where T : struct { object tmp = reader.GetValue(index); diff --git a/Dapper.Tests/AsyncTests.cs b/Dapper.Tests/AsyncTests.cs index 9e556edeb..bc8cef105 100644 --- a/Dapper.Tests/AsyncTests.cs +++ b/Dapper.Tests/AsyncTests.cs @@ -282,7 +282,6 @@ public async Task TestMultiClosedConnAsyncViaFirstOrDefault() } } -#if !NETCOREAPP1_0 [Fact] public async Task ExecuteReaderOpenAsync() { @@ -311,7 +310,6 @@ public async Task ExecuteReaderClosedAsync() Assert.Equal(4, (int)dt.Rows[0][1]); } } -#endif [Fact] public async Task LiteralReplacementOpen() diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 5a0164992..83dbf0b54 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -1,51 +1,28 @@  Dapper.Tests - Dapper.Tests Dapper Core Test Suite false - true - true netcoreapp2.1;net462;net472 false + $(DefineConstants);MSSQLCLIENT $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB - - - - - $(DefineConstants);MSSQLCLIENT - - - - - - - - - - %(Filename)%(Extension) - PreserveNewest - - - - - %(Filename)%(Extension) - PreserveNewest - - - + + + + @@ -56,22 +33,11 @@ - - - - - - - - - - - - - - - + + + + diff --git a/Dapper.Tests/Helpers/XunitSkippable.cs b/Dapper.Tests/Helpers/XunitSkippable.cs index 260ba0251..8befa9e12 100644 --- a/Dapper.Tests/Helpers/XunitSkippable.cs +++ b/Dapper.Tests/Helpers/XunitSkippable.cs @@ -20,12 +20,15 @@ public static void If(object obj, string reason = null) if (obj is T) Skip.Inconclusive(reason ?? $"not valid for {typeof(T).FullName}"); } } + +#pragma warning disable RCS1194 // Implement exception constructors. public class SkipTestException : Exception { public SkipTestException(string reason) : base(reason) { } } +#pragma warning restore RCS1194 // Implement exception constructors. public class FactDiscoverer : Xunit.Sdk.FactDiscoverer { diff --git a/Dapper.Tests/MiscTests.cs b/Dapper.Tests/MiscTests.cs index 54c28f60c..857dcf9a2 100644 --- a/Dapper.Tests/MiscTests.cs +++ b/Dapper.Tests/MiscTests.cs @@ -4,38 +4,11 @@ using System.Data; using System.Data.Common; using System.Diagnostics; -using System.Linq; -using Xunit; - -#if NETCOREAPP1_0 -using System.Collections; -using System.Dynamic; -using System.Data.SqlTypes; -#else // net452 using System.IO; +using System.Linq; using System.Threading; using System.Threading.Tasks; -#endif - -#if NETCOREAPP1_0 -namespace System -{ - public enum GenericUriParserOptions - { - Default - } - - public class GenericUriParser - { - private readonly GenericUriParserOptions options; - - public GenericUriParser(GenericUriParserOptions options) - { - this.options = options; - } - } -} -#endif +using Xunit; namespace Dapper.Tests { @@ -523,7 +496,6 @@ public void TestInheritance() Assert.Equal("Four", list.First().Base2); } -#if !NETCOREAPP1_0 [Fact] public void ExecuteReader() { @@ -536,7 +508,6 @@ public void ExecuteReader() Assert.Equal(3, (int)dt.Rows[0][0]); Assert.Equal(4, (int)dt.Rows[0][1]); } -#endif [Fact] public void TestDbString() diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index b075fcbef..1658d0633 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -522,7 +522,6 @@ public void TestSqlDataRecordListParametersWithTypeHandlers() } } -#if !NETCOREAPP1_0 [Fact] public void DataTableParameters() { @@ -698,7 +697,6 @@ public SO29596645_OrganisationDTO() Rules = new SO29596645_RuleTableValuedParameters("@Rules"); } } -#endif #if ENTITY_FRAMEWORK private class HazGeo diff --git a/Dapper.Tests/ProcedureTests.cs b/Dapper.Tests/ProcedureTests.cs index 35fa9b2a7..2e320779a 100644 --- a/Dapper.Tests/ProcedureTests.cs +++ b/Dapper.Tests/ProcedureTests.cs @@ -112,9 +112,7 @@ @TaxInvoiceNumber nvarchar(20) private class PracticeRebateOrders { public string fTaxInvoiceNumber; -#if !NETCOREAPP1_0 [System.Xml.Serialization.XmlElement(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] -#endif public string TaxInvoiceNumber { get { return fTaxInvoiceNumber; } diff --git a/Dapper.Tests/ProviderTests.cs b/Dapper.Tests/ProviderTests.cs index b51ed2d2e..cd67b75dc 100644 --- a/Dapper.Tests/ProviderTests.cs +++ b/Dapper.Tests/ProviderTests.cs @@ -30,25 +30,26 @@ public void ClientId_SystemDataSqlClient() => TestClientId(); [Fact] - public void ClientId_MicrosoftDataSqlClient() - => TestClientId(); + public void ClearPool_SystemDataSqlClient() + => ClearPool(); + [Fact] + public void ClearAllPools_SystemDataSqlClient() + => ClearAllPools(); +#if MSSQLCLIENT [Fact] - public void ClearPool_SystemDataSqlClient() - => ClearPool(); + public void ClientId_MicrosoftDataSqlClient() + => TestClientId(); [Fact] public void ClearPool_MicrosoftDataSqlClient() => ClearPool(); - [Fact] - public void ClearAllPools_SystemDataSqlClient() - => ClearAllPools(); - [Fact] public void ClearAllPools_MicrosoftDataSqlClient() => ClearAllPools(); +#endif private static void TestClientId() where T : SqlServerDatabaseProvider, new() @@ -97,11 +98,13 @@ private static void Test(DbConnection connection) public void DbNumber_SystemData(int create, int test, bool result) => Test(create, test, result); +#if MSSQLCLIENT [Theory] [InlineData(51000, 51000, true)] [InlineData(51000, 43, false)] public void DbNumber_MicrosoftData(int create, int test, bool result) => Test(create, test, result); +#endif private void Test(int create, int test, bool result) where T : SqlServerDatabaseProvider, new() diff --git a/Dapper.Tests/TestBase.cs b/Dapper.Tests/TestBase.cs index 05f242319..c03dbc311 100644 --- a/Dapper.Tests/TestBase.cs +++ b/Dapper.Tests/TestBase.cs @@ -1,11 +1,9 @@ using System; using System.Data; -using System.Globalization; -using Xunit; using System.Data.Common; -#if !NETCOREAPP1_0 +using System.Globalization; using System.Threading; -#endif +using Xunit; namespace Dapper.Tests { @@ -93,13 +91,8 @@ protected void SkipIfMsDataClient() protected static CultureInfo ActiveCulture { -#if NETCOREAPP1_0 - get { return CultureInfo.CurrentCulture; } - set { CultureInfo.CurrentCulture = value; } -#else get { return Thread.CurrentThread.CurrentCulture; } set { Thread.CurrentThread.CurrentCulture = value; } -#endif } static TestBase() @@ -109,9 +102,6 @@ static TestBase() Console.WriteLine("Using Connectionstring: {0}", provider.GetConnectionString()); var factory = provider.Factory; Console.WriteLine("Using Provider: {0}", factory.GetType().FullName); -#if NETCOREAPP1_0 - Console.WriteLine("CoreCLR (netcoreapp1.0)"); -#else Console.WriteLine(".NET: " + Environment.Version); Console.Write("Loading native assemblies for SQL types..."); try @@ -124,7 +114,6 @@ static TestBase() Console.WriteLine("failed."); Console.Error.WriteLine(ex.Message); } -#endif } public virtual void Dispose() diff --git a/Dapper.Tests/TypeHandlerTests.cs b/Dapper.Tests/TypeHandlerTests.cs index b10b0b092..c690daaf9 100644 --- a/Dapper.Tests/TypeHandlerTests.cs +++ b/Dapper.Tests/TypeHandlerTests.cs @@ -82,13 +82,8 @@ public void TestCustomTypeMap() 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 diff --git a/Dapper.sln b/Dapper.sln index a7ad2a7b8..dd0dba3a6 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -8,13 +8,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig appveyor.yml = appveyor.yml build.ps1 = build.ps1 - Directory.build.props = Directory.build.props global.json = global.json docs\index.md = docs\index.md License.txt = License.txt nuget.config = nuget.config Readme.md = Readme.md - semver.txt = semver.txt version.json = version.json EndProjectSection EndProject @@ -36,7 +34,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Ra EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" ProjectSection(SolutionItems) = preProject - Directory.build.props = Directory.build.props + Directory.Build.props = Directory.Build.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index e62df4975..aca83c4de 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -1,8 +1,8 @@  - Dapper - orm;sql;micro-orm + Dapper Dapper + orm;sql;micro-orm A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0 diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index 1956948cc..b43ae1571 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -29,15 +29,10 @@ private async static Task ThrowDisposedAsync() await Task.Yield(); // will never hit this - already thrown and handled return result; } -#if !NETSTANDARD1_3 public override void Close() { } public override DataTable GetSchemaTable() => ThrowDisposed(); public override object InitializeLifetimeService() => ThrowDisposed(); -#endif protected override void Dispose(bool disposing) { } -#if NET451 - public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType) => ThrowDisposed(); -#endif public override bool GetBoolean(int ordinal) => ThrowDisposed(); public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => ThrowDisposed(); public override float GetFloat(int ordinal) => ThrowDisposed(); @@ -119,14 +114,9 @@ public DbWrappedReader(IDbCommand cmd, DbDataReader reader) public override bool HasRows => _reader.HasRows; -#if !NETSTANDARD1_3 public override void Close() => _reader.Close(); public override DataTable GetSchemaTable() => _reader.GetSchemaTable(); public override object InitializeLifetimeService() => _reader.InitializeLifetimeService(); -#endif -#if NET451 - public override System.Runtime.Remoting.ObjRef CreateObjRef(Type requestedType) => _reader.CreateObjRef(requestedType); -#endif public override int Depth => _reader.Depth; @@ -142,9 +132,7 @@ protected override void Dispose(bool disposing) { if (disposing) { -#if !NETSTANDARD1_3 _reader.Close(); -#endif _reader.Dispose(); _reader = DisposedReader.Instance; // all future ops are no-ops _cmd?.Dispose(); diff --git a/Directory.build.props b/Directory.Build.props similarity index 69% rename from Directory.build.props rename to Directory.Build.props index c1390ca5a..b7a0b509a 100644 --- a/Directory.build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 2017 Stack Exchange, Inc. + 2019 Stack Exchange, Inc. true true @@ -12,22 +12,19 @@ Apache-2.0 git https://github.com/StackExchange/Dapper + false true embedded en-US false 2.4.1 - - - - + true + - - - + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 9009095e3..ab75a6fd0 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ skip_commits: - '**/*.md' install: - - choco install dotnetcore-sdk --version 3.0.100-preview9-014004 + - choco install dotnetcore-sdk --version 3.0.100 environment: Appveyor: true diff --git a/global.json b/global.json index 45f541bad..e6b403b58 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "3.0.100-preview9" + "version": "3.0.100" } } \ No newline at end of file diff --git a/semver.txt b/semver.txt deleted file mode 100644 index 624afd388..000000000 --- a/semver.txt +++ /dev/null @@ -1 +0,0 @@ -1.50.7 \ No newline at end of file From 7769253d59a55a02304b7abc474a926eeeee07ca Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 27 Sep 2019 10:39:40 +0100 Subject: [PATCH 103/312] framework and tooling lib updates; does *NOT* include the full corpus of 3rd party ADO.NET providers --- Dapper.Contrib/Dapper.Contrib.csproj | 4 ++-- Dapper.ProviderTools/Dapper.ProviderTools.csproj | 2 +- Dapper.Rainbow/Dapper.Rainbow.csproj | 2 +- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 2 +- Dapper.StrongName/Dapper.StrongName.csproj | 2 +- Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj | 6 +++--- Dapper.Tests/Dapper.Tests.csproj | 6 +++--- Dapper.sln | 1 + Dapper/Dapper.csproj | 2 +- Directory.Build.props | 2 +- 10 files changed, 15 insertions(+), 14 deletions(-) diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj index d357df12e..57cc47c14 100644 --- a/Dapper.Contrib/Dapper.Contrib.csproj +++ b/Dapper.Contrib/Dapper.Contrib.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index 518f99839..941fa17f3 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -11,7 +11,7 @@ 8.0 - + diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj index 0968f0fed..1bcc7645e 100644 --- a/Dapper.Rainbow/Dapper.Rainbow.csproj +++ b/Dapper.Rainbow/Dapper.Rainbow.csproj @@ -14,7 +14,7 @@ - + diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index 5e924cfea..0054813b5 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -13,7 +13,7 @@ - + diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 2a429f291..329a86e2d 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -14,6 +14,6 @@ - + diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index d681ce4e2..4fa08d601 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -3,7 +3,7 @@ Dapper.Tests.Contrib Dapper Contrib Test Suite false - netcoreapp2.0 + netcoreapp2.1 false @@ -15,9 +15,9 @@ - + - + all diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 83dbf0b54..f25f3288e 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -17,12 +17,12 @@ - + - + - + diff --git a/Dapper.sln b/Dapper.sln index dd0dba3a6..7b8e4ef3f 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig appveyor.yml = appveyor.yml build.ps1 = build.ps1 + Directory.Build.props = Directory.Build.props global.json = global.json docs\index.md = docs\index.md License.txt = License.txt diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index aca83c4de..0f97e8327 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -9,6 +9,6 @@ - + diff --git a/Directory.Build.props b/Directory.Build.props index b7a0b509a..06fce5847 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -24,7 +24,7 @@ - + \ No newline at end of file From d12f1aa99980964715310a9a193cdac23af7e47c Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Fri, 27 Sep 2019 09:41:49 +0000 Subject: [PATCH 104/312] Use loop variable instead of 0 (#1334) * Use loop variable instead of 0 * Changed unit test, added additional nesting level --- Dapper.Tests/AsyncTests.cs | 7 +++++-- Dapper.Tests/SharedTypes/Address.cs | 9 ++------- Dapper.Tests/SharedTypes/Index.cs | 7 +++++++ Dapper/DynamicParameters.cs | 5 ++--- 4 files changed, 16 insertions(+), 12 deletions(-) create mode 100644 Dapper.Tests/SharedTypes/Index.cs diff --git a/Dapper.Tests/AsyncTests.cs b/Dapper.Tests/AsyncTests.cs index bc8cef105..d363caf95 100644 --- a/Dapper.Tests/AsyncTests.cs +++ b/Dapper.Tests/AsyncTests.cs @@ -476,7 +476,7 @@ public async Task Issue346_QueryAsyncConvert() public async Task TestSupportForDynamicParametersOutputExpressionsAsync() { { - var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; + var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2, Index = new Index() } }; var p = new DynamicParameters(bob); p.Output(bob, b => b.PersonId); @@ -484,19 +484,22 @@ public async Task TestSupportForDynamicParametersOutputExpressionsAsync() p.Output(bob, b => b.NumberOfLegs); p.Output(bob, b => b.Address.Name); p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address.Index.Id); await connection.ExecuteAsync(@" SET @Occupation = 'grillmaster' SET @PersonId = @PersonId + 1 SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' -SET @AddressPersonId = @PersonId", p).ConfigureAwait(false); +SET @AddressPersonId = @PersonId +SET @AddressIndexId = '01088'", p).ConfigureAwait(false); Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); Assert.Equal(1, bob.NumberOfLegs); Assert.Equal("bobs burgers", bob.Address.Name); Assert.Equal(2, bob.Address.PersonId); + Assert.Equal("01088", bob.Address.Index.Id); } } diff --git a/Dapper.Tests/SharedTypes/Address.cs b/Dapper.Tests/SharedTypes/Address.cs index 3aaa5a560..6c156953a 100644 --- a/Dapper.Tests/SharedTypes/Address.cs +++ b/Dapper.Tests/SharedTypes/Address.cs @@ -1,15 +1,10 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Dapper.Tests +namespace Dapper.Tests { public class Address { public int AddressId { get; set; } public string Name { get; set; } public int PersonId { get; set; } + public Index Index { get; set; } } } diff --git a/Dapper.Tests/SharedTypes/Index.cs b/Dapper.Tests/SharedTypes/Index.cs new file mode 100644 index 000000000..ed0e10bc4 --- /dev/null +++ b/Dapper.Tests/SharedTypes/Index.cs @@ -0,0 +1,7 @@ +namespace Dapper.Tests +{ + public class Index + { + public string Id { get; set; } + } +} diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index f37c159ad..d8a78a351 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -407,10 +407,9 @@ public DynamicParameters Output(T target, Expression> express il.Emit(OpCodes.Castclass, typeof(T)); // [T] // Count - 1 to skip the last member access - var i = 0; - for (; i < (chain.Count - 1); i++) + for (var i = 0; i < chain.Count - 1; i++) { - var member = chain[0].Member; + var member = chain[i].Member; if (member is PropertyInfo) { From 981cfe4ddfde421b9d572d8f5c2cda259be20a65 Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Fri, 27 Sep 2019 09:42:46 +0000 Subject: [PATCH 105/312] Replaced ContainsKey and [] with TryGetValue (#1339) --- Dapper.Contrib/SqlMapperExtensions.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 078f0c3ad..f182cf3ee 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -541,9 +541,9 @@ private static ISqlAdapter GetFormatter(IDbConnection connection) var name = GetDatabaseType?.Invoke(connection).ToLower() ?? connection.GetType().Name.ToLower(); - return !AdapterDictionary.ContainsKey(name) - ? DefaultAdapter - : AdapterDictionary[name]; + return AdapterDictionary.TryGetValue(name, out var adapter) + ? adapter + : DefaultAdapter; } private static class ProxyGenerator From daf5bea70abccb32413c619ef9f995a6f86c67a0 Mon Sep 17 00:00:00 2001 From: Bernard Vander Beken Date: Fri, 27 Sep 2019 11:42:54 +0200 Subject: [PATCH 106/312] Fix spelling (#1340) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 5bf101812..52f086037 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,7 +24,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command Primary changes: -- remove the System.Data.SqlClient depenency, allowing consumers to use System.Data.SqlClient or Microsoft.Data.SqlClient (or neither, or both) as they choose +- remove the System.Data.SqlClient dependency, allowing consumers to use System.Data.SqlClient or Microsoft.Data.SqlClient (or neither, or both) as they choose - this means that some users may need to *re-add* one of the above as a `` for their project to build, if they were previously relying on Dapper to provide System.Data.SqlClient - the `AsTableValuedParameter(this IEnumerable)` extension method is now `AsTableValuedParameter(this IEnumerable) where T : IDataRecord`; this is a breaking change but should be code-compatible and just requires a rebuild - unify the target platform at NetStandard2.0 (and .NET Framework 4.6.2 for the EF DB geometry/geography types) From 247e498f0e52c88d0c58c6f1fa50457bb6ab5888 Mon Sep 17 00:00:00 2001 From: mgravell Date: Fri, 27 Sep 2019 10:47:36 +0100 Subject: [PATCH 107/312] since we're targeting net461 again, ensure contrib tests have coverage --- .../Dapper.Tests.Contrib.csproj | 60 ++++++++++--------- 1 file changed, 32 insertions(+), 28 deletions(-) diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 4fa08d601..89869d666 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -1,28 +1,32 @@ - - - Dapper.Tests.Contrib - Dapper Contrib Test Suite - false - netcoreapp2.1 - false - - - - - - - - - - - - - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - + + + Dapper.Tests.Contrib + Dapper Contrib Test Suite + false + netcoreapp2.1;net462 + false + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + From a18dc63c688b5173c5dde8b1243b74e26fa262c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=A9bastien=20Ros?= Date: Fri, 25 Oct 2019 00:10:24 -0700 Subject: [PATCH 108/312] Use InvariantCulture in FlexibleConvert (#1363) In Sqlite, DateTime is stored as String, and custom serializers ultimately use Convert.ChangeType to do the conversion. The generated code however doesn't set the Invariant culture, and the conversion can fail if the current culture doesn't match the standard DateTime serialization format from Sqlite. --- Dapper.Tests/Providers/SqliteTests.cs | 43 ++++++++++++++++++++++++++- Dapper/SqlMapper.cs | 3 +- 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/Dapper.Tests/Providers/SqliteTests.cs b/Dapper.Tests/Providers/SqliteTests.cs index 5609a5eaf..a5aeb086f 100644 --- a/Dapper.Tests/Providers/SqliteTests.cs +++ b/Dapper.Tests/Providers/SqliteTests.cs @@ -2,6 +2,7 @@ using System; using System.Data.Common; using System.Linq; +using System.Threading; using System.Threading.Tasks; using Xunit; @@ -123,6 +124,46 @@ private void Isse467_SqliteParameterNaming(bool prefix) var i = Convert.ToInt32(cmd.ExecuteScalar()); Assert.Equal(42, i); } - } + } + + [FactSqlite] + public void DateTimeIsParsedWithInvariantCulture() + { + connection.Execute("CREATE TABLE [PersonWithDob] ([Id] integer primary key autoincrement, [DoB] DATETIME not null )"); + + var localMorning = DateTime.Parse("2019-07-31 01:00:00"); + + var culture = Thread.CurrentThread.CurrentCulture; + + try + { + connection.Execute("INSERT INTO [PersonWithDob] ([DoB]) VALUES (@DoB)", new PersonWithDob { DoB = localMorning }); + + // Before we read the column, use Farsi this is a way to ensure the + // InvariantCulture is used as otherwise it would fail because Farsi + // is not able to parse a DateTime that is formatted with Invariant + + var farsi = System.Globalization.CultureInfo.GetCultureInfo("fa-IR"); + Thread.CurrentThread.CurrentCulture = farsi; + Thread.CurrentThread.CurrentUICulture = farsi; + + var person = connection.QueryFirst("SELECT * FROM [PersonWithDob]"); + + Assert.Equal(localMorning, person.DoB); + } + finally + { + Thread.CurrentThread.CurrentCulture = culture; + Thread.CurrentThread.CurrentUICulture = culture; + + connection.Execute("DROP TABLE [PersonWithDob]"); + } + } + + private class PersonWithDob + { + public int Id { get; set; } + public DateTime DoB { get; set; } + } } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 8a5771034..5289ca7f1 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -3563,7 +3563,8 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro { il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token] il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null); // stack is now [target][target][value][member-type] - il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type) }), null); // stack is now [target][target][boxed-member-type-value] + il.EmitCall(OpCodes.Call, InvariantCulture, null); // stack is now [target][target][value][member-type][culture] + il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type), typeof(IFormatProvider) }), null); // stack is now [target][target][boxed-member-type-value] il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } } From 1922ef4a8d1fa7e87e9d3fa72daaf18e2ae327cd Mon Sep 17 00:00:00 2001 From: Bernard Vander Beken Date: Sun, 3 Nov 2019 16:34:00 +0100 Subject: [PATCH 109/312] Use consistent punctuation in XML comment (#1357) --- Dapper/SqlMapper.Async.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index fe510ed9a..acb0d9565 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -1218,7 +1218,7 @@ public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDef ExecuteScalarImplAsync(cnn, command); /// - /// Execute parameterized SQL that selects a single value + /// Execute parameterized SQL that selects a single value. /// /// The type to return. /// The connection to execute on. From 61fa083ca3b711067b6b2a0448b156836d696794 Mon Sep 17 00:00:00 2001 From: Wei Date: Wed, 25 Dec 2019 03:14:13 +0800 Subject: [PATCH 110/312] Add unit test for SqlBuilder with Dapper query (#1369) --- Dapper.Tests/Dapper.Tests.csproj | 1 + Dapper.Tests/SqlBuilderTests.cs | 46 ++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 Dapper.Tests/SqlBuilderTests.cs diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index f25f3288e..30df68cc5 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -13,6 +13,7 @@ + diff --git a/Dapper.Tests/SqlBuilderTests.cs b/Dapper.Tests/SqlBuilderTests.cs new file mode 100644 index 000000000..ab448242e --- /dev/null +++ b/Dapper.Tests/SqlBuilderTests.cs @@ -0,0 +1,46 @@ +using System.Linq; +using Xunit; + +namespace Dapper.Tests +{ + [Collection("SqlBuilderTests")] + public sealed class SystemSqlClientSqlBuilderTests : SqlBuilderTests { } +#if MSSQLCLIENT + [Collection("SqlBuilderTests")] + public sealed class MicrosoftSqlClientSqlBuilderTests : SqlBuilderTests { } +#endif + public abstract class SqlBuilderTests : TestBase where TProvider : DatabaseProvider + { + [Fact] + public void TestSqlBuilderWithDapperQuery() + { + var sb = new SqlBuilder(); + var template = sb.AddTemplate("SELECT /**select**/ FROM #Users /**where**/"); + sb.Where("Age <= @Age", new { Age = 18 }) + .Where("Country = @Country", new { Country = "USA" }) + .Select("Name,Age,Country"); + + const string createSql = @" + create table #Users (Name varchar(20),Age int,Country nvarchar(5)); + insert #Users values('Sam',16,'USA'),('Tom',25,'UK'),('Henry',14,'UK')"; + try + { + connection.Execute(createSql); + + var result = connection.Query(template.RawSql,template.Parameters).ToArray(); + + Assert.Equal("SELECT Name,Age,Country\n FROM #Users WHERE Age <= @Age AND Country = @Country\n", template.RawSql); + + Assert.Single(result); + + Assert.Equal(16, (int)result[0].Age); + Assert.Equal("Sam", (string)result[0].Name); + Assert.Equal("USA", (string)result[0].Country); + } + finally + { + connection.Execute("drop table #Users"); + } + } + } +} From 121d710067928822feef1c48763605fa8c4c6156 Mon Sep 17 00:00:00 2001 From: Brendan Gooden Date: Tue, 21 Jan 2020 11:58:35 +1030 Subject: [PATCH 111/312] Allow checking for custom handlers outside Dapper library (For use in SimpleCRUD extension) --- Dapper/SqlMapper.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 5289ca7f1..518441b1c 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -260,8 +260,12 @@ public static void RemoveTypeMap(Type type) /// The type to handle. /// The handler to process the . public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerImpl(type, handler, true); - - internal static bool HasTypeHandler(Type type) => typeHandlers.ContainsKey(type); + /// + /// Determine if the specified type will be processed by a custom handler. + /// + /// + /// + public static bool HasTypeHandler(Type type) => typeHandlers.ContainsKey(type); /// /// Configure the specified type to be processed by a custom handler. From 4ad80f880eddd20885d515068a4ba1f1a930a90c Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 26 Mar 2020 09:53:21 +0000 Subject: [PATCH 112/312] allow SDK roll-forward --- global.json | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/global.json b/global.json index e6b403b58..95fc4f328 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,8 @@ { "sdk": { - "version": "3.0.100" + "version": "3.0.100", + "rollForward": "latestMajor", + "allowPrerelease": false } } \ No newline at end of file From 765300a32b18f5a2d15f54feed9b46960c3af13f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sun, 5 Apr 2020 13:43:31 +0100 Subject: [PATCH 113/312] Enable deterministic build (#1433) * - enable deterministic build - lib updates for core packages * set source root if needed * Restore VS test runner xUnit won't run in VS without it :) Co-authored-by: Nick Craver --- Dapper.Contrib/Dapper.Contrib.csproj | 4 ++-- Dapper.ProviderTools/Dapper.ProviderTools.csproj | 2 +- Dapper.Rainbow/Dapper.Rainbow.csproj | 2 +- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 2 +- Dapper.StrongName/Dapper.StrongName.csproj | 2 +- Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj | 9 +++------ Dapper.Tests/Dapper.Tests.csproj | 11 ++++------- Dapper/Dapper.csproj | 2 +- Directory.Build.props | 14 +++++++++++--- Directory.Build.targets | 9 +++++++++ 10 files changed, 34 insertions(+), 23 deletions(-) create mode 100644 Directory.Build.targets diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj index 57cc47c14..662f6c33a 100644 --- a/Dapper.Contrib/Dapper.Contrib.csproj +++ b/Dapper.Contrib/Dapper.Contrib.csproj @@ -12,8 +12,8 @@ - - + + diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index 941fa17f3..0dea831e7 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -11,7 +11,7 @@ 8.0 - + diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj index 1bcc7645e..85a890cc4 100644 --- a/Dapper.Rainbow/Dapper.Rainbow.csproj +++ b/Dapper.Rainbow/Dapper.Rainbow.csproj @@ -14,7 +14,7 @@ - + diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index 0054813b5..4d22af8e9 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -13,7 +13,7 @@ - + diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 329a86e2d..8d860862a 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -14,6 +14,6 @@ - + diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 89869d666..b60d95f9e 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -17,12 +17,9 @@ - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + + diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 30df68cc5..95721785f 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -18,18 +18,15 @@ - + - + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - + + diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 0f97e8327..ccc1d0a5e 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -9,6 +9,6 @@ - + diff --git a/Directory.Build.props b/Directory.Build.props index 06fce5847..f93527236 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -18,13 +18,21 @@ embedded en-US false - 2.4.1 true + + + true + true + true + + + + - - + + \ No newline at end of file diff --git a/Directory.Build.targets b/Directory.Build.targets new file mode 100644 index 000000000..bbbfb2d97 --- /dev/null +++ b/Directory.Build.targets @@ -0,0 +1,9 @@ + + + + $([System.IO.Path]::Combine('$(IntermediateOutputPath)','$(TargetFrameworkMoniker).AssemblyAttributes$(DefaultLanguageSourceExtension)')) + + + + + \ No newline at end of file From 7088263affed64a8d8c40da9b40c55c1e9b71bed Mon Sep 17 00:00:00 2001 From: mgravell Date: Mon, 6 Apr 2020 10:11:05 +0100 Subject: [PATCH 114/312] update release notes --- Dapper.sln | 5 +++++ docs/index.md | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/Dapper.sln b/Dapper.sln index 7b8e4ef3f..6cfbe863c 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -46,6 +46,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" + ProjectSection(SolutionItems) = preProject + docs\index.md = docs\index.md + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/docs/index.md b/docs/index.md index 52f086037..d702d3efe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,25 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes +### 2.0.35 + +- build tooling: enable "deterministic builds" and enable SDK roll-foward +- fix culture related formatting/parsing issue with Sqlite (#1363 via sebastienros) +- documentation fixes (#1357 via jawn) +- add tests for `SqlBuilder` (#1369 via shps951023) + +### 2.0.30 + +- upstream library updates; project (build) cleanup +- reinstated net461 build target +- add Dapper.ProviderTools library (to help with System vs Microsoft SqlClient migration, etc) +- fix double dictionary lookup (#1339 via DamirAinullin) +- fix bug with dynamic parameters accessing the wrong member (#1334 via DamirAinullin) +- fix explicit-key issue with `DeleteAsync` (#1309 via james-hester-ah) +- fix for `char` on Postgres (#1326 via jjonescz) +- documentation fixes (#1340 via jawn) +- test and benchmark fixes (#1337 via DamirAinullin, #1206 via yesmey, #1331 via andresrsanchez, #1335 via DamirAinullin) + ### 2.0.4 Primary changes: From 899c9feb631a1c3d3f868b4b1983da5ad3065509 Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Tue, 28 Apr 2020 18:59:12 +0000 Subject: [PATCH 115/312] Replaced constant with index in loop (#1443) --- Dapper/DynamicParameters.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index d8a78a351..5982caa1f 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -411,9 +411,9 @@ public DynamicParameters Output(T target, Expression> express { var member = chain[i].Member; - if (member is PropertyInfo) + if (member is PropertyInfo info) { - var get = ((PropertyInfo)member).GetGetMethod(true); + var get = info.GetGetMethod(true); il.Emit(OpCodes.Callvirt, get); // [Member{i}] } else // Else it must be a field! From 0b17133fd84522a11b53d9b2eac364f50ec06334 Mon Sep 17 00:00:00 2001 From: BlackjacketMack Date: Wed, 29 Apr 2020 11:24:55 -0400 Subject: [PATCH 116/312] Updated InListStringSplitCount comment to GTE. The comment suggested that more than the number specified in InListStringSplitCount and it would start to use string_split. However, it is greater-than-or-equal-to (GTE) the number specified by InListStringSplitCount. --- Dapper/SqlMapper.Settings.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index db517c7ad..77cd462a4 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -90,7 +90,7 @@ public static void SetDefaults() public static bool PadListExpansions { get; set; } /// /// If set (non-negative), when performing in-list expansions of integer types ("where id in @ids", etc), switch to a string_split based - /// operation if there are more than this many elements. Note that this feautre requires SQL Server 2016 / compatibility level 130 (or above). + /// operation if there are this many elements or more. Note that this feautre requires SQL Server 2016 / compatibility level 130 (or above). /// public static int InListStringSplitCount { get; set; } = -1; } From 455b3f3b114587f2c9b2467278caf3bf44b26dcd Mon Sep 17 00:00:00 2001 From: Chris Donnelly Date: Thu, 23 Jan 2020 19:22:51 -0600 Subject: [PATCH 117/312] Adding MightyOrm benchmark. --- Dapper.Tests.Performance/Benchmarks.Mighty.cs | 50 +++++++++++++++++++ .../Dapper.Tests.Performance.csproj | 1 + 2 files changed, 51 insertions(+) create mode 100644 Dapper.Tests.Performance/Benchmarks.Mighty.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Mighty.cs b/Dapper.Tests.Performance/Benchmarks.Mighty.cs new file mode 100644 index 000000000..4a1b69fdb --- /dev/null +++ b/Dapper.Tests.Performance/Benchmarks.Mighty.cs @@ -0,0 +1,50 @@ +using BenchmarkDotNet.Attributes; +using Mighty; +using System.ComponentModel; +using System.Linq; + +namespace Dapper.Tests.Performance +{ + [Description("Mighty")] + public class MightyBenchmarks : BenchmarkBase + { + private MightyOrm _model; + private MightyOrm _dynamicModel; + + [GlobalSetup] + public void Setup() + { + BaseSetup(); + _model = new MightyOrm(ConnectionString); + _dynamicModel = new MightyOrm(ConnectionString); + } + + [Benchmark(Description = "Query")] + public Post Query() + { + Step(); + return _model.Query("select * from Posts where Id = @0", _connection, i).First(); + } + + [Benchmark(Description = "Query")] + public dynamic QueryDynamic() + { + Step(); + return _dynamicModel.Query("select * from Posts where Id = @0", _connection, i).First(); + } + + [Benchmark(Description = "SingleFromQuery")] + public Post SingleFromQuery() + { + Step(); + return _model.SingleFromQuery("select * from Posts where Id = @0", _connection, i); + } + + [Benchmark(Description = "SingleFromQuery")] + public dynamic SingleFromQueryDynamic() + { + Step(); + return _dynamicModel.SingleFromQuery("select * from Posts where Id = @0", _connection, i); + } + } +} diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 5d8976546..7078f87b2 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -21,6 +21,7 @@ + From 6aece653bef6539c69215f23c49d89a3bf697db6 Mon Sep 17 00:00:00 2001 From: Chris Donnelly Date: Thu, 23 Jan 2020 19:44:14 -0600 Subject: [PATCH 118/312] MightyOrm needs a nonstandard connection string. We are now providing it programmatically so we don't disrupt SQL Server. --- Dapper.Tests.Performance/Benchmarks.Mighty.cs | 9 +++++++-- Dapper.Tests.Performance/Benchmarks.cs | 3 ++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.Mighty.cs b/Dapper.Tests.Performance/Benchmarks.Mighty.cs index 4a1b69fdb..738188151 100644 --- a/Dapper.Tests.Performance/Benchmarks.Mighty.cs +++ b/Dapper.Tests.Performance/Benchmarks.Mighty.cs @@ -15,8 +15,13 @@ public class MightyBenchmarks : BenchmarkBase public void Setup() { BaseSetup(); - _model = new MightyOrm(ConnectionString); - _dynamicModel = new MightyOrm(ConnectionString); + + // Mighty needs the connection string to contain the ProviderName in addition to everything else for Reasons. + // However, it appears the SQL Server driver chokes on it if it's in the full connection string, so we programatically add it here. + var connectionString = $"{ConnectionStringSettings.ConnectionString};ProviderName={ConnectionStringSettings.ProviderName}"; + + _model = new MightyOrm(connectionString); + _dynamicModel = new MightyOrm(connectionString); } [Benchmark(Description = "Query")] diff --git a/Dapper.Tests.Performance/Benchmarks.cs b/Dapper.Tests.Performance/Benchmarks.cs index 4bab0411a..f02f02952 100644 --- a/Dapper.Tests.Performance/Benchmarks.cs +++ b/Dapper.Tests.Performance/Benchmarks.cs @@ -11,7 +11,8 @@ public abstract class BenchmarkBase { protected static readonly Random _rand = new Random(); protected SqlConnection _connection; - public static string ConnectionString { get; } = ConfigurationManager.ConnectionStrings["Main"].ConnectionString; + public static ConnectionStringSettings ConnectionStringSettings { get; } = ConfigurationManager.ConnectionStrings["Main"]; + public static string ConnectionString { get; } = ConnectionStringSettings.ConnectionString; protected int i; protected void BaseSetup() From 9b6c8c7d7fdeb8c3f8f87efd3f142798996afadc Mon Sep 17 00:00:00 2001 From: bryancrosby Date: Wed, 27 Nov 2019 10:02:46 -0800 Subject: [PATCH 119/312] Fix typo in Snapshotter Fix small typo and grammar --- Dapper.Rainbow/Snapshotter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Rainbow/Snapshotter.cs b/Dapper.Rainbow/Snapshotter.cs index d267d60cb..37c3357cf 100644 --- a/Dapper.Rainbow/Snapshotter.cs +++ b/Dapper.Rainbow/Snapshotter.cs @@ -12,7 +12,7 @@ namespace Dapper public static class Snapshotter { /// - /// Starts the snapshot of an objec by making a copy of the current state. + /// Starts the snapshot of an object by making a copy of the its current state. /// /// The type of object to snapshot. /// The object to snapshot. From c6952825e80c33d142e6697b963cfec2deb9a01f Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Fri, 1 May 2020 17:36:14 -0400 Subject: [PATCH 120/312] Update Dapper.Rainbow/Snapshotter.cs Co-authored-by: Youssef Victor <31348972+Youssef1313@users.noreply.github.com> --- Dapper.Rainbow/Snapshotter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Rainbow/Snapshotter.cs b/Dapper.Rainbow/Snapshotter.cs index 37c3357cf..317ac6585 100644 --- a/Dapper.Rainbow/Snapshotter.cs +++ b/Dapper.Rainbow/Snapshotter.cs @@ -12,7 +12,7 @@ namespace Dapper public static class Snapshotter { /// - /// Starts the snapshot of an object by making a copy of the its current state. + /// Starts the snapshot of an object by making a copy of its current state. /// /// The type of object to snapshot. /// The object to snapshot. From 0e04fe1daf71652c4bdfb1dc57178bcf39668050 Mon Sep 17 00:00:00 2001 From: AlexBagnolini Date: Sat, 2 May 2020 00:03:52 +0200 Subject: [PATCH 121/312] Added EF6 performance tests back in netcoreapp3.0 (#1361) * Added EF6 tests in netcoreapp3.0 * Fix up EF Core ref to always be 6.3.0 Co-authored-by: Alex Bagnolini Co-authored-by: Nick Craver --- Dapper.Tests.Performance/Benchmarks.EntityFramework.cs | 4 +--- Dapper.Tests.Performance/Dapper.Tests.Performance.csproj | 2 +- Dapper.Tests.Performance/EntityFramework/EFContext.cs | 4 +--- Dapper.Tests.Performance/LegacyTests.cs | 4 ++-- 4 files changed, 5 insertions(+), 9 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs b/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs index 3d44d0021..6660782d3 100644 --- a/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs +++ b/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs @@ -1,5 +1,4 @@ -#if NET4X -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using System.ComponentModel; using System.Linq; @@ -39,4 +38,3 @@ public Post NoTracking() } } } -#endif diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 7078f87b2..22f8a1fea 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -15,6 +15,7 @@ + @@ -37,7 +38,6 @@ - diff --git a/Dapper.Tests.Performance/EntityFramework/EFContext.cs b/Dapper.Tests.Performance/EntityFramework/EFContext.cs index 5dd7053a2..a1c4aa8b0 100644 --- a/Dapper.Tests.Performance/EntityFramework/EFContext.cs +++ b/Dapper.Tests.Performance/EntityFramework/EFContext.cs @@ -1,5 +1,4 @@ -#if NET4X -using System.Data.Common; +using System.Data.Common; using System.Data.Entity; namespace Dapper.Tests.Performance.EntityFramework @@ -13,4 +12,3 @@ public EFContext(DbConnection connection, bool owned = false) : base(connection, public DbSet Posts { get; set; } } } -#endif diff --git a/Dapper.Tests.Performance/LegacyTests.cs b/Dapper.Tests.Performance/LegacyTests.cs index 60c18c90f..014adae30 100644 --- a/Dapper.Tests.Performance/LegacyTests.cs +++ b/Dapper.Tests.Performance/LegacyTests.cs @@ -8,6 +8,7 @@ using Belgrade.SqlClient; using Dapper.Contrib.Extensions; using Dapper.Tests.Performance.Dashing; +using Dapper.Tests.Performance.EntityFramework; using Dapper.Tests.Performance.EntityFrameworkCore; using Dapper.Tests.Performance.NHibernate; using Dashing; @@ -22,7 +23,6 @@ using System.Threading.Tasks; #if NET4X using System.Data.Linq; -using Dapper.Tests.Performance.EntityFramework; using Dapper.Tests.Performance.Linq2Sql; using Dapper.Tests.Performance.Xpo; using NHibernate.Linq; @@ -343,7 +343,6 @@ public async Task RunAsync(int iterations) }, "DevExpress.XPO: FindObject"); }, "DevExpress.XPO"); -#if NET4X // Entity Framework Try(() => { @@ -357,6 +356,7 @@ public async Task RunAsync(int iterations) tests.Add(id => entityContext3.Posts.AsNoTracking().First(p => p.Id == id), "Entity Framework: No Tracking"); }, "Entity Framework"); +#if NET4X // Linq2SQL Try(() => { From 2c81a4085a6d3d25f5eaa32f904086f9add1d5cf Mon Sep 17 00:00:00 2001 From: JulianRooze Date: Sat, 2 May 2020 00:16:23 +0200 Subject: [PATCH 122/312] Fix handling of selecting nullable value tuples (#1400) `SingleOrDefault<(int, int)?>(query)` always returned null because the IsValueTuple did not handle nullable types and thus the `GenerateValueTupleDeserializer` method was never called. I've added test for the case when the query does not return something (the tuple should be null) and when it does return something. Additionally added tests for the edge-case where you select 8 or 15 element tuples to make sure nothing broke there. --- Dapper.Tests/TupleTests.cs | 84 ++++++++++++++++++++++++++++++++++++++ Dapper/SqlMapper.cs | 14 ++++++- 2 files changed, 96 insertions(+), 2 deletions(-) diff --git a/Dapper.Tests/TupleTests.cs b/Dapper.Tests/TupleTests.cs index bcf183db5..cf709f6e1 100644 --- a/Dapper.Tests/TupleTests.cs +++ b/Dapper.Tests/TupleTests.cs @@ -40,6 +40,22 @@ public void TupleReturnValue_TooManyColumns_Ignored() Assert.Equal("Fred", val.name); } + [Fact] + public void TupleReturnValue_NullableTuple_Works() + { + var val = connection.QuerySingleOrDefault<(int id, string name)?>("select 42, 'Fred', 123"); + Assert.NotNull(val); + Assert.Equal(42, val.Value.id); + Assert.Equal("Fred", val.Value.name); + } + + [Fact] + public void TupleReturnValue_NullableTuple_Works_When_Null() + { + var val = connection.QuerySingleOrDefault<(int id, string name)?>("select 42, 'Fred', 123 where 1 = 2"); + Assert.Null(val); + } + [Fact] public void TupleReturnValue_TooFewColumns_Unmapped() { @@ -75,7 +91,37 @@ public void TupleReturnValue_Works_With8Elements() Assert.Equal(7, val.e7); Assert.Equal(8, val.e8); } + + [Fact] + public void Nullable_TupleReturnValue_Works_With8Elements() + { + // C# encodes an 8-tuple as ValueTuple> + + var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8)?>( + "select 1, 2, 3, 4, 5, 6, 7, 8"); + + Assert.NotNull(val); + Assert.Equal(1, val.Value.e1); + Assert.Equal(2, val.Value.e2); + Assert.Equal(3, val.Value.e3); + Assert.Equal(4, val.Value.e4); + Assert.Equal(5, val.Value.e5); + Assert.Equal(6, val.Value.e6); + Assert.Equal(7, val.Value.e7); + Assert.Equal(8, val.Value.e8); + } + + [Fact] + public void Nullable_TupleReturnValue_Works_With8Elements_When_Null() + { + // C# encodes an 8-tuple as ValueTuple> + + var val = connection.QuerySingleOrDefault<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8)?>( + "select 1, 2, 3, 4, 5, 6, 7, 8 where 1 = 2"); + Assert.Null(val); + } + [Fact] public void TupleReturnValue_Works_With15Elements() { @@ -101,6 +147,44 @@ public void TupleReturnValue_Works_With15Elements() Assert.Equal(15, val.e15); } + + [Fact] + public void Nullable_TupleReturnValue_Works_With15Elements() + { + // C# encodes a 15-tuple as ValueTuple>> + + var val = connection.QuerySingle<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8, int e9, int e10, int e11, int e12, int e13, int e14, int e15)?>( + "select 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"); + + Assert.NotNull(val); + Assert.Equal(1, val.Value.e1); + Assert.Equal(2, val.Value.e2); + Assert.Equal(3, val.Value.e3); + Assert.Equal(4, val.Value.e4); + Assert.Equal(5, val.Value.e5); + Assert.Equal(6, val.Value.e6); + Assert.Equal(7, val.Value.e7); + Assert.Equal(8, val.Value.e8); + Assert.Equal(9, val.Value.e9); + Assert.Equal(10, val.Value.e10); + Assert.Equal(11, val.Value.e11); + Assert.Equal(12, val.Value.e12); + Assert.Equal(13, val.Value.e13); + Assert.Equal(14, val.Value.e14); + Assert.Equal(15, val.Value.e15); + } + + [Fact] + public void Nullable_TupleReturnValue_Works_With15Elements_When_Null() + { + // C# encodes a 15-tuple as ValueTuple>> + + var val = connection.QuerySingleOrDefault<(int e1, int e2, int e3, int e4, int e5, int e6, int e7, int e8, int e9, int e10, int e11, int e12, int e13, int e14, int e15)?>( + "select 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 where 1 = 2"); + + Assert.Null(val); + } + [Fact] public void TupleReturnValue_Works_WithStringField() { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 5289ca7f1..e222b3156 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2362,7 +2362,9 @@ internal static IList GetLiteralTokens(string sql) public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); - private static bool IsValueTuple(Type type) => type?.IsValueType == true && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal); + private static bool IsValueTuple(Type type) => (type?.IsValueType == true + && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal)) + || (type != null && IsValueTuple(Nullable.GetUnderlyingType(type))); internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { @@ -3078,7 +3080,8 @@ private static Func GetTypeDeserializerImpl( private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataReader reader, int startBound, int length, ILGenerator il) { - var currentValueTupleType = valueTupleType; + var nullableUnderlyingType = Nullable.GetUnderlyingType(valueTupleType); + var currentValueTupleType = nullableUnderlyingType ?? valueTupleType; var constructors = new List(); var languageTupleElementTypes = new List(); @@ -3163,6 +3166,13 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataRea il.Emit(OpCodes.Newobj, constructors[i]); } + if (nullableUnderlyingType != null) + { + var nullableTupleConstructor = valueTupleType.GetConstructor(new[] { nullableUnderlyingType }); + + il.Emit(OpCodes.Newobj, nullableTupleConstructor); + } + il.Emit(OpCodes.Box, valueTupleType); il.Emit(OpCodes.Ret); } From 5c87dc97382aa79422ff05b29abbadd035a72b62 Mon Sep 17 00:00:00 2001 From: Wei Date: Sat, 2 May 2020 07:25:12 +0800 Subject: [PATCH 123/312] SqlBuilder Support Update Set (#1404) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit I add update `Set` method and unit test. e.g: ```C# var sb = new SqlBuilder() .Set("vip = @vip", new { vip }) .Set("updatetime = @updatetime", new { updatetime }) .Where("id = @id", new { id }) ; var template = sb.AddTemplate("update #Users /**set**/ /**where**/"); connection.Execute(template.RawSql, template.Parameters); ``` RawSql will generate :  ``` update #Users Set vip = @vip , updatetime = @updatetime\n WHERE id = @id\n ``` using Dapper Execute in sqlserver it'll reqeust by below sql command ``` exec sp_executesql N'update #Users Set vip = @vip , updatetime = @updatetime WHERE id = @id ',N'@vip bit,@updatetime datetime,@id int',@vip=1,@updatetime='2020-01-01 00:00:00',@id=1 ``` Co-authored-by: Nick Craver Co-authored-by: Nick Craver --- Dapper.SqlBuilder/SqlBuilder.cs | 4 ++++ Dapper.Tests/SqlBuilderTests.cs | 40 ++++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Dapper.SqlBuilder/SqlBuilder.cs b/Dapper.SqlBuilder/SqlBuilder.cs index c83097f42..ad7b1602f 100644 --- a/Dapper.SqlBuilder/SqlBuilder.cs +++ b/Dapper.SqlBuilder/SqlBuilder.cs @@ -149,5 +149,9 @@ public SqlBuilder GroupBy(string sql, dynamic parameters = null) => public SqlBuilder Having(string sql, dynamic parameters = null) => AddClause("having", sql, parameters, "\nAND ", "HAVING ", "\n", false); + + public SqlBuilder Set(string sql, dynamic parameters = null) => + AddClause("set", sql, parameters, " , ", "SET ", "\n", false); + } } diff --git a/Dapper.Tests/SqlBuilderTests.cs b/Dapper.Tests/SqlBuilderTests.cs index ab448242e..83587ecd4 100644 --- a/Dapper.Tests/SqlBuilderTests.cs +++ b/Dapper.Tests/SqlBuilderTests.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using Xunit; namespace Dapper.Tests @@ -42,5 +43,42 @@ public void TestSqlBuilderWithDapperQuery() connection.Execute("drop table #Users"); } } + + [Fact] + public void TestSqlBuilderUpdateSet() + { + var id = 1; + var vip = true; + var updatetime = DateTime.Parse("2020/01/01"); + + var sb = new SqlBuilder() + .Set("vip = @vip", new { vip }) + .Set("updatetime = @updatetime", new { updatetime }) + .Where("id = @id", new { id }) + ; + var template = sb.AddTemplate("update #Users /**set**/ /**where**/"); + + const string createSql = @" + create table #Users (Id int,Name varchar(20),Age int,Country nvarchar(5),Vip bit,Updatetime datetime); + insert #Users (Id,Name,Age,Country) values(1,'Sam',16,'USA'),(2,'Tom',25,'UK'),(3,'Henry',14,'UK')"; + try + { + connection.Execute(createSql); + + var effectCount = connection.Execute(template.RawSql, template.Parameters); + + var result = connection.QueryFirst("select * from #Users where Id = 1"); + + Assert.Equal("update #Users SET vip = @vip , updatetime = @updatetime\n WHERE id = @id\n", template.RawSql); + + + Assert.True((bool)result.Vip); + Assert.Equal(updatetime, (DateTime)result.Updatetime); + } + finally + { + connection.Execute("drop table #Users"); + } + } } } From 663588f90b09145762c8d16f512575932caf802d Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 2 May 2020 07:52:40 -0400 Subject: [PATCH 124/312] AppVeyor: Build tweaks & misc fixes (#1450) Simplifying and improve build speed. - Decreases builds from 8-13 minutes down to ~2 minutes (while running more tests) - Moves to SQL Server 2019 on AppVeyor - Moves the 2 longest running tests with params to `[FactLongRunning]` - Bumps tests up to `netcoreapp3.1` - Also fixes .Contrib tests - Moves to `Build.csproj` - Simplifies `build.ps1` - Removed defunct `build.sh` - Nukes `IsAppveyor` (moves to environmental variable overloads) - Builds now work in Linux/macOS --- Build.csproj | 5 ++ Dapper.ProviderTools/BulkCopy.cs | 2 +- .../Dapper.ProviderTools.csproj | 1 - .../DbConnectionExtensions.cs | 2 +- Dapper.ProviderTools/DbExceptionExtensions.cs | 2 +- .../Dapper.Tests.Contrib.csproj | 5 +- Dapper.Tests.Contrib/Helpers/Attributes.cs | 46 ++++++++++++++ Dapper.Tests.Contrib/TestSuite.cs | 6 +- Dapper.Tests.Contrib/TestSuites.cs | 9 +-- .../Dapper.Tests.Performance.csproj | 3 +- Dapper.Tests/Dapper.Tests.csproj | 4 +- Dapper.Tests/ParameterTests.cs | 4 +- Dapper.Tests/Providers/MySQLTests.cs | 5 +- Dapper.Tests/Providers/OLDEBTests.cs | 4 +- Dapper.Tests/Providers/PostgresqlTests.cs | 5 +- Dapper.Tests/TestBase.cs | 10 +-- Directory.Build.props | 5 +- Readme.md | 6 +- appveyor.yml | 13 ++-- build.ps1 | 48 +++----------- build.sh | 63 ------------------- game | 1 - global.json | 2 +- 23 files changed, 104 insertions(+), 147 deletions(-) create mode 100644 Build.csproj create mode 100644 Dapper.Tests.Contrib/Helpers/Attributes.cs delete mode 100755 build.sh delete mode 100644 game diff --git a/Build.csproj b/Build.csproj new file mode 100644 index 000000000..1ae9b2d72 --- /dev/null +++ b/Build.csproj @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs index 4cfafe329..d56110360 100644 --- a/Dapper.ProviderTools/BulkCopy.cs +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; using Dapper.ProviderTools.Internal; -#nullable enable + namespace Dapper.ProviderTools { /// diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index 0dea831e7..fce48051e 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -8,7 +8,6 @@ net461;netstandard2.0 true enable - 8.0 diff --git a/Dapper.ProviderTools/DbConnectionExtensions.cs b/Dapper.ProviderTools/DbConnectionExtensions.cs index e7cce475f..05d21d5a7 100644 --- a/Dapper.ProviderTools/DbConnectionExtensions.cs +++ b/Dapper.ProviderTools/DbConnectionExtensions.cs @@ -3,7 +3,7 @@ using System.Data.Common; using System.Linq.Expressions; using System.Reflection; -#nullable enable + namespace Dapper.ProviderTools { /// diff --git a/Dapper.ProviderTools/DbExceptionExtensions.cs b/Dapper.ProviderTools/DbExceptionExtensions.cs index 1850f4e80..c14f1bc62 100644 --- a/Dapper.ProviderTools/DbExceptionExtensions.cs +++ b/Dapper.ProviderTools/DbExceptionExtensions.cs @@ -3,7 +3,7 @@ using System.Data.Common; using System.Linq.Expressions; using System.Reflection; -#nullable enable + namespace Dapper.ProviderTools { /// diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index b60d95f9e..6e5a826ac 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -3,11 +3,11 @@ Dapper.Tests.Contrib Dapper Contrib Test Suite false - netcoreapp2.1;net462 + netcoreapp3.1;net462 false - + @@ -25,5 +25,4 @@ - diff --git a/Dapper.Tests.Contrib/Helpers/Attributes.cs b/Dapper.Tests.Contrib/Helpers/Attributes.cs new file mode 100644 index 000000000..f6393c0f0 --- /dev/null +++ b/Dapper.Tests.Contrib/Helpers/Attributes.cs @@ -0,0 +1,46 @@ +using System; +using Xunit.Sdk; + +namespace Dapper.Tests +{ + /// + /// Override for that truncates our DisplayName down. + /// + /// Attribute that is applied to a method to indicate that it is a fact that should + /// be run by the test runner. It can also be extended to support a customized definition + /// of a test method. + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Dapper.Tests.FactDiscoverer", "Dapper.Tests.Contrib")] + public class FactAttribute : Xunit.FactAttribute + { + } + + /// + /// Override for that truncates our DisplayName down. + /// + /// Marks a test method as being a data theory. Data theories are tests which are + /// fed various bits of data from a data source, mapping to parameters on the test + /// method. If the data source contains multiple rows, then the test method is executed + /// multiple times (once with each data row). Data is provided by attributes which + /// derive from Xunit.Sdk.DataAttribute (notably, Xunit.InlineDataAttribute and Xunit.MemberDataAttribute). + /// + /// + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + [XunitTestCaseDiscoverer("Dapper.Tests.TheoryDiscoverer", "Dapper.Tests.Contrib")] + public class TheoryAttribute : Xunit.TheoryAttribute { } + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public sealed class FactLongRunningAttribute : FactAttribute + { + public FactLongRunningAttribute() + { +#if !LONG_RUNNING + Skip = "Long running"; +#endif + } + + public string Url { get; private set; } + } +} diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index 0e7defac0..11eeb7b01 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using System.Data; using System.Linq; -using System.Transactions; using Dapper.Contrib.Extensions; using Xunit; @@ -104,10 +103,11 @@ public class GenericType public abstract partial class TestSuite { - protected static readonly bool IsAppVeyor = Environment.GetEnvironmentVariable("Appveyor")?.ToUpperInvariant() == "TRUE"; - public abstract IDbConnection GetConnection(); + protected static string GetConnectionString(string name, string defaultConnectionString) => + Environment.GetEnvironmentVariable(name) ?? defaultConnectionString; + private IDbConnection GetOpenConnection() { var connection = GetConnection(); diff --git a/Dapper.Tests.Contrib/TestSuites.cs b/Dapper.Tests.Contrib/TestSuites.cs index ed84508c9..793d87bfe 100644 --- a/Dapper.Tests.Contrib/TestSuites.cs +++ b/Dapper.Tests.Contrib/TestSuites.cs @@ -23,9 +23,8 @@ public class SqlServerTestSuite : TestSuite { private const string DbName = "tempdb"; public static string ConnectionString => - IsAppVeyor - ? @"Server=(local)\SQL2016;Database=tempdb;User ID=sa;Password=Password12!" - : $"Data Source=.;Initial Catalog={DbName};Integrated Security=True"; + GetConnectionString("SqlServerConnectionString", $"Data Source=.;Initial Catalog={DbName};Integrated Security=True"); + public override IDbConnection GetConnection() => new SqlConnection(ConnectionString); static SqlServerTestSuite() @@ -62,9 +61,7 @@ static SqlServerTestSuite() public class MySqlServerTestSuite : TestSuite { public static string ConnectionString { get; } = - IsAppVeyor - ? "Server=localhost;Database=test;Uid=root;Pwd=Password12!;UseAffectedRows=false;" - : "Server=localhost;Database=tests;Uid=test;Pwd=pass;UseAffectedRows=false;"; + GetConnectionString("MySqlConnectionString", "Server=localhost;Database=tests;Uid=test;Pwd=pass;UseAffectedRows=false;"); public override IDbConnection GetConnection() { diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 22f8a1fea..060b8b050 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -5,7 +5,8 @@ Dapper Core Performance Suite Exe false - net462;netcoreapp3.0 + net462;netcoreapp3.1 + false diff --git a/Dapper.Tests/Dapper.Tests.csproj b/Dapper.Tests/Dapper.Tests.csproj index 95721785f..bcd3eab91 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/Dapper.Tests/Dapper.Tests.csproj @@ -3,7 +3,7 @@ Dapper.Tests Dapper Core Test Suite false - netcoreapp2.1;net462;net472 + netcoreapp3.1;net462;net472 false $(DefineConstants);MSSQLCLIENT @@ -13,10 +13,10 @@ - + diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index 1658d0633..418299f47 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -1442,10 +1442,10 @@ public void Issue601_InternationalParameterNamesWork() Assert.Equal(42, result); } - [Fact] + [FactLongRunning] public void TestListExpansionPadding_Enabled() => TestListExpansionPadding(true); - [Fact] + [FactLongRunning] public void TestListExpansionPadding_Disabled() => TestListExpansionPadding(false); private void TestListExpansionPadding(bool enabled) diff --git a/Dapper.Tests/Providers/MySQLTests.cs b/Dapper.Tests/Providers/MySQLTests.cs index 8052c0c4f..6df775b68 100644 --- a/Dapper.Tests/Providers/MySQLTests.cs +++ b/Dapper.Tests/Providers/MySQLTests.cs @@ -10,9 +10,8 @@ namespace Dapper.Tests public sealed class MySqlProvider : DatabaseProvider { public override DbProviderFactory Factory => MySql.Data.MySqlClient.MySqlClientFactory.Instance; - public override string GetConnectionString() => IsAppVeyor - ? "Server=localhost;Database=test;Uid=root;Pwd=Password12!;" - : "Server=localhost;Database=tests;Uid=test;Pwd=pass;"; + public override string GetConnectionString() => + GetConnectionString("MySqlConnectionString", "Server=localhost;Database=tests;Uid=test;Pwd=pass;"); public DbConnection GetMySqlConnection(bool open = true, bool convertZeroDatetime = false, bool allowZeroDatetime = false) diff --git a/Dapper.Tests/Providers/OLDEBTests.cs b/Dapper.Tests/Providers/OLDEBTests.cs index bfdffb3de..5fdbe472e 100644 --- a/Dapper.Tests/Providers/OLDEBTests.cs +++ b/Dapper.Tests/Providers/OLDEBTests.cs @@ -11,9 +11,7 @@ public class OLEDBProvider : DatabaseProvider { public override DbProviderFactory Factory => OleDbFactory.Instance; public override string GetConnectionString() => - IsAppVeyor - ? @"Provider=SQLOLEDB;Data Source=(local)\SQL2016;Initial Catalog=tempdb;User Id=sa;Password=Password12!" - : "Provider=SQLOLEDB;Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI"; + GetConnectionString("OLEDBConnectionString", "Provider=SQLOLEDB;Data Source=.;Initial Catalog=tempdb;Integrated Security=SSPI"); } public class OLDEBTests : TestBase diff --git a/Dapper.Tests/Providers/PostgresqlTests.cs b/Dapper.Tests/Providers/PostgresqlTests.cs index 416be50e2..9366c244c 100644 --- a/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/Dapper.Tests/Providers/PostgresqlTests.cs @@ -9,9 +9,8 @@ namespace Dapper.Tests public class PostgresProvider : DatabaseProvider { public override DbProviderFactory Factory => Npgsql.NpgsqlFactory.Instance; - public override string GetConnectionString() => IsAppVeyor - ? "Server=localhost;Port=5432;User Id=postgres;Password=Password12!;Database=test" - : "Server=localhost;Port=5432;User Id=dappertest;Password=dapperpass;Database=dappertest"; // ;Encoding = UNICODE + public override string GetConnectionString() => + GetConnectionString("PostgesConnectionString", "Server=localhost;Port=5432;User Id=dappertest;Password=dapperpass;Database=dappertest"); } public class PostgresqlTests : TestBase { diff --git a/Dapper.Tests/TestBase.cs b/Dapper.Tests/TestBase.cs index c03dbc311..b2953d4d8 100644 --- a/Dapper.Tests/TestBase.cs +++ b/Dapper.Tests/TestBase.cs @@ -15,10 +15,12 @@ public abstract class DatabaseProvider { public abstract DbProviderFactory Factory { get; } - public static bool IsAppVeyor { get; } = Environment.GetEnvironmentVariable("Appveyor")?.ToUpperInvariant() == "TRUE"; public virtual void Dispose() { } public abstract string GetConnectionString(); + protected string GetConnectionString(string name, string defaultConnectionString) => + Environment.GetEnvironmentVariable(name) ?? defaultConnectionString; + public DbConnection GetOpenConnection() { var conn = Factory.CreateConnection(); @@ -47,10 +49,8 @@ public DbParameter CreateRawParameter(string name, object value) public abstract class SqlServerDatabaseProvider : DatabaseProvider { - public override string GetConnectionString() => - IsAppVeyor - ? @"Server=(local)\SQL2016;Database=tempdb;User ID=sa;Password=Password12!" - : "Data Source=.;Initial Catalog=tempdb;Integrated Security=True"; + public override string GetConnectionString() => + GetConnectionString("SqlServerConnectionString", "Data Source=.;Initial Catalog=tempdb;Integrated Security=True"); public DbConnection GetOpenConnection(bool mars) { diff --git a/Directory.Build.props b/Directory.Build.props index f93527236..3932bf239 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -20,6 +20,8 @@ false true + + 8.0 @@ -32,7 +34,8 @@ - + + \ No newline at end of file diff --git a/Readme.md b/Readme.md index 32ead193a..78ba15b0c 100644 --- a/Readme.md +++ b/Readme.md @@ -114,9 +114,9 @@ Performance A key feature of Dapper is performance. The following metrics show how long it takes to execute a `SELECT` statement against a DB (in various config, each labeled) and map the data returned to objects. -The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests.Performance) (contributions welcome!) and can be run once compiled via: -``` -Dapper.Tests.Performance.exe -f * --join +The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests.Performance) (contributions welcome!) and can be run via: +```bash +dotnet run -p .\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join ``` Output from the latest run is: ``` ini diff --git a/appveyor.yml b/appveyor.yml index ab75a6fd0..44178cbdd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -image: Visual Studio 2017 +image: Visual Studio 2019 skip_branch_with_pr: true skip_tags: true @@ -6,9 +6,6 @@ skip_commits: files: - '**/*.md' -install: - - choco install dotnetcore-sdk --version 3.0.100 - environment: Appveyor: true # Postgres @@ -24,14 +21,20 @@ environment: MYSQL_ENV_MYSQL_USER: root MYSQL_ENV_MYSQL_PASSWORD: Password12! MYSQL_ENV_MYSQL_DATABASE: test + # Connection strings for tests: + MySqlConnectionString: Server=localhost;Database=test;Uid=root;Pwd=Password12! + OLEDBConnectionString: Provider=SQLOLEDB;Data Source=(local)\SQL2019;Initial Catalog=tempdb;User Id=sa;Password=Password12! + PostgesConnectionString: Server=localhost;Port=5432;User Id=postgres;Password=Password12!;Database=test + SqlServerConnectionString: Server=(local)\SQL2019;Database=tempdb;User ID=sa;Password=Password12! + services: - - mssql2016 - mysql - postgresql init: - git config --global core.autocrlf input - SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%PATH% + - net start MSSQL$SQL2019 nuget: disable_publish_on_pr: true diff --git a/build.ps1 b/build.ps1 index de16c65b5..f406c745f 100644 --- a/build.ps1 +++ b/build.ps1 @@ -11,61 +11,33 @@ Write-Host " RunTests: $RunTests" Write-Host " dotnet --version:" (dotnet --version) $packageOutputFolder = "$PSScriptRoot\.nupkgs" -$projectsToBuild = - 'Dapper', - 'Dapper.StrongName', - 'Dapper.Contrib', - 'Dapper.EntityFramework', - 'Dapper.EntityFramework.StrongName', - 'Dapper.Rainbow', - 'Dapper.SqlBuilder' - -$testsToRun = - 'Dapper.Tests', - 'Dapper.Tests.Contrib' if ($PullRequestNumber) { Write-Host "Building for a pull request (#$PullRequestNumber), skipping packaging." -ForegroundColor Yellow $CreatePackages = $false } -Write-Host "Restoring all projects..." -ForegroundColor "Magenta" -dotnet restore -Write-Host "Done restoring." -ForegroundColor "Green" - -Write-Host "Building all projects..." -ForegroundColor "Magenta" -dotnet build -c Release --no-restore /p:CI=true +Write-Host "Building all projects (Build.csproj traversal)..." -ForegroundColor "Magenta" +dotnet build ".\Build.csproj" -c Release /p:CI=true Write-Host "Done building." -ForegroundColor "Green" if ($RunTests) { - foreach ($project in $testsToRun) { - Write-Host "Running tests: $project (all frameworks)" -ForegroundColor "Magenta" - Push-Location ".\$project" - - dotnet test -c Release - if ($LastExitCode -ne 0) { - Write-Host "Error with tests, aborting build." -Foreground "Red" - Pop-Location - Exit 1 - } - - Write-Host "Tests passed!" -ForegroundColor "Green" - Pop-Location + Write-Host "Running tests: Build.csproj traversal (all frameworks)" -ForegroundColor "Magenta" + dotnet test ".\Build.csproj" -c Release --no-build + if ($LastExitCode -ne 0) { + Write-Host "Error with tests, aborting build." -Foreground "Red" + Exit 1 } + Write-Host "Tests passed!" -ForegroundColor "Green" } if ($CreatePackages) { - mkdir -Force $packageOutputFolder | Out-Null + New-Item -ItemType Directory -Path $packageOutputFolder -Force | Out-Null Write-Host "Clearing existing $packageOutputFolder..." -NoNewline Get-ChildItem $packageOutputFolder | Remove-Item Write-Host "done." -ForegroundColor "Green" Write-Host "Building all packages" -ForegroundColor "Green" - - foreach ($project in $projectsToBuild) { - Write-Host "Packing $project (dotnet pack)..." -ForegroundColor "Magenta" - dotnet pack ".\$project\$project.csproj" --no-build -c Release /p:PackageOutputPath=$packageOutputFolder /p:NoPackageAnalysis=true /p:CI=true - Write-Host "" - } + dotnet pack ".\Build.csproj" --no-build -c Release /p:PackageOutputPath=$packageOutputFolder /p:CI=true } Write-Host "Build Complete." -ForegroundColor "Green" diff --git a/build.sh b/build.sh deleted file mode 100755 index 48be0b749..000000000 --- a/build.sh +++ /dev/null @@ -1,63 +0,0 @@ -#!/bin/bash - -echo "" -echo "Installing dotnet cli..." -echo "" - -export DOTNET_INSTALL_DIR="./.dotnet/" - -tools/install.sh - -origPath=$PATH -export PATH="./dotnet/bin/:$PATH" - -if [ $? -ne 0 ]; then - echo >&2 ".NET Execution Environment installation has failed." - exit 1 -fi - -export DOTNET_HOME="$DOTNET_INSTALL_DIR/cli" -export PATH="$DOTNET_HOME/bin:$PATH" - -export autoGeneratedVersion=false - -# Generate version number if not set -if [[ -z "$BuildSemanticVersion" ]]; then - autoVersion="$((($(date +%s) - 1451606400)/60))-$(date +%S)" - export BuildSemanticVersion="rc2-$autoVersion" - autoGeneratedVersion=true - - echo "Set version to $BuildSemanticVersion" -fi - -sed -i '' "s/99.99.99-rc2/1.0.0-$BuildSemanticVersion/g" */*/project.json - -# Restore packages and build product -dotnet restore -v Minimal # Restore all packages - -# Build all -# Note the exclude: https://github.com/dotnet/cli/issues/1342 -for d in Dapper*/; do - if [ "$d" != "*.EntityFramework.StrongName" ]; then - echo "Building $d" - pushd "$d" - dotnet build -f netstandard1.3 - popd - fi -done - -# Run tests -for d in *.Tests*/; do - echo "Testing $d" - pushd "$d" - dotnet test -f netcoreapp1.0 - popd -done - -sed -i '' "s/1.0.0-$BuildSemanticVersion/99.99.99-rc2/g" */*/project.json - -if [ $autoGeneratedVersion ]; then - unset BuildSemanticVersion -fi - -export PATH=$origPath \ No newline at end of file diff --git a/game b/game deleted file mode 100644 index b90753f27..000000000 --- a/game +++ /dev/null @@ -1 +0,0 @@ -It's 's fault! \ No newline at end of file diff --git a/global.json b/global.json index 95fc4f328..8d86ec578 100644 --- a/global.json +++ b/global.json @@ -1,7 +1,7 @@ { "sdk": { - "version": "3.0.100", + "version": "3.1.100", "rollForward": "latestMajor", "allowPrerelease": false } From 9b010240c74af4fa8884cc5d724cf0a54e8dc507 Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Sat, 2 May 2020 12:15:46 +0000 Subject: [PATCH 125/312] Set initial capacity for collections (#1449) * Set initial capacity for collections * Revert Massive changes Co-authored-by: Nick Craver --- Dapper.Contrib/SqlMapperExtensions.cs | 2 +- Dapper.Tests.Contrib/TestSuite.Async.cs | 14 +++++++------- Dapper.Tests.Contrib/TestSuite.cs | 12 ++++++------ Dapper.Tests/MiscTests.cs | 2 +- Dapper.Tests/ParameterTests.cs | 12 ++++++------ Dapper/SqlMapper.cs | 6 +++--- 6 files changed, 24 insertions(+), 24 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index f182cf3ee..f6125e6ab 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -61,7 +61,7 @@ public interface ITableNameMapper private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); private static readonly Dictionary AdapterDictionary - = new Dictionary + = new Dictionary(6) { ["sqlconnection"] = new SqlServerAdapter(), ["sqlceconnection"] = new SqlCeServerAdapter(), diff --git a/Dapper.Tests.Contrib/TestSuite.Async.cs b/Dapper.Tests.Contrib/TestSuite.Async.cs index 96271647d..fcd11802a 100644 --- a/Dapper.Tests.Contrib/TestSuite.Async.cs +++ b/Dapper.Tests.Contrib/TestSuite.Async.cs @@ -26,7 +26,7 @@ public async Task TypeWithGenericParameterCanBeInsertedAsync() Assert.Single(connection.GetAll>()); - var objectsToInsert = new List> + var objectsToInsert = new List>(2) { new GenericType { @@ -83,7 +83,7 @@ public async Task TypeWithGenericParameterCanBeDeletedAsync() } } - [Fact] + [Fact] public async Task GetAsyncSucceedsAfterDeleteAsyncWhenExplicitKeyPresent() { using (var connection = GetOpenConnection()) @@ -227,7 +227,7 @@ public async Task BuilderSelectClauseAsync() await connection.DeleteAllAsync().ConfigureAwait(false); var rand = new Random(8675309); - var data = new List(); + var data = new List(100); for (var i = 0; i < 100; i++) { var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; @@ -296,7 +296,7 @@ private async Task InsertHelperAsync(Func, T> helper) { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); @@ -334,7 +334,7 @@ private async Task UpdateHelperAsync(Func, T> helper) { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); @@ -379,7 +379,7 @@ private async Task DeleteHelperAsync(Func, T> helper) { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); @@ -404,7 +404,7 @@ public async Task GetAllAsync() { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/Dapper.Tests.Contrib/TestSuite.cs index 11eeb7b01..71dbd8adf 100644 --- a/Dapper.Tests.Contrib/TestSuite.cs +++ b/Dapper.Tests.Contrib/TestSuite.cs @@ -130,7 +130,7 @@ public void TypeWithGenericParameterCanBeInserted() Assert.Single(connection.GetAll>()); - var objectsToInsert = new List> + var objectsToInsert = new List>(2) { new GenericType { @@ -380,7 +380,7 @@ private void InsertHelper(Func, T> helper) { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); @@ -418,7 +418,7 @@ private void UpdateHelper(Func, T> helper) { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); @@ -463,7 +463,7 @@ private void DeleteHelper(Func, T> helper) { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); @@ -588,7 +588,7 @@ public void GetAll() { const int numberOfEntities = 10; - var users = new List(); + var users = new List(numberOfEntities); for (var i = 0; i < numberOfEntities; i++) users.Add(new User { Name = "User " + i, Age = i }); @@ -684,7 +684,7 @@ public void BuilderSelectClause() using (var connection = GetOpenConnection()) { var rand = new Random(8675309); - var data = new List(); + var data = new List(100); for (int i = 0; i < 100; i++) { var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; diff --git a/Dapper.Tests/MiscTests.cs b/Dapper.Tests/MiscTests.cs index 857dcf9a2..bb5871210 100644 --- a/Dapper.Tests/MiscTests.cs +++ b/Dapper.Tests/MiscTests.cs @@ -301,7 +301,7 @@ 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 + 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"} diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index 418299f47..cfdb1fec0 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -109,7 +109,7 @@ public void AddParameters(IDbCommand command, SqlMapper.Identity identity) AddStructured(command, number_list); } } - + private class IntCustomParam : SqlMapper.ICustomQueryParameter { private readonly IEnumerable numbers; @@ -564,7 +564,7 @@ public void SO29533765_DataTableParametersViaDynamicParameters() var table = new DataTable { TableName = "MyTVPType", Columns = { { "id", typeof(int) } }, Rows = { { 1 }, { 2 }, { 3 } } }; table.SetTypeName(table.TableName); // per SO29533765 - IDictionary args = new Dictionary + IDictionary args = new Dictionary(1) { ["ids"] = table }; @@ -841,7 +841,7 @@ public void TestAppendingAnonClasses() [Fact] public void TestAppendingADictionary() { - var dictionary = new Dictionary + var dictionary = new Dictionary(2) { ["A"] = 1, ["B"] = "two" @@ -891,7 +891,7 @@ public void TestAppendingAListAsDictionary() { var p = new DynamicParameters(); var list = new int[] { 1, 2, 3 }; - var args = new Dictionary { ["ids"] = list }; + var args = new Dictionary(1) { ["ids"] = list }; p.AddDynamicParams(args); var result = connection.Query("select * from (select 1 A union all select 2 union all select 3) X where A in @ids", p).ToList(); @@ -1187,7 +1187,7 @@ public void SO25297173_DynamicIn() insert @table values(6); insert @table values(7); SELECT value FROM @table WHERE value IN @myIds"; - var queryParams = new Dictionary + var queryParams = new Dictionary(1) { ["myIds"] = new[] { 5, 6 } }; @@ -1224,7 +1224,7 @@ public void Test_AddDynamicParametersRepeatedIfParamTypeIsDbStiringShouldWork() [Fact] public void AllowIDictionaryParameters() { - var parameters = new Dictionary + var parameters = new Dictionary(1) { ["param1"] = 0 }; diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index e222b3156..f5d458377 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -165,7 +165,7 @@ where pair.Value > 1 static SqlMapper() { - typeMap = new Dictionary + typeMap = new Dictionary(37) { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, @@ -380,7 +380,7 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType && typeof(IEnumerable).IsAssignableFrom(type)) { var argTypes = type.GetGenericArguments(); - if(typeof(IDataRecord).IsAssignableFrom(argTypes[0])) + if (typeof(IDataRecord).IsAssignableFrom(argTypes[0])) { try { @@ -2438,7 +2438,7 @@ internal static Action CreateParamInfoGenerator(Identity ide } else { // might still all be accounted for; check the hard way - var positionByName = new Dictionary(StringComparer.OrdinalIgnoreCase); + var positionByName = new Dictionary(ctorParams.Length, StringComparer.OrdinalIgnoreCase); foreach (var param in ctorParams) { positionByName[param.Name] = param.Position; From 601a5c28fb39ae8a1f4889dfb65f21f556e27df1 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 2 May 2020 10:19:16 -0400 Subject: [PATCH 126/312] Fix for #1202 Behavior is expected, but doc isn't clear - clarifying! --- Dapper.Contrib/Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Contrib/Readme.md b/Dapper.Contrib/Readme.md index eeeb0838f..773ab6ff3 100644 --- a/Dapper.Contrib/Readme.md +++ b/Dapper.Contrib/Readme.md @@ -118,7 +118,7 @@ Special Attributes ---------- Dapper.Contrib makes use of some optional attributes: -* `[Table("Tablename")]` - use another table name instead of the name of the class +* `[Table("Tablename")]` - use another table name instead of the (by default pluralized) name of the class ```csharp [Table ("emps")] From f486848e88a240708b473bdc67d7a12bc6586809 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 2 May 2020 11:12:58 -0400 Subject: [PATCH 127/312] Update benchmarks (#1451) This fixes things for `netcoreapp3.1` and fixes the outstanding #1205. --- .../Benchmarks.Massive.cs | 1 + .../Benchmarks.PetaPoco.cs | 1 + Dapper.Tests.Performance/Benchmarks.cs | 8 +- Dapper.Tests.Performance/Config.cs | 28 +++--- .../Dapper.Tests.Performance.csproj | 3 +- Readme.md | 85 +++++++++---------- 6 files changed, 67 insertions(+), 59 deletions(-) diff --git a/Dapper.Tests.Performance/Benchmarks.Massive.cs b/Dapper.Tests.Performance/Benchmarks.Massive.cs index 98a63d953..a0ee11e10 100644 --- a/Dapper.Tests.Performance/Benchmarks.Massive.cs +++ b/Dapper.Tests.Performance/Benchmarks.Massive.cs @@ -14,6 +14,7 @@ public class MassiveBenchmarks : BenchmarkBase public void Setup() { BaseSetup(); + RegisterSqlFactory(); _model = new DynamicModel(ConnectionString); } diff --git a/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs b/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs index 74fe362ef..1a3c99735 100644 --- a/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs +++ b/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs @@ -14,6 +14,7 @@ public class PetaPocoBenchmarks : BenchmarkBase public void Setup() { BaseSetup(); + RegisterSqlFactory(); _db = new Database(ConnectionString, "System.Data.SqlClient"); _db.OpenSharedConnection(); _dbFast = new Database(ConnectionString, "System.Data.SqlClient"); diff --git a/Dapper.Tests.Performance/Benchmarks.cs b/Dapper.Tests.Performance/Benchmarks.cs index f02f02952..aa5effb53 100644 --- a/Dapper.Tests.Performance/Benchmarks.cs +++ b/Dapper.Tests.Performance/Benchmarks.cs @@ -1,5 +1,4 @@ using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Order; using System; using System.Configuration; using System.Data.SqlClient; @@ -22,6 +21,13 @@ protected void BaseSetup() _connection.Open(); } + protected void RegisterSqlFactory() + { +#if NETCOREAPP + System.Data.Common.DbProviderFactories.RegisterFactory("System.Data.SqlClient", SqlClientFactory.Instance); +#endif + } + protected void Step() { i++; diff --git a/Dapper.Tests.Performance/Config.cs b/Dapper.Tests.Performance/Config.cs index 981b2c926..b25eb3b82 100644 --- a/Dapper.Tests.Performance/Config.cs +++ b/Dapper.Tests.Performance/Config.cs @@ -16,24 +16,24 @@ public class Config : ManualConfig public Config() { - Add(ConsoleLogger.Default); + AddLogger(ConsoleLogger.Default); - Add(CsvExporter.Default); - Add(MarkdownExporter.GitHub); - Add(HtmlExporter.Default); + AddExporter(CsvExporter.Default); + AddExporter(MarkdownExporter.GitHub); + AddExporter(HtmlExporter.Default); var md = MemoryDiagnoser.Default; - Add(md); - Add(new ORMColum()); - Add(TargetMethodColumn.Method); - Add(new ReturnColum()); - Add(StatisticColumn.Mean); - Add(StatisticColumn.StdDev); - Add(StatisticColumn.Error); - Add(BaselineRatioColumn.RatioMean); - Add(DefaultColumnProviders.Metrics); + AddDiagnoser(md); + AddColumn(new ORMColum()); + AddColumn(TargetMethodColumn.Method); + AddColumn(new ReturnColum()); + AddColumn(StatisticColumn.Mean); + AddColumn(StatisticColumn.StdDev); + AddColumn(StatisticColumn.Error); + AddColumn(BaselineRatioColumn.RatioMean); + AddColumnProvider(DefaultColumnProviders.Metrics); - Add(Job.ShortRun + AddJob(Job.ShortRun .WithLaunchCount(1) .WithWarmupCount(2) .WithUnrollFactor(Iterations) diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 060b8b050..8653da0f6 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -13,7 +13,7 @@ - + @@ -27,6 +27,7 @@ + diff --git a/Readme.md b/Readme.md index 78ba15b0c..60892c764 100644 --- a/Readme.md +++ b/Readme.md @@ -120,52 +120,51 @@ dotnet run -p .\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * -- ``` Output from the latest run is: ``` ini -BenchmarkDotNet=v0.11.1, OS=Windows 10.0.17134.254 (1803/April2018Update/Redstone4) +BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.208 (2004/?/20H1) Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores -Frequency=2742188 Hz, Resolution=364.6723 ns, Timer=TSC - [Host] : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 - ShortRun : .NET Framework 4.7.2 (CLR 4.0.30319.42000), 64bit RyuJIT-v4.7.3163.0 +.NET Core SDK=3.1.201 + [Host] : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT + ShortRun : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT ``` -| ORM | Method | Return | Mean | Gen 0 | Gen 1 | Gen 2 | Allocated | -|------------- |------------------------------ |-------- |------------:|--------:|-------:|-------:|----------:| -| LINQ to DB | 'First (Compiled)' | Post | 78.75 us | 0.7500 | - | - | 2.66 KB | -| LINQ to DB | Query<T> | Post | 80.38 us | 2.1250 | - | - | 6.87 KB | -| Hand Coded | SqlCommand | Post | 87.16 us | 2.5000 | 1.0000 | 0.2500 | 12.24 KB | -| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 87.80 us | 4.3750 | - | - | 13.5 KB | -| Belgrade | ExecuteReader | Post | 87.85 us | 3.6250 | 0.7500 | - | 11.27 KB | -| Dapper | QueryFirstOrDefault<T> | Post | 91.51 us | 2.8750 | 0.8750 | 0.2500 | 13.46 KB | -| Hand Coded | DataTable | dynamic | 91.74 us | 2.2500 | 0.6250 | - | 12.45 KB | -| Dapper | 'Query<T> (buffered)' | Post | 94.05 us | 2.8750 | 0.8750 | 0.2500 | 13.79 KB | -| Dapper | 'Query<dynamic> (buffered)' | dynamic | 95.25 us | 2.5000 | 1.0000 | 0.2500 | 13.87 KB | -| Massive | 'Query (dynamic)' | dynamic | 96.18 us | 3.2500 | 0.8750 | 0.3750 | 14.19 KB | -| PetaPoco | 'Fetch<T> (Fast)' | Post | 96.57 us | 2.7500 | 0.8750 | 0.2500 | 13.65 KB | -| PetaPoco | Fetch<T> | Post | 97.62 us | 2.8750 | 0.8750 | 0.2500 | 14.59 KB | -| Dapper | 'Contrib Get<T>' | Post | 98.85 us | 2.8750 | 1.0000 | 0.2500 | 14.45 KB | -| ServiceStack | SingleById<T> | Post | 102.39 us | 3.1250 | 0.8750 | 0.3750 | 17.52 KB | -| LINQ to DB | First | Post | 103.54 us | 1.7500 | - | - | 5.51 KB | -| Susanoo | 'Execute<T> (Static)' | Post | 105.07 us | 2.8750 | 0.8750 | 0.2500 | 14.98 KB | -| Dashing | Get | Post | 105.80 us | 3.1250 | 0.8750 | 0.3750 | 14.82 KB | -| Susanoo | 'Execut<dynamic> (Static)' | dynamic | 109.26 us | 3.1250 | 0.8750 | 0.2500 | 14.97 KB | -| LINQ to SQL | 'First (Compiled)' | Post | 114.62 us | 3.1250 | - | - | 9.82 KB | -| Dapper | 'Query<T> (unbuffered)' | Post | 119.72 us | 3.1250 | 0.8750 | 0.2500 | 13.83 KB | -| Susanoo | 'Execute<dynamic> (Cache)' | dynamic | 124.02 us | 3.6250 | 1.0000 | 0.5000 | 20.4 KB | -| Susanoo | 'Execute<T> (Cache)' | Post | 126.92 us | 4.2500 | 1.0000 | 0.5000 | 20.88 KB | -| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 139.89 us | 2.5000 | 1.0000 | 0.2500 | 13.87 KB | -| EF 6 | SqlQuery | Post | 143.86 us | 5.2500 | 0.7500 | - | 27.86 KB | -| EF Core | 'First (Compiled)' | Post | 148.42 us | 5.0000 | - | - | 16.08 KB | -| NHibernate | Get<T> | Post | 196.88 us | 5.7500 | 1.0000 | - | 32.5 KB | -| EF Core | First | Post | 197.91 us | 6.5000 | - | - | 20.25 KB | -| NHibernate | HQL | Post | 207.84 us | 6.0000 | 0.7500 | - | 35 KB | -| EF Core | 'First (No Tracking)' | Post | 213.58 us | 4.2500 | 0.7500 | 0.2500 | 21.36 KB | -| EF Core | SqlQuery | Post | 247.25 us | 6.5000 | - | - | 20.56 KB | -| EF 6 | First | Post | 247.53 us | 15.5000 | - | - | 48.29 KB | -| NHibernate | Criteria | Post | 253.30 us | 13.2500 | 1.2500 | 0.2500 | 65.32 KB | -| EF 6 | 'First (No Tracking)' | Post | 265.80 us | 10.5000 | 1.0000 | - | 55.09 KB | -| LINQ to SQL | ExecuteQuery | Post | 284.74 us | 7.0000 | 1.0000 | 0.5000 | 42.33 KB | -| NHibernate | SQL | Post | 313.85 us | 26.5000 | 1.0000 | - | 101.01 KB | -| LINQ to SQL | First | Post | 968.14 us | 4.0000 | 1.0000 | - | 14.68 KB | -| NHibernate | LINQ | Post | 1,062.16 us | 11.0000 | 2.0000 | - | 62.37 KB | +| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | +|--------------- |------------------------------ |-------- |----------:|----------:|----------:|--------:|-------:|-------:|----------:| +| Belgrade | ExecuteReader | Post | 94.46 μs | 8.115 μs | 12.268 μs | 1.7500 | 0.5000 | - | 8.42 KB | +| Hand Coded | DataTable | dynamic | 105.43 μs | 0.998 μs | 1.508 μs | 3.0000 | - | - | 9.37 KB | +| Hand Coded | SqlCommand | Post | 106.58 μs | 1.191 μs | 1.801 μs | 1.5000 | 0.7500 | 0.1250 | 7.42 KB | +| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 119.52 μs | 1.320 μs | 2.219 μs | 3.6250 | - | - | 11.39 KB | +| Dapper | 'Query<dynamic> (buffered)' | dynamic | 119.93 μs | 1.943 μs | 2.937 μs | 2.3750 | 1.0000 | 0.2500 | 11.73 KB | +| Massive | 'Query (dynamic)' | dynamic | 120.31 μs | 1.340 μs | 2.252 μs | 2.2500 | 1.0000 | 0.1250 | 12.07 KB | +| Dapper | QueryFirstOrDefault<T> | Post | 121.57 μs | 1.564 μs | 2.364 μs | 1.7500 | 0.7500 | - | 11.35 KB | +| Dapper | 'Query<T> (buffered)' | Post | 121.67 μs | 2.913 μs | 4.403 μs | 1.8750 | 0.8750 | - | 11.65 KB | +| PetaPoco | 'Fetch<T> (Fast)' | Post | 124.91 μs | 4.015 μs | 6.747 μs | 2.0000 | 1.0000 | - | 11.5 KB | +| Mighty | Query<T> | Post | 125.23 μs | 2.932 μs | 4.433 μs | 2.2500 | 1.0000 | - | 12.6 KB | +| LINQ to DB | Query<T> | Post | 125.76 μs | 2.038 μs | 3.081 μs | 2.2500 | 0.7500 | 0.2500 | 10.62 KB | +| PetaPoco | Fetch<T> | Post | 127.48 μs | 4.283 μs | 6.475 μs | 2.0000 | 1.0000 | - | 12.18 KB | +| LINQ to DB | 'First (Compiled)' | Post | 128.89 μs | 2.627 μs | 3.971 μs | 2.5000 | 0.7500 | - | 10.92 KB | +| Mighty | Query<dynamic> | dynamic | 129.20 μs | 2.577 μs | 3.896 μs | 2.0000 | 1.0000 | - | 12.43 KB | +| Mighty | SingleFromQuery<T> | Post | 129.41 μs | 2.094 μs | 3.166 μs | 2.2500 | 1.0000 | - | 12.6 KB | +| Mighty | SingleFromQuery<dynamic> | dynamic | 130.59 μs | 2.432 μs | 3.677 μs | 2.0000 | 1.0000 | - | 12.43 KB | +| Dapper | 'Contrib Get<T>' | Post | 134.74 μs | 1.816 μs | 2.746 μs | 2.5000 | 1.0000 | 0.2500 | 12.29 KB | +| ServiceStack | SingleById<T> | Post | 135.01 μs | 1.213 μs | 2.320 μs | 3.0000 | 1.0000 | 0.2500 | 15.27 KB | +| LINQ to DB | First | Post | 151.87 μs | 3.826 μs | 5.784 μs | 3.0000 | 1.0000 | 0.2500 | 13.97 KB | +| EF 6 | SqlQuery | Post | 171.00 μs | 1.460 μs | 2.791 μs | 3.7500 | 1.0000 | - | 23.67 KB | +| DevExpress.XPO | GetObjectByKey<T> | Post | 172.36 μs | 3.758 μs | 5.681 μs | 5.5000 | 1.2500 | - | 29.06 KB | +| Dapper | 'Query<T> (unbuffered)' | Post | 174.40 μs | 3.296 μs | 4.983 μs | 2.0000 | 1.0000 | - | 11.77 KB | +| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 174.45 μs | 1.988 μs | 3.340 μs | 2.0000 | 1.0000 | - | 11.81 KB | +| DevExpress.XPO | FindObject<T> | Post | 181.76 μs | 5.554 μs | 9.333 μs | 8.0000 | - | - | 27.15 KB | +| DevExpress.XPO | Query<T> | Post | 189.81 μs | 4.187 μs | 8.004 μs | 10.0000 | - | - | 31.61 KB | +| EF Core | 'First (Compiled)' | Post | 199.72 μs | 3.983 μs | 7.616 μs | 4.5000 | - | - | 13.8 KB | +| NHibernate | Get<T> | Post | 248.71 μs | 6.604 μs | 11.098 μs | 5.0000 | 1.0000 | - | 29.79 KB | +| EF Core | First | Post | 253.20 μs | 3.033 μs | 5.097 μs | 5.5000 | - | - | 17.7 KB | +| NHibernate | HQL | Post | 258.70 μs | 11.716 μs | 17.712 μs | 5.0000 | 1.0000 | - | 32.1 KB | +| EF Core | SqlQuery | Post | 268.89 μs | 19.349 μs | 32.516 μs | 6.0000 | - | - | 18.5 KB | +| EF 6 | First | Post | 278.46 μs | 12.094 μs | 18.284 μs | 13.5000 | - | - | 44.18 KB | +| EF Core | 'First (No Tracking)' | Post | 280.88 μs | 8.192 μs | 13.765 μs | 3.0000 | 0.5000 | - | 19.38 KB | +| NHibernate | Criteria | Post | 304.90 μs | 2.232 μs | 4.267 μs | 11.0000 | 1.0000 | - | 60.29 KB | +| EF 6 | 'First (No Tracking)' | Post | 316.55 μs | 7.667 μs | 11.592 μs | 8.5000 | 1.0000 | - | 50.95 KB | +| NHibernate | SQL | Post | 335.41 μs | 3.111 μs | 4.703 μs | 19.0000 | 1.0000 | - | 78.86 KB | +| NHibernate | LINQ | Post | 807.79 μs | 27.207 μs | 45.719 μs | 8.0000 | 2.0000 | - | 53.65 KB | Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (Ctrl+F5). From 3371bf2b7f1a9c2938b9a33f381cb027036fb431 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 7 May 2020 07:21:39 -0400 Subject: [PATCH 128/312] Normalize deserialization errors for bad data (#1453) This normalizes all the cases of trying to set null to non-null things and giving a better exception when it happens. Today when a user does something like this: ```cs conn.QueryFirstOrDefault("Select null;"); ``` ...we'll get a null ref in the `(T)val;` portion of the deserialization pass. It's not very intuitive as to what's gone wrong. The same is true for value tuples and all `Query*()` methods. However, we handle this in a much friendlier way in the generated type deserializers (already, in master) with a message like: > Error parsing column 1 (Foo=bar - String) This gives the type we _actually_ got for the column and the position it was in. This PR improves the case for anything from `Query` to `QueryFirst`, etc. We could be more intuitive _if we passed the expected type in_ (which we don't today via the IL Path), or if we can come up with a generic error message along the lines of "... - use a nullable type (e.g. `int?` instead of `int`) if null is expected.". Or something like that - thoughts? We're using `DataException` because that's what we're already doing on type deserialization in the default paths. That's the generic exception type that'll always work (it's not always a cast or parsing exception...it could be either)...and since it's already in use, sticking with it here. Overall changes: - DRYs up `QueryRowImpl` and `QueryRowAsync` (single row paths) - into `ReadRow` - DRYs up value setting code between `Query`, `QueryAsync`, `QueryRowImpl`, `QueryRowAsync` - into `GetValue` - When trying to set a null to a non-nullable type, it'll throw a much better exception. - Adds tests to ensure these error messages are consistent. Theoretical breaking change: - Someone's doing this and catching null ref exceptions or string format exceptions. If that's the case, we'll change their exception expectations here - I think in net we should still do this. Related to: - #567 - #1421 - #1440 ### TODO - [ ] ValueTuple support - these are handled in yet another path and still throw a different casting error. Note: they won't throw a null error because that's a column and we won't set it - unless `.ApplyNullValues` is set in settings. ### Benchmarks: ```ini BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.208 (2004/?/20H1) Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=3.1.201 [Host] : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT ShortRun : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT ``` #### Before (3 runs) | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | |------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:| | Dapper | QueryFirstOrDefault | dynamic | 117.4 us | 1.62 us | 2.46 us | 3.6250 | - | - | 11.39 KB | | Dapper | 'Query (buffered)' | Post | 119.4 us | 0.58 us | 0.88 us | 1.8750 | 0.8750 | - | 11.65 KB | | Dapper | QueryFirstOrDefault | Post | 120.7 us | 0.78 us | 1.30 us | 1.8750 | 0.8750 | - | 11.35 KB | | Dapper | 'Query (buffered)' | dynamic | 125.0 us | 2.62 us | 4.41 us | 2.5000 | 1.0000 | 0.2500 | 11.73 KB | | Dapper | 'Contrib Get' | Post | 127.8 us | 3.19 us | 4.82 us | 2.5000 | 1.0000 | 0.2500 | 12.29 KB | | Dapper | 'Query (unbuffered)' | Post | 160.8 us | 1.19 us | 2.28 us | 2.0000 | 1.0000 | - | 11.77 KB | | Dapper | 'Query (unbuffered)' | dynamic | 170.6 us | 1.71 us | 2.58 us | 2.0000 | 1.0000 | - | 11.81 KB | | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | |------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:| | Dapper | QueryFirstOrDefault | dynamic | 114.5 us | 1.81 us | 2.73 us | 3.6250 | - | - | 11.39 KB | | Dapper | QueryFirstOrDefault | Post | 117.9 us | 0.93 us | 1.56 us | 1.8750 | 0.8750 | - | 11.35 KB | | Dapper | 'Query (buffered)' | dynamic | 121.3 us | 1.52 us | 2.55 us | 2.5000 | 1.0000 | 0.2500 | 11.73 KB | | Dapper | 'Query (buffered)' | Post | 122.7 us | 1.09 us | 2.08 us | 2.0000 | 1.0000 | - | 11.65 KB | | Dapper | 'Contrib Get' | Post | 127.9 us | 1.40 us | 2.12 us | 2.5000 | 0.7500 | 0.2500 | 12.29 KB | | Dapper | 'Query (unbuffered)' | Post | 166.9 us | 1.02 us | 1.71 us | 2.0000 | 1.0000 | - | 11.77 KB | | Dapper | 'Query (unbuffered)' | dynamic | 170.1 us | 2.19 us | 3.31 us | 2.0000 | 1.0000 | - | 11.81 KB | | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | |------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:| | Dapper | QueryFirstOrDefault | dynamic | 115.5 us | 3.14 us | 4.75 us | 3.6250 | - | - | 11.39 KB | | Dapper | QueryFirstOrDefault | Post | 121.5 us | 4.29 us | 7.21 us | 1.8750 | 0.8750 | - | 11.35 KB | | Dapper | 'Query (buffered)' | Post | 123.9 us | 2.16 us | 3.27 us | 2.0000 | 1.0000 | - | 11.65 KB | | Dapper | 'Query (buffered)' | dynamic | 127.0 us | 3.65 us | 5.52 us | 2.3750 | 0.8750 | 0.3750 | 11.73 KB | | Dapper | 'Contrib Get' | Post | 130.0 us | 3.18 us | 4.80 us | 2.5000 | 0.7500 | 0.2500 | 12.29 KB | | Dapper | 'Query (unbuffered)' | dynamic | 166.7 us | 1.39 us | 2.33 us | 2.7500 | 1.0000 | 0.2500 | 11.81 KB | | Dapper | 'Query (unbuffered)' | Post | 170.6 us | 2.18 us | 3.29 us | 2.2500 | 1.0000 | 0.2500 | 11.77 KB | #### After (3 runs): | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | |------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:| | Dapper | QueryFirstOrDefault | dynamic | 113.2 us | 0.45 us | 0.75 us | 3.6250 | - | - | 11.39 KB | | Dapper | 'Query (buffered)' | dynamic | 118.7 us | 1.97 us | 2.98 us | 1.8750 | 0.8750 | - | 11.72 KB | | Dapper | QueryFirstOrDefault | Post | 119.6 us | 1.12 us | 1.88 us | 1.8750 | 0.8750 | - | 11.35 KB | | Dapper | 'Query (buffered)' | Post | 121.8 us | 4.16 us | 6.28 us | 2.5000 | 1.0000 | 0.2500 | 11.64 KB | | Dapper | 'Contrib Get' | Post | 124.0 us | 1.21 us | 2.04 us | 2.0000 | 1.0000 | - | 12.28 KB | | Dapper | 'Query (unbuffered)' | Post | 162.4 us | 1.95 us | 3.27 us | 2.2500 | 1.0000 | 0.2500 | 11.76 KB | | Dapper | 'Query (unbuffered)' | dynamic | 173.0 us | 2.68 us | 4.50 us | 2.5000 | 1.0000 | 0.2500 | 11.8 KB | | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | |------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:| | Dapper | QueryFirstOrDefault | dynamic | 114.1 us | 1.42 us | 2.15 us | 3.6250 | - | - | 11.39 KB | | Dapper | QueryFirstOrDefault | Post | 120.4 us | 1.23 us | 1.87 us | 1.8750 | 0.8750 | - | 11.35 KB | | Dapper | 'Query (buffered)' | dynamic | 121.4 us | 3.90 us | 5.90 us | 1.8750 | 0.8750 | - | 11.72 KB | | Dapper | 'Query (buffered)' | Post | 125.7 us | 1.65 us | 2.50 us | 2.0000 | 1.0000 | - | 11.64 KB | | Dapper | 'Contrib Get' | Post | 132.1 us | 3.01 us | 4.55 us | 2.2500 | 0.7500 | - | 12.28 KB | | Dapper | 'Query (unbuffered)' | dynamic | 162.1 us | 2.29 us | 3.85 us | 2.7500 | 1.0000 | 0.2500 | 11.8 KB | | Dapper | 'Query (unbuffered)' | Post | 166.9 us | 2.19 us | 3.31 us | 2.2500 | 1.0000 | 0.2500 | 11.76 KB | | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | |------- |------------------------------ |-------- |---------:|--------:|--------:|-------:|-------:|-------:|----------:| | Dapper | QueryFirstOrDefault | Post | 114.1 us | 2.80 us | 4.23 us | 1.8750 | 0.8750 | - | 11.35 KB | | Dapper | 'Query (buffered)' | Post | 115.5 us | 1.30 us | 1.97 us | 2.6250 | 1.0000 | 0.3750 | 11.64 KB | | Dapper | QueryFirstOrDefault | dynamic | 120.8 us | 2.99 us | 4.53 us | 3.6250 | - | - | 11.39 KB | | Dapper | 'Query (buffered)' | dynamic | 123.7 us | 3.61 us | 5.46 us | 2.0000 | 1.0000 | - | 11.72 KB | | Dapper | 'Contrib Get' | Post | 129.2 us | 2.79 us | 4.22 us | 2.0000 | 1.0000 | - | 12.28 KB | | Dapper | 'Query (unbuffered)' | Post | 163.0 us | 3.67 us | 6.17 us | 2.2500 | 1.0000 | 0.2500 | 11.76 KB | | Dapper | 'Query (unbuffered)' | dynamic | 167.2 us | 0.73 us | 1.10 us | 2.2500 | 1.0000 | - | 11.8 KB | ...in short: it's within the jitter on this laptop and no difference is discernable. It's worth looking at for performance because these are hot paths. I want to de-dupe the repeated code here, but given the hot path nature whether it inlines is important. --- Dapper.Tests/MiscTests.cs | 85 +++++++++++++++++++++++++++++++++++++++ Dapper/SqlMapper.Async.cs | 31 ++------------ Dapper/SqlMapper.cs | 83 +++++++++++++++++++++++++------------- 3 files changed, 144 insertions(+), 55 deletions(-) diff --git a/Dapper.Tests/MiscTests.cs b/Dapper.Tests/MiscTests.cs index bb5871210..1407d84bd 100644 --- a/Dapper.Tests/MiscTests.cs +++ b/Dapper.Tests/MiscTests.cs @@ -167,6 +167,91 @@ public void Test_Single_First_Default() 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=)"); + // 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() { diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index acb0d9565..f52ba355c 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -437,14 +437,7 @@ private static async Task> QueryAsync(this IDbConnection cnn, while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { object val = func(reader); - if (val == null || val is T) - { - buffer.Add((T)val); - } - else - { - buffer.Add((T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture)); - } + buffer.Add(GetValue(reader, effectiveType, val)); } while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } command.OnCompleted(); @@ -484,29 +477,11 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false); - T result = default(T); + T result = default; if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { - var tuple = info.Deserializer; - int hash = GetColumnHash(reader); - if (tuple.Func == null || tuple.Hash != hash) - { - tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); - if (command.AddToCache) SetQueryCache(identity, info); - } - - var func = tuple.Func; + result = ReadRow(info, identity, ref command, effectiveType, reader); - object val = func(reader); - if (val == null || val is T) - { - result = (T)val; - } - else - { - var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; - result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); - } if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore rows after the first */ } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index f5d458377..d0493a36c 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -12,6 +12,7 @@ using System.Linq; using System.Reflection; using System.Reflection.Emit; +using System.Runtime.CompilerServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -1096,14 +1097,7 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini while (reader.Read()) { object val = func(reader); - if (val == null || val is T) - { - yield return (T)val; - } - else - { - yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); - } + yield return GetValue(reader, effectiveType, val); } while (reader.NextResult()) { /* ignore subsequent result sets */ } // happy path; close the reader cleanly - no @@ -1179,31 +1173,14 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow); wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader - T result = default(T); + T result = default; if (reader.Read() && reader.FieldCount != 0) { // with the CloseConnection flag, so the reader will deal with the connection; we // still need something in the "finally" to ensure that broken SQL still results // in the connection closing itself - var tuple = info.Deserializer; - int hash = GetColumnHash(reader); - if (tuple.Func == null || tuple.Hash != hash) - { - tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); - if (command.AddToCache) SetQueryCache(identity, info); - } + result = ReadRow(info, identity, ref command, effectiveType, reader); - var func = tuple.Func; - object val = func(reader); - if (val == null || val is T) - { - result = (T)val; - } - else - { - var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; - result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); - } if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); while (reader.Read()) { /* ignore subsequent rows */ } } @@ -1236,6 +1213,53 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti } } + /// + /// Shared value deserilization path for QueryRowImpl and QueryRowAsync + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T ReadRow(CacheInfo info, Identity identity, ref CommandDefinition command, Type effectiveType, IDataReader reader) + { + var tuple = info.Deserializer; + int hash = GetColumnHash(reader); + if (tuple.Func == null || tuple.Hash != hash) + { + tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); + if (command.AddToCache) SetQueryCache(identity, info); + } + + var func = tuple.Func; + object val = func(reader); + return GetValue(reader, effectiveType, val); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T GetValue(IDataReader reader, Type effectiveType, object val) + { + if (val is T tVal) + { + return tVal; + } + else if (val == null && (!effectiveType.IsValueType || Nullable.GetUnderlyingType(effectiveType) != null)) + { + return default; + } + else + { + try + { + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } + catch (Exception ex) + { +#pragma warning disable CS0618 // Type or member is obsolete + ThrowDataException(ex, 0, reader, val); +#pragma warning restore CS0618 // Type or member is obsolete + return default; // For the compiler - we've already thrown + } + } + } + /// /// Perform a multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . @@ -3619,6 +3643,11 @@ public static void ThrowDataException(Exception ex, int index, IDataReader reade if (reader != null && index >= 0 && index < reader.FieldCount) { name = reader.GetName(index); + if (name == string.Empty) + { + // Otherwise we throw (=value) below, which isn't intuitive + name = "(Unnamed Column)"; + } try { if (value == null || value is DBNull) From d403fa5b5481291ca84aaf11ac4d61a1034d3b72 Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Thu, 7 May 2020 11:22:25 +0000 Subject: [PATCH 129/312] Removed excessive casts (#1452) Removed excessive casts --- Dapper/SqlMapper.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index d0493a36c..a3b11669e 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -646,7 +646,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => - Query(cnn, sql, param as object, transaction, buffered, commandTimeout, commandType); + Query(cnn, sql, param, transaction, buffered, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. @@ -659,7 +659,7 @@ public static IEnumerable Query(this IDbConnection cnn, string sql, obj /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryFirst(cnn, sql, param as object, transaction, commandTimeout, commandType); + QueryFirst(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. @@ -672,7 +672,7 @@ public static dynamic QueryFirst(this IDbConnection cnn, string sql, object para /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryFirstOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); + QueryFirstOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. @@ -685,7 +685,7 @@ public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, ob /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QuerySingle(cnn, sql, param as object, transaction, commandTimeout, commandType); + QuerySingle(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Return a dynamic object with properties matching the columns. @@ -698,7 +698,7 @@ public static dynamic QuerySingle(this IDbConnection cnn, string sql, object par /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QuerySingleOrDefault(cnn, sql, param as object, transaction, commandTimeout, commandType); + QuerySingleOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); /// /// Executes a query, returning the data typed as . From f4aec37711c33c845a79eebc0b93ebacaacc8ca8 Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Fri, 8 May 2020 22:01:02 +0000 Subject: [PATCH 130/312] Replace sync methods calls with async analogs (#1457) Replace sync methods calls with async analogs --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index c454fce50..8d5fd7511 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -313,7 +313,7 @@ public static async Task DeleteAsync(this IDbConnection connection, T e sb.AppendFormat("DELETE FROM {0} WHERE ", name); var adapter = GetFormatter(connection); - + for (var i = 0; i < allKeyProperties.Count; i++) { var property = allKeyProperties[i]; @@ -379,7 +379,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran var cmd = $"INSERT INTO {tableName} ({columnList}) values ({parameterList}); SELECT SCOPE_IDENTITY() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - var first = multi.Read().FirstOrDefault(); + var first = await multi.ReadFirstOrDefaultAsync().ConfigureAwait(false); if (first == null || first.id == null) return 0; var id = (int)first.id; @@ -531,7 +531,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - var id = (int)multi.Read().First().id; + var id = (int)(await multi.ReadFirstAsync().ConfigureAwait(false)).id; var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); if (pi.Length == 0) return id; From 2bc267f5293184f45ad7427a4b462d014e0a46db Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Sat, 9 May 2020 20:00:24 +0000 Subject: [PATCH 131/312] Fixes of grammar typos (#1458) Fixes of grammar typos --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 12 ++++++------ Dapper.Contrib/SqlMapperExtensions.cs | 16 ++++++++-------- Dapper.ProviderTools/DbConnectionExtensions.cs | 2 +- Dapper.ProviderTools/DbExceptionExtensions.cs | 4 ++-- Dapper.Rainbow/Database.Async.cs | 2 +- Dapper/CustomPropertyTypeMap.cs | 2 +- Dapper/DefaultTypeMap.cs | 2 +- Dapper/DynamicParameters.cs | 6 +++--- Dapper/SqlMapper.Settings.cs | 2 +- Dapper/SqlMapper.TypeHandler.cs | 2 +- Dapper/SqlMapper.cs | 6 +++--- 11 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 8d5fd7511..2c662dca3 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -34,13 +34,13 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i GetQueries[type.TypeHandle] = sql; } - var dynParms = new DynamicParameters(); - dynParms.Add("@id", id); + var dynParams = new DynamicParameters(); + dynParams.Add("@id", id); if (!type.IsInterface) - return (await connection.QueryAsync(sql, dynParms, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); + return (await connection.QueryAsync(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); - var res = (await connection.QueryAsync(sql, dynParms).ConfigureAwait(false)).FirstOrDefault() as IDictionary; + var res = (await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() as IDictionary; if (res == null) return null; @@ -68,7 +68,7 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i } /// - /// Returns a list of entites from table "Ts". + /// Returns a list of entities from table "Ts". /// Id of T must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension /// for optimal performance. @@ -499,7 +499,7 @@ public async Task InsertAsync(IDbConnection connection, IDbTransaction tran var results = await connection.QueryAsync(sb.ToString(), entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - // Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys + // Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys var id = 0; foreach (var p in propertyInfos) { diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index f6125e6ab..1196d1e5c 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -180,14 +180,14 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction GetQueries[type.TypeHandle] = sql; } - var dynParms = new DynamicParameters(); - dynParms.Add("@id", id); + var dynParams = new DynamicParameters(); + dynParams.Add("@id", id); T obj; if (type.IsInterface) { - var res = connection.Query(sql, dynParms).FirstOrDefault() as IDictionary; + var res = connection.Query(sql, dynParams).FirstOrDefault() as IDictionary; if (res == null) return null; @@ -213,16 +213,16 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction } else { - obj = connection.Query(sql, dynParms, transaction, commandTimeout: commandTimeout).FirstOrDefault(); + obj = connection.Query(sql, dynParams, transaction, commandTimeout: commandTimeout).FirstOrDefault(); } return obj; } /// - /// Returns a list of entites from table "Ts". + /// Returns a list of entities from table "Ts". /// Id of T must be marked with [Key] attribute. /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension - /// for optimal performance. + /// for optimal performance. /// /// Interface or type to create and populate /// Open SqlConnection @@ -1007,7 +1007,7 @@ public int Insert(IDbConnection connection, IDbTransaction transaction, int? com var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList(); - // Return the key by assinging the corresponding property in the object - by product is that it supports compound primary keys + // Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys var id = 0; foreach (var p in propertyInfos) { @@ -1094,7 +1094,7 @@ public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) } /// -/// The Firebase SQL adapeter. +/// The Firebase SQL adapter. /// public partial class FbAdapter : ISqlAdapter { diff --git a/Dapper.ProviderTools/DbConnectionExtensions.cs b/Dapper.ProviderTools/DbConnectionExtensions.cs index 05d21d5a7..0b6729d95 100644 --- a/Dapper.ProviderTools/DbConnectionExtensions.cs +++ b/Dapper.ProviderTools/DbConnectionExtensions.cs @@ -7,7 +7,7 @@ namespace Dapper.ProviderTools { /// - /// Helper utilties for working with database connections + /// Helper utilities for working with database connections /// public static class DbConnectionExtensions { diff --git a/Dapper.ProviderTools/DbExceptionExtensions.cs b/Dapper.ProviderTools/DbExceptionExtensions.cs index c14f1bc62..e69b25764 100644 --- a/Dapper.ProviderTools/DbExceptionExtensions.cs +++ b/Dapper.ProviderTools/DbExceptionExtensions.cs @@ -7,7 +7,7 @@ namespace Dapper.ProviderTools { /// - /// Helper utilties for working with database exceptions + /// Helper utilities for working with database exceptions /// public static class DbExceptionExtensions { @@ -17,7 +17,7 @@ public static class DbExceptionExtensions public static bool IsNumber(this DbException exception, int number) => exception != null && ByTypeHelpers.Get(exception.GetType()).IsNumber(exception, number); - + private sealed class ByTypeHelpers { private static readonly ConcurrentDictionary s_byType diff --git a/Dapper.Rainbow/Database.Async.cs b/Dapper.Rainbow/Database.Async.cs index ab42864d4..fcbc6a204 100644 --- a/Dapper.Rainbow/Database.Async.cs +++ b/Dapper.Rainbow/Database.Async.cs @@ -34,7 +34,7 @@ public partial class Table /// /// The Id of the record to update. /// The new record. - /// The number of affeced rows. + /// The number of affected rows. public Task UpdateAsync(TId id, dynamic data) { List paramNames = GetParamNames((object)data); diff --git a/Dapper/CustomPropertyTypeMap.cs b/Dapper/CustomPropertyTypeMap.cs index 9228db2b0..6771e359f 100644 --- a/Dapper/CustomPropertyTypeMap.cs +++ b/Dapper/CustomPropertyTypeMap.cs @@ -52,7 +52,7 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, /// Returns property based on selector strategy /// /// DataReader column name - /// Poperty member map + /// Property member map public SqlMapper.IMemberMap GetMember(string columnName) { var prop = _propertySelector(_type, columnName); diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index 8366ae5ee..b278250cb 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -147,7 +147,7 @@ public SqlMapper.IMemberMap GetMember(string columnName) var backingFieldName = "<" + columnName + ">k__BackingField"; // preference order is: - // exact match over underscre match, exact case over wrong case, backing fields over regular fields, match-inc-underscores over match-exc-underscores + // exact match over underscore match, exact case over wrong case, backing fields over regular fields, match-inc-underscores over match-exc-underscores var field = _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) ?? _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index 5982caa1f..ccbc9aa74 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -294,7 +294,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) } } - // note: most non-priveleged implementations would use: this.ReplaceLiterals(command); + // note: most non-privileged implementations would use: this.ReplaceLiterals(command); if (literals.Count != 0) SqlMapper.ReplaceLiterals(this, command, literals); } @@ -448,8 +448,8 @@ public DynamicParameters Output(T target, Expression> express cache[lookup] = setter; } - // Queue the preparation to be fired off when adding parameters to the DbCommand - MAKECALLBACK: + // Queue the preparation to be fired off when adding parameters to the DbCommand + MAKECALLBACK: (outputCallbacks ?? (outputCallbacks = new List())).Add(() => { // Finally, prep the parameter and attach the callback to it diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index 77cd462a4..0f5323591 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -90,7 +90,7 @@ public static void SetDefaults() public static bool PadListExpansions { get; set; } /// /// If set (non-negative), when performing in-list expansions of integer types ("where id in @ids", etc), switch to a string_split based - /// operation if there are this many elements or more. Note that this feautre requires SQL Server 2016 / compatibility level 130 (or above). + /// operation if there are this many elements or more. Note that this feature requires SQL Server 2016 / compatibility level 130 (or above). /// public static int InListStringSplitCount { get; set; } = -1; } diff --git a/Dapper/SqlMapper.TypeHandler.cs b/Dapper/SqlMapper.TypeHandler.cs index 7a712ee49..e8745985e 100644 --- a/Dapper/SqlMapper.TypeHandler.cs +++ b/Dapper/SqlMapper.TypeHandler.cs @@ -56,7 +56,7 @@ public abstract class StringTypeHandler : TypeHandler protected abstract T Parse(string xml); /// - /// Format an instace into a string (the instance will never be null) + /// Format an instance into a string (the instance will never be null) /// /// The string to format. protected abstract string Format(T xml); diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index a3b11669e..fc1860019 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2208,7 +2208,7 @@ private static bool TryStringSplit(ref IEnumerable list, int splitAt, stri } /// - /// OBSOLETE: For internal usage only. Sanitizes the paramter value with proper type casting. + /// OBSOLETE: For internal usage only. Sanitizes the parameter value with proper type casting. /// /// The value to sanitize. [Obsolete(ObsoleteInternalUsageOnly, false)] @@ -2261,7 +2261,7 @@ private static IEnumerable FilterParameters(IEnumerable /// The parameter lookup to do replacements with. - /// The command to repalce parameters in. + /// The command to replace parameters in. public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand command) { var tokens = GetLiteralTokens(command.CommandText); @@ -3012,7 +3012,7 @@ public static ITypeMap GetTypeMap(Type type) /// Set custom mapping for type deserializers /// /// Entity type to override - /// Mapping rules impementation, null to remove custom map + /// Mapping rules implementation, null to remove custom map public static void SetTypeMap(Type type, ITypeMap map) { if (type == null) From 4fb1ea29d490d13251b0135658ecc337aeb60cdb Mon Sep 17 00:00:00 2001 From: Damir Ainullin Date: Sun, 10 May 2020 10:52:06 +0000 Subject: [PATCH 132/312] Replace empty array with Array.Empty, add compound assignment, fix of grammar typo (#1459) Misc cleanup --- Dapper.Rainbow/Database.cs | 4 ++-- Dapper.Tests/ParameterTests.cs | 4 ++-- Dapper/CustomPropertyTypeMap.cs | 2 +- Dapper/DynamicParameters.cs | 6 +++--- Dapper/SqlMapper.cs | 20 ++++++++++---------- 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index d9ab4cb6f..d2a2c31c9 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -199,7 +199,7 @@ internal void InitDatabase(DbConnection connection, int commandTimeout) { _connection = connection; _commandTimeout = commandTimeout; - tableConstructor = tableConstructor ?? CreateTableConstructorForTable(); + tableConstructor ??= CreateTableConstructorForTable(); tableConstructor(this as TDatabase); } @@ -320,7 +320,7 @@ private bool TableExists(string name) name = name.Replace("[", ""); name = name.Replace("]", ""); - if (name.Contains(".")) + if (name.IndexOf('.') > 0) { var parts = name.Split('.'); if (parts.Length == 2) diff --git a/Dapper.Tests/ParameterTests.cs b/Dapper.Tests/ParameterTests.cs index cfdb1fec0..91dc8d6b4 100644 --- a/Dapper.Tests/ParameterTests.cs +++ b/Dapper.Tests/ParameterTests.cs @@ -197,8 +197,8 @@ public void PassInIntArray() public void PassInEmptyIntArray() { Assert.Equal( - new int[0], - connection.Query("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = new int[0] }) + Array.Empty(), + connection.Query("select * from (select 1 as Id union all select 2 union all select 3) as X where Id in @Ids", new { Ids = Array.Empty() }) ); } diff --git a/Dapper/CustomPropertyTypeMap.cs b/Dapper/CustomPropertyTypeMap.cs index 6771e359f..ececeeda9 100644 --- a/Dapper/CustomPropertyTypeMap.cs +++ b/Dapper/CustomPropertyTypeMap.cs @@ -29,7 +29,7 @@ public CustomPropertyTypeMap(Type type, Func propert /// DataReader column types /// Default constructor public ConstructorInfo FindConstructor(string[] names, Type[] types) => - _type.GetConstructor(new Type[0]); + _type.GetConstructor(Array.Empty()); /// /// Always returns null diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index ccbc9aa74..abc278630 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -55,7 +55,7 @@ public void AddDynamicParams(object param) var dictionary = obj as IEnumerable>; if (dictionary == null) { - templates = templates ?? new List(); + templates ??= new List(); templates.Add(obj); } else @@ -78,7 +78,7 @@ public void AddDynamicParams(object param) if (subDynamic.templates != null) { - templates = templates ?? new List(); + templates ??= new List(); foreach (var t in subDynamic.templates) { templates.Add(t); @@ -450,7 +450,7 @@ public DynamicParameters Output(T target, Expression> express // Queue the preparation to be fired off when adding parameters to the DbCommand MAKECALLBACK: - (outputCallbacks ?? (outputCallbacks = new List())).Add(() => + (outputCallbacks ??= new List()).Add(() => { // Finally, prep the parameter and attach the callback to it var targetMemberType = lastMemberAccess?.Type; diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index fc1860019..75b0bd7b4 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1132,7 +1132,7 @@ internal enum Row SingleOrDefault = 3 } - private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = new int[0]; + private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = Array.Empty(); private static void ThrowMultipleRows(Row row) { switch (row) @@ -1214,7 +1214,7 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti } /// - /// Shared value deserilization path for QueryRowImpl and QueryRowAsync + /// Shared value deserialization path for QueryRowImpl and QueryRowAsync /// [MethodImpl(MethodImplOptions.AggressiveInlining)] private static T ReadRow(CacheInfo info, Identity identity, ref CommandDefinition command, Type effectiveType, IDataReader reader) @@ -1429,7 +1429,7 @@ private static IEnumerable MultiMap MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize) { object param = command.Parameters; - identity = identity ?? new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); + identity ??= new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null; @@ -1499,7 +1499,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn } object param = command.Parameters; - identity = identity ?? new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); + identity ??= new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null; @@ -2386,8 +2386,8 @@ internal static IList GetLiteralTokens(string sql) public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); - private static bool IsValueTuple(Type type) => (type?.IsValueType == true - && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal)) + private static bool IsValueTuple(Type type) => (type?.IsValueType == true + && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal)) || (type != null && IsValueTuple(Nullable.GetUnderlyingType(type))); internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) @@ -2410,7 +2410,7 @@ internal static Action CreateParamInfoGenerator(Identity ide bool isStruct = type.IsValueType; var _sizeLocal = (LocalBuilder)null; - LocalBuilder GetSizeLocal() => _sizeLocal ?? (_sizeLocal = il.DeclareLocal(typeof(int))); + LocalBuilder GetSizeLocal() => _sizeLocal ??= il.DeclareLocal(typeof(int)); il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] LocalBuilder typedParameterLocal; @@ -3055,7 +3055,7 @@ public static Func GetTypeDeserializer( private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary locals, Type type, bool initAndLoad) { if (type == null) throw new ArgumentNullException(nameof(type)); - locals = locals ?? new Dictionary(); + locals ??= new Dictionary(); if (!locals.TryGetValue(type, out LocalBuilder found)) { found = il.DeclareLocal(type); @@ -3193,7 +3193,7 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataRea if (nullableUnderlyingType != null) { var nullableTupleConstructor = valueTupleType.GetConstructor(new[] { nullableUnderlyingType }); - + il.Emit(OpCodes.Newobj, nullableTupleConstructor); } @@ -3777,7 +3777,7 @@ private static string __ToStringRecycle(this StringBuilder obj) { if (obj == null) return ""; var s = obj.ToString(); - perThreadStringBuilderCache = perThreadStringBuilderCache ?? obj; + perThreadStringBuilderCache ??= obj; return s; } } From b17a989950090c43c958adf59982636335aff285 Mon Sep 17 00:00:00 2001 From: "Oleg V. Kozlyuk" Date: Tue, 12 May 2020 01:12:12 +0300 Subject: [PATCH 133/312] Add correct feature flag for ClickHouse Properly indicate array support for ClickHouse connections ClickHouse: https://clickhouse.tech/ ClickHouse .NET client: https://github.com/DarkWanderer/ClickHouse.Client --- Dapper/FeatureSupport.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Dapper/FeatureSupport.cs b/Dapper/FeatureSupport.cs index 402d86e48..089e846ca 100644 --- a/Dapper/FeatureSupport.cs +++ b/Dapper/FeatureSupport.cs @@ -10,7 +10,8 @@ internal class FeatureSupport { private static readonly FeatureSupport Default = new FeatureSupport(false), - Postgres = new FeatureSupport(true); + Postgres = new FeatureSupport(true), + ClickHouse = new FeatureSupport(true); /// /// Gets the feature set based on the passed connection @@ -20,6 +21,7 @@ public static FeatureSupport Get(IDbConnection connection) { string name = connection?.GetType().Name; if (string.Equals(name, "npgsqlconnection", StringComparison.OrdinalIgnoreCase)) return Postgres; + if (string.Equals(name, "clickhouseconnection", StringComparison.OrdinalIgnoreCase)) return ClickHouse; return Default; } From e723ceed16c0591503530dc9a7b5de5dc6b7db03 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 20 Jun 2020 09:40:54 -0400 Subject: [PATCH 134/312] master -> main changes --- Readme.md | 4 ++-- appveyor.yml | 4 ++-- docs/index.md | 2 +- version.json | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/Readme.md b/Readme.md index 60892c764..54a518fd6 100644 --- a/Readme.md +++ b/Readme.md @@ -114,7 +114,7 @@ Performance A key feature of Dapper is performance. The following metrics show how long it takes to execute a `SELECT` statement against a DB (in various config, each labeled) and map the data returned to objects. -The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests.Performance) (contributions welcome!) and can be run via: +The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/main/Dapper.Tests.Performance) (contributions welcome!) and can be run via: ```bash dotnet run -p .\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join ``` @@ -389,7 +389,7 @@ Dapper has no DB specific implementation details, it works across all .NET ADO p Do you have a comprehensive list of examples? --------------------- -Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/Dapper/tree/master/Dapper.Tests). +Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/Dapper/tree/main/Dapper.Tests). Who is using this? --------------------- diff --git a/appveyor.yml b/appveyor.yml index 44178cbdd..d91b9d196 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -55,14 +55,14 @@ deploy: - provider: NuGet server: https://www.myget.org/F/stackoverflow/api/v2 on: - branch: master + branch: main api_key: secure: P/UHxq2DEs0GI1SoDXDesHjRVsSVgdywz5vmsnhFQQY5aJgO3kP+QfhwfhXz19Rw symbol_server: https://www.myget.org/F/stackoverflow/symbols/api/v2/package - provider: NuGet server: https://www.myget.org/F/dapper/api/v2 on: - branch: master + branch: main api_key: secure: PV7ERAltWWLhy7AT2h+Vb5c1BM9/WFgvggb+rKyQ8hDg3fYqpZauYdidOOgt2lp4 symbol_server: https://www.myget.org/F/dapper/api/v2/package \ No newline at end of file diff --git a/docs/index.md b/docs/index.md index d702d3efe..abcdc515d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ ## Overview -A brief guide is available [on github](https://github.com/StackExchange/dapper-dot-net/blob/master/Readme.md) +A brief guide is available [on github](https://github.com/StackExchange/Dapper/blob/main/Readme.md) Questions on Stack Overflow should be tagged [`dapper`](https://stackoverflow.com/questions/tagged/dapper) diff --git a/version.json b/version.json index 5b13ff57a..9ceaa3b42 100644 --- a/version.json +++ b/version.json @@ -2,7 +2,7 @@ "version": "2.0", "assemblyVersion": "2.0.0.0", "publicReleaseRefSpec": [ - "^refs/heads/master$", + "^refs/heads/main$", "^refs/tags/v\\d+\\.\\d+" ], "nugetPackageVersion": { From cea7a012b0d4c504fb4c546f4b7679979dbd8093 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=B0=AD=E4=B9=9D=E9=BC=8E?= <109224573@qq.com> Date: Mon, 27 Jul 2020 13:57:20 +0800 Subject: [PATCH 135/312] Fix Syntax error --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 54a518fd6..e92462648 100644 --- a/Readme.md +++ b/Readme.md @@ -65,7 +65,7 @@ This method will execute SQL and return a dynamic list. Example usage: ```csharp -var rows = connection.Query("select 1 A, 2 B union all select 3, 4"); +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); From e47d819d4e3be933af97bde6b1b6f4f8c41fa534 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dogac=20Aky=C4=B1ld=C4=B1z?= Date: Fri, 31 Jul 2020 23:39:56 +0300 Subject: [PATCH 136/312] Fix Typo. (#1508) --- Dapper/SqlMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 75b0bd7b4..3bbd83161 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -712,7 +712,7 @@ public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, o /// The command timeout (in seconds). /// The type of command to execute. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) From d2877e04000e00e9b668eb7c89fc46242f6ba63f Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Fri, 31 Jul 2020 16:41:35 -0400 Subject: [PATCH 137/312] Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index e92462648..c5fd5aad9 100644 --- a/Readme.md +++ b/Readme.md @@ -65,7 +65,7 @@ This method will execute SQL and return a dynamic list. Example usage: ```csharp -var rows = connection.Query("select 1 A, 2 B union all select 3, 4").ToList(); +var rows = connection.Query("select 1 A, 2 B union all select 3, 4").AsList(); Assert.Equal(1, (int)rows[0].A); Assert.Equal(2, (int)rows[0].B); From e2ffcdd1a4007d4184917b05a647040a6cf304e2 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Tue, 18 Aug 2020 21:54:34 -0400 Subject: [PATCH 138/312] Ignore JetBrains files --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f7d0ca7ad..541ac51fb 100644 --- a/.gitignore +++ b/.gitignore @@ -19,4 +19,5 @@ TestResults/ Dapper.Tests/*.sdf Dapper.Tests/SqlServerTypes/ .dotnet/* -BenchmarkDotNet.Artifacts/ \ No newline at end of file +BenchmarkDotNet.Artifacts/ +.idea/ \ No newline at end of file From 78ed4b2077daed83bf284f9e8a05d98e9cb54190 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 11 Oct 2020 14:56:35 -0400 Subject: [PATCH 139/312] Tests: move into folders One of several moves to make this something we can dev on all platforms and build on Actions for v3 onwards. --- Build.csproj | 4 +- Readme.md | 2 +- .../Benchmarks.Belgrade.cs | 0 .../Benchmarks.Dapper.cs | 0 .../Benchmarks.Dashing.cs | 0 .../Benchmarks.EntityFramework.cs | 0 .../Benchmarks.EntityFrameworkCore.cs | 0 .../Benchmarks.HandCoded.cs | 0 .../Benchmarks.Linq2DB.cs | 0 .../Benchmarks.Linq2Sql.cs | 0 .../Benchmarks.Massive.cs | 0 .../Benchmarks.Mighty.cs | 0 .../Benchmarks.NHibernate.cs | 0 .../Benchmarks.PetaPoco.cs | 0 .../Benchmarks.ServiceStack.cs | 0 .../Benchmarks.Susanoo.cs | 0 .../Benchmarks.XPO.cs | 0 .../Dapper.Tests.Performance}/Benchmarks.cs | 0 .../Dapper.Tests.Performance}/Config.cs | 0 .../Dapper.Tests.Performance.csproj | 8 +--- .../Dashing/DashingConfiguration.cs | 0 .../Dapper.Tests.Performance}/Dashing/Post.cs | 0 .../EntityFramework/EFContext.cs | 0 .../EntityFrameworkCore/EFCoreContext.cs | 0 .../Helpers/ORMColum.cs | 0 .../Helpers/ReturnColum.cs | 0 .../Dapper.Tests.Performance}/LegacyTests.cs | 0 .../Linq2DB/ConnectionStringSettings.cs | 0 .../Linq2DB/Linq2DBContext.cs | 0 .../Linq2DB/Linq2DbSettings.cs | 0 .../Linq2Sql/DataClasses.dbml | 0 .../Linq2Sql/DataClasses.dbml.layout | 0 .../Linq2Sql/DataClasses.designer.cs | 0 .../Massive/Massive.cs | 0 .../NHibernate/NHibernateHelper.cs | 0 .../NHibernate/Post.hbm.xml | 0 .../NHibernate/hibernate.cfg.xml | 0 .../PetaPoco/PetaPoco.cs | 0 .../Dapper.Tests.Performance}/Post.cs | 0 .../Dapper.Tests.Performance}/Program.cs | 0 .../Soma/SomaConfig.cs | 0 .../SqlDataReaderHelper.cs | 0 .../Dapper.Tests.Performance}/XPO/Post.cs | 0 .../Dapper.Tests.Performance}/app.config | 0 benchmarks/Directory.Build.props | 33 ++++++++++++++++ .../Dapper.Tests.Contrib.csproj | 12 +----- .../Helpers/Attributes.cs | 0 .../Dapper.Tests.Contrib}/TestSuite.Async.cs | 0 .../Dapper.Tests.Contrib}/TestSuite.cs | 0 .../Dapper.Tests.Contrib}/TestSuites.cs | 0 .../Dapper.Tests}/App.config | 0 .../Dapper.Tests}/AsyncTests.cs | 0 .../Dapper.Tests}/ConstructorTests.cs | 0 .../Dapper.Tests}/Dapper.Tests.csproj | 12 ++---- .../Dapper.Tests}/DataReaderTests.cs | 0 .../Dapper.Tests}/DecimalTests.cs | 0 .../Dapper.Tests}/EnumTests.cs | 0 .../Dapper.Tests}/Helpers/Attributes.cs | 0 .../Dapper.Tests}/Helpers/Common.cs | 0 .../Helpers/SqlServerTypesLoader.cs | 0 .../Helpers/TransactedConnection.cs | 0 .../Dapper.Tests}/Helpers/XunitSkippable.cs | 0 .../Dapper.Tests}/LiteralTests.cs | 0 .../Dapper.Tests}/MiscTests.cs | 0 .../Dapper.Tests}/MultiMapTests.cs | 0 .../Dapper.Tests}/NullTests.cs | 0 .../Dapper.Tests}/ParameterTests.cs | 0 .../Dapper.Tests}/ProcedureTests.cs | 0 .../Dapper.Tests}/ProviderTests.cs | 0 .../Providers/EntityFrameworkTests.cs | 0 .../Dapper.Tests}/Providers/FirebirdTests.cs | 0 .../Dapper.Tests}/Providers/Linq2SqlTests.cs | 0 .../Dapper.Tests}/Providers/MySQLTests.cs | 0 .../Dapper.Tests}/Providers/OLDEBTests.cs | 0 .../Providers/PostgresqlTests.cs | 0 .../Dapper.Tests}/Providers/SqliteTests.cs | 0 .../Dapper.Tests}/QueryMultipleTests.cs | 0 .../Dapper.Tests}/SharedTypes/Address.cs | 0 .../Dapper.Tests}/SharedTypes/Bar1.cs | 0 .../Dapper.Tests}/SharedTypes/Category.cs | 0 .../Dapper.Tests}/SharedTypes/Comment.cs | 0 .../Dapper.Tests}/SharedTypes/Dog.cs | 0 .../Dapper.Tests}/SharedTypes/Enums.cs | 0 .../Dapper.Tests}/SharedTypes/Foo1.cs | 0 .../Dapper.Tests}/SharedTypes/HazNameId.cs | 0 .../Dapper.Tests}/SharedTypes/Index.cs | 0 .../Dapper.Tests}/SharedTypes/Person.cs | 0 .../Dapper.Tests}/SharedTypes/Post.cs | 0 .../Dapper.Tests}/SharedTypes/Product.cs | 0 .../Dapper.Tests}/SharedTypes/ReviewBoard.cs | 0 .../Dapper.Tests}/SharedTypes/ShortEnum.cs | 0 .../Dapper.Tests}/SharedTypes/SomeType.cs | 0 .../Dapper.Tests}/SharedTypes/User.cs | 0 .../Dapper.Tests}/SqlBuilderTests.cs | 0 .../Dapper.Tests}/TestBase.cs | 0 .../Dapper.Tests}/TransactionTests.cs | 0 .../Dapper.Tests}/TupleTests.cs | 0 .../Dapper.Tests}/TypeHandlerTests.cs | 0 .../Dapper.Tests}/XmlTests.cs | 0 tests/Directory.Build.props | 39 +++++++++++++++++++ tests/Directory.Build.targets | 5 +++ 101 files changed, 88 insertions(+), 27 deletions(-) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Belgrade.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Dapper.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Dashing.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.EntityFramework.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.EntityFrameworkCore.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.HandCoded.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Linq2DB.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Linq2Sql.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Massive.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Mighty.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.NHibernate.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.PetaPoco.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.ServiceStack.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.Susanoo.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.XPO.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Benchmarks.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Config.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Dapper.Tests.Performance.csproj (86%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Dashing/DashingConfiguration.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Dashing/Post.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/EntityFramework/EFContext.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/EntityFrameworkCore/EFCoreContext.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Helpers/ORMColum.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Helpers/ReturnColum.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/LegacyTests.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Linq2DB/ConnectionStringSettings.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Linq2DB/Linq2DBContext.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Linq2DB/Linq2DbSettings.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Linq2Sql/DataClasses.dbml (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Linq2Sql/DataClasses.dbml.layout (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Linq2Sql/DataClasses.designer.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Massive/Massive.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/NHibernate/NHibernateHelper.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/NHibernate/Post.hbm.xml (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/NHibernate/hibernate.cfg.xml (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/PetaPoco/PetaPoco.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Post.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Program.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/Soma/SomaConfig.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/SqlDataReaderHelper.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/XPO/Post.cs (100%) rename {Dapper.Tests.Performance => benchmarks/Dapper.Tests.Performance}/app.config (100%) create mode 100644 benchmarks/Directory.Build.props rename {Dapper.Tests.Contrib => tests/Dapper.Tests.Contrib}/Dapper.Tests.Contrib.csproj (51%) rename {Dapper.Tests.Contrib => tests/Dapper.Tests.Contrib}/Helpers/Attributes.cs (100%) rename {Dapper.Tests.Contrib => tests/Dapper.Tests.Contrib}/TestSuite.Async.cs (100%) rename {Dapper.Tests.Contrib => tests/Dapper.Tests.Contrib}/TestSuite.cs (100%) rename {Dapper.Tests.Contrib => tests/Dapper.Tests.Contrib}/TestSuites.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/App.config (100%) rename {Dapper.Tests => tests/Dapper.Tests}/AsyncTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/ConstructorTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Dapper.Tests.csproj (69%) rename {Dapper.Tests => tests/Dapper.Tests}/DataReaderTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/DecimalTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/EnumTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Helpers/Attributes.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Helpers/Common.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Helpers/SqlServerTypesLoader.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Helpers/TransactedConnection.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Helpers/XunitSkippable.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/LiteralTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/MiscTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/MultiMapTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/NullTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/ParameterTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/ProcedureTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/ProviderTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Providers/EntityFrameworkTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Providers/FirebirdTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Providers/Linq2SqlTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Providers/MySQLTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Providers/OLDEBTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Providers/PostgresqlTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/Providers/SqliteTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/QueryMultipleTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Address.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Bar1.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Category.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Comment.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Dog.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Enums.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Foo1.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/HazNameId.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Index.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Person.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Post.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/Product.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/ReviewBoard.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/ShortEnum.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/SomeType.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SharedTypes/User.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/SqlBuilderTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/TestBase.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/TransactionTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/TupleTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/TypeHandlerTests.cs (100%) rename {Dapper.Tests => tests/Dapper.Tests}/XmlTests.cs (100%) create mode 100644 tests/Directory.Build.props create mode 100644 tests/Directory.Build.targets diff --git a/Build.csproj b/Build.csproj index 1ae9b2d72..f2f2d0d6b 100644 --- a/Build.csproj +++ b/Build.csproj @@ -1,5 +1,7 @@ - + + + \ No newline at end of file diff --git a/Readme.md b/Readme.md index c5fd5aad9..a7bbfff85 100644 --- a/Readme.md +++ b/Readme.md @@ -116,7 +116,7 @@ A key feature of Dapper is performance. The following metrics show how long it t The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/main/Dapper.Tests.Performance) (contributions welcome!) and can be run via: ```bash -dotnet run -p .\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join +dotnet run -p .\benchmarks\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join ``` Output from the latest run is: ``` ini diff --git a/Dapper.Tests.Performance/Benchmarks.Belgrade.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Belgrade.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Dapper.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Dapper.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Dapper.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Dapper.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Dashing.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Dashing.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Dashing.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Dashing.cs diff --git a/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.EntityFramework.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFramework.cs diff --git a/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs diff --git a/Dapper.Tests.Performance/Benchmarks.HandCoded.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.HandCoded.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.HandCoded.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.HandCoded.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Linq2DB.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Massive.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Massive.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Massive.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Massive.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Mighty.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Mighty.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Mighty.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Mighty.cs diff --git a/Dapper.Tests.Performance/Benchmarks.NHibernate.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.NHibernate.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.NHibernate.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.NHibernate.cs diff --git a/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.PetaPoco.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs diff --git a/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.ServiceStack.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.ServiceStack.cs diff --git a/Dapper.Tests.Performance/Benchmarks.Susanoo.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Susanoo.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.Susanoo.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.Susanoo.cs diff --git a/Dapper.Tests.Performance/Benchmarks.XPO.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.XPO.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.XPO.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.XPO.cs diff --git a/Dapper.Tests.Performance/Benchmarks.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.cs similarity index 100% rename from Dapper.Tests.Performance/Benchmarks.cs rename to benchmarks/Dapper.Tests.Performance/Benchmarks.cs diff --git a/Dapper.Tests.Performance/Config.cs b/benchmarks/Dapper.Tests.Performance/Config.cs similarity index 100% rename from Dapper.Tests.Performance/Config.cs rename to benchmarks/Dapper.Tests.Performance/Config.cs diff --git a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj similarity index 86% rename from Dapper.Tests.Performance/Dapper.Tests.Performance.csproj rename to benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 8653da0f6..75d3c039c 100644 --- a/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -1,16 +1,12 @@  Dapper.Tests.Performance - Dapper.Tests.Performance Dapper Core Performance Suite Exe - false net462;netcoreapp3.1 - false + false - - @@ -39,7 +35,7 @@ $(DefineConstants);NET4X - + diff --git a/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs b/benchmarks/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs similarity index 100% rename from Dapper.Tests.Performance/Dashing/DashingConfiguration.cs rename to benchmarks/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs diff --git a/Dapper.Tests.Performance/Dashing/Post.cs b/benchmarks/Dapper.Tests.Performance/Dashing/Post.cs similarity index 100% rename from Dapper.Tests.Performance/Dashing/Post.cs rename to benchmarks/Dapper.Tests.Performance/Dashing/Post.cs diff --git a/Dapper.Tests.Performance/EntityFramework/EFContext.cs b/benchmarks/Dapper.Tests.Performance/EntityFramework/EFContext.cs similarity index 100% rename from Dapper.Tests.Performance/EntityFramework/EFContext.cs rename to benchmarks/Dapper.Tests.Performance/EntityFramework/EFContext.cs diff --git a/Dapper.Tests.Performance/EntityFrameworkCore/EFCoreContext.cs b/benchmarks/Dapper.Tests.Performance/EntityFrameworkCore/EFCoreContext.cs similarity index 100% rename from Dapper.Tests.Performance/EntityFrameworkCore/EFCoreContext.cs rename to benchmarks/Dapper.Tests.Performance/EntityFrameworkCore/EFCoreContext.cs diff --git a/Dapper.Tests.Performance/Helpers/ORMColum.cs b/benchmarks/Dapper.Tests.Performance/Helpers/ORMColum.cs similarity index 100% rename from Dapper.Tests.Performance/Helpers/ORMColum.cs rename to benchmarks/Dapper.Tests.Performance/Helpers/ORMColum.cs diff --git a/Dapper.Tests.Performance/Helpers/ReturnColum.cs b/benchmarks/Dapper.Tests.Performance/Helpers/ReturnColum.cs similarity index 100% rename from Dapper.Tests.Performance/Helpers/ReturnColum.cs rename to benchmarks/Dapper.Tests.Performance/Helpers/ReturnColum.cs diff --git a/Dapper.Tests.Performance/LegacyTests.cs b/benchmarks/Dapper.Tests.Performance/LegacyTests.cs similarity index 100% rename from Dapper.Tests.Performance/LegacyTests.cs rename to benchmarks/Dapper.Tests.Performance/LegacyTests.cs diff --git a/Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs b/benchmarks/Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs similarity index 100% rename from Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs rename to benchmarks/Dapper.Tests.Performance/Linq2DB/ConnectionStringSettings.cs diff --git a/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs b/benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs similarity index 100% rename from Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs rename to benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs diff --git a/Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs b/benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs similarity index 100% rename from Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs rename to benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DbSettings.cs diff --git a/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml b/benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml similarity index 100% rename from Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml rename to benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml diff --git a/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml.layout b/benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml.layout similarity index 100% rename from Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml.layout rename to benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.dbml.layout diff --git a/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs b/benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs similarity index 100% rename from Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs rename to benchmarks/Dapper.Tests.Performance/Linq2Sql/DataClasses.designer.cs diff --git a/Dapper.Tests.Performance/Massive/Massive.cs b/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs similarity index 100% rename from Dapper.Tests.Performance/Massive/Massive.cs rename to benchmarks/Dapper.Tests.Performance/Massive/Massive.cs diff --git a/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs b/benchmarks/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs similarity index 100% rename from Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs rename to benchmarks/Dapper.Tests.Performance/NHibernate/NHibernateHelper.cs diff --git a/Dapper.Tests.Performance/NHibernate/Post.hbm.xml b/benchmarks/Dapper.Tests.Performance/NHibernate/Post.hbm.xml similarity index 100% rename from Dapper.Tests.Performance/NHibernate/Post.hbm.xml rename to benchmarks/Dapper.Tests.Performance/NHibernate/Post.hbm.xml diff --git a/Dapper.Tests.Performance/NHibernate/hibernate.cfg.xml b/benchmarks/Dapper.Tests.Performance/NHibernate/hibernate.cfg.xml similarity index 100% rename from Dapper.Tests.Performance/NHibernate/hibernate.cfg.xml rename to benchmarks/Dapper.Tests.Performance/NHibernate/hibernate.cfg.xml diff --git a/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs b/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs similarity index 100% rename from Dapper.Tests.Performance/PetaPoco/PetaPoco.cs rename to benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs diff --git a/Dapper.Tests.Performance/Post.cs b/benchmarks/Dapper.Tests.Performance/Post.cs similarity index 100% rename from Dapper.Tests.Performance/Post.cs rename to benchmarks/Dapper.Tests.Performance/Post.cs diff --git a/Dapper.Tests.Performance/Program.cs b/benchmarks/Dapper.Tests.Performance/Program.cs similarity index 100% rename from Dapper.Tests.Performance/Program.cs rename to benchmarks/Dapper.Tests.Performance/Program.cs diff --git a/Dapper.Tests.Performance/Soma/SomaConfig.cs b/benchmarks/Dapper.Tests.Performance/Soma/SomaConfig.cs similarity index 100% rename from Dapper.Tests.Performance/Soma/SomaConfig.cs rename to benchmarks/Dapper.Tests.Performance/Soma/SomaConfig.cs diff --git a/Dapper.Tests.Performance/SqlDataReaderHelper.cs b/benchmarks/Dapper.Tests.Performance/SqlDataReaderHelper.cs similarity index 100% rename from Dapper.Tests.Performance/SqlDataReaderHelper.cs rename to benchmarks/Dapper.Tests.Performance/SqlDataReaderHelper.cs diff --git a/Dapper.Tests.Performance/XPO/Post.cs b/benchmarks/Dapper.Tests.Performance/XPO/Post.cs similarity index 100% rename from Dapper.Tests.Performance/XPO/Post.cs rename to benchmarks/Dapper.Tests.Performance/XPO/Post.cs diff --git a/Dapper.Tests.Performance/app.config b/benchmarks/Dapper.Tests.Performance/app.config similarity index 100% rename from Dapper.Tests.Performance/app.config rename to benchmarks/Dapper.Tests.Performance/app.config diff --git a/benchmarks/Directory.Build.props b/benchmarks/Directory.Build.props new file mode 100644 index 000000000..4507d6aa8 --- /dev/null +++ b/benchmarks/Directory.Build.props @@ -0,0 +1,33 @@ + + + + false + false + false + false + $(DefineConstants);WINDOWS + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj similarity index 51% rename from Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj rename to tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 6e5a826ac..986a82a04 100644 --- a/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -2,26 +2,18 @@ Dapper.Tests.Contrib Dapper Contrib Test Suite - false netcoreapp3.1;net462 - false - + - - - + - - - - diff --git a/Dapper.Tests.Contrib/Helpers/Attributes.cs b/tests/Dapper.Tests.Contrib/Helpers/Attributes.cs similarity index 100% rename from Dapper.Tests.Contrib/Helpers/Attributes.cs rename to tests/Dapper.Tests.Contrib/Helpers/Attributes.cs diff --git a/Dapper.Tests.Contrib/TestSuite.Async.cs b/tests/Dapper.Tests.Contrib/TestSuite.Async.cs similarity index 100% rename from Dapper.Tests.Contrib/TestSuite.Async.cs rename to tests/Dapper.Tests.Contrib/TestSuite.Async.cs diff --git a/Dapper.Tests.Contrib/TestSuite.cs b/tests/Dapper.Tests.Contrib/TestSuite.cs similarity index 100% rename from Dapper.Tests.Contrib/TestSuite.cs rename to tests/Dapper.Tests.Contrib/TestSuite.cs diff --git a/Dapper.Tests.Contrib/TestSuites.cs b/tests/Dapper.Tests.Contrib/TestSuites.cs similarity index 100% rename from Dapper.Tests.Contrib/TestSuites.cs rename to tests/Dapper.Tests.Contrib/TestSuites.cs diff --git a/Dapper.Tests/App.config b/tests/Dapper.Tests/App.config similarity index 100% rename from Dapper.Tests/App.config rename to tests/Dapper.Tests/App.config diff --git a/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs similarity index 100% rename from Dapper.Tests/AsyncTests.cs rename to tests/Dapper.Tests/AsyncTests.cs diff --git a/Dapper.Tests/ConstructorTests.cs b/tests/Dapper.Tests/ConstructorTests.cs similarity index 100% rename from Dapper.Tests/ConstructorTests.cs rename to tests/Dapper.Tests/ConstructorTests.cs diff --git a/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj similarity index 69% rename from Dapper.Tests/Dapper.Tests.csproj rename to tests/Dapper.Tests/Dapper.Tests.csproj index bcd3eab91..443b752d6 100644 --- a/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,9 +2,7 @@ Dapper.Tests Dapper Core Test Suite - false netcoreapp3.1;net462;net472 - false $(DefineConstants);MSSQLCLIENT @@ -13,10 +11,8 @@ - - - - + + @@ -25,12 +21,10 @@ - - - + diff --git a/Dapper.Tests/DataReaderTests.cs b/tests/Dapper.Tests/DataReaderTests.cs similarity index 100% rename from Dapper.Tests/DataReaderTests.cs rename to tests/Dapper.Tests/DataReaderTests.cs diff --git a/Dapper.Tests/DecimalTests.cs b/tests/Dapper.Tests/DecimalTests.cs similarity index 100% rename from Dapper.Tests/DecimalTests.cs rename to tests/Dapper.Tests/DecimalTests.cs diff --git a/Dapper.Tests/EnumTests.cs b/tests/Dapper.Tests/EnumTests.cs similarity index 100% rename from Dapper.Tests/EnumTests.cs rename to tests/Dapper.Tests/EnumTests.cs diff --git a/Dapper.Tests/Helpers/Attributes.cs b/tests/Dapper.Tests/Helpers/Attributes.cs similarity index 100% rename from Dapper.Tests/Helpers/Attributes.cs rename to tests/Dapper.Tests/Helpers/Attributes.cs diff --git a/Dapper.Tests/Helpers/Common.cs b/tests/Dapper.Tests/Helpers/Common.cs similarity index 100% rename from Dapper.Tests/Helpers/Common.cs rename to tests/Dapper.Tests/Helpers/Common.cs diff --git a/Dapper.Tests/Helpers/SqlServerTypesLoader.cs b/tests/Dapper.Tests/Helpers/SqlServerTypesLoader.cs similarity index 100% rename from Dapper.Tests/Helpers/SqlServerTypesLoader.cs rename to tests/Dapper.Tests/Helpers/SqlServerTypesLoader.cs diff --git a/Dapper.Tests/Helpers/TransactedConnection.cs b/tests/Dapper.Tests/Helpers/TransactedConnection.cs similarity index 100% rename from Dapper.Tests/Helpers/TransactedConnection.cs rename to tests/Dapper.Tests/Helpers/TransactedConnection.cs diff --git a/Dapper.Tests/Helpers/XunitSkippable.cs b/tests/Dapper.Tests/Helpers/XunitSkippable.cs similarity index 100% rename from Dapper.Tests/Helpers/XunitSkippable.cs rename to tests/Dapper.Tests/Helpers/XunitSkippable.cs diff --git a/Dapper.Tests/LiteralTests.cs b/tests/Dapper.Tests/LiteralTests.cs similarity index 100% rename from Dapper.Tests/LiteralTests.cs rename to tests/Dapper.Tests/LiteralTests.cs diff --git a/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs similarity index 100% rename from Dapper.Tests/MiscTests.cs rename to tests/Dapper.Tests/MiscTests.cs diff --git a/Dapper.Tests/MultiMapTests.cs b/tests/Dapper.Tests/MultiMapTests.cs similarity index 100% rename from Dapper.Tests/MultiMapTests.cs rename to tests/Dapper.Tests/MultiMapTests.cs diff --git a/Dapper.Tests/NullTests.cs b/tests/Dapper.Tests/NullTests.cs similarity index 100% rename from Dapper.Tests/NullTests.cs rename to tests/Dapper.Tests/NullTests.cs diff --git a/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs similarity index 100% rename from Dapper.Tests/ParameterTests.cs rename to tests/Dapper.Tests/ParameterTests.cs diff --git a/Dapper.Tests/ProcedureTests.cs b/tests/Dapper.Tests/ProcedureTests.cs similarity index 100% rename from Dapper.Tests/ProcedureTests.cs rename to tests/Dapper.Tests/ProcedureTests.cs diff --git a/Dapper.Tests/ProviderTests.cs b/tests/Dapper.Tests/ProviderTests.cs similarity index 100% rename from Dapper.Tests/ProviderTests.cs rename to tests/Dapper.Tests/ProviderTests.cs diff --git a/Dapper.Tests/Providers/EntityFrameworkTests.cs b/tests/Dapper.Tests/Providers/EntityFrameworkTests.cs similarity index 100% rename from Dapper.Tests/Providers/EntityFrameworkTests.cs rename to tests/Dapper.Tests/Providers/EntityFrameworkTests.cs diff --git a/Dapper.Tests/Providers/FirebirdTests.cs b/tests/Dapper.Tests/Providers/FirebirdTests.cs similarity index 100% rename from Dapper.Tests/Providers/FirebirdTests.cs rename to tests/Dapper.Tests/Providers/FirebirdTests.cs diff --git a/Dapper.Tests/Providers/Linq2SqlTests.cs b/tests/Dapper.Tests/Providers/Linq2SqlTests.cs similarity index 100% rename from Dapper.Tests/Providers/Linq2SqlTests.cs rename to tests/Dapper.Tests/Providers/Linq2SqlTests.cs diff --git a/Dapper.Tests/Providers/MySQLTests.cs b/tests/Dapper.Tests/Providers/MySQLTests.cs similarity index 100% rename from Dapper.Tests/Providers/MySQLTests.cs rename to tests/Dapper.Tests/Providers/MySQLTests.cs diff --git a/Dapper.Tests/Providers/OLDEBTests.cs b/tests/Dapper.Tests/Providers/OLDEBTests.cs similarity index 100% rename from Dapper.Tests/Providers/OLDEBTests.cs rename to tests/Dapper.Tests/Providers/OLDEBTests.cs diff --git a/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs similarity index 100% rename from Dapper.Tests/Providers/PostgresqlTests.cs rename to tests/Dapper.Tests/Providers/PostgresqlTests.cs diff --git a/Dapper.Tests/Providers/SqliteTests.cs b/tests/Dapper.Tests/Providers/SqliteTests.cs similarity index 100% rename from Dapper.Tests/Providers/SqliteTests.cs rename to tests/Dapper.Tests/Providers/SqliteTests.cs diff --git a/Dapper.Tests/QueryMultipleTests.cs b/tests/Dapper.Tests/QueryMultipleTests.cs similarity index 100% rename from Dapper.Tests/QueryMultipleTests.cs rename to tests/Dapper.Tests/QueryMultipleTests.cs diff --git a/Dapper.Tests/SharedTypes/Address.cs b/tests/Dapper.Tests/SharedTypes/Address.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Address.cs rename to tests/Dapper.Tests/SharedTypes/Address.cs diff --git a/Dapper.Tests/SharedTypes/Bar1.cs b/tests/Dapper.Tests/SharedTypes/Bar1.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Bar1.cs rename to tests/Dapper.Tests/SharedTypes/Bar1.cs diff --git a/Dapper.Tests/SharedTypes/Category.cs b/tests/Dapper.Tests/SharedTypes/Category.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Category.cs rename to tests/Dapper.Tests/SharedTypes/Category.cs diff --git a/Dapper.Tests/SharedTypes/Comment.cs b/tests/Dapper.Tests/SharedTypes/Comment.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Comment.cs rename to tests/Dapper.Tests/SharedTypes/Comment.cs diff --git a/Dapper.Tests/SharedTypes/Dog.cs b/tests/Dapper.Tests/SharedTypes/Dog.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Dog.cs rename to tests/Dapper.Tests/SharedTypes/Dog.cs diff --git a/Dapper.Tests/SharedTypes/Enums.cs b/tests/Dapper.Tests/SharedTypes/Enums.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Enums.cs rename to tests/Dapper.Tests/SharedTypes/Enums.cs diff --git a/Dapper.Tests/SharedTypes/Foo1.cs b/tests/Dapper.Tests/SharedTypes/Foo1.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Foo1.cs rename to tests/Dapper.Tests/SharedTypes/Foo1.cs diff --git a/Dapper.Tests/SharedTypes/HazNameId.cs b/tests/Dapper.Tests/SharedTypes/HazNameId.cs similarity index 100% rename from Dapper.Tests/SharedTypes/HazNameId.cs rename to tests/Dapper.Tests/SharedTypes/HazNameId.cs diff --git a/Dapper.Tests/SharedTypes/Index.cs b/tests/Dapper.Tests/SharedTypes/Index.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Index.cs rename to tests/Dapper.Tests/SharedTypes/Index.cs diff --git a/Dapper.Tests/SharedTypes/Person.cs b/tests/Dapper.Tests/SharedTypes/Person.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Person.cs rename to tests/Dapper.Tests/SharedTypes/Person.cs diff --git a/Dapper.Tests/SharedTypes/Post.cs b/tests/Dapper.Tests/SharedTypes/Post.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Post.cs rename to tests/Dapper.Tests/SharedTypes/Post.cs diff --git a/Dapper.Tests/SharedTypes/Product.cs b/tests/Dapper.Tests/SharedTypes/Product.cs similarity index 100% rename from Dapper.Tests/SharedTypes/Product.cs rename to tests/Dapper.Tests/SharedTypes/Product.cs diff --git a/Dapper.Tests/SharedTypes/ReviewBoard.cs b/tests/Dapper.Tests/SharedTypes/ReviewBoard.cs similarity index 100% rename from Dapper.Tests/SharedTypes/ReviewBoard.cs rename to tests/Dapper.Tests/SharedTypes/ReviewBoard.cs diff --git a/Dapper.Tests/SharedTypes/ShortEnum.cs b/tests/Dapper.Tests/SharedTypes/ShortEnum.cs similarity index 100% rename from Dapper.Tests/SharedTypes/ShortEnum.cs rename to tests/Dapper.Tests/SharedTypes/ShortEnum.cs diff --git a/Dapper.Tests/SharedTypes/SomeType.cs b/tests/Dapper.Tests/SharedTypes/SomeType.cs similarity index 100% rename from Dapper.Tests/SharedTypes/SomeType.cs rename to tests/Dapper.Tests/SharedTypes/SomeType.cs diff --git a/Dapper.Tests/SharedTypes/User.cs b/tests/Dapper.Tests/SharedTypes/User.cs similarity index 100% rename from Dapper.Tests/SharedTypes/User.cs rename to tests/Dapper.Tests/SharedTypes/User.cs diff --git a/Dapper.Tests/SqlBuilderTests.cs b/tests/Dapper.Tests/SqlBuilderTests.cs similarity index 100% rename from Dapper.Tests/SqlBuilderTests.cs rename to tests/Dapper.Tests/SqlBuilderTests.cs diff --git a/Dapper.Tests/TestBase.cs b/tests/Dapper.Tests/TestBase.cs similarity index 100% rename from Dapper.Tests/TestBase.cs rename to tests/Dapper.Tests/TestBase.cs diff --git a/Dapper.Tests/TransactionTests.cs b/tests/Dapper.Tests/TransactionTests.cs similarity index 100% rename from Dapper.Tests/TransactionTests.cs rename to tests/Dapper.Tests/TransactionTests.cs diff --git a/Dapper.Tests/TupleTests.cs b/tests/Dapper.Tests/TupleTests.cs similarity index 100% rename from Dapper.Tests/TupleTests.cs rename to tests/Dapper.Tests/TupleTests.cs diff --git a/Dapper.Tests/TypeHandlerTests.cs b/tests/Dapper.Tests/TypeHandlerTests.cs similarity index 100% rename from Dapper.Tests/TypeHandlerTests.cs rename to tests/Dapper.Tests/TypeHandlerTests.cs diff --git a/Dapper.Tests/XmlTests.cs b/tests/Dapper.Tests/XmlTests.cs similarity index 100% rename from Dapper.Tests/XmlTests.cs rename to tests/Dapper.Tests/XmlTests.cs diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 000000000..3557a7eb4 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,39 @@ + + + + Library + false + false + false + false + + Full + $(DefineConstants);WINDOWS + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Directory.Build.targets b/tests/Directory.Build.targets new file mode 100644 index 000000000..2af8b3c02 --- /dev/null +++ b/tests/Directory.Build.targets @@ -0,0 +1,5 @@ + + + false + + \ No newline at end of file From 6dd097be417054f2025b839e556478ba366c89ae Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 12 Oct 2020 07:30:07 -0400 Subject: [PATCH 140/312] Fix .sln file --- Dapper.sln | 6 +++--- Directory.Build.props | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Dapper.sln b/Dapper.sln index 6cfbe863c..db66c87ad 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -21,13 +21,13 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "Dapper\Dapper.csp EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.StrongName", "Dapper.StrongName\Dapper.StrongName.csproj", "{549C51A1-222B-4E12-96F1-3AEFF45A7709}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "tests\Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Contrib", "Dapper.Contrib\Dapper.Contrib.csproj", "{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework", "Dapper.EntityFramework\Dapper.EntityFramework.csproj", "{BE401F7B-8611-4A1E-AEAA-5CB700128C16}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Contrib", "Dapper.Tests.Contrib\Dapper.Tests.Contrib.csproj", "{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Contrib", "tests\Dapper.Tests.Contrib\Dapper.Tests.Contrib.csproj", "{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{196928F0-7052-4585-90E8-817BD720F5E3}" EndProject @@ -42,7 +42,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.StrongName", "Dapper.EntityFramework.StrongName\Dapper.EntityFramework.StrongName.csproj", "{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "benchmarks\Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" EndProject diff --git a/Directory.Build.props b/Directory.Build.props index 3932bf239..7a7990da7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -36,6 +36,6 @@ - + \ No newline at end of file From deadc1e3bb20dddf1064036c5755823c951c7743 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 15 Oct 2020 20:33:15 -0400 Subject: [PATCH 141/312] Start of Docker bits --- .github/workflows/main.yml | 53 ++++++++++++++++++++++++++++++++++++++ tests/docker-compose.yml | 27 +++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 .github/workflows/main.yml create mode 100644 tests/docker-compose.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 000000000..b0b99a41b --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,53 @@ +name: Main Build + +on: + pull_request: + push: + branches: + - main + paths: + - '*' + - '!/docs/*' # Don't run workflow when files are only in the /docs directory + +jobs: + vm-job: + name: Ubuntu + runs-on: ubuntu-latest + services: + postgres: + image: postgres + ports: + - 5432/tcp + env: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + sqlserver: + image: mcr.microsoft.com/mssql/server:2017-latest-ubuntu + ports: + - 1433/tcp + env: + ACCEPT_EULA: Y + SA_PASSWORD: g0d4mm!tSQLServer + mysql: + image: mysql + ports: + - 3306/tcp + env: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: test + steps: + - name: Checkout code + uses: actions/checkout@v1 + - name: .NET Build + run: dotnet build Build.csproj -c Release /p:CI=true + - name: .NET Test + run: dotnet test Build.csproj -c Release --no-build /p:CI=true + env: + EnableTestLogging: true + MySQLConnectionString: server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true + PostgreSqlConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; + SQLServerConnectionString: Server=tcp:127.0.0.1,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=g0d4mm!tSQLServer; + - name: .NET Lib Pack + run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml new file mode 100644 index 000000000..9c0e35028 --- /dev/null +++ b/tests/docker-compose.yml @@ -0,0 +1,27 @@ +version: "3" +services: + mysql: + image: mysql:8 + container_name: mysql + ports: + - 3306:3306 + environment: + MYSQL_ROOT_PASSWORD: root + MYSQL_DATABASE: test + postgres: + image: postgres:alpine + container_name: postgres + ports: + - 5432:5432 + environment: + POSTGRES_USER: postgres + POSTGRES_PASSWORD: postgres + POSTGRES_DB: test + sqlserver: + image: microsoft/mssql-server-linux:2019-latest + container_name: sql-server-db + ports: + - 1433:1433 + environment: + ACCEPT_EULA: Y + SA_PASSWORD: "saAccountI#v3rySecure" From a6f99ced9206763d078c25275be99be0626dbae6 Mon Sep 17 00:00:00 2001 From: "Paul.42" Date: Fri, 16 Oct 2020 15:07:42 -0700 Subject: [PATCH 142/312] update tests link in readme --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index a7bbfff85..6e1909d9a 100644 --- a/Readme.md +++ b/Readme.md @@ -389,7 +389,7 @@ Dapper has no DB specific implementation details, it works across all .NET ADO p Do you have a comprehensive list of examples? --------------------- -Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/Dapper/tree/main/Dapper.Tests). +Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/Dapper/tree/main/tests/Dapper.Tests). Who is using this? --------------------- From 3707223a1a7f47e606bed0e0fcde39fb47fa68b4 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 17 Oct 2020 08:53:44 -0400 Subject: [PATCH 143/312] Tweak docker bits To test locally: ``` $env:SqlServerConnectionString = "Server=.;Database=tempdb;User Id=sa;Password=saAccountI#v3rySecure;" $env:OLEDBConnectionString = "Provider=SQLOLEDB;Server=.;Database=tempdb;User Id=sa;Password=saAccountI#v3rySecure;" $env:MySQLConnectionString = "server=localhost;Port=3306;Uid=root;Pwd=root;Database=test;Allow User Variables=true" $env:PostgreSqlConnectionString = "Server=localhost;Port=5432;Database=test;User Id=postgres;Password=postgres;" ``` --- .github/workflows/main.yml | 3 ++- tests/docker-compose.yml | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b0b99a41b..e9712679f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,7 +47,8 @@ jobs: env: EnableTestLogging: true MySQLConnectionString: server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true + OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:127.0.0.1,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=saAccountI#v3rySecure; PostgreSqlConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; - SQLServerConnectionString: Server=tcp:127.0.0.1,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=g0d4mm!tSQLServer; + SQLServerConnectionString: Server=tcp:127.0.0.1,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=saAccountI#v3rySecure; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index 9c0e35028..ba165773d 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -18,7 +18,7 @@ services: POSTGRES_PASSWORD: postgres POSTGRES_DB: test sqlserver: - image: microsoft/mssql-server-linux:2019-latest + image: mcr.microsoft.com/mssql/server:2019-latest container_name: sql-server-db ports: - 1433:1433 From 8d26e67c01acc6f8cf16f3f1e4911f7fd4a7c040 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 17 Oct 2020 09:07:48 -0400 Subject: [PATCH 144/312] Tweaks! --- .github/workflows/main.yml | 6 +++--- tests/docker-compose.yml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e9712679f..62fc6fb14 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -29,7 +29,7 @@ jobs: - 1433/tcp env: ACCEPT_EULA: Y - SA_PASSWORD: g0d4mm!tSQLServer + SA_PASSWORD: "Password." mysql: image: mysql ports: @@ -47,8 +47,8 @@ jobs: env: EnableTestLogging: true MySQLConnectionString: server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true - OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:127.0.0.1,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=saAccountI#v3rySecure; + OLEDBConnectionString: Provider=SQLOLEDB;Server=localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; PostgreSqlConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; - SQLServerConnectionString: Server=tcp:127.0.0.1,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=saAccountI#v3rySecure; + SQLServerConnectionString: Server=localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true diff --git a/tests/docker-compose.yml b/tests/docker-compose.yml index ba165773d..5e76d0a57 100644 --- a/tests/docker-compose.yml +++ b/tests/docker-compose.yml @@ -24,4 +24,4 @@ services: - 1433:1433 environment: ACCEPT_EULA: Y - SA_PASSWORD: "saAccountI#v3rySecure" + SA_PASSWORD: "Password." From 0b0fdf51b3064eeede7bb31c7ceb9c73a7f947d2 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 17 Oct 2020 09:55:19 -0400 Subject: [PATCH 145/312] Bump to 2019 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 62fc6fb14..2c8affe50 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: POSTGRES_DB: test options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 sqlserver: - image: mcr.microsoft.com/mssql/server:2017-latest-ubuntu + image: mcr.microsoft.com/mssql/server:2019-latest-ubuntu ports: - 1433/tcp env: From 9c862af74395a0e2661c3a9e9eead4bed0b50c06 Mon Sep 17 00:00:00 2001 From: wswind <18399096+wswind@users.noreply.github.com> Date: Mon, 19 Oct 2020 10:43:13 +0800 Subject: [PATCH 146/312] Update Readme.md remove a right parenthesis in literal replacements demo code. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 6e1909d9a..4f48ce904 100644 --- a/Readme.md +++ b/Readme.md @@ -201,7 +201,7 @@ Literal replacements Dapper supports literal replacements for bool and numeric types. ```csharp -connection.Query("select * from User where UserTypeId = {=Admin}", new { UserTypeId.Admin })); +connection.Query("select * from User where UserTypeId = {=Admin}", new { UserTypeId.Admin }); ``` The literal replacement is not sent as a parameter; this allows better plans and filtered index usage but should usually be used sparingly and after testing. This feature is particularly useful when the value being injected From 7c1627be7fb00b6139d60409093f5b69842318ab Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 22 Oct 2020 20:17:40 -0400 Subject: [PATCH 147/312] Fix container path --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2c8affe50..015116acd 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -24,7 +24,7 @@ jobs: POSTGRES_DB: test options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 sqlserver: - image: mcr.microsoft.com/mssql/server:2019-latest-ubuntu + image: mcr.microsoft.com/mssql/server:2019-latest ports: - 1433/tcp env: From 72eb732f38762360c25e794f290f880e338f5637 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 22 Oct 2020 20:28:07 -0400 Subject: [PATCH 148/312] Woops --- .github/workflows/main.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 015116acd..a8e8b77bb 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,8 +47,8 @@ jobs: env: EnableTestLogging: true MySQLConnectionString: server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true - OLEDBConnectionString: Provider=SQLOLEDB;Server=localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; + OLEDBConnectionString: Provider=SQLOLEDB;Server=localhost;${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; PostgreSqlConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; - SQLServerConnectionString: Server=localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; + SQLServerConnectionString: Server=localhost;${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true From c52bd401f05101e7e500eabc56714f3445801f68 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 22 Oct 2020 20:38:41 -0400 Subject: [PATCH 149/312] Take 32 --- .github/workflows/main.yml | 9 ++++----- tests/Directory.Build.props | 2 ++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a8e8b77bb..ec115696f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,12 +43,11 @@ jobs: - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: .NET Test - run: dotnet test Build.csproj -c Release --no-build /p:CI=true + run: dotnet test Build.csproj -c Release --logger GitHubActions /p:CI=true env: - EnableTestLogging: true MySQLConnectionString: server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true - OLEDBConnectionString: Provider=SQLOLEDB;Server=localhost;${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - PostgreSqlConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; - SQLServerConnectionString: Server=localhost;${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; + OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; + PostgreSqlConnectionString: Server=.;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; + SQLServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 3557a7eb4..ebfb9c757 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -6,6 +6,7 @@ false false false + true Full $(DefineConstants);WINDOWS @@ -14,6 +15,7 @@ + From 453f9ed2863aa4ef7fa786a768e1dd3374aacb5a Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 22 Oct 2020 20:52:06 -0400 Subject: [PATCH 150/312] GODDAMMIT --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ec115696f..289452787 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,6 +48,6 @@ jobs: MySQLConnectionString: server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; PostgreSqlConnectionString: Server=.;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; - SQLServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; + SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true From 1c4f33d665f72e912f10598a13c13deeacb014ce Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 22 Oct 2020 20:59:16 -0400 Subject: [PATCH 151/312] Fix TestMultiSelectWithSomeEmptyGrids on *nix --- tests/Dapper.Tests/QueryMultipleTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Dapper.Tests/QueryMultipleTests.cs b/tests/Dapper.Tests/QueryMultipleTests.cs index bb311dcb0..857525dcf 100644 --- a/tests/Dapper.Tests/QueryMultipleTests.cs +++ b/tests/Dapper.Tests/QueryMultipleTests.cs @@ -236,7 +236,7 @@ private void TestMultiSelectWithSomeEmptyGrids(bool buffered) } catch (ObjectDisposedException ex) { // expected; success - Assert.Equal("The reader has been disposed; this can happen after all data has been consumed\r\nObject name: 'Dapper.SqlMapper+GridReader'.", ex.Message); + Assert.Equal("The reader has been disposed; this can happen after all data has been consumed\r\nObject name: 'Dapper.SqlMapper+GridReader'.", ex.Message, ignoreLineEndingDifferences: true); } Assert.Single(one); From 0e64c419ba408fa8a2325d5c857a1fe38cecaf4d Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 22 Oct 2020 21:00:38 -0400 Subject: [PATCH 152/312] Split test runs --- .github/workflows/main.yml | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 289452787..bad249279 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,10 +42,17 @@ jobs: uses: actions/checkout@v1 - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - - name: .NET Test - run: dotnet test Build.csproj -c Release --logger GitHubActions /p:CI=true + - name: Dapper Tests + run: dotnet test tests/Dapper.Tests/Dapper.Tests.csproj -c Release --logger GitHubActions /p:CI=true env: - MySQLConnectionString: server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true + MySQLConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true + OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; + PostgreSqlConnectionString: Server=.;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; + SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; + - name: Dapper.Contrib Tests + run: dotnet test tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj -c Release --logger GitHubActions /p:CI=true + env: + MySQLConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; PostgreSqlConnectionString: Server=.;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; From acbd6f7eadda118dd37241793cc4698695a01cc9 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Fri, 23 Oct 2020 07:57:55 -0400 Subject: [PATCH 153/312] Moar matching --- .github/workflows/main.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index bad249279..7bf43250f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -45,16 +45,16 @@ jobs: - name: Dapper Tests run: dotnet test tests/Dapper.Tests/Dapper.Tests.csproj -c Release --logger GitHubActions /p:CI=true env: - MySQLConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true + MySqlConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - PostgreSqlConnectionString: Server=.;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; + PostgesConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: Dapper.Contrib Tests run: dotnet test tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj -c Release --logger GitHubActions /p:CI=true env: - MySQLConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true + MySqlConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - PostgreSqlConnectionString: Server=.;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; + PostgesConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true From 7778f18f9ffe90292bf51c653451901b492fbaf6 Mon Sep 17 00:00:00 2001 From: Alexey Golub Date: Thu, 29 Oct 2020 02:21:05 +0200 Subject: [PATCH 154/312] Update GitHubActionsTestLogger Bumped GitHubActionsTestLogger to v1.1.2 as it fixes a few issues related to reporting in async tests. https://github.com/Tyrrrz/GitHubActionsTestLogger/blob/master/Changelog.md#v112-26-oct-2020 --- tests/Directory.Build.props | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index ebfb9c757..9e73ccf63 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -15,7 +15,7 @@ - + @@ -38,4 +38,4 @@ - \ No newline at end of file + From 6ab170c160f0689e2e2bf7e6eb603447f734d454 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 17 Nov 2020 09:57:17 +0000 Subject: [PATCH 155/312] .Net 5 (#1572) * initial net5.0 tweaks; also fix current IDE warnings * lib updates and fixes * more cleanup * fix snafu * skip locals init * disable xunit shadow copy in tests * test records * explore scenario that currently works re #1571 --- Dapper.Contrib/Dapper.Contrib.csproj | 3 +- Dapper.Contrib/SqlMapperExtensions.Async.cs | 10 +- Dapper.Contrib/SqlMapperExtensions.cs | 16 +- Dapper.EntityFramework/DbGeometryHandler.cs | 4 +- Dapper.ProviderTools/BulkCopy.cs | 6 +- .../Dapper.ProviderTools.csproj | 2 +- Dapper.Rainbow/Dapper.Rainbow.csproj | 2 +- Dapper.Rainbow/Database.cs | 5 +- Dapper.Rainbow/Snapshotter.cs | 10 +- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 2 +- Dapper.StrongName/Dapper.StrongName.csproj | 7 +- Dapper.sln | 5 + Dapper/CommandDefinition.cs | 4 +- Dapper/Dapper.csproj | 11 +- Dapper/DynamicParameters.cs | 59 ++--- Dapper/Properties/AssemblyInfo.cs | 4 + Dapper/SqlMapper.Async.cs | 240 +++++++++--------- Dapper/SqlMapper.DapperRow.cs | 2 +- Dapper/SqlMapper.DapperRowMetaObject.cs | 3 +- Dapper/SqlMapper.GridReader.Async.cs | 4 +- Dapper/SqlMapper.GridReader.cs | 11 +- Dapper/SqlMapper.IDataReader.cs | 2 +- Dapper/SqlMapper.Identity.cs | 26 +- Dapper/SqlMapper.Link.cs | 2 +- Dapper/SqlMapper.LiteralToken.cs | 5 +- Dapper/SqlMapper.TypeDeserializerCache.cs | 4 +- Dapper/SqlMapper.TypeHandler.cs | 2 +- Dapper/SqlMapper.cs | 116 ++++----- Dapper/WrappedReader.cs | 8 + Directory.Build.props | 4 +- .../Benchmarks.EntityFrameworkCore.cs | 2 +- .../Benchmarks.XPO.cs | 6 +- .../Dapper.Tests.Performance.csproj | 30 ++- .../Dashing/DashingConfiguration.cs | 4 +- .../Dapper.Tests.Performance/LegacyTests.cs | 2 +- .../Massive/Massive.cs | 6 +- .../PetaPoco/PetaPoco.cs | 14 +- .../Dapper.Tests.Contrib.csproj | 13 +- tests/Dapper.Tests.Contrib/TestSuites.cs | 2 +- tests/Dapper.Tests.Contrib/xunit.runner.json | 4 + tests/Dapper.Tests/AsyncTests.cs | 8 +- tests/Dapper.Tests/Dapper.Tests.csproj | 23 +- tests/Dapper.Tests/MiscTests.cs | 52 +++- tests/Dapper.Tests/ProviderTests.cs | 2 +- tests/Dapper.Tests/Providers/MySQLTests.cs | 2 +- tests/Dapper.Tests/TestBase.cs | 4 +- tests/Dapper.Tests/TypeHandlerTests.cs | 4 +- tests/Dapper.Tests/xunit.runner.json | 4 + tests/Directory.Build.props | 4 +- 49 files changed, 425 insertions(+), 340 deletions(-) create mode 100644 Dapper/Properties/AssemblyInfo.cs create mode 100644 tests/Dapper.Tests.Contrib/xunit.runner.json create mode 100644 tests/Dapper.Tests/xunit.runner.json diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj index 662f6c33a..b26861366 100644 --- a/Dapper.Contrib/Dapper.Contrib.csproj +++ b/Dapper.Contrib/Dapper.Contrib.csproj @@ -5,8 +5,9 @@ orm;sql;micro-orm;dapper The official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities. Sam Saffron;Johan Danforth - net461;netstandard2.0 + net461;netstandard2.0;net5.0 false + $(NoWarn);CA1050 diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs index 2c662dca3..c93e39a48 100644 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ b/Dapper.Contrib/SqlMapperExtensions.Async.cs @@ -40,10 +40,10 @@ public static async Task GetAsync(this IDbConnection connection, dynamic i if (!type.IsInterface) return (await connection.QueryAsync(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); - var res = (await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() as IDictionary; - - if (res == null) + if (!((await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary res)) + { return null; + } var obj = ProxyGenerator.GetInterfaceProxy(); @@ -101,7 +101,7 @@ public static Task> GetAllAsync(this IDbConnection connection, private static async Task> GetAllAsyncImpl(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string sql, Type type) where T : class { - var result = await connection.QueryAsync(sql).ConfigureAwait(false); + var result = await connection.QueryAsync(sql, transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); var list = new List(); foreach (IDictionary res in result) { @@ -140,7 +140,7 @@ public static Task InsertAsync(this IDbConnection connection, T entityTo int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class { var type = typeof(T); - sqlAdapter = sqlAdapter ?? GetFormatter(connection); + sqlAdapter ??= GetFormatter(connection); var isList = false; if (type.IsArray) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index 1196d1e5c..eb05d190d 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -187,10 +187,10 @@ public static T Get(this IDbConnection connection, dynamic id, IDbTransaction if (type.IsInterface) { - var res = connection.Query(sql, dynParams).FirstOrDefault() as IDictionary; - - if (res == null) + if (!(connection.Query(sql, dynParams).FirstOrDefault() is IDictionary res)) + { return null; + } obj = ProxyGenerator.GetInterfaceProxy(); @@ -273,7 +273,9 @@ public static IEnumerable GetAll(this IDbConnection connection, IDbTransac /// /// Specify a custom table name mapper based on the POCO type name /// +#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API public static TableNameMapperDelegate TableNameMapper; +#pragma warning restore CA2211 // Non-constant fields should not be visible private static string GetTableName(Type type) { @@ -534,7 +536,9 @@ public static bool DeleteAll(this IDbConnection connection, IDbTransaction tr /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object). /// Please note that this callback is global and will be used by all the calls that require a database specific adapter. /// +#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API public static GetDatabaseTypeDelegate GetDatabaseType; +#pragma warning restore CA2211 // Non-constant fields should not be visible private static ISqlAdapter GetFormatter(IDbConnection connection) { @@ -552,7 +556,7 @@ private static class ProxyGenerator private static AssemblyBuilder GetAsmBuilder(string name) { -#if NETSTANDARD2_0 +#if !NET461 return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); #else return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); @@ -681,8 +685,8 @@ private static void CreateProperty(TypeBuilder typeBuilder, string propertyNa if (isIdentity) { var keyAttribute = typeof(KeyAttribute); - var myConstructorInfo = keyAttribute.GetConstructor(new Type[] { }); - var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, new object[] { }); + var myConstructorInfo = keyAttribute.GetConstructor(Type.EmptyTypes); + var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, Array.Empty()); property.SetCustomAttribute(attributeBuilder); } diff --git a/Dapper.EntityFramework/DbGeometryHandler.cs b/Dapper.EntityFramework/DbGeometryHandler.cs index f4e518f1c..2b7c130bd 100644 --- a/Dapper.EntityFramework/DbGeometryHandler.cs +++ b/Dapper.EntityFramework/DbGeometryHandler.cs @@ -35,9 +35,9 @@ public override void SetValue(IDbDataParameter parameter, DbGeometry value) parsed = SqlGeometry.STGeomFromWKB(new SqlBytes(value.AsBinary()), value.CoordinateSystemId); } parameter.Value = parsed ?? DBNull.Value; - if (parameter is SqlParameter) + if (parameter is SqlParameter sqlP) { - ((SqlParameter)parameter).UdtTypeName = "geometry"; + sqlP.UdtTypeName = "geometry"; } } diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs index d56110360..3773b48f8 100644 --- a/Dapper.ProviderTools/BulkCopy.cs +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -136,7 +136,11 @@ private static readonly ConcurrentDictionary?> /// /// Release any resources associated with this instance /// - public void Dispose() => Dispose(true); + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } /// /// Release any resources associated with this instance diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index fce48051e..dbc92136d 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -5,7 +5,7 @@ Dapper Provider Tools Provider-agnostic ADO.NET helper utilities Marc Gravell - net461;netstandard2.0 + net461;netstandard2.0;net5.0 true enable diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj index 85a890cc4..dcf0f4e40 100644 --- a/Dapper.Rainbow/Dapper.Rainbow.csproj +++ b/Dapper.Rainbow/Dapper.Rainbow.csproj @@ -6,7 +6,7 @@ Trivial micro-orm implemented on Dapper, provides with CRUD helpers. Sam Saffron 2017 Sam Saffron - net461;netstandard2.0 + net461;netstandard2.0;net5.0 false diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index d2a2c31c9..bd41cd558 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -45,7 +45,7 @@ public string TableName { get { - tableName = tableName ?? database.DetermineTableName(likelyTableName); + tableName ??= database.DetermineTableName(likelyTableName); return tableName; } } @@ -142,7 +142,9 @@ internal static List GetParamNames(object o) foreach (var prop in o.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public).Where(p => p.GetGetMethod(false) != null)) { var attribs = prop.GetCustomAttributes(typeof(IgnorePropertyAttribute), true); +#pragma warning disable IDE0019 // Use pattern matching - complex enough here var attr = attribs.FirstOrDefault() as IgnorePropertyAttribute; +#pragma warning restore IDE0019 // Use pattern matching if (attr == null || (!attr.Value)) { paramNames.Add(prop.Name); @@ -478,6 +480,7 @@ public virtual void Dispose() _connection.Close(); _connection = null; } + GC.SuppressFinalize(this); } } } diff --git a/Dapper.Rainbow/Snapshotter.cs b/Dapper.Rainbow/Snapshotter.cs index 317ac6585..c4e44a2e9 100644 --- a/Dapper.Rainbow/Snapshotter.cs +++ b/Dapper.Rainbow/Snapshotter.cs @@ -69,14 +69,14 @@ public DynamicParameters Diff() private static T Clone(T myObject) { - cloner = cloner ?? GenerateCloner(); + cloner ??= GenerateCloner(); return cloner(myObject); } private static DynamicParameters Diff(T original, T current) { var dm = new DynamicParameters(); - differ = differ ?? GenerateDiffer(); + differ ??= GenerateDiffer(); foreach (var pair in differ(original, current)) { dm.Add(pair.Name, pair.NewValue); @@ -98,8 +98,8 @@ private static List RelevantProperties() private static bool AreEqual(U first, U second) { - if (EqualityComparer.Default.Equals(first, default(U)) && EqualityComparer.Default.Equals(second, default(U))) return true; - if (EqualityComparer.Default.Equals(first, default(U))) return false; + if (EqualityComparer.Default.Equals(first, default) && EqualityComparer.Default.Equals(second, default)) return true; + if (EqualityComparer.Default.Equals(first, default)) return false; return first.Equals(second); } @@ -191,7 +191,7 @@ private static Func> GenerateDiffer() private static Func GenerateCloner() { var dm = new DynamicMethod("DoClone", typeof(T), new Type[] { typeof(T) }, true); - var ctor = typeof(T).GetConstructor(new Type[] { }); + var ctor = typeof(T).GetConstructor(Type.EmptyTypes); var il = dm.GetILGenerator(); diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index 4d22af8e9..74fe251d3 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -5,7 +5,7 @@ Dapper SqlBuilder component The Dapper SqlBuilder component, for building SQL queries dynamically. Sam Saffron, Johan Danforth - net461;netstandard2.0 + net461;netstandard2.0;net5.0 false false diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 8d860862a..023a4eae4 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -5,14 +5,19 @@ Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0 + net461;netstandard2.0;net5.0 true true + + + $(DefineConstants);PLAT_NO_REMOTING;PLAT_SKIP_LOCALS_INIT + true + diff --git a/Dapper.sln b/Dapper.sln index db66c87ad..ff06dd2c0 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -39,6 +39,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" + ProjectSection(SolutionItems) = preProject + tests\Directory.Build.props = tests\Directory.Build.props + tests\Directory.Build.targets = tests\Directory.Build.targets + tests\docker-compose.yml = tests\docker-compose.yml + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.StrongName", "Dapper.EntityFramework.StrongName\Dapper.EntityFramework.StrongName.csproj", "{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}" EndProject diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index 110cd5b2b..8c193cebc 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -19,7 +19,7 @@ internal static CommandDefinition ForCallback(object parameters) } else { - return default(CommandDefinition); + return default; } } @@ -85,7 +85,7 @@ internal void OnCompleted() /// The cancellation token for this command. public CommandDefinition(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered - , CancellationToken cancellationToken = default(CancellationToken) + , CancellationToken cancellationToken = default ) { CommandText = commandText; diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index ccc1d0a5e..25f87ccf6 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,9 +5,16 @@ orm;sql;micro-orm A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0 + net461;netstandard2.0;net5.0 + + + + + + + $(DefineConstants);PLAT_NO_REMOTING;PLAT_SKIP_LOCALS_INIT + true - diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index abc278630..dc4f5a36e 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -49,24 +49,7 @@ public void AddDynamicParams(object param) var obj = param; if (obj != null) { - var subDynamic = obj as DynamicParameters; - if (subDynamic == null) - { - var dictionary = obj as IEnumerable>; - if (dictionary == null) - { - templates ??= new List(); - templates.Add(obj); - } - else - { - foreach (var kvp in dictionary) - { - Add(kvp.Key, kvp.Value, null, null, null); - } - } - } - else + if (obj is DynamicParameters subDynamic) { if (subDynamic.parameters != null) { @@ -85,6 +68,21 @@ public void AddDynamicParams(object param) } } } + else + { + if (obj is IEnumerable> dictionary) + { + foreach (var kvp in dictionary) + { + Add(kvp.Key, kvp.Value, null, null, null); + } + } + else + { + templates ??= new List(); + templates.Add(obj); + } + } } } @@ -320,7 +318,7 @@ public T Get(string name) { throw new ApplicationException("Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)"); } - return default(T); + return default; } return (T)val; } @@ -337,12 +335,13 @@ public T Get(string name) /// The DynamicParameters instance public DynamicParameters Output(T target, Expression> expression, DbType? dbType = null, int? size = null) { - var failMessage = "Expression must be a property/field chain off of a(n) {0} instance"; - failMessage = string.Format(failMessage, typeof(T).Name); - Action @throw = () => throw new InvalidOperationException(failMessage); + static void ThrowInvalidChain() + => throw new InvalidOperationException($"Expression must be a property/field chain off of a(n) {typeof(T).Name} instance"); // Is it even a MemberExpression? +#pragma warning disable IDE0019 // Use pattern matching - already complex enough var lastMemberAccess = expression.Body as MemberExpression; +#pragma warning restore IDE0019 // Use pattern matching if (lastMemberAccess == null || (!(lastMemberAccess.Member is PropertyInfo) @@ -350,14 +349,14 @@ public DynamicParameters Output(T target, Expression> express { if (expression.Body.NodeType == ExpressionType.Convert && expression.Body.Type == typeof(object) - && ((UnaryExpression)expression.Body).Operand is MemberExpression) + && ((UnaryExpression)expression.Body).Operand is MemberExpression member) { // It's got to be unboxed - lastMemberAccess = (MemberExpression)((UnaryExpression)expression.Body).Operand; + lastMemberAccess = member; } else { - @throw(); + ThrowInvalidChain(); } } @@ -374,10 +373,12 @@ public DynamicParameters Output(T target, Expression> express names.Insert(0, diving?.Member.Name); chain.Insert(0, diving); +#pragma warning disable IDE0019 // use pattern matching; this is fine! var constant = diving?.Expression as ParameterExpression; diving = diving?.Expression as MemberExpression; +#pragma warning restore IDE0019 // use pattern matching - if (constant != null && constant.Type == typeof(T)) + if (constant is object && constant.Type == typeof(T)) { break; } @@ -385,7 +386,7 @@ public DynamicParameters Output(T target, Expression> express || (!(diving.Member is PropertyInfo) && !(diving.Member is FieldInfo))) { - @throw(); + ThrowInvalidChain(); } } while (diving != null); @@ -430,9 +431,9 @@ public DynamicParameters Output(T target, Expression> express // GET READY var lastMember = lastMemberAccess.Member; - if (lastMember is PropertyInfo) + if (lastMember is PropertyInfo property) { - var set = ((PropertyInfo)lastMember).GetSetMethod(true); + var set = property.GetSetMethod(true); il.Emit(OpCodes.Callvirt, set); // SET } else diff --git a/Dapper/Properties/AssemblyInfo.cs b/Dapper/Properties/AssemblyInfo.cs new file mode 100644 index 000000000..d6c9623b5 --- /dev/null +++ b/Dapper/Properties/AssemblyInfo.cs @@ -0,0 +1,4 @@ + +#if PLAT_SKIP_LOCALS_INIT +[module: System.Runtime.CompilerServices.SkipLocalsInit] +#endif diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index f52ba355c..3e6487423 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -23,7 +23,7 @@ public static partial class SqlMapper /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// /// Execute a query asynchronously using Task. @@ -85,7 +85,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Co /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// /// Execute a single-row query asynchronously using Task. @@ -98,7 +98,7 @@ public static Task> QueryAsync(this IDbConnection cnn, string /// The command timeout (in seconds). /// The type of command to execute. public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -111,7 +111,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, obj /// The command timeout (in seconds). /// The type of command to execute. public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -124,7 +124,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string /// The command timeout (in seconds). /// The type of command to execute. public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -137,7 +137,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, ob /// The command timeout (in seconds). /// The type of command to execute. public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -149,7 +149,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, strin /// The command timeout (in seconds). /// The type of command to execute. public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -161,7 +161,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, /// The command timeout (in seconds). /// The type of command to execute. public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -173,7 +173,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, str /// The command timeout (in seconds). /// The type of command to execute. public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -185,7 +185,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, /// The command timeout (in seconds). /// The type of command to execute. public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a query asynchronously using Task. @@ -201,7 +201,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, st public static Task> QueryAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - return QueryAsync(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + return QueryAsync(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); } /// @@ -218,7 +218,7 @@ public static Task> QueryAsync(this IDbConnection cnn, Type public static Task QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - return QueryRowAsync(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + return QueryRowAsync(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a single-row query asynchronously using Task. @@ -234,7 +234,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, Type type, st public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - return QueryRowAsync(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + return QueryRowAsync(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a single-row query asynchronously using Task. @@ -250,7 +250,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type public static Task QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - return QueryRowAsync(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + return QueryRowAsync(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a single-row query asynchronously using Task. @@ -266,7 +266,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, Type type, s public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); - return QueryRowAsync(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default(CancellationToken))); + return QueryRowAsync(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// @@ -410,54 +410,52 @@ private static async Task> QueryAsync(this IDbConnection cnn, var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; - using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); + DbDataReader reader = null; + try { - DbDataReader reader = null; - try - { - if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); - reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false); + if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); + reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false); - var tuple = info.Deserializer; - int hash = GetColumnHash(reader); - if (tuple.Func == null || tuple.Hash != hash) - { - if (reader.FieldCount == 0) - return Enumerable.Empty(); - tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); - if (command.AddToCache) SetQueryCache(identity, info); - } + var tuple = info.Deserializer; + int hash = GetColumnHash(reader); + if (tuple.Func == null || tuple.Hash != hash) + { + if (reader.FieldCount == 0) + return Enumerable.Empty(); + tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); + if (command.AddToCache) SetQueryCache(identity, info); + } - var func = tuple.Func; + var func = tuple.Func; - if (command.Buffered) - { - var buffer = new List(); - var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; - while (await reader.ReadAsync(cancel).ConfigureAwait(false)) - { - object val = func(reader); - buffer.Add(GetValue(reader, effectiveType, val)); - } - while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } - command.OnCompleted(); - return buffer; - } - else + if (command.Buffered) + { + var buffer = new List(); + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { - // can't use ReadAsync / cancellation; but this will have to do - wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior - var deferred = ExecuteReaderSync(reader, func, command.Parameters); - reader = null; // to prevent it being disposed before the caller gets to see it - return deferred; + object val = func(reader); + buffer.Add(GetValue(reader, effectiveType, val)); } + while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } + command.OnCompleted(); + return buffer; } - finally + else { - using (reader) { /* dispose if non-null */ } - if (wasClosed) cnn.Close(); + // can't use ReadAsync / cancellation; but this will have to do + wasClosed = false; // don't close if handing back an open reader; rely on the command-behavior + var deferred = ExecuteReaderSync(reader, func, command.Parameters); + reader = null; // to prevent it being disposed before the caller gets to see it + return deferred; } } + finally + { + using (reader) { /* dispose if non-null */ } + if (wasClosed) cnn.Close(); + } } private static async Task QueryRowAsync(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) @@ -467,36 +465,34 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; - using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); + DbDataReader reader = null; + try { - DbDataReader reader = null; - try - { - if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); - reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0 - ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition - : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false); + if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); + reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, (row & Row.Single) != 0 + ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition + : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false); - T result = default; - if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) - { - result = ReadRow(info, identity, ref command, effectiveType, reader); + T result = default; + if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) + { + result = ReadRow(info, identity, ref command, effectiveType, reader); - if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); - while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore rows after the first */ } - } - else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one - { - ThrowZeroRows(row); - } - while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore result sets after the first */ } - return result; + if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore rows after the first */ } } - finally + else if ((row & Row.FirstOrDefault) == 0) // demanding a row, and don't have one { - using (reader) { /* dispose if non-null */ } - if (wasClosed) cnn.Close(); + ThrowZeroRows(row); } + while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore result sets after the first */ } + return result; + } + finally + { + using (reader) { /* dispose if non-null */ } + if (wasClosed) cnn.Close(); } } @@ -511,7 +507,7 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T /// Is it a stored proc or a batch? /// The number of rows affected. public static Task ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default(CancellationToken))); + ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// /// Execute a command asynchronously using Task. @@ -609,25 +605,23 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD } else { - using (var cmd = command.TrySetupAsyncCommand(cnn, null)) + using var cmd = command.TrySetupAsyncCommand(cnn, null); + foreach (var obj in multiExec) { - foreach (var obj in multiExec) + if (isFirst) { - if (isFirst) - { - masterSql = cmd.CommandText; - isFirst = false; - var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); - info = GetCacheInfo(identity, obj, command.AddToCache); - } - else - { - cmd.CommandText = masterSql; // because we do magic replaces on "in" etc - cmd.Parameters.Clear(); // current code is Add-tastic - } - info.ParamReader(cmd, obj); - total += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); + masterSql = cmd.CommandText; + isFirst = false; + var identity = new Identity(command.CommandText, cmd.CommandType, cnn, null, obj.GetType()); + info = GetCacheInfo(identity, obj, command.AddToCache); } + else + { + cmd.CommandText = masterSql; // because we do magic replaces on "in" etc + cmd.Parameters.Clear(); // current code is Add-tastic + } + info.ParamReader(cmd, obj); + total += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); } } @@ -645,19 +639,17 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; - using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) + using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); + try { - try - { - if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); - var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); - command.OnCompleted(); - return result; - } - finally - { - if (wasClosed) cnn.Close(); - } + if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); + var result = await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); + command.OnCompleted(); + return result; + } + finally + { + if (wasClosed) cnn.Close(); } } @@ -680,7 +672,7 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini /// An enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, - new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform a asynchronous multi-mapping query with 2 input types. @@ -717,7 +709,7 @@ public static Task> QueryAsync(th /// An enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, - new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform a asynchronous multi-mapping query with 3 input types. @@ -756,7 +748,7 @@ public static Task> QueryAsyncAn enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, - new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform a asynchronous multi-mapping query with 4 input types. @@ -797,7 +789,7 @@ public static Task> QueryAsyncAn enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, - new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform a asynchronous multi-mapping query with 5 input types. @@ -840,7 +832,7 @@ public static Task> QueryAsyncAn enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, - new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform a asynchronous multi-mapping query with 6 input types. @@ -885,7 +877,7 @@ public static Task> QueryAsyncAn enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, - new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)), map, splitOn); + new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// /// Perform an asynchronous multi-mapping query with 7 input types. @@ -916,13 +908,11 @@ private static async Task> MultiMapAsync(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true); - return command.Buffered ? results.ToList() : results; - } + using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); + using var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false); + if (!command.Buffered) wasClosed = false; // handing back open reader; rely on command-behavior + var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true); + return command.Buffered ? results.ToList() : results; } finally { @@ -948,7 +938,7 @@ private static async Task> MultiMapAsyncAn enumerable of . public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { - var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default(CancellationToken)); + var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default); return MultiMapAsync(cnn, command, types, map, splitOn); } @@ -966,12 +956,10 @@ private static async Task> MultiMapAsync(this IDbC try { if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); - using (var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader)) - using (var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false)) - { - var results = MultiMapImpl(null, default(CommandDefinition), types, map, splitOn, reader, identity, true); - return command.Buffered ? results.ToList() : results; - } + using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); + using var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, command.CancellationToken).ConfigureAwait(false); + var results = MultiMapImpl(null, default, types, map, splitOn, reader, identity, true); + return command.Buffered ? results.ToList() : results; } finally { diff --git a/Dapper/SqlMapper.DapperRow.cs b/Dapper/SqlMapper.DapperRow.cs index bb7afb0a9..8e55aa9c2 100644 --- a/Dapper/SqlMapper.DapperRow.cs +++ b/Dapper/SqlMapper.DapperRow.cs @@ -76,7 +76,7 @@ public override string ToString() } } - return sb.Append('}').__ToStringRecycle(); + return sb.Append('}').ToStringRecycle(); } public IEnumerator> GetEnumerator() diff --git a/Dapper/SqlMapper.DapperRowMetaObject.cs b/Dapper/SqlMapper.DapperRowMetaObject.cs index 3a71b81ab..de33fbdd9 100644 --- a/Dapper/SqlMapper.DapperRowMetaObject.cs +++ b/Dapper/SqlMapper.DapperRowMetaObject.cs @@ -89,11 +89,10 @@ public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.Se return callMethod; } - static readonly string[] s_nixKeys = new string[0]; public override IEnumerable GetDynamicMemberNames() { if(HasValue && Value is IDictionary lookup) return lookup.Keys; - return s_nixKeys; + return Array.Empty(); } } } diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index 89528826b..139b8b27e 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -141,7 +141,7 @@ private async Task NextResultAsync() { if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false)) { - readCount++; + // readCount++; gridIndex++; IsConsumed = false; } @@ -197,7 +197,7 @@ private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type t if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); IsConsumed = true; - T result = default(T); + T result = default; if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { var typedIdentity = identity.ForGrid(type, gridIndex); diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index 2a05198eb..f58822007 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -168,7 +168,7 @@ private T ReadRow(Type type, Row row) if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); IsConsumed = true; - T result = default(T); + T result = default; if (reader.Read() && reader.FieldCount != 0) { var typedIdentity = identity.ForGrid(type, gridIndex); @@ -210,7 +210,7 @@ private IEnumerable MultiReadInternal(null, default(CommandDefinition), func, splitOn, reader, identity, false)) + foreach (var r in MultiMapImpl(null, default, func, splitOn, reader, identity, false)) { yield return r; } @@ -226,7 +226,7 @@ private IEnumerable MultiReadInternal(Type[] types, Func(null, default(CommandDefinition), types, map, splitOn, reader, identity, false)) + foreach (var r in MultiMapImpl(null, default, types, map, splitOn, reader, identity, false)) { yield return r; } @@ -383,7 +383,7 @@ private IEnumerable ReadDeferred(int index, Func dese } } - private int gridIndex, readCount; + private int gridIndex; //, readCount; private readonly IParameterCallbacks callbacks; /// @@ -400,7 +400,7 @@ private void NextResult() { if (reader.NextResult()) { - readCount++; + // readCount++; gridIndex++; IsConsumed = false; } @@ -431,6 +431,7 @@ public void Dispose() Command.Dispose(); Command = null; } + GC.SuppressFinalize(this); } } } diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs index 02000ca45..dca4ebc6d 100644 --- a/Dapper/SqlMapper.IDataReader.cs +++ b/Dapper/SqlMapper.IDataReader.cs @@ -138,7 +138,7 @@ public static Func GetRowParser(this IDataReader reader, Ty public static Func GetRowParser(this IDataReader reader, Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { - concreteType = concreteType ?? typeof(T); + concreteType ??= typeof(T); var func = GetDeserializer(concreteType, reader, startIndex, length, returnNullIfFirstMissing); if (concreteType.IsValueType) { diff --git a/Dapper/SqlMapper.Identity.cs b/Dapper/SqlMapper.Identity.cs index 3ae04993b..acaf2989c 100644 --- a/Dapper/SqlMapper.Identity.cs +++ b/Dapper/SqlMapper.Identity.cs @@ -39,20 +39,16 @@ bool Map() return count; } internal override int TypeCount => s_typeCount; - internal override Type GetType(int index) - { - switch (index) - { - case 0: return typeof(TFirst); - case 1: return typeof(TSecond); - case 2: return typeof(TThird); - case 3: return typeof(TFourth); - case 4: return typeof(TFifth); - case 5: return typeof(TSixth); - case 6: return typeof(TSeventh); - default: return base.GetType(index); - } - } + internal override Type GetType(int index) => index switch { + 0 => typeof(TFirst), + 1 => typeof(TSecond), + 2 => typeof(TThird), + 3 => typeof(TFourth), + 4 => typeof(TFifth), + 5 => typeof(TSixth), + 6 => typeof(TSeventh), + _ => base.GetType(index), + }; } internal sealed class IdentityWithTypes : Identity { @@ -199,7 +195,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti public bool Equals(Identity other) { if (ReferenceEquals(this, other)) return true; - if (ReferenceEquals(other, null)) return false; + if (other is null) return false; int typeCount; return gridIndex == other.gridIndex diff --git a/Dapper/SqlMapper.Link.cs b/Dapper/SqlMapper.Link.cs index 602336fe7..277f2a9e9 100644 --- a/Dapper/SqlMapper.Link.cs +++ b/Dapper/SqlMapper.Link.cs @@ -24,7 +24,7 @@ public static bool TryGet(Link link, TKey key, out TValue value) } link = link.Tail; } - value = default(TValue); + value = default; return false; } diff --git a/Dapper/SqlMapper.LiteralToken.cs b/Dapper/SqlMapper.LiteralToken.cs index d5ae56583..5ad399ab8 100644 --- a/Dapper/SqlMapper.LiteralToken.cs +++ b/Dapper/SqlMapper.LiteralToken.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; namespace Dapper { @@ -25,7 +26,7 @@ internal LiteralToken(string token, string member) Member = member; } - internal static readonly IList None = new LiteralToken[0]; + internal static IList None => Array.Empty(); } } } diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index ef53b8b7a..088f464c7 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -111,9 +111,7 @@ public override string ToString() } public override bool Equals(object obj) - { - return obj is DeserializerKey && Equals((DeserializerKey)obj); - } + => obj is DeserializerKey key && Equals(key); public bool Equals(DeserializerKey other) { diff --git a/Dapper/SqlMapper.TypeHandler.cs b/Dapper/SqlMapper.TypeHandler.cs index e8745985e..4e9591d9a 100644 --- a/Dapper/SqlMapper.TypeHandler.cs +++ b/Dapper/SqlMapper.TypeHandler.cs @@ -78,7 +78,7 @@ public override void SetValue(IDbDataParameter parameter, T value) /// The typed value public override T Parse(object value) { - if (value == null || value is DBNull) return default(T); + if (value == null || value is DBNull) return default; return Parse((string)value); } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 3bbd83161..b468d7b39 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -13,6 +13,7 @@ using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -73,7 +74,7 @@ private static void CollectCacheGarbage() { if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN) { - _queryCache.TryRemove(pair.Key, out CacheInfo cache); + _queryCache.TryRemove(pair.Key, out CacheInfo _); } } } @@ -112,7 +113,7 @@ private static void PurgeQueryCacheByType(Type type) foreach (var entry in _queryCache) { if (entry.Key.type == type) - _queryCache.TryRemove(entry.Key, out CacheInfo cache); + _queryCache.TryRemove(entry.Key, out CacheInfo _); } TypeDeserializerCache.Purge(type); } @@ -339,7 +340,7 @@ public static DbType GetDbType(object value) { if (value == null || value is DBNull) return DbType.Object; - return LookupDbType(value.GetType(), "n/a", false, out ITypeHandler handler); + return LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _); } /// @@ -504,7 +505,9 @@ public static T ExecuteScalar(this IDbConnection cnn, CommandDefinition comma private static IEnumerable GetMultiExec(object param) { +#pragma warning disable IDE0038 // Use pattern matching - complicated enough! return (param is IEnumerable +#pragma warning restore IDE0038 // Use pattern matching && !(param is string || param is IEnumerable> || param is IDynamicParameters) @@ -1135,22 +1138,22 @@ internal enum Row private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = Array.Empty(); private static void ThrowMultipleRows(Row row) { - switch (row) - { // get the standard exception from the runtime - case Row.Single: ErrTwoRows.Single(); break; - case Row.SingleOrDefault: ErrTwoRows.SingleOrDefault(); break; - default: throw new InvalidOperationException(); - } + _ = row switch + { + Row.Single => ErrTwoRows.Single(), + Row.SingleOrDefault => ErrTwoRows.SingleOrDefault(), + _ => throw new InvalidOperationException(), + }; } private static void ThrowZeroRows(Row row) { - switch (row) - { // get the standard exception from the runtime - case Row.First: ErrZeroRows.First(); break; - case Row.Single: ErrZeroRows.Single(); break; - default: throw new InvalidOperationException(); - } + _ = row switch + { // get the standard exception from the runtime + Row.First => ErrZeroRows.First(), + Row.Single => ErrZeroRows.Single(), + _ => throw new InvalidOperationException(), + }; } private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType) @@ -1557,25 +1560,16 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn } private static Func GenerateMapper(Func deserializer, Func[] otherDeserializers, object map) + => otherDeserializers.Length switch { - switch (otherDeserializers.Length) - { - case 1: - return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r)); - case 2: - return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r)); - case 3: - return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r)); - case 4: - return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r)); - case 5: - return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r)); - case 6: - return r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r)); - default: - throw new NotSupportedException(); - } - } + 1 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r)), + 2 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r)), + 3 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r)), + 4 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r)), + 5 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r)), + 6 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r), (TThird)otherDeserializers[1](r), (TFourth)otherDeserializers[2](r), (TFifth)otherDeserializers[3](r), (TSixth)otherDeserializers[4](r), (TSeventh)otherDeserializers[5](r)), + _ => throw new NotSupportedException(), + }; private static Func GenerateMapper(int length, Func deserializer, Func[] otherDeserializers, Func map) { @@ -1824,9 +1818,15 @@ private static Exception MultiMapException(IDataRecord reader) try { hasFields = reader != null && reader.FieldCount != 0; } catch { /* don't throw when trying to throw */ } if (hasFields) + { +#pragma warning disable CA2208 // Instantiate argument exceptions correctly return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); +#pragma warning restore CA2208 // Instantiate argument exceptions correctly + } else + { return new InvalidOperationException("No columns were selected"); + } } internal static Func GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) @@ -2114,7 +2114,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj { sb.Append(',').Append(variableName).Append(i).Append(suffix); } - return sb.__ToStringRecycle(); + return sb.ToStringRecycle(); } else { @@ -2125,7 +2125,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj sb.Append(',').Append(variableName); if (!byPosition) sb.Append(i); else sb.Append(namePrefix).Append(i).Append(variableName); } - return sb.Append(')').__ToStringRecycle(); + return sb.Append(')').ToStringRecycle(); } }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); } @@ -2136,25 +2136,20 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, bool byPosition) { if (list == null || splitAt < 0) return false; - switch (list) + return list switch { - case IEnumerable l: - return TryStringSplit(ref l, splitAt, namePrefix, command, "int", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); - case IEnumerable l: - return TryStringSplit(ref l, splitAt, namePrefix, command, "bigint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); - case IEnumerable l: - return TryStringSplit(ref l, splitAt, namePrefix, command, "smallint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); - case IEnumerable l: - return TryStringSplit(ref l, splitAt, namePrefix, command, "tinyint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))); - } - return false; + IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "int", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), + IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "bigint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), + IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "smallint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), + IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "tinyint", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), + _ => false, + }; } private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition, Action append) { - var typed = list as ICollection; - if (typed == null) + if (!(list is ICollection typed)) { typed = list.ToList(); list = typed; // because we still need to be able to iterate it, even if we fail here @@ -2217,15 +2212,10 @@ public static object SanitizeParameterValue(object value) if (value == null) return DBNull.Value; if (value is Enum) { - TypeCode typeCode; - if (value is IConvertible) - { - typeCode = ((IConvertible)value).GetTypeCode(); - } - else - { - typeCode = Type.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); - } + TypeCode typeCode = value is IConvertible convertible + ? convertible.GetTypeCode() + : Type.GetTypeCode(Enum.GetUnderlyingType(value.GetType())); + switch (typeCode) { case TypeCode.Byte: return (byte)value; @@ -2336,7 +2326,7 @@ public static string Format(object value) } else { - return sb.Append(')').__ToStringRecycle(); + return sb.Append(')').ToStringRecycle(); } } throw new NotSupportedException($"The type '{value.GetType().Name}' is not supported for SQL literals."); @@ -2948,8 +2938,8 @@ private static Func GetStructDeserializer(Type type, Type e private static T Parse(object value) { - if (value == null || value is DBNull) return default(T); - if (value is T) return (T)value; + if (value is null || value is DBNull) return default; + if (value is T t) return t; var type = typeof(T); type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum) @@ -2977,7 +2967,9 @@ private static readonly MethodInfo /// Gets type-map for the given type /// /// Type map instance, default is to create new instance of DefaultTypeMap +#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API public static Func TypeMapProvider = (Type type) => new DefaultTypeMap(type); +#pragma warning restore CA2211 // Non-constant fields should not be visible /// /// Gets type-map for the given . @@ -3537,7 +3529,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro else { bool handled = false; - OpCode opCode = default(OpCode); + OpCode opCode = default; switch (Type.GetTypeCode(from)) { case TypeCode.Boolean: @@ -3773,7 +3765,7 @@ private static StringBuilder GetStringBuilder() return new StringBuilder(); } - private static string __ToStringRecycle(this StringBuilder obj) + private static string ToStringRecycle(this StringBuilder obj) { if (obj == null) return ""; var s = obj.ToString(); diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index b43ae1571..f092823f5 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -31,6 +31,10 @@ private async static Task ThrowDisposedAsync() } public override void Close() { } public override DataTable GetSchemaTable() => ThrowDisposed(); + +#if PLAT_NO_REMOTING + [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif public override object InitializeLifetimeService() => ThrowDisposed(); protected override void Dispose(bool disposing) { } public override bool GetBoolean(int ordinal) => ThrowDisposed(); @@ -116,6 +120,10 @@ public DbWrappedReader(IDbCommand cmd, DbDataReader reader) public override void Close() => _reader.Close(); public override DataTable GetSchemaTable() => _reader.GetSchemaTable(); + +#if PLAT_NO_REMOTING + [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif public override object InitializeLifetimeService() => _reader.InitializeLifetimeService(); public override int Depth => _reader.Depth; diff --git a/Directory.Build.props b/Directory.Build.props index 7a7990da7..9d62e68ee 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -13,7 +13,7 @@ git https://github.com/StackExchange/Dapper false - + $(NOWARN);IDE0056;IDE0057;IDE0079 true embedded en-US @@ -21,7 +21,7 @@ true - 8.0 + 9.0 diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs index bd79a8d79..c76cdb28e 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs @@ -40,7 +40,7 @@ public Post Compiled() public Post SqlQuery() { Step(); - return Context.Posts.FromSql("select * from Posts where Id = {0}", i).First(); + return Context.Posts.FromSqlRaw("select * from Posts where Id = {0}", i).First(); } [Benchmark(Description = "First (No Tracking)")] diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.XPO.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.XPO.cs index 62a5fe295..d4bf36ce5 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.XPO.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.XPO.cs @@ -19,8 +19,10 @@ public void Setup() BaseSetup(); IDataLayer dataLayer = XpoDefault.GetDataLayer(_connection, DevExpress.Xpo.DB.AutoCreateOption.SchemaAlreadyExists); dataLayer.Dictionary.GetDataStoreSchema(typeof(Xpo.Post)); - _session = new UnitOfWork(dataLayer, dataLayer); - _session.IdentityMapBehavior = IdentityMapBehavior.Strong; + _session = new UnitOfWork(dataLayer, dataLayer) + { + IdentityMapBehavior = IdentityMapBehavior.Strong + }; _session.TypesManager.EnsureIsTypedObjectValid(); } diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 75d3c039c..d75589e06 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -5,26 +5,26 @@ Exe net462;netcoreapp3.1 false + $(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060 - + - + - - - - - - + + + + + - - + + - - - + + + @@ -40,8 +40,12 @@ + + + + diff --git a/benchmarks/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs b/benchmarks/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs index 29bc63242..03d8f6f34 100644 --- a/benchmarks/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs +++ b/benchmarks/Dapper.Tests.Performance/Dashing/DashingConfiguration.cs @@ -6,7 +6,7 @@ public class DashingConfiguration : BaseConfiguration { public DashingConfiguration() { - this.Add(); + Add(); } } -} \ No newline at end of file +} diff --git a/benchmarks/Dapper.Tests.Performance/LegacyTests.cs b/benchmarks/Dapper.Tests.Performance/LegacyTests.cs index 014adae30..ebd899dfb 100644 --- a/benchmarks/Dapper.Tests.Performance/LegacyTests.cs +++ b/benchmarks/Dapper.Tests.Performance/LegacyTests.cs @@ -143,7 +143,7 @@ public async Task RunAsync(int iterations) tests.Add(id => entityContext.Posts.First(p => p.Id == id), "Entity Framework Core"); var entityContext2 = new EFCoreContext(ConnectionString); - tests.Add(id => entityContext2.Posts.FromSql("select * from Posts where Id = {0}", id).First(), "Entity Framework Core: FromSql"); + tests.Add(id => entityContext2.Posts.FromSqlRaw("select * from Posts where Id = {0}", id).First(), "Entity Framework Core: FromSql"); var entityContext3 = new EFCoreContext(ConnectionString); tests.Add(id => entityContext3.Posts.AsNoTracking().First(p => p.Id == id), "Entity Framework Core: No Tracking"); diff --git a/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs b/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs index 27fa4d727..f452a95cb 100644 --- a/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs +++ b/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs @@ -115,9 +115,9 @@ public class DynamicModel { private readonly DbProviderFactory _factory; #pragma warning disable 0649 -#pragma warning disable RCS1169 // Mark field as read-only. +#pragma warning disable RCS1169,IDE0044 // Mark field as read-only. private string _connectionString; -#pragma warning restore RCS1169 // Mark field as read-only. +#pragma warning restore RCS1169,IDE0044 // Mark field as read-only. #pragma warning restore 0649 public DynamicModel(string connectionStringName = "", string tableName = "", string primaryKeyField = "") @@ -452,4 +452,4 @@ public virtual dynamic Single(object key, string columns = "*") } } } -#pragma warning restore RCS1141 // Add parameter to documentation comment. \ No newline at end of file +#pragma warning restore RCS1141 // Add parameter to documentation comment. diff --git a/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs b/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs index 40690fd95..add3df772 100644 --- a/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs +++ b/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs @@ -612,10 +612,12 @@ public static bool SplitSqlForPaging(string sql, out string sqlCount, out string throw new Exception("Unable to parse SQL statement for paged query"); // Setup the paged result - var result = new Page(); - result.CurrentPage = page; - result.ItemsPerPage = itemsPerPage; - result.TotalItems = ExecuteScalar(sqlCount, args); + var result = new Page + { + CurrentPage = page, + ItemsPerPage = itemsPerPage, + TotalItems = ExecuteScalar(sqlCount, args) + }; result.TotalPages = result.TotalItems / itemsPerPage; if ((result.TotalItems % itemsPerPage) != 0) result.TotalPages++; @@ -829,7 +831,7 @@ public int Update(string tableName, string primaryKeyName, object poco, object p // Don't update the primary key, but grab the value if we don't have it if (i.Key == primaryKeyName) { - primaryKeyValue = primaryKeyValue ?? i.Value.PropertyInfo.GetValue(poco, null); + primaryKeyValue ??= i.Value.PropertyInfo.GetValue(poco, null); continue; } @@ -1441,4 +1443,4 @@ public void Build(StringBuilder sb, List args, Sql lhs) } } } -#pragma warning restore RCS1023 // Format empty block. \ No newline at end of file +#pragma warning restore RCS1023 // Format empty block. diff --git a/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj index 986a82a04..333da6238 100644 --- a/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ b/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj @@ -2,17 +2,22 @@ Dapper.Tests.Contrib Dapper Contrib Test Suite - netcoreapp3.1;net462 + netcoreapp3.1;net462;net5.0 + $(NoWarn);CA1816;IDE0063;xUnit1004 + + + PreserveNewest + - - - + + + diff --git a/tests/Dapper.Tests.Contrib/TestSuites.cs b/tests/Dapper.Tests.Contrib/TestSuites.cs index 793d87bfe..55d4ef5cf 100644 --- a/tests/Dapper.Tests.Contrib/TestSuites.cs +++ b/tests/Dapper.Tests.Contrib/TestSuites.cs @@ -1,5 +1,5 @@ using Microsoft.Data.Sqlite; -using MySql.Data.MySqlClient; +using MySqlConnector; using System; using System.Data; using System.Data.SqlClient; diff --git a/tests/Dapper.Tests.Contrib/xunit.runner.json b/tests/Dapper.Tests.Contrib/xunit.runner.json new file mode 100644 index 000000000..f23143776 --- /dev/null +++ b/tests/Dapper.Tests.Contrib/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "shadowCopy": false +} \ No newline at end of file diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index d363caf95..34990cb40 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -35,7 +35,7 @@ public abstract class AsyncTests : TestBase where TProvide { private DbConnection _marsConnection; - private DbConnection MarsConnection => _marsConnection ?? (_marsConnection = Provider.GetOpenConnection(true)); + private DbConnection MarsConnection => _marsConnection ??= Provider.GetOpenConnection(true); [Fact] public async Task TestBasicStringUsageAsync() @@ -323,7 +323,7 @@ public async Task LiteralReplacementClosed() using (var conn = GetClosedConnection()) await LiteralReplacement(conn).ConfigureAwait(false); } - private async Task LiteralReplacement(IDbConnection conn) + private static async Task LiteralReplacement(IDbConnection conn) { try { @@ -352,7 +352,7 @@ public async Task LiteralReplacementDynamicClosed() using (var conn = GetClosedConnection()) await LiteralReplacementDynamic(conn).ConfigureAwait(false); } - private async Task LiteralReplacementDynamic(IDbConnection conn) + private static async Task LiteralReplacementDynamic(IDbConnection conn) { var args = new DynamicParameters(); args.Add("id", 123); @@ -841,7 +841,7 @@ public abstract class AsyncQueryCacheTests : TestBase wher private readonly ITestOutputHelper _log; public AsyncQueryCacheTests(ITestOutputHelper log) => _log = log; private DbConnection _marsConnection; - private DbConnection MarsConnection => _marsConnection ?? (_marsConnection = Provider.GetOpenConnection(true)); + private DbConnection MarsConnection => _marsConnection ??= Provider.GetOpenConnection(true); public override void Dispose() { diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 443b752d6..5f74e4b84 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,8 +2,9 @@ Dapper.Tests Dapper Core Test Suite - netcoreapp3.1;net462;net472 + netcoreapp3.1;net462;net472;net5.0 $(DefineConstants);MSSQLCLIENT + $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208 @@ -13,14 +14,16 @@ - - - - - - - + + + + + + + + + @@ -31,5 +34,9 @@ + + + PreserveNewest + diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 1407d84bd..ae6eb31a6 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -1,15 +1,24 @@ -using Microsoft.CSharp.RuntimeBinder; -using System; +using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Data.Common; using System.Diagnostics; -using System.IO; using System.Linq; -using System.Threading; 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")] @@ -56,9 +65,20 @@ public enum TrapEnum : int private struct CarWithAllProps { public string Name { get; set; } - public int Age { get; set; } + public int Age { get; init; } + public Car.TrapEnum Trap { get; init; } + } - public Car.TrapEnum Trap { get; set; } + 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] @@ -71,6 +91,26 @@ public void TestStructs() 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() { diff --git a/tests/Dapper.Tests/ProviderTests.cs b/tests/Dapper.Tests/ProviderTests.cs index cd67b75dc..be51af1d0 100644 --- a/tests/Dapper.Tests/ProviderTests.cs +++ b/tests/Dapper.Tests/ProviderTests.cs @@ -106,7 +106,7 @@ public void DbNumber_MicrosoftData(int create, int test, bool result) => Test(create, test, result); #endif - private void Test(int create, int test, bool result) + private static void Test(int create, int test, bool result) where T : SqlServerDatabaseProvider, new() { var provider = new T(); diff --git a/tests/Dapper.Tests/Providers/MySQLTests.cs b/tests/Dapper.Tests/Providers/MySQLTests.cs index 6df775b68..8cf596fb4 100644 --- a/tests/Dapper.Tests/Providers/MySQLTests.cs +++ b/tests/Dapper.Tests/Providers/MySQLTests.cs @@ -9,7 +9,7 @@ namespace Dapper.Tests { public sealed class MySqlProvider : DatabaseProvider { - public override DbProviderFactory Factory => MySql.Data.MySqlClient.MySqlClientFactory.Instance; + public override DbProviderFactory Factory => MySqlConnector.MySqlConnectorFactory.Instance; public override string GetConnectionString() => GetConnectionString("MySqlConnectionString", "Server=localhost;Database=tests;Uid=test;Pwd=pass;"); diff --git a/tests/Dapper.Tests/TestBase.cs b/tests/Dapper.Tests/TestBase.cs index b2953d4d8..afc9a0f47 100644 --- a/tests/Dapper.Tests/TestBase.cs +++ b/tests/Dapper.Tests/TestBase.cs @@ -18,7 +18,7 @@ public abstract class DatabaseProvider public virtual void Dispose() { } public abstract string GetConnectionString(); - protected string GetConnectionString(string name, string defaultConnectionString) => + protected static string GetConnectionString(string name, string defaultConnectionString) => Environment.GetEnvironmentVariable(name) ?? defaultConnectionString; public DbConnection GetOpenConnection() @@ -85,7 +85,7 @@ protected void SkipIfMsDataClient() protected DbConnection GetOpenConnection() => Provider.GetOpenConnection(); protected DbConnection GetClosedConnection() => Provider.GetClosedConnection(); protected DbConnection _connection; - protected DbConnection connection => _connection ?? (_connection = Provider.GetOpenConnection()); + protected DbConnection connection => _connection ??= Provider.GetOpenConnection(); public TProvider Provider { get; } = DatabaseProvider.Instance; diff --git a/tests/Dapper.Tests/TypeHandlerTests.cs b/tests/Dapper.Tests/TypeHandlerTests.cs index c690daaf9..9742fdf38 100644 --- a/tests/Dapper.Tests/TypeHandlerTests.cs +++ b/tests/Dapper.Tests/TypeHandlerTests.cs @@ -378,9 +378,9 @@ private RatingValueHandler() public override RatingValue Parse(object value) { - if (value is int) + if (value is int i) { - return new RatingValue() { Value = (int)value }; + return new RatingValue() { Value = i }; } throw new FormatException("Invalid conversion to RatingValue"); diff --git a/tests/Dapper.Tests/xunit.runner.json b/tests/Dapper.Tests/xunit.runner.json new file mode 100644 index 000000000..f23143776 --- /dev/null +++ b/tests/Dapper.Tests/xunit.runner.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", + "shadowCopy": false +} \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 9e73ccf63..5c3d2a119 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -16,9 +16,9 @@ - + - + From 9fcbb3b729afc1d93b3ce5289fe1a43eda246235 Mon Sep 17 00:00:00 2001 From: zqlovejyc <943620963@qq.com> Date: Thu, 19 Nov 2020 16:08:01 +0800 Subject: [PATCH 156/312] Fix/1480 fix ReadFirstOrDefaultAsync throw exception --- Dapper/SqlMapper.GridReader.Async.cs | 16 +++++++++++++- Dapper/SqlMapper.GridReader.cs | 33 +++++++++++++++++----------- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index 139b8b27e..a5961fcd0 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; +using System.Globalization; using System.Linq; using System.Threading; using System.Threading.Tasks; @@ -210,7 +211,20 @@ private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type t deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } - result = (T)deserializer.Func(reader); + + object val = deserializer.Func(reader); + + if (val != null) + { + if (val is T) + result = (T)val; + else + { + var convertToType = Nullable.GetUnderlyingType(type) ?? type; + result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } + } + if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent rows */ } } diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index f58822007..dd1696127 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -182,15 +182,18 @@ private T ReadRow(Type type, Row row) cache.Deserializer = deserializer; } object val = deserializer.Func(reader); - if (val == null || val is T) - { - result = (T)val; - } - else + + if (val != null) { - var convertToType = Nullable.GetUnderlyingType(type) ?? type; - result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + if (val is T) + result = (T)val; + else + { + var convertToType = Nullable.GetUnderlyingType(type) ?? type; + result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } } + if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); while (reader.Read()) { /* ignore subsequent rows */ } } @@ -363,15 +366,19 @@ private IEnumerable ReadDeferred(int index, Func dese var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (index == gridIndex && reader.Read()) { + T result = default; object val = deserializer(reader); - if (val == null || val is T) + if (val != null) { - yield return (T)val; - } - else - { - yield return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + if (val is T) + result = (T)val; + else + { + result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + } } + + yield return result; } } finally // finally so that First etc progresses things even when multiple rows From d7ad382543c547b4945c68910c8892d9e2a40d30 Mon Sep 17 00:00:00 2001 From: zqlovejyc <943620963@qq.com> Date: Thu, 19 Nov 2020 19:23:58 +0800 Subject: [PATCH 157/312] Transfer duplicate code and encapsulate it as a static method --- Dapper/SqlMapper.GridReader.Async.cs | 15 ++--------- Dapper/SqlMapper.GridReader.cs | 39 ++++++++++------------------ 2 files changed, 15 insertions(+), 39 deletions(-) diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index a5961fcd0..d227ee63c 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -179,7 +179,7 @@ private Task> ReadAsyncImpl(Type type, bool buffered) else { var result = ReadDeferred(gridIndex, deserializer.Func, type); - if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario + if (buffered) result = result?.ToList(); // for the "not a DbDataReader" scenario return Task.FromResult(result); } } @@ -212,18 +212,7 @@ private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type t cache.Deserializer = deserializer; } - object val = deserializer.Func(reader); - - if (val != null) - { - if (val is T) - result = (T)val; - else - { - var convertToType = Nullable.GetUnderlyingType(type) ?? type; - result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); - } - } + result = ConvertTo(deserializer.Func(reader)); if ((row & Row.Single) != 0 && await reader.ReadAsync(cancel).ConfigureAwait(false)) ThrowMultipleRows(row); while (await reader.ReadAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent rows */ } diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index dd1696127..3dcf4252b 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -159,7 +159,7 @@ private IEnumerable ReadImpl(Type type, bool buffered) } IsConsumed = true; var result = ReadDeferred(gridIndex, deserializer.Func, type); - return buffered ? result.ToList() : result; + return buffered ? result?.ToList() : result; } private T ReadRow(Type type, Row row) @@ -181,18 +181,8 @@ private T ReadRow(Type type, Row row) deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } - object val = deserializer.Func(reader); - if (val != null) - { - if (val is T) - result = (T)val; - else - { - var convertToType = Nullable.GetUnderlyingType(type) ?? type; - result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); - } - } + result = ConvertTo(deserializer.Func(reader)); if ((row & Row.Single) != 0 && reader.Read()) ThrowMultipleRows(row); while (reader.Read()) { /* ignore subsequent rows */ } @@ -363,22 +353,9 @@ private IEnumerable ReadDeferred(int index, Func dese { try { - var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (index == gridIndex && reader.Read()) { - T result = default; - object val = deserializer(reader); - if (val != null) - { - if (val is T) - result = (T)val; - else - { - result = (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); - } - } - - yield return result; + yield return ConvertTo(deserializer(reader)); } } finally // finally so that First etc progresses things even when multiple rows @@ -440,6 +417,16 @@ public void Dispose() } GC.SuppressFinalize(this); } + + private static T ConvertTo(object value) + { + if (value is null or DBNull) + return default; + else if (value is T t) + return t; + else + return (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture); + } } } } From 264eefa47657471c87e5ca503a613907d67ef8c8 Mon Sep 17 00:00:00 2001 From: Sam Royal <20678198+royal@users.noreply.github.com> Date: Thu, 17 Dec 2020 14:14:14 +0000 Subject: [PATCH 158/312] Update Readme.md Fix path to Dapper.Tests.Performance --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 4f48ce904..9779556e3 100644 --- a/Readme.md +++ b/Readme.md @@ -114,7 +114,7 @@ Performance A key feature of Dapper is performance. The following metrics show how long it takes to execute a `SELECT` statement against a DB (in various config, each labeled) and map the data returned to objects. -The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/main/Dapper.Tests.Performance) (contributions welcome!) and can be run via: +The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/main/benchmarks/Dapper.Tests.Performance) (contributions welcome!) and can be run via: ```bash dotnet run -p .\benchmarks\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join ``` From b2b716da0dfbf2e4d754983c2ca959ce63f78175 Mon Sep 17 00:00:00 2001 From: DarkWanderer Date: Wed, 20 May 2020 14:27:21 +0300 Subject: [PATCH 159/312] Add support for Array return value (i.e.: QueryAsync)) --- Dapper/SqlMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index b468d7b39..f17e0cdd8 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1795,7 +1795,7 @@ private static Func GetDeserializer(Type type, IDataReader return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } Type underlyingType = null; - if (!(typeMap.ContainsKey(type) || type.IsEnum || type.FullName == LinqBinary + if (!(typeMap.ContainsKey(type) || type.IsEnum || type.IsArray || type.FullName == LinqBinary || (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum))) { if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) From cfce48ee3b72bb03ed1f2ecc50cad3c2d24f0dc1 Mon Sep 17 00:00:00 2001 From: DarkWanderer Date: Wed, 20 May 2020 14:48:14 +0300 Subject: [PATCH 160/312] Added array underlying type conversion --- Dapper/SqlMapper.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index f17e0cdd8..e4825af37 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1246,6 +1246,14 @@ private static T GetValue(IDataReader reader, Type effectiveType, object val) { return default; } + else if (val is Array array && typeof(T).IsArray) + { + var elementType = typeof(T).GetElementType(); + var result = Array.CreateInstance(elementType, array.Length); + for (int i = 0; i < array.Length; i++) + result.SetValue(Convert.ChangeType(array.GetValue(i), elementType, CultureInfo.InvariantCulture), i); + return (T)(object)result; + } else { try From f724638c200dc0639e461bb4c0b3143072d18881 Mon Sep 17 00:00:00 2001 From: DarkWanderer Date: Mon, 24 Aug 2020 12:18:40 +0300 Subject: [PATCH 161/312] Add PostgreSQL test for array --- tests/Dapper.Tests/Providers/PostgresqlTests.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 9366c244c..00d85ba21 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -2,6 +2,7 @@ using System.Data; using System.Data.Common; using System.Linq; +using Microsoft.VisualBasic; using Xunit; namespace Dapper.Tests @@ -77,6 +78,17 @@ public void TestPostgresqlChar() } } + [FactPostgresql] + public void TestPostgresqlArray() + { + using (var conn = GetOpenNpgsqlConnection()) + { + var r = conn.Query("select array[1,2,3]").ToList(); + Assert.Single(r); + Assert.Equal(new[] { 1, 2, 3 }, r.Single()); + } + } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactPostgresqlAttribute : FactAttribute { From 55b10233f17987b843773ab28a0511a7ae47f0e3 Mon Sep 17 00:00:00 2001 From: DarkWanderer Date: Mon, 24 Aug 2020 12:52:14 +0300 Subject: [PATCH 162/312] Removed accidental namespace addition, made test name clearer --- tests/Dapper.Tests/Providers/PostgresqlTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 00d85ba21..0f1c4e456 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -2,7 +2,6 @@ using System.Data; using System.Data.Common; using System.Linq; -using Microsoft.VisualBasic; using Xunit; namespace Dapper.Tests @@ -79,7 +78,7 @@ public void TestPostgresqlChar() } [FactPostgresql] - public void TestPostgresqlArray() + public void TestPostgresqlSelectArray() { using (var conn = GetOpenNpgsqlConnection()) { From 8876c3f690dc0919f0d4de01f9d37c9cbd977e37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vedran=20Bilopavlovi=C4=87?= Date: Tue, 29 Dec 2020 11:58:43 +0100 Subject: [PATCH 163/312] Add Norm benchmarks tests --- .../Benchmarks.Norm.cs | 42 +++++++++++++++++++ .../Dapper.Tests.Performance.csproj | 5 +++ 2 files changed, 47 insertions(+) create mode 100644 benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs new file mode 100644 index 000000000..fdf6b045a --- /dev/null +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs @@ -0,0 +1,42 @@ +#if NET4X +#else +using BenchmarkDotNet.Attributes; +using System.ComponentModel; +using System.Linq; +using Norm; +using System; + +namespace Dapper.Tests.Performance +{ + [Description("Norm")] + public class NormBenchmarks : BenchmarkBase + { + [GlobalSetup] + public void Setup() + { + BaseSetup(); + } + + [Benchmark(Description = "Read (class)")] + public Post Read() + { + Step(); + return _connection.Read("select * from Posts where Id = @Id", i).First(); + } + + [Benchmark(Description = "Read (simple values in a tuple)")] + public (int, string, DateTime, DateTime, int?, int?, int?, int?, int?, int?, int?, int?) ReadSimpleValues() + { + Step(); + return _connection.Read("select * from Posts where Id = @Id", i).First(); + } + + [Benchmark(Description = "Read<(T1, T2, ...)> (named tuple)")] + public (int Id, string Text, DateTime CreationDate, DateTime LastChangeDate, int? Counter1, int? Counter2, int? Counter3, int? Counter4, int? Counter5, int? Counter6, int? Counter7, int? Counter8) ReadTuple() + { + Step(); + return _connection.Read<(int Id, string Text, DateTime CreationDate, DateTime LastChangeDate, int? Counter1, int? Counter2, int? Counter3, int? Counter4, int? Counter5, int? Counter6, int? Counter7, int? Counter8)>("select * from Posts where Id = @Id", i).First(); + } + } +} +#endif diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index d75589e06..42d367728 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -48,4 +48,9 @@ + + + 3.1.0 + + From e0f04e95ec63c8db647072eaa8e853d41440b09c Mon Sep 17 00:00:00 2001 From: GitHubPang <61439577+GitHubPang@users.noreply.github.com> Date: Sat, 9 Jan 2021 12:32:10 +0800 Subject: [PATCH 164/312] Fix typos --- Dapper.Contrib/SqlMapperExtensions.cs | 4 ++-- Dapper.Rainbow/Database.Async.cs | 8 ++++---- Dapper/SqlMapper.Async.cs | 24 ++++++++++++------------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs index eb05d190d..9a30e805c 100644 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ b/Dapper.Contrib/SqlMapperExtensions.cs @@ -47,7 +47,7 @@ public interface ITableNameMapper /// The connection to get a database type name from. public delegate string GetDatabaseTypeDelegate(IDbConnection connection); /// - /// The function to get a a table name from a given + /// The function to get a table name from a given /// /// The to get a table name for. public delegate string TableNameMapperDelegate(Type type); @@ -730,7 +730,7 @@ public class KeyAttribute : Attribute } /// - /// Specifies that this field is a explicitly set primary key in the database + /// Specifies that this field is an explicitly set primary key in the database /// [AttributeUsage(AttributeTargets.Property)] public class ExplicitKeyAttribute : Attribute diff --git a/Dapper.Rainbow/Database.Async.cs b/Dapper.Rainbow/Database.Async.cs index fcbc6a204..cdbf7e41f 100644 --- a/Dapper.Rainbow/Database.Async.cs +++ b/Dapper.Rainbow/Database.Async.cs @@ -111,7 +111,7 @@ public Task QueryFirstOrDefaultAsync(string sql, dynamic param = null) => _connection.QueryFirstOrDefaultAsync(sql, param as object, _transaction, _commandTimeout); /// - /// Perform a asynchronous multi-mapping query with 2 input types. + /// Perform an asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -129,7 +129,7 @@ public Task> QueryAsync(string sq _connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// - /// Perform a asynchronous multi-mapping query with 3 input types. + /// Perform an asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -148,7 +148,7 @@ public Task> QueryAsync(s _connection.QueryAsync(sql, map, param as object, transaction, buffered, splitOn, commandTimeout); /// - /// Perform a asynchronous multi-mapping query with 4 input types. + /// Perform an asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -168,7 +168,7 @@ public Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 5 input types. + /// Perform an asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 3e6487423..73fbef6de 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -654,7 +654,7 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini } /// - /// Perform a asynchronous multi-mapping query with 2 input types. + /// Perform an asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -675,7 +675,7 @@ public static Task> QueryAsync(th new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 2 input types. + /// Perform an asynchronous multi-mapping query with 2 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -690,7 +690,7 @@ public static Task> QueryAsync(th MultiMapAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 3 input types. + /// Perform an asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -712,7 +712,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 3 input types. + /// Perform an asynchronous multi-mapping query with 3 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -728,7 +728,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 4 input types. + /// Perform an asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -751,7 +751,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 4 input types. + /// Perform an asynchronous multi-mapping query with 4 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -768,7 +768,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 5 input types. + /// Perform an asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -792,7 +792,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 5 input types. + /// Perform an asynchronous multi-mapping query with 5 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -810,7 +810,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 6 input types. + /// Perform an asynchronous multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -835,7 +835,7 @@ public static Task> QueryAsync - /// Perform a asynchronous multi-mapping query with 6 input types. + /// Perform an asynchronous multi-mapping query with 6 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -854,7 +854,7 @@ public static Task> QueryAsync(cnn, command, map, splitOn); /// - /// Perform a asynchronous multi-mapping query with 7 input types. + /// Perform an asynchronous multi-mapping query with 7 input types. /// This returns a single type, combined from the raw types via . /// /// The first type in the recordset. @@ -921,7 +921,7 @@ private static async Task> MultiMapAsync - /// Perform a asynchronous multi-mapping query with an arbitrary number of input types. + /// Perform an asynchronous multi-mapping query with an arbitrary number of input types. /// This returns a single type, combined from the raw types via . /// /// The combined type to return. From 0366248861dfcf19086b054c7e26e71fdf6b8f74 Mon Sep 17 00:00:00 2001 From: Rollerss Date: Mon, 1 Feb 2021 21:57:53 -0800 Subject: [PATCH 165/312] Corrected typos --- Dapper/SqlMapper.Settings.cs | 2 +- Dapper/SqlMapper.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index 0f5323591..d425d44d5 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -44,7 +44,7 @@ internal static bool DisableCommandBehaviorOptimizations(CommandBehavior behavio { if (ex.Message.Contains(nameof(CommandBehavior.SingleResult)) || ex.Message.Contains(nameof(CommandBehavior.SingleRow))) - { // some providers just just allow these, so: try again without them and stop issuing them + { // some providers just allow these, so: try again without them and stop issuing them SetAllowedCommandBehaviors(CommandBehavior.SingleResult | CommandBehavior.SingleRow, false); return true; } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index b468d7b39..ad321c0cc 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -3622,8 +3622,8 @@ private static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type /// Throws a data exception, only used internally /// /// The exception to throw. - /// The index the exception occured at. - /// The reader the exception occured in. + /// The index the exception occurred at. + /// The reader the exception occurred in. /// The value that caused the exception. [Obsolete(ObsoleteInternalUsageOnly, false)] public static void ThrowDataException(Exception ex, int index, IDataReader reader, object value) From f6825237f51d350f61229f1db09e2ca81d7cad7f Mon Sep 17 00:00:00 2001 From: Steve Desmond Date: Wed, 17 Feb 2021 14:28:57 -0500 Subject: [PATCH 166/312] Add basic RepoDb benchmark --- .../Benchmarks.RepoDB.cs | 26 +++++++++++++++++++ .../Dapper.Tests.Performance.csproj | 1 + 2 files changed, 27 insertions(+) create mode 100644 benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs new file mode 100644 index 000000000..58f2d1e75 --- /dev/null +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs @@ -0,0 +1,26 @@ +using BenchmarkDotNet.Attributes; +using System.ComponentModel; +using System.Linq; +using RepoDb; + +namespace Dapper.Tests.Performance +{ + [Description("RepoDB")] + public class RepoDbBenchmarks : BenchmarkBase + { + [GlobalSetup] + public void Setup() + { + BaseSetup(); + SqlServerBootstrap.Initialize(); + ClassMapper.Add("Posts"); + } + + [Benchmark(Description = "Query")] + public Post Query() + { + Step(); + return _connection.Query(i).FirstOrDefault(); + } + } +} diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index d75589e06..c5e7eeaea 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -22,6 +22,7 @@ + From cabdf4fc627fd16cf61e2b707726dc9d04c13cb1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vedran=20Bilopavlovi=C4=87?= Date: Mon, 22 Feb 2021 12:43:38 +0100 Subject: [PATCH 167/312] Add conditions for Norm tests in Perfomance project --- benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs | 6 ++---- .../Dapper.Tests.Performance.csproj | 5 ++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs index fdf6b045a..163821f42 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs @@ -1,6 +1,4 @@ -#if NET4X -#else -using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Attributes; using System.ComponentModel; using System.Linq; using Norm; @@ -39,4 +37,4 @@ public Post Read() } } } -#endif + diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 42d367728..08c9c1865 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -48,9 +48,12 @@ - + 3.1.0 + + + From 252e8e074247d749cc22c0631a6058a6ae64aaf9 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Thu, 1 Apr 2021 08:40:59 +0600 Subject: [PATCH 168/312] As far as I aware, Soma related code no longer in this project. And thus can be removed --- .../Dapper.Tests.Performance.csproj | 1 - benchmarks/Dapper.Tests.Performance/Post.cs | 9 --------- .../Dapper.Tests.Performance/Soma/SomaConfig.cs | 16 ---------------- 3 files changed, 26 deletions(-) delete mode 100644 benchmarks/Dapper.Tests.Performance/Soma/SomaConfig.cs diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index d75589e06..d51d41988 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -37,7 +37,6 @@ - diff --git a/benchmarks/Dapper.Tests.Performance/Post.cs b/benchmarks/Dapper.Tests.Performance/Post.cs index 245df5224..514f24ee1 100644 --- a/benchmarks/Dapper.Tests.Performance/Post.cs +++ b/benchmarks/Dapper.Tests.Performance/Post.cs @@ -1,20 +1,11 @@ using System; -#if NET4X -using Soma.Core; -#endif namespace Dapper.Tests.Performance { [ServiceStack.DataAnnotations.Alias("Posts")] -#if NET4X - [Table(Name = "Posts")] -#endif [LinqToDB.Mapping.Table(Name = "Posts")] public class Post { -#if NET4X - [Id(IdKind.Identity)] -#endif [LinqToDB.Mapping.PrimaryKey, LinqToDB.Mapping.Identity] public int Id { get; set; } [LinqToDB.Mapping.Column, LinqToDB.Mapping.Nullable] diff --git a/benchmarks/Dapper.Tests.Performance/Soma/SomaConfig.cs b/benchmarks/Dapper.Tests.Performance/Soma/SomaConfig.cs deleted file mode 100644 index 6f81e0577..000000000 --- a/benchmarks/Dapper.Tests.Performance/Soma/SomaConfig.cs +++ /dev/null @@ -1,16 +0,0 @@ -#if NET4X -using Soma.Core; -using System; - -namespace Dapper.Tests.Performance.Soma -{ - internal class SomaConfig : MsSqlConfig - { - public override string ConnectionString => BenchmarkBase.ConnectionString; - - public override Action Logger => noOp; - - private static readonly Action noOp = x => { /* nope */ }; - } -} -#endif From bc334ca679b98204b8062edf9df56599a4f620ae Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Wed, 7 Apr 2021 10:32:33 +0600 Subject: [PATCH 169/312] [Benchmarks] Add SqlMarshal benhmarks [SqlMarshal](https://github.com/kant2002/sqlmarshal) is NativeAOT friendly mini-ORM which uses source code generators. ``` // * Summary * BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042 Intel Core i7-6700HQ CPU 2.60GHz (Skylake), 1 CPU, 8 logical and 4 physical cores .NET Core SDK=5.0.201 [Host] : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT ShortRun : .NET Core 3.1.13 (CoreCLR 4.700.21.11102, CoreFX 4.700.21.11602), X64 RyuJIT | ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | |--------------- |------------------------------ |-------- |---------:|----------:|----------:|--------:|-------:|-------:|----------:| | Belgrade | ExecuteReader | Post | 119.0 us | 22.26 us | 37.41 us | 2.6250 | 1.0000 | - | 13.53 KB | | Hand Coded | DataTable | dynamic | 123.9 us | 2.95 us | 5.64 us | 3.0000 | - | - | 9.37 KB | | Hand Coded | SqlCommand | Post | 124.8 us | 16.91 us | 32.33 us | 1.2500 | 0.5000 | - | 7.42 KB | | SqlMarshal | SqlCommand | Post | 132.8 us | 2.77 us | 4.66 us | 2.0000 | 0.7500 | - | 11.27 KB | | Dapper | QueryFirstOrDefault | dynamic | 135.0 us | 3.64 us | 6.96 us | 3.5000 | - | - | 11.39 KB | | LINQ to DB | Query | Post | 135.9 us | 3.52 us | 5.92 us | 1.7500 | 0.7500 | - | 10.64 KB | | LINQ to DB | 'First (Compiled)' | Post | 136.5 us | 2.99 us | 5.72 us | 1.7500 | 0.7500 | - | 11.23 KB | | PetaPoco | 'Fetch (Fast)' | Post | 136.8 us | 4.67 us | 7.85 us | 2.0000 | 1.0000 | - | 11.52 KB | | Dapper | 'Query (buffered)' | dynamic | 137.6 us | 6.44 us | 12.31 us | 2.0000 | 1.0000 | - | 11.72 KB | | Mighty | Query | dynamic | 141.7 us | 4.99 us | 9.54 us | 2.0000 | 1.0000 | - | 12.43 KB | | Massive | 'Query (dynamic)' | dynamic | 142.1 us | 1.59 us | 2.66 us | 2.0000 | 1.0000 | - | 12.07 KB | | Dapper | 'Query (buffered)' | Post | 143.0 us | 7.98 us | 13.41 us | 2.0000 | 1.0000 | - | 11.64 KB | | Mighty | SingleFromQuery | dynamic | 144.8 us | 3.95 us | 6.64 us | 2.5000 | 1.0000 | - | 12.43 KB | | Dapper | QueryFirstOrDefault | Post | 147.0 us | 22.39 us | 42.80 us | 1.7500 | 0.7500 | - | 11.35 KB | | PetaPoco | Fetch | Post | 149.2 us | 22.91 us | 43.80 us | 2.5000 | 1.0000 | 0.2500 | 12.2 KB | | Dapper | 'Contrib Get' | Post | 149.9 us | 13.30 us | 25.42 us | 2.0000 | 1.0000 | - | 12.28 KB | | ServiceStack | SingleById | Post | 151.8 us | 3.95 us | 7.56 us | 2.7500 | 1.0000 | 0.2500 | 15.27 KB | | Mighty | SingleFromQuery | Post | 152.5 us | 14.77 us | 24.83 us | 2.0000 | 1.0000 | - | 12.53 KB | | Mighty | Query | Post | 162.7 us | 45.49 us | 76.44 us | 2.0000 | 1.0000 | - | 12.53 KB | | Dapper | 'Query (unbuffered)' | Post | 191.4 us | 6.95 us | 11.67 us | 2.2500 | 1.0000 | 0.2500 | 11.76 KB | | LINQ to DB | First | Post | 200.6 us | 24.67 us | 47.18 us | 2.7500 | 1.0000 | 0.2500 | 15.79 KB | | DevExpress.XPO | GetObjectByKey | Post | 204.8 us | 5.69 us | 10.89 us | 5.5000 | 1.5000 | - | 29.16 KB | | EF Core | 'First (Compiled)' | Post | 212.4 us | 15.70 us | 30.01 us | 2.5000 | - | - | 8.39 KB | | EF 6 | SqlQuery | Post | 251.1 us | 92.97 us | 140.55 us | 3.7500 | 1.0000 | - | 23.67 KB | | Dapper | 'Query (unbuffered)' | dynamic | 255.9 us | 99.08 us | 149.80 us | 2.5000 | 1.0000 | 0.2500 | 11.8 KB | | DevExpress.XPO | Query | Post | 285.4 us | 92.67 us | 140.10 us | 10.2500 | - | - | 31.71 KB | | NHibernate | HQL | Post | 289.8 us | 18.26 us | 34.92 us | 5.5000 | 1.0000 | - | 31.32 KB | | NHibernate | Get | Post | 302.1 us | 16.69 us | 28.05 us | 5.5000 | 1.0000 | - | 29.37 KB | | EF Core | 'First (No Tracking)' | Post | 334.9 us | 50.05 us | 84.11 us | 3.5000 | 0.5000 | - | 19.53 KB | | EF 6 | First | Post | 339.4 us | 43.67 us | 66.03 us | 14.0000 | - | - | 44.18 KB | | EF Core | SqlQuery | Post | 344.0 us | 55.47 us | 83.86 us | 5.5000 | - | - | 17.59 KB | | EF Core | First | Post | 357.5 us | 51.72 us | 78.20 us | 4.0000 | - | - | 12.3 KB | | NHibernate | Criteria | Post | 357.6 us | 37.63 us | 63.23 us | 11.5000 | 1.0000 | - | 56.5 KB | | EF 6 | 'First (No Tracking)' | Post | 383.1 us | 33.98 us | 57.10 us | 9.0000 | 1.0000 | - | 50.95 KB | | NHibernate | SQL | Post | 399.2 us | 40.65 us | 68.31 us | 19.0000 | 1.0000 | - | 78.9 KB | | DevExpress.XPO | FindObject | Post | 602.5 us | 361.92 us | 547.18 us | 8.0000 | - | - | 27.2 KB | | NHibernate | LINQ | Post | 925.9 us | 47.62 us | 80.02 us | 12.0000 | 2.0000 | - | 53.59 KB | ``` --- .../Benchmarks.SqlMarshal.cs | 25 +++++++++++++++++++ .../Dapper.Tests.Performance.csproj | 1 + 2 files changed, 26 insertions(+) create mode 100644 benchmarks/Dapper.Tests.Performance/Benchmarks.SqlMarshal.cs diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.SqlMarshal.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.SqlMarshal.cs new file mode 100644 index 000000000..e1f98976f --- /dev/null +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.SqlMarshal.cs @@ -0,0 +1,25 @@ +using BenchmarkDotNet.Attributes; +using System.ComponentModel; + +namespace Dapper.Tests.Performance +{ + [Description("SqlMarshal")] + public partial class SqlMarshalBenchmarks : BenchmarkBase + { + [GlobalSetup] + public void Setup() + { + BaseSetup(); + } + + [Benchmark(Description = "SqlCommand")] + public Post SqlCommand() + { + Step(); + return ReadPost("select Top 1 * from Posts where Id = @id", i); + } + + [SqlMarshal("")] + private partial Post ReadPost([RawSql]string sql, int id); + } +} diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index d75589e06..94bd2a026 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -23,6 +23,7 @@ + From 64685835ae6195ee5d6c40a9c1f20c505a7a243a Mon Sep 17 00:00:00 2001 From: Alexandr Buzadji Date: Fri, 1 Jan 2021 12:26:02 -0500 Subject: [PATCH 170/312] Belgrade benchmark bug fix. The used Map method is asynchronous, hence without waiting for operation to complete the benchmark is only counting the query invocation time. --- benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs index db2272a8a..93b811cda 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs @@ -2,6 +2,7 @@ using Belgrade.SqlClient.SqlDb; using Belgrade.SqlClient; using System.ComponentModel; +using System.Threading.Tasks; namespace Dapper.Tests.Performance { @@ -18,11 +19,11 @@ public void Setup() } [Benchmark(Description = "ExecuteReader")] - public Post ExecuteReader() + public async Task ExecuteReader() { Step(); var post = new Post(); - _mapper.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", i).Map( + await _mapper.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", i).Map( reader => { post.Id = reader.GetInt32(0); From f499062bd23ffa1dec7536c24908d19b9e932a97 Mon Sep 17 00:00:00 2001 From: Alexandr Buzadji Date: Tue, 27 Apr 2021 22:19:55 -0400 Subject: [PATCH 171/312] Use `FirstOrDefault` method instead of `Map` in Belgrade benchmark. `FirstOrDefault` is probably meant to be used here, as there is selected and returned a single record by ID. Also changing the benchmark description, as "ExecuteReader" looks to be an obsolete name of the method that was used before refactoring. --- .../Benchmarks.Belgrade.cs | 40 +++++++++---------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs index 93b811cda..d35e278eb 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Belgrade.cs @@ -18,30 +18,28 @@ public void Setup() _mapper = new QueryMapper(ConnectionString); } - [Benchmark(Description = "ExecuteReader")] - public async Task ExecuteReader() + [Benchmark(Description = "FirstOrDefault")] + public Task FirstOrDefault() { Step(); - var post = new Post(); - await _mapper.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", i).Map( - reader => - { - post.Id = reader.GetInt32(0); - post.Text = reader.GetString(1); - post.CreationDate = reader.GetDateTime(2); - post.LastChangeDate = reader.GetDateTime(3); + return _mapper.Sql("SELECT TOP 1 * FROM Posts WHERE Id = @Id").Param("Id", i).FirstOrDefault( + reader => new Post + { + Id = reader.GetInt32(0), + Text = reader.GetString(1), + CreationDate = reader.GetDateTime(2), + LastChangeDate = reader.GetDateTime(3), - post.Counter1 = reader.IsDBNull(4) ? null : (int?)reader.GetInt32(4); - post.Counter2 = reader.IsDBNull(5) ? null : (int?)reader.GetInt32(5); - post.Counter3 = reader.IsDBNull(6) ? null : (int?)reader.GetInt32(6); - post.Counter4 = reader.IsDBNull(7) ? null : (int?)reader.GetInt32(7); - post.Counter5 = reader.IsDBNull(8) ? null : (int?)reader.GetInt32(8); - post.Counter6 = reader.IsDBNull(9) ? null : (int?)reader.GetInt32(9); - post.Counter7 = reader.IsDBNull(10) ? null : (int?)reader.GetInt32(10); - post.Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11); - post.Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12); - }); - return post; + Counter1 = reader.IsDBNull(4) ? null : (int?)reader.GetInt32(4), + Counter2 = reader.IsDBNull(5) ? null : (int?)reader.GetInt32(5), + Counter3 = reader.IsDBNull(6) ? null : (int?)reader.GetInt32(6), + Counter4 = reader.IsDBNull(7) ? null : (int?)reader.GetInt32(7), + Counter5 = reader.IsDBNull(8) ? null : (int?)reader.GetInt32(8), + Counter6 = reader.IsDBNull(9) ? null : (int?)reader.GetInt32(9), + Counter7 = reader.IsDBNull(10) ? null : (int?)reader.GetInt32(10), + Counter8 = reader.IsDBNull(11) ? null : (int?)reader.GetInt32(11), + Counter9 = reader.IsDBNull(12) ? null : (int?)reader.GetInt32(12), + }); } } } From e26aa7b2d61b2aa597931915d13cf4b43d7f1fac Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 29 Apr 2021 11:04:12 +0100 Subject: [PATCH 172/312] fix #1656 (git reorg) --- Dapper/SqlMapper.cs | 2 +- Directory.Build.props | 6 ++-- Readme.md | 6 ++-- .../Dapper.Tests.Performance/Program.cs | 2 +- docs/index.md | 34 +++++++++---------- tests/Dapper.Tests/Providers/MySQLTests.cs | 6 ++-- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index ad321c0cc..84008301d 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1,6 +1,6 @@ /* License: http://www.apache.org/licenses/LICENSE-2.0 - Home page: https://github.com/StackExchange/dapper-dot-net + Home page: https://github.com/DapperLib/Dapper-dot-net */ using System; diff --git a/Directory.Build.props b/Directory.Build.props index 9d62e68ee..ed12b2ecc 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,11 +7,11 @@ ../Dapper.snk $(AssemblyName) - https://stackexchange.github.io/Dapper/ - https://github.com/StackExchange/Dapper + https://dapperlib.github.io/Dapper/ + https://github.com/DapperLib/Dapper Apache-2.0 git - https://github.com/StackExchange/Dapper + https://github.com/DapperLib/Dapper false $(NOWARN);IDE0056;IDE0057;IDE0079 true diff --git a/Readme.md b/Readme.md index 9779556e3..b897a8c27 100644 --- a/Readme.md +++ b/Readme.md @@ -4,7 +4,7 @@ Dapper - a simple object mapper for .Net Release Notes ------------- -Located at [stackexchange.github.io/Dapper](https://stackexchange.github.io/Dapper/) +Located at [dapperlib.github.io/Dapper](https://dapperlib.github.io/Dapper/) Packages -------- @@ -114,7 +114,7 @@ Performance A key feature of Dapper is performance. The following metrics show how long it takes to execute a `SELECT` statement against a DB (in various config, each labeled) and map the data returned to objects. -The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/StackExchange/Dapper/tree/main/benchmarks/Dapper.Tests.Performance) (contributions welcome!) and can be run via: +The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/DapperLib/Dapper/tree/main/benchmarks/Dapper.Tests.Performance) (contributions welcome!) and can be run via: ```bash dotnet run -p .\benchmarks\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join ``` @@ -389,7 +389,7 @@ Dapper has no DB specific implementation details, it works across all .NET ADO p Do you have a comprehensive list of examples? --------------------- -Dapper has a comprehensive test suite in the [test project](https://github.com/StackExchange/Dapper/tree/main/tests/Dapper.Tests). +Dapper has a comprehensive test suite in the [test project](https://github.com/DapperLib/Dapper/tree/main/tests/Dapper.Tests). Who is using this? --------------------- diff --git a/benchmarks/Dapper.Tests.Performance/Program.cs b/benchmarks/Dapper.Tests.Performance/Program.cs index 669c9febc..1e102cf87 100644 --- a/benchmarks/Dapper.Tests.Performance/Program.cs +++ b/benchmarks/Dapper.Tests.Performance/Program.cs @@ -16,7 +16,7 @@ public static void Main(string[] args) #endif WriteLine("Welcome to Dapper's ORM performance benchmark suite, based on BenchmarkDotNet."); Write(" If you find a problem, please report it at: "); - WriteLineColor("https://github.com/StackExchange/Dapper", ConsoleColor.Blue); + WriteLineColor("https://github.com/DapperLib/Dapper", ConsoleColor.Blue); WriteLine(" Or if you're up to it, please submit a pull request! We welcome new additions."); WriteLine(); diff --git a/docs/index.md b/docs/index.md index abcdc515d..f13cc5aeb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,7 +2,7 @@ ## Overview -A brief guide is available [on github](https://github.com/StackExchange/Dapper/blob/main/Readme.md) +A brief guide is available [on github](https://github.com/DapperLib/Dapper/blob/main/Readme.md) Questions on Stack Overflow should be tagged [`dapper`](https://stackoverflow.com/questions/tagged/dapper) @@ -67,14 +67,14 @@ Other changes merged: ### 1.60.1 -- Fix [#1196](https://github.com/StackExchange/Dapper/issues/1196) - versioning fix only ([#1198](https://github.com/StackExchange/Dapper/pull/1198)) - assembly version is now locked at 1.60.0 to resolve some mismatch issues with .NET Core assembly loading/binding. +- Fix [#1196](https://github.com/DapperLib/Dapper/issues/1196) - versioning fix only ([#1198](https://github.com/DapperLib/Dapper/pull/1198)) - assembly version is now locked at 1.60.0 to resolve some mismatch issues with .NET Core assembly loading/binding. ### 1.50.7 -- Fix [#1190](https://github.com/StackExchange/Dapper/issues/1190) - incorrect unmanaged pointer when processing parameters that are a boxed struct (rare error relating to GC) -- Fix [#1111](https://github.com/StackExchange/Dapper/issues/1111) - make `SqlMapper.Parse` consistent with `QueryImpl` +- Fix [#1190](https://github.com/DapperLib/Dapper/issues/1190) - incorrect unmanaged pointer when processing parameters that are a boxed struct (rare error relating to GC) +- Fix [#1111](https://github.com/DapperLib/Dapper/issues/1111) - make `SqlMapper.Parse` consistent with `QueryImpl` - Fix #111- - improve error message for invalid literal types -- Fix [#1149](https://github.com/StackExchange/Dapper/pull/1149) - improve error messages in "contrib" +- Fix [#1149](https://github.com/DapperLib/Dapper/pull/1149) - improve error messages in "contrib" - Improved detection of empty table-valued-parameters ### 1.50.5 @@ -91,7 +91,7 @@ Other changes merged: ### 1.50.2 -- Fix issue [#569](https://github.com/StackExchange/Dapper/issues/569) (`in` expansions using ODBC pseudo-positional arguments) +- Fix issue [#569](https://github.com/DapperLib/Dapper/issues/569) (`in` expansions using ODBC pseudo-positional arguments) ### 1.50.1 @@ -108,7 +108,7 @@ Other changes merged: ### 1.50.0-rc2b - New `InListStringSplitCount` global setting; if set (non-negative), `in @foo` expansions (of at least the specified size) of primitive types (`int`, `tinyint`, `smallint`, `bigint`) are implemented via the SQL Server 2016 (compat level 130) `STRING_SPLIT` function -- Fix for incorrect conversions in `GridReader` ([#254](https://github.com/StackExchange/Dapper/issues/254)) +- Fix for incorrect conversions in `GridReader` ([#254](https://github.com/DapperLib/Dapper/issues/254)) ### 1.50.0-rc2 / 1.50.0-rc2a @@ -117,18 +117,18 @@ Other changes merged: ### 1.50-beta9 - Fix for `PadListExpansions` to work correctly with `not in` scenarios; now uses last non-null value instead of `null`; if none available, don't pad -- Fix problems with single-result/single-row not being supported by all providers (basically: sqlite, [#466](https://github.com/StackExchange/Dapper/issues/466)) -- Fix problems with enums - nulls ([#467](https://github.com/StackExchange/Dapper/issues/467)) and primitive values ([#468](https://github.com/StackExchange/Dapper/issues/468)) -- Add support for C# 6 get-only properties ([#473](https://github.com/StackExchange/Dapper/issues/473)) -- Add support for various xml types ([#427](https://github.com/StackExchange/Dapper/issues/427)) +- Fix problems with single-result/single-row not being supported by all providers (basically: sqlite, [#466](https://github.com/DapperLib/Dapper/issues/466)) +- Fix problems with enums - nulls ([#467](https://github.com/DapperLib/Dapper/issues/467)) and primitive values ([#468](https://github.com/DapperLib/Dapper/issues/468)) +- Add support for C# 6 get-only properties ([#473](https://github.com/DapperLib/Dapper/issues/473)) +- Add support for various xml types ([#427](https://github.com/DapperLib/Dapper/issues/427)) ### 1.50-beta8 - Addition of `GetRowParser` extension method on `IDataReader` API - allows manual construction of discriminated unions, etc - Addition of `Settings.PadListExpansions` - reduces query-plan saturation by padding list expansions with `null` values (opt-in, because on some DB configurations this could change the meaning) *(note: bad choice of `null` revised in 1.50-beta9)* - Addition of `Settings.ApplyNullValues` - assigns (rather than ignores) `null` values when possible -- Fix for [#461](https://github.com/StackExchange/Dapper/issues/461) - ensure type-handlers work for constructor-based initialization -- Fix for [#455](https://github.com/StackExchange/Dapper/issues/455) - make the `LookupDbType` method available again +- Fix for [#461](https://github.com/DapperLib/Dapper/issues/461) - ensure type-handlers work for constructor-based initialization +- Fix for [#455](https://github.com/DapperLib/Dapper/issues/455) - make the `LookupDbType` method available again ### 1.50-beta7 @@ -137,7 +137,7 @@ Other changes merged: ### 1.50-beta6 -- Fix for issue [#424](https://github.com/StackExchange/Dapper/issues/424) - defensive `SqlDataRecord` handling +- Fix for issue [#424](https://github.com/DapperLib/Dapper/issues/424) - defensive `SqlDataRecord` handling ### 1.50-beta5 @@ -170,7 +170,7 @@ Other changes merged: - improve error message if an array is used as a parameter in an invalid context - Add `Type[]` support for `GridReader.Read` scenarios (@NikolayGlynchak) - Support for custom type-maps in collection parameters (@gjsduarte) -- Fix incorrect cast in `QueryAsync` (@phnx47, [#346](https://github.com/StackExchange/Dapper/issues/346)) +- Fix incorrect cast in `QueryAsync` (@phnx47, [#346](https://github.com/DapperLib/Dapper/issues/346)) - Fix incorrect null handling re `UdtTypeName` (@perliedman) - Support for `SqlDataRecord` (@sqmgh) - Allow `DbString` default for `IsAnsi` to be specified (@kppullin) @@ -221,11 +221,11 @@ Other changes merged: ### 1.36 -- Fix Issue [#192](https://github.com/StackExchange/Dapper/issues/192) (expanded parameter naming glitch) and Issue [#178](https://github.com/StackExchange/Dapper/issues/178) (execute reader now wraps the command/reader pair, to extend the command lifetime; note that the underlying command/reader are available by casting to `IWrappedDataReader`) +- Fix Issue [#192](https://github.com/DapperLib/Dapper/issues/192) (expanded parameter naming glitch) and Issue [#178](https://github.com/DapperLib/Dapper/issues/178) (execute reader now wraps the command/reader pair, to extend the command lifetime; note that the underlying command/reader are available by casting to `IWrappedDataReader`) ### 1.35 -- Fix Issue [#151](https://github.com/StackExchange/Dapper/issues/151) (Execute should work with `ExpandoObject` etc); Fix Issue #182 (better support for db-type when using `object` values); +- Fix Issue [#151](https://github.com/DapperLib/Dapper/issues/151) (Execute should work with `ExpandoObject` etc); Fix Issue #182 (better support for db-type when using `object` values); - Output expressions / callbacks in dynamic args (via Derek); arbitrary number of types in multi-mapping (via James Holwell); - Fix `DbString`/Oracle bug (via Mauro Cerutti); new support for **named positional arguments** diff --git a/tests/Dapper.Tests/Providers/MySQLTests.cs b/tests/Dapper.Tests/Providers/MySQLTests.cs index 8cf596fb4..4b17ff80b 100644 --- a/tests/Dapper.Tests/Providers/MySQLTests.cs +++ b/tests/Dapper.Tests/Providers/MySQLTests.cs @@ -38,7 +38,7 @@ public void DapperEnumValue_Mysql() } } - [FactMySql(Skip = "See https://github.com/StackExchange/Dapper/issues/552, not resolved on the MySQL end.")] + [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/552, not resolved on the MySQL end.")] public void Issue552_SignedUnsignedBooleans() { using (var conn = Provider.GetMySqlConnection(true, false, false)) @@ -96,7 +96,7 @@ public void Issue295_NullableDateTime_MySql_ConvertZeroDatetime() } } - [FactMySql(Skip = "See https://github.com/StackExchange/Dapper/issues/295, AllowZeroDateTime=True is not supported")] + [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/295, AllowZeroDateTime=True is not supported")] public void Issue295_NullableDateTime_MySql_AllowZeroDatetime() { using (var conn = Provider.GetMySqlConnection(true, false, true)) @@ -105,7 +105,7 @@ public void Issue295_NullableDateTime_MySql_AllowZeroDatetime() } } - [FactMySql(Skip = "See https://github.com/StackExchange/Dapper/issues/295, AllowZeroDateTime=True is not supported")] + [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/295, AllowZeroDateTime=True is not supported")] public void Issue295_NullableDateTime_MySql_ConvertAllowZeroDatetime() { using (var conn = Provider.GetMySqlConnection(true, true, true)) From 3504109a5f2a834d8be96243cf60efd2ff777f69 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 29 Apr 2021 12:11:13 +0100 Subject: [PATCH 173/312] Add Dapper logo --- Dapper.png | Bin 0 -> 6093 bytes Dapper.sln | 1 + Directory.Build.props | 5 +++++ License.txt | 7 ++++++- 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 Dapper.png diff --git a/Dapper.png b/Dapper.png new file mode 100644 index 0000000000000000000000000000000000000000..ec2c30fb5bece81783068164db9423249e548fa5 GIT binary patch literal 6093 zcmZ8l3p7++*dDiX4HE|Qk!y*JOEe`S3?f9P2?s^GDV-UYTq@TT69#4al=@P-kdcbc zlr&-_*O11oIvC2Oo1%Qgzfb=4t$+P%&Fr)HyPx-YpZDE+pL5np-{IjbC#^1x!{Ow% zxj1^`aH0_SUz8LBmYgSVw7?~A$95me;^HE%l>bTo5NQAVf#K3`I#~<+mS#Z*0G2HO z7YX|R;g^vA55F|~pYIaG677HOQfCRXW;h`O$RkC1d+fv|CVo}m*(vb8Dtxs=6Sa7D zul_NGcf4Y0neIXFp)9Q(_h6W1`fA5N_x9}h2U-aGg~O>l+2**_Cnj)GU~{-jW}DZQh-vr3gt-;#N6^O>4a1zIjI zny&AgGt@K~CrjtQN)g)*{UsRSD?7-MTBj-LsSZ>mwo zZv_0CyZ1!f`V$3akoXqlQvG!CgQ2VW3eOMjr`3mv2tG}(Zf!}hDpx&~9vru*-#f_(BTJ~tU59N25%`Cl>sWxieohBs1aoa{5>4e6&7c$VVro!PO z9qR;NQNoA#w-KpthjkwL^vcDPDV-u$sqV1k%LJhoa-SNs;FD+b_g9yQ2||VTrLA-W^j`%obw+6ga(lOKpbWB)em|&o+EbW3 zDP@l_P;`-l>YR8PQNm(oP2z;DmsxM8B&jJd%1snHzx}2RWD(oF6OZbUo5ODw@79V-2S_wmXgP=M)A=3(Fd}%=j2G?9pod+1o1v4LVi1{L$G)sw4A`d zcBibvZ1J)#IV|w76v?kz*nbr*+E>u-nZ!&U)tE+$c2iAmFDEzzH>dTa$|9=x>0>Su z1c#wbMtEB^*yV@sYDq{oeR&;4HXRJDRY}1v;$XTKR&>w-X!$gzD!!J_idhHMvm{Ar zj&J}?HvOl`aWuD{_3K1m)d9FNTl?F$z44!Zh(To`F4g^QC!Z{*b*bW?oqGQ+9!INF z#eYc%Pncc_wFbMKusTYxNh)=hP0u#zrIQZuHs$~UBsN^itNs&p||JD z8$O<&3o4Z1k;e)a$PNeEeCuA8x@z2fpEFdrD<#JG7ahLyg~enU z_>apAb@cXk#9w?}1Cdl_t*&f1YTc3rrPfnI|+8!-}wwI-97vKkZUCsd-iRW~I<9aolMxe{F zcWvb#f~edTST-JD}q4_Phq3Ow^m_^ z_m7b=QKB=67-wP4A5Q&ue>jgFi^Jw>o{nKiw{tRpi~7E+9jpC;uL_tND*H8Y>GE0! z#Esf^)G&|9>Ume-r}JtD8&o3d>&*hmhg3a+Hhtjj(>phVj$=Blb7h41nTcXN(BpQz zFR#2Ns+9`tM~KIhprNwTGCQ!Wwul{R_Dwew2>P3^wmC-~38?EH(a{TkX~`IS#F_2{ zfnR*KI6U6_c*dw1IOk3XI|&UPThMG87*4MF$$VLP>KgCY+ehUWn5>Tn3QB4w)SE69 z9MaMY^y0MY-sL+x=GnQNZ!TW!sBgnOedFz86%zLKm50z)7A59WsH^oh_9W_Vf{*DXyjsm)Ozyyu=6eah;TiA*BPlsk8lDRZN1;;=~P?RA^brjyeVdbQN&k5V5p$o8;TjsWm zX9x8R21xkR!q!n^vqu%PH zuC+y`7)TH ztF6c_X>@0=+dwuK=7cG4yus#=IsvBu@r9Bgl}kja^u})SzREWEquNxXvTDEw;eAl> zvrx*S8ui1N2hcKA6RFYO7#gALpvjG1pp+-*ky|_6l@>xgN+OC>OK$bc8+Lt+2MUjscmJHCy4Aa1sc(?qpuYqqHDR1+M zU@3-w&$`ZT{J~!lE3}FeV^wcA)~?vWRsW*#bRhDUxq}Xa`VMyk$DY$UW6h9jYp0yP zEtRg{6g4pq`JAgTF#(Hp`un4Br4e`kK2W9xUqWc?{6NfAii;zM(DwMY_|21Epb~~3 z{8PO@R#D_)g?_UOp3GdA_RNX$S&(Gkug*&EJN=@dGF)52$`MyEQ4I?EsffY8cy%+MT*9k?q0y=SKKuHk^607r-!y# zoBYft3yHm2=W6dsYp^>?lxa{2a$cu|((YOJyeX2x43;NDH=?aZu_&&rJaF zRVUbzM~eXaE<>cihDE#vjPuHsI9QPpa>>4k{uF~Y%BMGUi@@77W~lRcd}cGk8|Zll zfZC_sjL3&iI}RI~Lcv@wL!dov5Z{vdV5;uj+fl?re6LdYi|90?y}tKTlBB#M%@R1N zAAf(zGLHP_9dhUW&ajw$>~Z1sL>Xu;*Gn=TeY}ozMU zK=2W|2rueTxHxV3DrSrnVh+Yu27>QwG`M29fJ&2slP22l4oH&V3ttlSjAu|$+Uzgm zF+&kxn3|m^=6?HEL{N}cdcL2`xNgbGJzF}zu##I5%B$qlFHHR=aup%D??Lc|>{Y@B zJqR@%`Ea*X9N|%$jEyi2BkRK|mJ<-yS^bGWl-Wz69%2D=h+=h$mTAlZRn;j^Spqi) zE(C){=kQLrc*F@#Vh&tPlYxW{77%!YyE!OI;F`%1kjBiU!HIb@VCQ{x&=VywLPRmD zm4EQ*0PmzhutkVd?&0g^eGou+F$*&;T+v?zLfGavy`X?%Y~xYFTX^7gP&dKhayD~7 zlH*<<$|lxESqv9YplFa)-82XVUTu*h3|(aC5M6=Z3q-XNQ1h3WsGsPxa~+1Kn(#() zh_#lL!+sQK3vl};_VjGCF*$hx!Lhf3Kwr~Jh8WG6XrBo$ltrvnwvM=i3;@P_8TfCZ z6Wl$z$p_jG{IYp+RDNh4KsWjZF&|7udk*EP5=^C-(_iy<{rweY1NX6bPvOQM~r>R1@a-U;?qm(wUq8XZSv_ zSc&!ey$HQPtgU=B>=y;8UU)}X1pg(gaOtI4wJFm{j6-#qxLlR@S zj0n!lxc7aq{F|RgmFK9{-~c@lijWlis|;cmqK|2N3)^cc;nh*eVOeG0(Zg-vOup{1 zlIM(KyD)V^oQCa7KZo^6rEAH_Mhj@43{rq?ae2;iqyXIP0=4}kL}+0E^fYZ$>W`Z* z;*e;>BFe$(@|=~BeYgcr--k~ZWn(5OqF@J4U+AVeE zDR~ks#qAIgC_I=pMDdL41kRyzgO5|(8Z-AJGlu6^!TizD(-U4H5zE>6#6r0Bd5X-R z08x@lo;blnNg@GB9XOa6cDo`r*+`o_lO875=O>Ne86j7Z{dS-S?(k_==$*ZI0@H|%*b_W5zc!x}#ot1pYut%cW4tMIp& zxh{T}T~+Wc2k&1Oya*x7+P90c|G5z30R_y|>hmZi2kI+h_Ai6eRpxHhbuVjoJBH>~ z;yu$!XIdVj_&~BsqT5EL19?Ni6(%_LVS_Y-28xjvj?}TkS`85jG9^%NS$;znj~`tA zB5Hoek8e2WA)Yc+67cG(G)#ZHTa&|&$XfxXgLF9B$UnBqXwd}&dD@4+r9L^(b4qqY z4`dEYijY+1reJ--YXwo@Ju;ZF^w4FYdz0h)X?@GNtp zfdTWK0Fs^~2!`FS6h^Kic}sKn54wm5uhXSm7sTO2v2`4V*L=1q?H8(Z2(L6 z>10(LcoOT4+&QiYp`AU_DM>OA-hYca&|6`Nsjlb-Zp9VKei(0a9++15XZ~rI`Tz@Rk$*`fnnTcm^IYeBW9)%`&dci$?H ztOcd8_?$7N1fl9F4Ju$Xw*kzt_r0C!1rB##`1hNf1RcJ96=*jeI)brYuK*mzB&_9c z3z30P*R5Dv5D*Mnb*oHcuntmqEQJ4DG{z?E!PrPID@LvZ|J;yfXRK&1^0Tp`XO*!) zftx;7Rn508htP}%Us$8^z)Mj1O{0?{#!d=bq`Z5&&NKLQ$O#js7aVH?O6w#3?SQ#h&gMh@6ir%Qo&$_3ync&R5sKnnKd zg15AtU;#>RxNAaaiT_S<2$g1H`@{+>S8f)NRvHertKnFWC;=IN!=rdeL}9u8<*o>( z-1aX@kfvtnu?8I2d~OvyI6iWMi0js+yO-IJNmFA}Bfm$-RYXX&{RKL3j=*i2u`nNz zsmea3qnXXD4k#I)kJ6uxflgY$HiG#lCwxpke0Lo)fTGOBXk!<{XK$B`|DNyP6lK5} zJ$ASksd2~0v($YG{cF??{(QE%-#BvWS>Z9cGXuiWc#7*#Q&1I} zZ1jwEMuxNYL2t4Q=eHU7MvRDhIwGj_D=@|x|8Hj<=rnZ!%O*P+5)y2*6G7Fh6#d{@ z%TtsEg~8-7@Oe2AuzWo&Nq{P1K@+i06FlxN#;yXb(x!)K@`McoBN)t&!6sO0XMl5% z*subKcOR(G2E56j6LxL@9Za5h98ehr2v5MZ`Z{Pbm;+z<;93a+WcB(xV@wW5x~p0I zG+F}O*^J$2yWp|wSDK$*HAH;Ej6)9{wZtKpJrYjlzyJKnw&;g=n9Z9vCTUW3-~%{r N8^yzsW4|Z)e*iE3Yx@8I literal 0 HcmV?d00001 diff --git a/Dapper.sln b/Dapper.sln index ff06dd2c0..f52b99815 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -8,6 +8,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution .editorconfig = .editorconfig appveyor.yml = appveyor.yml build.ps1 = build.ps1 + Dapper.png = Dapper.png Directory.Build.props = Directory.Build.props global.json = global.json docs\index.md = docs\index.md diff --git a/Directory.Build.props b/Directory.Build.props index ed12b2ecc..1af0c4d3e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,6 +10,7 @@ https://dapperlib.github.io/Dapper/ https://github.com/DapperLib/Dapper Apache-2.0 + protobuf-net.png git https://github.com/DapperLib/Dapper false @@ -37,5 +38,9 @@ + + True + + \ No newline at end of file diff --git a/License.txt b/License.txt index 0f3335224..aa7849358 100644 --- a/License.txt +++ b/License.txt @@ -1 +1,6 @@ -http://www.apache.org/licenses/LICENSE-2.0 \ No newline at end of file +The Dapper library and tools are licenced under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0 + +The Dapper logo is copyright Marc Gravell 2021 onwards; it is fine to use the Dapper logo when referencing the Dapper library and utilities, but +the Dapper logo (including derivatives) must not be used in a way that misrepresents an external product or library as being affiliated or endorsed +with Dapper. For example, you must not use the Dapper logo as the package icon on your own external tool (even if it uses Dapper internally), +without written permission. If in doubt: ask. \ No newline at end of file From ce8c85af4deece6e9b3511753a8b670bb85f13d8 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 29 Apr 2021 12:19:34 +0100 Subject: [PATCH 174/312] copypasta --- Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Build.props b/Directory.Build.props index 1af0c4d3e..0cb2bf374 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -10,7 +10,7 @@ https://dapperlib.github.io/Dapper/ https://github.com/DapperLib/Dapper Apache-2.0 - protobuf-net.png + Dapper.png git https://github.com/DapperLib/Dapper false From 964a27d68dc8517a1cf9db2ceedc3828e861a05c Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 29 Apr 2021 19:45:04 +0100 Subject: [PATCH 175/312] add release notes for 2.0.78 and 2.0.90 --- docs/index.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/docs/index.md b/docs/index.md index f13cc5aeb..93426de72 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,6 +20,35 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes +### unreleased + +(note: new PRs will note be merged until they add release note wording here) + +### 2.0.90 + +- logo added; license updated to mention logo usage (via mgravell) +- moved to DapperLib org; links updated (#1656) +- RepoDb benchmark added (#1626 via stevedesmond-ca) +- excise unrelated Soma tests (#1642 via kant2002) +- SqlMarshl benchmark added (#1646 via kant2002) +- documentation fixes (#1615 via Rollerss, #1604 via GitHubPang) + +### 2.0.78 + +- fix `DynamicParameters` loop bug - wrong index (#1443 via DamirAinullin) +- fix nullable tuple handling (#1400 via JulianRooze) +- support update set in `SqlBuilder` (#1404 via Wei) +- initialize collections with counts when possible (#1449 via DamirAinullin) +- general code cleanup (#1452, #1457, #1458, #1459 all via DamirAinullin) +- C# 9 and .NET 5 preparation/cleanup (#1572 via mgravell) +- GitHub action, Docker, AppVeyor work (build/test) work (#1563 via Tyrrrz, #1559 via craver, #1450 via craver) +- Test project rationalization (#1556 via craver) +- ClickHouse detection (#1462 via DarkWanderer) +- Switched to "main" branch (via craver) +- documentation fixed (#1596 via royal, #1560 via wswind, #1558 via paul42, #1507 via imba-tjd, #1508 via dogac00, 899c9feb via BlackjacketMack, 0b17133 via BlackjacketMack, 9b6c8c7d via bryancrosby, #1202 via craver) +- MightyOrm benchmark added (455b3f3b via cdonnellytx) +- EF6 performance test updates (#1361 via AlexBagnolini) + ### 2.0.35 - build tooling: enable "deterministic builds" and enable SDK roll-foward From 0b196eec9b109fb56850020e749155bbe0b382d4 Mon Sep 17 00:00:00 2001 From: GitHubPang <61439577+GitHubPang@users.noreply.github.com> Date: Mon, 3 May 2021 12:47:28 +0800 Subject: [PATCH 176/312] Fix typo in index.md --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 93426de72..28738b70b 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased -(note: new PRs will note be merged until they add release note wording here) +(note: new PRs will not be merged until they add release note wording here) ### 2.0.90 From 11931f9e6c8855b1a64b4819f150b8008427ba8d Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 8 May 2021 20:36:15 -0400 Subject: [PATCH 177/312] Dapper.Contrib: removal (repo split) Dapper.Contrib is now located at https://github.com/DapperLib/Dapper.Contrib --- .github/workflows/main.yml | 7 - .gitignore | 3 +- Dapper.Contrib/Dapper.Contrib.csproj | 22 - Dapper.Contrib/Readme.md | 172 --- Dapper.Contrib/SqlMapperExtensions.Async.cs | 577 -------- Dapper.Contrib/SqlMapperExtensions.cs | 1155 ----------------- Dapper.sln | 14 - Readme.md | 1 - benchmarks/Directory.Build.props | 2 +- global.json | 8 - .../Dapper.Tests.Contrib.csproj | 25 - .../Helpers/Attributes.cs | 46 - tests/Dapper.Tests.Contrib/TestSuite.Async.cs | 476 ------- tests/Dapper.Tests.Contrib/TestSuite.cs | 757 ----------- tests/Dapper.Tests.Contrib/TestSuites.cs | 178 --- tests/Dapper.Tests.Contrib/xunit.runner.json | 4 - tests/Directory.Build.props | 1 - 17 files changed, 3 insertions(+), 3445 deletions(-) delete mode 100644 Dapper.Contrib/Dapper.Contrib.csproj delete mode 100644 Dapper.Contrib/Readme.md delete mode 100644 Dapper.Contrib/SqlMapperExtensions.Async.cs delete mode 100644 Dapper.Contrib/SqlMapperExtensions.cs delete mode 100644 global.json delete mode 100644 tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj delete mode 100644 tests/Dapper.Tests.Contrib/Helpers/Attributes.cs delete mode 100644 tests/Dapper.Tests.Contrib/TestSuite.Async.cs delete mode 100644 tests/Dapper.Tests.Contrib/TestSuite.cs delete mode 100644 tests/Dapper.Tests.Contrib/TestSuites.cs delete mode 100644 tests/Dapper.Tests.Contrib/xunit.runner.json diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7bf43250f..a31c48f20 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -49,12 +49,5 @@ jobs: OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; PostgesConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - - name: Dapper.Contrib Tests - run: dotnet test tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj -c Release --logger GitHubActions /p:CI=true - env: - MySqlConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true - OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - PostgesConnectionString: Server=localhost;Port=${{ job.services.postgres.ports[5432] }};Database=test;User Id=postgres;Password=postgres; - SqlServerConnectionString: Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; - name: .NET Lib Pack run: dotnet pack Build.csproj --no-build -c Release /p:PackageOutputPath=%CD%\.nupkgs /p:CI=true diff --git a/.gitignore b/.gitignore index 541ac51fb..02815d7d6 100644 --- a/.gitignore +++ b/.gitignore @@ -20,4 +20,5 @@ Dapper.Tests/*.sdf Dapper.Tests/SqlServerTypes/ .dotnet/* BenchmarkDotNet.Artifacts/ -.idea/ \ No newline at end of file +.idea/ +.DS_Store \ No newline at end of file diff --git a/Dapper.Contrib/Dapper.Contrib.csproj b/Dapper.Contrib/Dapper.Contrib.csproj deleted file mode 100644 index b26861366..000000000 --- a/Dapper.Contrib/Dapper.Contrib.csproj +++ /dev/null @@ -1,22 +0,0 @@ - - - Dapper.Contrib - Dapper.Contrib - orm;sql;micro-orm;dapper - The official collection of get, insert, update and delete helpers for Dapper.net. Also handles lists of entities and optional "dirty" tracking of interface-based entities. - Sam Saffron;Johan Danforth - net461;netstandard2.0;net5.0 - false - $(NoWarn);CA1050 - - - - - - - - - - - - \ No newline at end of file diff --git a/Dapper.Contrib/Readme.md b/Dapper.Contrib/Readme.md deleted file mode 100644 index 773ab6ff3..000000000 --- a/Dapper.Contrib/Readme.md +++ /dev/null @@ -1,172 +0,0 @@ -Dapper.Contrib - more extensions for dapper -=========================================== - -Features --------- -Dapper.Contrib contains a number of helper methods for inserting, getting, -updating and deleting records. - -The full list of extension methods in Dapper.Contrib right now are: - -```csharp -T Get(id); -IEnumerable GetAll(); -int Insert(T obj); -int Insert(Enumerable list); -bool Update(T obj); -bool Update(Enumerable list); -bool Delete(T obj); -bool Delete(Enumerable list); -bool DeleteAll(); -``` - -For these extensions to work, the entity in question _MUST_ have a -key property. Dapper will automatically use a property named "`id`" -(case-insensitive) as the key property, if one is present. - -```csharp -public class Car -{ - public int Id { get; set; } // Works by convention - public string Name { get; set; } -} -``` - -If the entity doesn't follow this convention, decorate -a specific property with a `[Key]` or `[ExplicitKey]` attribute. - -```csharp -public class User -{ - [Key] - int TheId { get; set; } - string Name { get; set; } - int Age { get; set; } -} -``` - -`[Key]` should be used for database-generated keys (e.g. autoincrement columns), -while `[ExplicitKey]` should be used for explicit keys generated in code. - -`Get` methods -------- - -Get one specific entity based on id - -```csharp -var car = connection.Get(1); -``` - -or a list of all entities in the table. - -```csharp -var cars = connection.GetAll(); -``` - -`Insert` methods -------- - -Insert one entity - -```csharp -connection.Insert(new Car { Name = "Volvo" }); -``` - -or a list of entities. - -```csharp -connection.Insert(cars); -``` - - - -`Update` methods -------- -Update one specific entity - -```csharp -connection.Update(new Car() { Id = 1, Name = "Saab" }); -``` - -or update a list of entities. - -```csharp -connection.Update(cars); -``` - -`Delete` methods -------- -Delete an entity by the specified `[Key]` property - -```csharp -connection.Delete(new Car() { Id = 1 }); -``` - -a list of entities - -```csharp -connection.Delete(cars); -``` - -or _ALL_ entities in the table. - -```csharp -connection.DeleteAll(); -``` - -Special Attributes ----------- -Dapper.Contrib makes use of some optional attributes: - -* `[Table("Tablename")]` - use another table name instead of the (by default pluralized) name of the class - - ```csharp - [Table ("emps")] - public class Employee - { - public int Id { get; set; } - public string Name { get; set; } - } - ``` -* `[Key]` - this property represents a database-generated identity/key - - ```csharp - public class Employee - { - [Key] - public int EmployeeId { get; set; } - public string Name { get; set; } - } - ``` -* `[ExplicitKey]` - this property represents an explicit identity/key which is - *not* automatically generated by the database - - ```csharp - public class Employee - { - [ExplicitKey] - public Guid EmployeeId { get; set; } - public string Name { get; set; } - } - ``` -* `[Write(true/false)]` - this property is (not) writeable -* `[Computed]` - this property is computed and should not be part of updates - -Limitations and caveats -------- - -### SQLite - -`SQLiteConnection` exposes an `Update` event that clashes with the `Update` -extension provided by Dapper.Contrib. There are 2 ways to deal with this. - -1. Call the `Update` method explicitly from `SqlMapperExtensions` - - ```Csharp - SqlMapperExtensions.Update(_conn, new Employee { Id = 1, Name = "Mercedes" }); - ``` -2. Make the method signature unique by passing a type parameter to `Update` - - ```Csharp - connection.Update(new Car() { Id = 1, Name = "Maruti" }); - ``` diff --git a/Dapper.Contrib/SqlMapperExtensions.Async.cs b/Dapper.Contrib/SqlMapperExtensions.Async.cs deleted file mode 100644 index c93e39a48..000000000 --- a/Dapper.Contrib/SqlMapperExtensions.Async.cs +++ /dev/null @@ -1,577 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; -using Dapper; - -namespace Dapper.Contrib.Extensions -{ - public static partial class SqlMapperExtensions - { - /// - /// Returns a single entity by a single id from table "Ts" asynchronously using Task. T must be of interface type. - /// Id must be marked with [Key] attribute. - /// Created entity is tracked/intercepted for changes and used by the Update() extension. - /// - /// Interface type to create and populate - /// Open SqlConnection - /// Id of the entity to get, must be marked with [Key] attribute - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// Entity of T - public static async Task GetAsync(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - if (!GetQueries.TryGetValue(type.TypeHandle, out string sql)) - { - var key = GetSingleKey(nameof(GetAsync)); - var name = GetTableName(type); - - sql = $"SELECT * FROM {name} WHERE {key.Name} = @id"; - GetQueries[type.TypeHandle] = sql; - } - - var dynParams = new DynamicParameters(); - dynParams.Add("@id", id); - - if (!type.IsInterface) - return (await connection.QueryAsync(sql, dynParams, transaction, commandTimeout).ConfigureAwait(false)).FirstOrDefault(); - - if (!((await connection.QueryAsync(sql, dynParams).ConfigureAwait(false)).FirstOrDefault() is IDictionary res)) - { - return null; - } - - var obj = ProxyGenerator.GetInterfaceProxy(); - - foreach (var property in TypePropertiesCache(type)) - { - var val = res[property.Name]; - if (val == null) continue; - if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - var genericType = Nullable.GetUnderlyingType(property.PropertyType); - if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); - } - else - { - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); - } - } - - ((IProxy)obj).IsDirty = false; //reset change tracking and return - - return obj; - } - - /// - /// Returns a list of entities from table "Ts". - /// Id of T must be marked with [Key] attribute. - /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension - /// for optimal performance. - /// - /// Interface or type to create and populate - /// Open SqlConnection - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// Entity of T - public static Task> GetAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - var cacheType = typeof(List); - - if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql)) - { - GetSingleKey(nameof(GetAll)); - var name = GetTableName(type); - - sql = "SELECT * FROM " + name; - GetQueries[cacheType.TypeHandle] = sql; - } - - if (!type.IsInterface) - { - return connection.QueryAsync(sql, null, transaction, commandTimeout); - } - return GetAllAsyncImpl(connection, transaction, commandTimeout, sql, type); - } - - private static async Task> GetAllAsyncImpl(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string sql, Type type) where T : class - { - var result = await connection.QueryAsync(sql, transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); - var list = new List(); - foreach (IDictionary res in result) - { - var obj = ProxyGenerator.GetInterfaceProxy(); - foreach (var property in TypePropertiesCache(type)) - { - var val = res[property.Name]; - if (val == null) continue; - if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - var genericType = Nullable.GetUnderlyingType(property.PropertyType); - if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); - } - else - { - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); - } - } - ((IProxy)obj).IsDirty = false; //reset change tracking and return - list.Add(obj); - } - return list; - } - - /// - /// Inserts an entity into table "Ts" asynchronously using Task and returns identity id. - /// - /// The type being inserted. - /// Open SqlConnection - /// Entity to insert - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// The specific ISqlAdapter to use, auto-detected based on connection if null - /// Identity of inserted entity - public static Task InsertAsync(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, - int? commandTimeout = null, ISqlAdapter sqlAdapter = null) where T : class - { - var type = typeof(T); - sqlAdapter ??= GetFormatter(connection); - - var isList = false; - if (type.IsArray) - { - isList = true; - type = type.GetElementType(); - } - else if (type.IsGenericType) - { - var typeInfo = type.GetTypeInfo(); - bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || - typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - if (implementsGenericIEnumerableOrIsGenericIEnumerable) - { - isList = true; - type = type.GetGenericArguments()[0]; - } - } - - var name = GetTableName(type); - var sbColumnList = new StringBuilder(null); - var allProperties = TypePropertiesCache(type); - var keyProperties = KeyPropertiesCache(type).ToList(); - var computedProperties = ComputedPropertiesCache(type); - var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); - - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) - { - var property = allPropertiesExceptKeyAndComputed[i]; - sqlAdapter.AppendColumnName(sbColumnList, property.Name); - if (i < allPropertiesExceptKeyAndComputed.Count - 1) - sbColumnList.Append(", "); - } - - var sbParameterList = new StringBuilder(null); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) - { - var property = allPropertiesExceptKeyAndComputed[i]; - sbParameterList.AppendFormat("@{0}", property.Name); - if (i < allPropertiesExceptKeyAndComputed.Count - 1) - sbParameterList.Append(", "); - } - - if (!isList) //single entity - { - return sqlAdapter.InsertAsync(connection, transaction, commandTimeout, name, sbColumnList.ToString(), - sbParameterList.ToString(), keyProperties, entityToInsert); - } - - //insert list of entities - var cmd = $"INSERT INTO {name} ({sbColumnList}) values ({sbParameterList})"; - return connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout); - } - - /// - /// Updates entity in table "Ts" asynchronously using Task, checks if the entity is modified if the entity is tracked by the Get() extension. - /// - /// Type to be updated - /// Open SqlConnection - /// Entity to be updated - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// true if updated, false if not found or not modified (tracked entities) - public static async Task UpdateAsync(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - if ((entityToUpdate is IProxy proxy) && !proxy.IsDirty) - { - return false; - } - - var type = typeof(T); - - if (type.IsArray) - { - type = type.GetElementType(); - } - else if (type.IsGenericType) - { - var typeInfo = type.GetTypeInfo(); - bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || - typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - if (implementsGenericIEnumerableOrIsGenericIEnumerable) - { - type = type.GetGenericArguments()[0]; - } - } - - var keyProperties = KeyPropertiesCache(type).ToList(); - var explicitKeyProperties = ExplicitKeyPropertiesCache(type); - if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) - throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); - - var name = GetTableName(type); - - var sb = new StringBuilder(); - sb.AppendFormat("update {0} set ", name); - - var allProperties = TypePropertiesCache(type); - keyProperties.AddRange(explicitKeyProperties); - var computedProperties = ComputedPropertiesCache(type); - var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < nonIdProps.Count; i++) - { - var property = nonIdProps[i]; - adapter.AppendColumnNameEqualsValue(sb, property.Name); - if (i < nonIdProps.Count - 1) - sb.Append(", "); - } - sb.Append(" where "); - for (var i = 0; i < keyProperties.Count; i++) - { - var property = keyProperties[i]; - adapter.AppendColumnNameEqualsValue(sb, property.Name); - if (i < keyProperties.Count - 1) - sb.Append(" and "); - } - var updated = await connection.ExecuteAsync(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction).ConfigureAwait(false); - return updated > 0; - } - - /// - /// Delete entity in table "Ts" asynchronously using Task. - /// - /// Type of entity - /// Open SqlConnection - /// Entity to delete - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// true if deleted, false if not found - public static async Task DeleteAsync(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - if (entityToDelete == null) - throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete)); - - var type = typeof(T); - - if (type.IsArray) - { - type = type.GetElementType(); - } - else if (type.IsGenericType) - { - var typeInfo = type.GetTypeInfo(); - bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || - typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - if (implementsGenericIEnumerableOrIsGenericIEnumerable) - { - type = type.GetGenericArguments()[0]; - } - } - - var keyProperties = KeyPropertiesCache(type); - var explicitKeyProperties = ExplicitKeyPropertiesCache(type); - if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) - throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); - - var name = GetTableName(type); - var allKeyProperties = keyProperties.Concat(explicitKeyProperties).ToList(); - - var sb = new StringBuilder(); - sb.AppendFormat("DELETE FROM {0} WHERE ", name); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < allKeyProperties.Count; i++) - { - var property = allKeyProperties[i]; - adapter.AppendColumnNameEqualsValue(sb, property.Name); - if (i < allKeyProperties.Count - 1) - sb.Append(" AND "); - } - var deleted = await connection.ExecuteAsync(sb.ToString(), entityToDelete, transaction, commandTimeout).ConfigureAwait(false); - return deleted > 0; - } - - /// - /// Delete all entities in the table related to the type T asynchronously using Task. - /// - /// Type of entity - /// Open SqlConnection - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// true if deleted, false if none found - public static async Task DeleteAllAsync(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - var statement = "DELETE FROM " + GetTableName(type); - var deleted = await connection.ExecuteAsync(statement, null, transaction, commandTimeout).ConfigureAwait(false); - return deleted > 0; - } - } -} - -public partial interface ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); -} - -public partial class SqlServerAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) values ({parameterList}); SELECT SCOPE_IDENTITY() id"; - var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - - var first = await multi.ReadFirstOrDefaultAsync().ConfigureAwait(false); - if (first == null || first.id == null) return 0; - - var id = (int)first.id; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (pi.Length == 0) return id; - - var idp = pi[0]; - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return id; - } -} - -public partial class SqlCeServerAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; - await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - var r = (await connection.QueryAsync("SELECT @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false)).ToList(); - - if (r[0] == null || r[0].id == null) return 0; - var id = (int)r[0].id; - - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (pi.Length == 0) return id; - - var idp = pi[0]; - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return id; - } -} - -public partial class MySqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, - string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList})"; - await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - var r = await connection.QueryAsync("SELECT LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); - - var id = r.First().id; - if (id == null) return 0; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (pi.Length == 0) return Convert.ToInt32(id); - - var idp = pi[0]; - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return Convert.ToInt32(id); - } -} - -public partial class PostgresAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var sb = new StringBuilder(); - sb.AppendFormat("INSERT INTO {0} ({1}) VALUES ({2})", tableName, columnList, parameterList); - - // If no primary key then safe to assume a join table with not too much data to return - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (propertyInfos.Length == 0) - { - sb.Append(" RETURNING *"); - } - else - { - sb.Append(" RETURNING "); - bool first = true; - foreach (var property in propertyInfos) - { - if (!first) - sb.Append(", "); - first = false; - sb.Append(property.Name); - } - } - - var results = await connection.QueryAsync(sb.ToString(), entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - - // Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys - var id = 0; - foreach (var p in propertyInfos) - { - var value = ((IDictionary)results.First())[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); - if (id == 0) - id = Convert.ToInt32(value); - } - return id; - } -} - -public partial class SQLiteAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; - var multi = await connection.QueryMultipleAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - - var id = (int)(await multi.ReadFirstAsync().ConfigureAwait(false)).id; - var pi = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (pi.Length == 0) return id; - - var idp = pi[0]; - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return id; - } -} - -public partial class FbAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public async Task InsertAsync(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; - await connection.ExecuteAsync(cmd, entityToInsert, transaction, commandTimeout).ConfigureAwait(false); - - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - var keyName = propertyInfos[0].Name; - var r = await connection.QueryAsync($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout).ConfigureAwait(false); - - var id = r.First().ID; - if (id == null) return 0; - if (propertyInfos.Length == 0) return Convert.ToInt32(id); - - var idp = propertyInfos[0]; - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return Convert.ToInt32(id); - } -} diff --git a/Dapper.Contrib/SqlMapperExtensions.cs b/Dapper.Contrib/SqlMapperExtensions.cs deleted file mode 100644 index 9a30e805c..000000000 --- a/Dapper.Contrib/SqlMapperExtensions.cs +++ /dev/null @@ -1,1155 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Collections.Concurrent; -using System.Reflection.Emit; -using System.Threading; - -using Dapper; - -namespace Dapper.Contrib.Extensions -{ - /// - /// The Dapper.Contrib extensions for Dapper - /// - public static partial class SqlMapperExtensions - { - /// - /// Defined a proxy object with a possibly dirty state. - /// - public interface IProxy //must be kept public - { - /// - /// Whether the object has been changed. - /// - bool IsDirty { get; set; } - } - - /// - /// Defines a table name mapper for getting table names from types. - /// - public interface ITableNameMapper - { - /// - /// Gets a table name from a given . - /// - /// The to get a name from. - /// The table name for the given . - string GetTableName(Type type); - } - - /// - /// The function to get a database type from the given . - /// - /// The connection to get a database type name from. - public delegate string GetDatabaseTypeDelegate(IDbConnection connection); - /// - /// The function to get a table name from a given - /// - /// The to get a table name for. - public delegate string TableNameMapperDelegate(Type type); - - private static readonly ConcurrentDictionary> KeyProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> ExplicitKeyProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> TypeProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary> ComputedProperties = new ConcurrentDictionary>(); - private static readonly ConcurrentDictionary GetQueries = new ConcurrentDictionary(); - private static readonly ConcurrentDictionary TypeTableName = new ConcurrentDictionary(); - - private static readonly ISqlAdapter DefaultAdapter = new SqlServerAdapter(); - private static readonly Dictionary AdapterDictionary - = new Dictionary(6) - { - ["sqlconnection"] = new SqlServerAdapter(), - ["sqlceconnection"] = new SqlCeServerAdapter(), - ["npgsqlconnection"] = new PostgresAdapter(), - ["sqliteconnection"] = new SQLiteAdapter(), - ["mysqlconnection"] = new MySqlAdapter(), - ["fbconnection"] = new FbAdapter() - }; - - private static List ComputedPropertiesCache(Type type) - { - if (ComputedProperties.TryGetValue(type.TypeHandle, out IEnumerable pi)) - { - return pi.ToList(); - } - - var computedProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ComputedAttribute)).ToList(); - - ComputedProperties[type.TypeHandle] = computedProperties; - return computedProperties; - } - - private static List ExplicitKeyPropertiesCache(Type type) - { - if (ExplicitKeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi)) - { - return pi.ToList(); - } - - var explicitKeyProperties = TypePropertiesCache(type).Where(p => p.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)).ToList(); - - ExplicitKeyProperties[type.TypeHandle] = explicitKeyProperties; - return explicitKeyProperties; - } - - private static List KeyPropertiesCache(Type type) - { - if (KeyProperties.TryGetValue(type.TypeHandle, out IEnumerable pi)) - { - return pi.ToList(); - } - - var allProperties = TypePropertiesCache(type); - var keyProperties = allProperties.Where(p => p.GetCustomAttributes(true).Any(a => a is KeyAttribute)).ToList(); - - if (keyProperties.Count == 0) - { - var idProp = allProperties.Find(p => string.Equals(p.Name, "id", StringComparison.CurrentCultureIgnoreCase)); - if (idProp != null && !idProp.GetCustomAttributes(true).Any(a => a is ExplicitKeyAttribute)) - { - keyProperties.Add(idProp); - } - } - - KeyProperties[type.TypeHandle] = keyProperties; - return keyProperties; - } - - private static List TypePropertiesCache(Type type) - { - if (TypeProperties.TryGetValue(type.TypeHandle, out IEnumerable pis)) - { - return pis.ToList(); - } - - var properties = type.GetProperties().Where(IsWriteable).ToArray(); - TypeProperties[type.TypeHandle] = properties; - return properties.ToList(); - } - - private static bool IsWriteable(PropertyInfo pi) - { - var attributes = pi.GetCustomAttributes(typeof(WriteAttribute), false).AsList(); - if (attributes.Count != 1) return true; - - var writeAttribute = (WriteAttribute)attributes[0]; - return writeAttribute.Write; - } - - private static PropertyInfo GetSingleKey(string method) - { - var type = typeof(T); - var keys = KeyPropertiesCache(type); - var explicitKeys = ExplicitKeyPropertiesCache(type); - var keyCount = keys.Count + explicitKeys.Count; - if (keyCount > 1) - throw new DataException($"{method} only supports an entity with a single [Key] or [ExplicitKey] property. [Key] Count: {keys.Count}, [ExplicitKey] Count: {explicitKeys.Count}"); - if (keyCount == 0) - throw new DataException($"{method} only supports an entity with a [Key] or an [ExplicitKey] property"); - - return keys.Count > 0 ? keys[0] : explicitKeys[0]; - } - - /// - /// Returns a single entity by a single id from table "Ts". - /// Id must be marked with [Key] attribute. - /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension - /// for optimal performance. - /// - /// Interface or type to create and populate - /// Open SqlConnection - /// Id of the entity to get, must be marked with [Key] attribute - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// Entity of T - public static T Get(this IDbConnection connection, dynamic id, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - - if (!GetQueries.TryGetValue(type.TypeHandle, out string sql)) - { - var key = GetSingleKey(nameof(Get)); - var name = GetTableName(type); - - sql = $"select * from {name} where {key.Name} = @id"; - GetQueries[type.TypeHandle] = sql; - } - - var dynParams = new DynamicParameters(); - dynParams.Add("@id", id); - - T obj; - - if (type.IsInterface) - { - if (!(connection.Query(sql, dynParams).FirstOrDefault() is IDictionary res)) - { - return null; - } - - obj = ProxyGenerator.GetInterfaceProxy(); - - foreach (var property in TypePropertiesCache(type)) - { - var val = res[property.Name]; - if (val == null) continue; - if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - var genericType = Nullable.GetUnderlyingType(property.PropertyType); - if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); - } - else - { - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); - } - } - - ((IProxy)obj).IsDirty = false; //reset change tracking and return - } - else - { - obj = connection.Query(sql, dynParams, transaction, commandTimeout: commandTimeout).FirstOrDefault(); - } - return obj; - } - - /// - /// Returns a list of entities from table "Ts". - /// Id of T must be marked with [Key] attribute. - /// Entities created from interfaces are tracked/intercepted for changes and used by the Update() extension - /// for optimal performance. - /// - /// Interface or type to create and populate - /// Open SqlConnection - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// Entity of T - public static IEnumerable GetAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - var cacheType = typeof(List); - - if (!GetQueries.TryGetValue(cacheType.TypeHandle, out string sql)) - { - GetSingleKey(nameof(GetAll)); - var name = GetTableName(type); - - sql = "select * from " + name; - GetQueries[cacheType.TypeHandle] = sql; - } - - if (!type.IsInterface) return connection.Query(sql, null, transaction, commandTimeout: commandTimeout); - - var result = connection.Query(sql); - var list = new List(); - foreach (IDictionary res in result) - { - var obj = ProxyGenerator.GetInterfaceProxy(); - foreach (var property in TypePropertiesCache(type)) - { - var val = res[property.Name]; - if (val == null) continue; - if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(Nullable<>)) - { - var genericType = Nullable.GetUnderlyingType(property.PropertyType); - if (genericType != null) property.SetValue(obj, Convert.ChangeType(val, genericType), null); - } - else - { - property.SetValue(obj, Convert.ChangeType(val, property.PropertyType), null); - } - } - ((IProxy)obj).IsDirty = false; //reset change tracking and return - list.Add(obj); - } - return list; - } - - /// - /// Specify a custom table name mapper based on the POCO type name - /// -#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API - public static TableNameMapperDelegate TableNameMapper; -#pragma warning restore CA2211 // Non-constant fields should not be visible - - private static string GetTableName(Type type) - { - if (TypeTableName.TryGetValue(type.TypeHandle, out string name)) return name; - - if (TableNameMapper != null) - { - name = TableNameMapper(type); - } - else - { - //NOTE: This as dynamic trick falls back to handle both our own Table-attribute as well as the one in EntityFramework - var tableAttrName = - type.GetCustomAttribute(false)?.Name - ?? (type.GetCustomAttributes(false).FirstOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic)?.Name; - - if (tableAttrName != null) - { - name = tableAttrName; - } - else - { - name = type.Name + "s"; - if (type.IsInterface && name.StartsWith("I")) - name = name.Substring(1); - } - } - - TypeTableName[type.TypeHandle] = name; - return name; - } - - /// - /// Inserts an entity into table "Ts" and returns identity id or number of inserted rows if inserting a list. - /// - /// The type to insert. - /// Open SqlConnection - /// Entity to insert, can be list of entities - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// Identity of inserted entity, or number of inserted rows if inserting a list - public static long Insert(this IDbConnection connection, T entityToInsert, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var isList = false; - - var type = typeof(T); - - if (type.IsArray) - { - isList = true; - type = type.GetElementType(); - } - else if (type.IsGenericType) - { - var typeInfo = type.GetTypeInfo(); - bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || - typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - if (implementsGenericIEnumerableOrIsGenericIEnumerable) - { - isList = true; - type = type.GetGenericArguments()[0]; - } - } - - var name = GetTableName(type); - var sbColumnList = new StringBuilder(null); - var allProperties = TypePropertiesCache(type); - var keyProperties = KeyPropertiesCache(type); - var computedProperties = ComputedPropertiesCache(type); - var allPropertiesExceptKeyAndComputed = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) - { - var property = allPropertiesExceptKeyAndComputed[i]; - adapter.AppendColumnName(sbColumnList, property.Name); //fix for issue #336 - if (i < allPropertiesExceptKeyAndComputed.Count - 1) - sbColumnList.Append(", "); - } - - var sbParameterList = new StringBuilder(null); - for (var i = 0; i < allPropertiesExceptKeyAndComputed.Count; i++) - { - var property = allPropertiesExceptKeyAndComputed[i]; - sbParameterList.AppendFormat("@{0}", property.Name); - if (i < allPropertiesExceptKeyAndComputed.Count - 1) - sbParameterList.Append(", "); - } - - int returnVal; - var wasClosed = connection.State == ConnectionState.Closed; - if (wasClosed) connection.Open(); - - if (!isList) //single entity - { - returnVal = adapter.Insert(connection, transaction, commandTimeout, name, sbColumnList.ToString(), - sbParameterList.ToString(), keyProperties, entityToInsert); - } - else - { - //insert list of entities - var cmd = $"insert into {name} ({sbColumnList}) values ({sbParameterList})"; - returnVal = connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - } - if (wasClosed) connection.Close(); - return returnVal; - } - - /// - /// Updates entity in table "Ts", checks if the entity is modified if the entity is tracked by the Get() extension. - /// - /// Type to be updated - /// Open SqlConnection - /// Entity to be updated - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// true if updated, false if not found or not modified (tracked entities) - public static bool Update(this IDbConnection connection, T entityToUpdate, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - if (entityToUpdate is IProxy proxy && !proxy.IsDirty) - { - return false; - } - - var type = typeof(T); - - if (type.IsArray) - { - type = type.GetElementType(); - } - else if (type.IsGenericType) - { - var typeInfo = type.GetTypeInfo(); - bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || - typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - if (implementsGenericIEnumerableOrIsGenericIEnumerable) - { - type = type.GetGenericArguments()[0]; - } - } - - var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy - var explicitKeyProperties = ExplicitKeyPropertiesCache(type); - if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) - throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); - - var name = GetTableName(type); - - var sb = new StringBuilder(); - sb.AppendFormat("update {0} set ", name); - - var allProperties = TypePropertiesCache(type); - keyProperties.AddRange(explicitKeyProperties); - var computedProperties = ComputedPropertiesCache(type); - var nonIdProps = allProperties.Except(keyProperties.Union(computedProperties)).ToList(); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < nonIdProps.Count; i++) - { - var property = nonIdProps[i]; - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 - if (i < nonIdProps.Count - 1) - sb.Append(", "); - } - sb.Append(" where "); - for (var i = 0; i < keyProperties.Count; i++) - { - var property = keyProperties[i]; - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 - if (i < keyProperties.Count - 1) - sb.Append(" and "); - } - var updated = connection.Execute(sb.ToString(), entityToUpdate, commandTimeout: commandTimeout, transaction: transaction); - return updated > 0; - } - - /// - /// Delete entity in table "Ts". - /// - /// Type of entity - /// Open SqlConnection - /// Entity to delete - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// true if deleted, false if not found - public static bool Delete(this IDbConnection connection, T entityToDelete, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - if (entityToDelete == null) - throw new ArgumentException("Cannot Delete null Object", nameof(entityToDelete)); - - var type = typeof(T); - - if (type.IsArray) - { - type = type.GetElementType(); - } - else if (type.IsGenericType) - { - var typeInfo = type.GetTypeInfo(); - bool implementsGenericIEnumerableOrIsGenericIEnumerable = - typeInfo.ImplementedInterfaces.Any(ti => ti.IsGenericType && ti.GetGenericTypeDefinition() == typeof(IEnumerable<>)) || - typeInfo.GetGenericTypeDefinition() == typeof(IEnumerable<>); - - if (implementsGenericIEnumerableOrIsGenericIEnumerable) - { - type = type.GetGenericArguments()[0]; - } - } - - var keyProperties = KeyPropertiesCache(type).ToList(); //added ToList() due to issue #418, must work on a list copy - var explicitKeyProperties = ExplicitKeyPropertiesCache(type); - if (keyProperties.Count == 0 && explicitKeyProperties.Count == 0) - throw new ArgumentException("Entity must have at least one [Key] or [ExplicitKey] property"); - - var name = GetTableName(type); - keyProperties.AddRange(explicitKeyProperties); - - var sb = new StringBuilder(); - sb.AppendFormat("delete from {0} where ", name); - - var adapter = GetFormatter(connection); - - for (var i = 0; i < keyProperties.Count; i++) - { - var property = keyProperties[i]; - adapter.AppendColumnNameEqualsValue(sb, property.Name); //fix for issue #336 - if (i < keyProperties.Count - 1) - sb.Append(" and "); - } - var deleted = connection.Execute(sb.ToString(), entityToDelete, transaction, commandTimeout); - return deleted > 0; - } - - /// - /// Delete all entities in the table related to the type T. - /// - /// Type of entity - /// Open SqlConnection - /// The transaction to run under, null (the default) if none - /// Number of seconds before command execution timeout - /// true if deleted, false if none found - public static bool DeleteAll(this IDbConnection connection, IDbTransaction transaction = null, int? commandTimeout = null) where T : class - { - var type = typeof(T); - var name = GetTableName(type); - var statement = $"delete from {name}"; - var deleted = connection.Execute(statement, null, transaction, commandTimeout); - return deleted > 0; - } - - /// - /// Specifies a custom callback that detects the database type instead of relying on the default strategy (the name of the connection type object). - /// Please note that this callback is global and will be used by all the calls that require a database specific adapter. - /// -#pragma warning disable CA2211 // Non-constant fields should not be visible - I agree with you, but we can't do that until we break the API - public static GetDatabaseTypeDelegate GetDatabaseType; -#pragma warning restore CA2211 // Non-constant fields should not be visible - - private static ISqlAdapter GetFormatter(IDbConnection connection) - { - var name = GetDatabaseType?.Invoke(connection).ToLower() - ?? connection.GetType().Name.ToLower(); - - return AdapterDictionary.TryGetValue(name, out var adapter) - ? adapter - : DefaultAdapter; - } - - private static class ProxyGenerator - { - private static readonly Dictionary TypeCache = new Dictionary(); - - private static AssemblyBuilder GetAsmBuilder(string name) - { -#if !NET461 - return AssemblyBuilder.DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); -#else - return Thread.GetDomain().DefineDynamicAssembly(new AssemblyName { Name = name }, AssemblyBuilderAccess.Run); -#endif - } - - public static T GetInterfaceProxy() - { - Type typeOfT = typeof(T); - - if (TypeCache.TryGetValue(typeOfT, out Type k)) - { - return (T)Activator.CreateInstance(k); - } - var assemblyBuilder = GetAsmBuilder(typeOfT.Name); - - var moduleBuilder = assemblyBuilder.DefineDynamicModule("SqlMapperExtensions." + typeOfT.Name); //NOTE: to save, add "asdasd.dll" parameter - - var interfaceType = typeof(IProxy); - var typeBuilder = moduleBuilder.DefineType(typeOfT.Name + "_" + Guid.NewGuid(), - TypeAttributes.Public | TypeAttributes.Class); - typeBuilder.AddInterfaceImplementation(typeOfT); - typeBuilder.AddInterfaceImplementation(interfaceType); - - //create our _isDirty field, which implements IProxy - var setIsDirtyMethod = CreateIsDirtyProperty(typeBuilder); - - // Generate a field for each property, which implements the T - foreach (var property in typeof(T).GetProperties()) - { - var isId = property.GetCustomAttributes(true).Any(a => a is KeyAttribute); - CreateProperty(typeBuilder, property.Name, property.PropertyType, setIsDirtyMethod, isId); - } - -#if NETSTANDARD2_0 - var generatedType = typeBuilder.CreateTypeInfo().AsType(); -#else - var generatedType = typeBuilder.CreateType(); -#endif - - TypeCache.Add(typeOfT, generatedType); - return (T)Activator.CreateInstance(generatedType); - } - - private static MethodInfo CreateIsDirtyProperty(TypeBuilder typeBuilder) - { - var propType = typeof(bool); - var field = typeBuilder.DefineField("_" + nameof(IProxy.IsDirty), propType, FieldAttributes.Private); - var property = typeBuilder.DefineProperty(nameof(IProxy.IsDirty), - System.Reflection.PropertyAttributes.None, - propType, - new[] { propType }); - - const MethodAttributes getSetAttr = MethodAttributes.Public | MethodAttributes.NewSlot | MethodAttributes.SpecialName - | MethodAttributes.Final | MethodAttributes.Virtual | MethodAttributes.HideBySig; - - // Define the "get" and "set" accessor methods - var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + nameof(IProxy.IsDirty), - getSetAttr, - propType, - Type.EmptyTypes); - var currGetIl = currGetPropMthdBldr.GetILGenerator(); - currGetIl.Emit(OpCodes.Ldarg_0); - currGetIl.Emit(OpCodes.Ldfld, field); - currGetIl.Emit(OpCodes.Ret); - var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + nameof(IProxy.IsDirty), - getSetAttr, - null, - new[] { propType }); - var currSetIl = currSetPropMthdBldr.GetILGenerator(); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldarg_1); - currSetIl.Emit(OpCodes.Stfld, field); - currSetIl.Emit(OpCodes.Ret); - - property.SetGetMethod(currGetPropMthdBldr); - property.SetSetMethod(currSetPropMthdBldr); - var getMethod = typeof(IProxy).GetMethod("get_" + nameof(IProxy.IsDirty)); - var setMethod = typeof(IProxy).GetMethod("set_" + nameof(IProxy.IsDirty)); - typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); - typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); - - return currSetPropMthdBldr; - } - - private static void CreateProperty(TypeBuilder typeBuilder, string propertyName, Type propType, MethodInfo setIsDirtyMethod, bool isIdentity) - { - //Define the field and the property - var field = typeBuilder.DefineField("_" + propertyName, propType, FieldAttributes.Private); - var property = typeBuilder.DefineProperty(propertyName, - System.Reflection.PropertyAttributes.None, - propType, - new[] { propType }); - - const MethodAttributes getSetAttr = MethodAttributes.Public - | MethodAttributes.Virtual - | MethodAttributes.HideBySig; - - // Define the "get" and "set" accessor methods - var currGetPropMthdBldr = typeBuilder.DefineMethod("get_" + propertyName, - getSetAttr, - propType, - Type.EmptyTypes); - - var currGetIl = currGetPropMthdBldr.GetILGenerator(); - currGetIl.Emit(OpCodes.Ldarg_0); - currGetIl.Emit(OpCodes.Ldfld, field); - currGetIl.Emit(OpCodes.Ret); - - var currSetPropMthdBldr = typeBuilder.DefineMethod("set_" + propertyName, - getSetAttr, - null, - new[] { propType }); - - //store value in private field and set the isdirty flag - var currSetIl = currSetPropMthdBldr.GetILGenerator(); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldarg_1); - currSetIl.Emit(OpCodes.Stfld, field); - currSetIl.Emit(OpCodes.Ldarg_0); - currSetIl.Emit(OpCodes.Ldc_I4_1); - currSetIl.Emit(OpCodes.Call, setIsDirtyMethod); - currSetIl.Emit(OpCodes.Ret); - - //TODO: Should copy all attributes defined by the interface? - if (isIdentity) - { - var keyAttribute = typeof(KeyAttribute); - var myConstructorInfo = keyAttribute.GetConstructor(Type.EmptyTypes); - var attributeBuilder = new CustomAttributeBuilder(myConstructorInfo, Array.Empty()); - property.SetCustomAttribute(attributeBuilder); - } - - property.SetGetMethod(currGetPropMthdBldr); - property.SetSetMethod(currSetPropMthdBldr); - var getMethod = typeof(T).GetMethod("get_" + propertyName); - var setMethod = typeof(T).GetMethod("set_" + propertyName); - typeBuilder.DefineMethodOverride(currGetPropMthdBldr, getMethod); - typeBuilder.DefineMethodOverride(currSetPropMthdBldr, setMethod); - } - } - } - - /// - /// Defines the name of a table to use in Dapper.Contrib commands. - /// - [AttributeUsage(AttributeTargets.Class)] - public class TableAttribute : Attribute - { - /// - /// Creates a table mapping to a specific name for Dapper.Contrib commands - /// - /// The name of this table in the database. - public TableAttribute(string tableName) - { - Name = tableName; - } - - /// - /// The name of the table in the database - /// - public string Name { get; set; } - } - - /// - /// Specifies that this field is a primary key in the database - /// - [AttributeUsage(AttributeTargets.Property)] - public class KeyAttribute : Attribute - { - } - - /// - /// Specifies that this field is an explicitly set primary key in the database - /// - [AttributeUsage(AttributeTargets.Property)] - public class ExplicitKeyAttribute : Attribute - { - } - - /// - /// Specifies whether a field is writable in the database. - /// - [AttributeUsage(AttributeTargets.Property)] - public class WriteAttribute : Attribute - { - /// - /// Specifies whether a field is writable in the database. - /// - /// Whether a field is writable in the database. - public WriteAttribute(bool write) - { - Write = write; - } - - /// - /// Whether a field is writable in the database. - /// - public bool Write { get; } - } - - /// - /// Specifies that this is a computed column. - /// - [AttributeUsage(AttributeTargets.Property)] - public class ComputedAttribute : Attribute - { - } -} - -/// -/// The interface for all Dapper.Contrib database operations -/// Implementing this is each provider's model. -/// -public partial interface ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert); - - /// - /// Adds the name of a column. - /// - /// The string builder to append to. - /// The column name. - void AppendColumnName(StringBuilder sb, string columnName); - /// - /// Adds a column equality to a parameter. - /// - /// The string builder to append to. - /// The column name. - void AppendColumnNameEqualsValue(StringBuilder sb, string columnName); -} - -/// -/// The SQL Server database adapter. -/// -public partial class SqlServerAdapter : ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList});select SCOPE_IDENTITY() id"; - var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); - - var first = multi.Read().FirstOrDefault(); - if (first == null || first.id == null) return 0; - - var id = (int)first.id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (propertyInfos.Length == 0) return id; - - var idProperty = propertyInfos[0]; - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - - return id; - } - - /// - /// Adds the name of a column. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}]", columnName); - } - - /// - /// Adds a column equality to a parameter. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); - } -} - -/// -/// The SQL Server Compact Edition database adapter. -/// -public partial class SqlCeServerAdapter : ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; - connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - var r = connection.Query("select @@IDENTITY id", transaction: transaction, commandTimeout: commandTimeout).ToList(); - - if (r[0].id == null) return 0; - var id = (int)r[0].id; - - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (propertyInfos.Length == 0) return id; - - var idProperty = propertyInfos[0]; - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - - return id; - } - - /// - /// Adds the name of a column. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}]", columnName); - } - - /// - /// Adds a column equality to a parameter. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("[{0}] = @{1}", columnName, columnName); - } -} - -/// -/// The MySQL database adapter. -/// -public partial class MySqlAdapter : ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; - connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - var r = connection.Query("Select LAST_INSERT_ID() id", transaction: transaction, commandTimeout: commandTimeout); - - var id = r.First().id; - if (id == null) return 0; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (propertyInfos.Length == 0) return Convert.ToInt32(id); - - var idp = propertyInfos[0]; - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return Convert.ToInt32(id); - } - - /// - /// Adds the name of a column. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("`{0}`", columnName); - } - - /// - /// Adds a column equality to a parameter. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("`{0}` = @{1}", columnName, columnName); - } -} - -/// -/// The Postgres database adapter. -/// -public partial class PostgresAdapter : ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var sb = new StringBuilder(); - sb.AppendFormat("insert into {0} ({1}) values ({2})", tableName, columnList, parameterList); - - // If no primary key then safe to assume a join table with not too much data to return - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (propertyInfos.Length == 0) - { - sb.Append(" RETURNING *"); - } - else - { - sb.Append(" RETURNING "); - var first = true; - foreach (var property in propertyInfos) - { - if (!first) - sb.Append(", "); - first = false; - sb.Append(property.Name); - } - } - - var results = connection.Query(sb.ToString(), entityToInsert, transaction, commandTimeout: commandTimeout).ToList(); - - // Return the key by assigning the corresponding property in the object - by product is that it supports compound primary keys - var id = 0; - foreach (var p in propertyInfos) - { - var value = ((IDictionary)results[0])[p.Name.ToLower()]; - p.SetValue(entityToInsert, value, null); - if (id == 0) - id = Convert.ToInt32(value); - } - return id; - } - - /// - /// Adds the name of a column. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\"", columnName); - } - - /// - /// Adds a column equality to a parameter. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); - } -} - -/// -/// The SQLite database adapter. -/// -public partial class SQLiteAdapter : ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"INSERT INTO {tableName} ({columnList}) VALUES ({parameterList}); SELECT last_insert_rowid() id"; - var multi = connection.QueryMultiple(cmd, entityToInsert, transaction, commandTimeout); - - var id = (int)multi.Read().First().id; - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - if (propertyInfos.Length == 0) return id; - - var idProperty = propertyInfos[0]; - idProperty.SetValue(entityToInsert, Convert.ChangeType(id, idProperty.PropertyType), null); - - return id; - } - - /// - /// Adds the name of a column. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\"", columnName); - } - - /// - /// Adds a column equality to a parameter. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("\"{0}\" = @{1}", columnName, columnName); - } -} - -/// -/// The Firebase SQL adapter. -/// -public partial class FbAdapter : ISqlAdapter -{ - /// - /// Inserts into the database, returning the Id of the row created. - /// - /// The connection to use. - /// The transaction to use. - /// The command timeout to use. - /// The table to insert into. - /// The columns to set with this insert. - /// The parameters to set for this insert. - /// The key columns in this table. - /// The entity to insert. - /// The Id of the row created. - public int Insert(IDbConnection connection, IDbTransaction transaction, int? commandTimeout, string tableName, string columnList, string parameterList, IEnumerable keyProperties, object entityToInsert) - { - var cmd = $"insert into {tableName} ({columnList}) values ({parameterList})"; - connection.Execute(cmd, entityToInsert, transaction, commandTimeout); - - var propertyInfos = keyProperties as PropertyInfo[] ?? keyProperties.ToArray(); - var keyName = propertyInfos[0].Name; - var r = connection.Query($"SELECT FIRST 1 {keyName} ID FROM {tableName} ORDER BY {keyName} DESC", transaction: transaction, commandTimeout: commandTimeout); - - var id = r.First().ID; - if (id == null) return 0; - if (propertyInfos.Length == 0) return Convert.ToInt32(id); - - var idp = propertyInfos[0]; - idp.SetValue(entityToInsert, Convert.ChangeType(id, idp.PropertyType), null); - - return Convert.ToInt32(id); - } - - /// - /// Adds the name of a column. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnName(StringBuilder sb, string columnName) - { - sb.AppendFormat("{0}", columnName); - } - - /// - /// Adds a column equality to a parameter. - /// - /// The string builder to append to. - /// The column name. - public void AppendColumnNameEqualsValue(StringBuilder sb, string columnName) - { - sb.AppendFormat("{0} = @{1}", columnName, columnName); - } -} diff --git a/Dapper.sln b/Dapper.sln index f52b99815..4be17ed03 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -24,12 +24,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.StrongName", "Dapper EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "tests\Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Contrib", "Dapper.Contrib\Dapper.Contrib.csproj", "{4E409F8F-CFBB-4332-8B0A-FD5A283051FD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework", "Dapper.EntityFramework\Dapper.EntityFramework.csproj", "{BE401F7B-8611-4A1E-AEAA-5CB700128C16}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Contrib", "tests\Dapper.Tests.Contrib\Dapper.Tests.Contrib.csproj", "{DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{196928F0-7052-4585-90E8-817BD720F5E3}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{8A74F0B6-188F-45D2-8A4B-51E4F211805A}" @@ -75,18 +71,10 @@ Global {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.Build.0 = Debug|Any CPU {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.ActiveCfg = Release|Any CPU {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.Build.0 = Release|Any CPU - {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {4E409F8F-CFBB-4332-8B0A-FD5A283051FD}.Release|Any CPU.Build.0 = Release|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.Build.0 = Debug|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.ActiveCfg = Release|Any CPU {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.Build.0 = Release|Any CPU - {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A}.Release|Any CPU.Build.0 = Release|Any CPU {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.Build.0 = Debug|Any CPU {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -115,9 +103,7 @@ Global {FAC24C3F-68F9-4247-A4B9-21D487E99275} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {549C51A1-222B-4E12-96F1-3AEFF45A7709} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {052C0817-DB26-4925-8929-8C5E42D148D5} = {568BD46C-1C65-4D44-870C-12CD72563262} - {4E409F8F-CFBB-4332-8B0A-FD5A283051FD} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {BE401F7B-8611-4A1E-AEAA-5CB700128C16} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {DAB3C5B7-BCD1-4A5F-BB6B-50D2BB63DB4A} = {568BD46C-1C65-4D44-870C-12CD72563262} {196928F0-7052-4585-90E8-817BD720F5E3} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} diff --git a/Readme.md b/Readme.md index b897a8c27..d5bb250dc 100644 --- a/Readme.md +++ b/Readme.md @@ -14,7 +14,6 @@ MyGet Pre-release feed: https://www.myget.org/gallery/dapper | Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet | | ------- | ------------ | ----------------- | --------- | ----- | | [Dapper](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/v/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/vpre/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper](https://img.shields.io/nuget/dt/Dapper.svg)](https://www.nuget.org/packages/Dapper/) | [![Dapper MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper) | -| [Dapper.Contrib](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/v/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/vpre/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib](https://img.shields.io/nuget/dt/Dapper.Contrib.svg)](https://www.nuget.org/packages/Dapper.Contrib/) | [![Dapper.Contrib MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.Contrib.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.Contrib) | | [Dapper.EntityFramework](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/v/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/vpre/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework](https://img.shields.io/nuget/dt/Dapper.EntityFramework.svg)](https://www.nuget.org/packages/Dapper.EntityFramework/) | [![Dapper.EntityFramework MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.EntityFramework.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.EntityFramework) | | [Dapper.EntityFramework.StrongName](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/v/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/vpre/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName](https://img.shields.io/nuget/dt/Dapper.EntityFramework.StrongName.svg)](https://www.nuget.org/packages/Dapper.EntityFramework.StrongName/) | [![Dapper.EntityFramework.StrongName MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.EntityFramework.StrongName.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.EntityFramework.StrongName) | | [Dapper.Rainbow](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/v/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/vpre/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow](https://img.shields.io/nuget/dt/Dapper.Rainbow.svg)](https://www.nuget.org/packages/Dapper.Rainbow/) | [![Dapper.Rainbow MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.Rainbow.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.Rainbow) | diff --git a/benchmarks/Directory.Build.props b/benchmarks/Directory.Build.props index 4507d6aa8..837b3a850 100644 --- a/benchmarks/Directory.Build.props +++ b/benchmarks/Directory.Build.props @@ -10,7 +10,7 @@ - + diff --git a/global.json b/global.json deleted file mode 100644 index 8d86ec578..000000000 --- a/global.json +++ /dev/null @@ -1,8 +0,0 @@ - -{ - "sdk": { - "version": "3.1.100", - "rollForward": "latestMajor", - "allowPrerelease": false - } -} \ No newline at end of file diff --git a/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj b/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj deleted file mode 100644 index 333da6238..000000000 --- a/tests/Dapper.Tests.Contrib/Dapper.Tests.Contrib.csproj +++ /dev/null @@ -1,25 +0,0 @@ - - - Dapper.Tests.Contrib - Dapper Contrib Test Suite - netcoreapp3.1;net462;net5.0 - $(NoWarn);CA1816;IDE0063;xUnit1004 - - - - - - - PreserveNewest - - - - - - - - - - - - diff --git a/tests/Dapper.Tests.Contrib/Helpers/Attributes.cs b/tests/Dapper.Tests.Contrib/Helpers/Attributes.cs deleted file mode 100644 index f6393c0f0..000000000 --- a/tests/Dapper.Tests.Contrib/Helpers/Attributes.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System; -using Xunit.Sdk; - -namespace Dapper.Tests -{ - /// - /// Override for that truncates our DisplayName down. - /// - /// Attribute that is applied to a method to indicate that it is a fact that should - /// be run by the test runner. It can also be extended to support a customized definition - /// of a test method. - /// - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - [XunitTestCaseDiscoverer("Dapper.Tests.FactDiscoverer", "Dapper.Tests.Contrib")] - public class FactAttribute : Xunit.FactAttribute - { - } - - /// - /// Override for that truncates our DisplayName down. - /// - /// Marks a test method as being a data theory. Data theories are tests which are - /// fed various bits of data from a data source, mapping to parameters on the test - /// method. If the data source contains multiple rows, then the test method is executed - /// multiple times (once with each data row). Data is provided by attributes which - /// derive from Xunit.Sdk.DataAttribute (notably, Xunit.InlineDataAttribute and Xunit.MemberDataAttribute). - /// - /// - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - [XunitTestCaseDiscoverer("Dapper.Tests.TheoryDiscoverer", "Dapper.Tests.Contrib")] - public class TheoryAttribute : Xunit.TheoryAttribute { } - - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public sealed class FactLongRunningAttribute : FactAttribute - { - public FactLongRunningAttribute() - { -#if !LONG_RUNNING - Skip = "Long running"; -#endif - } - - public string Url { get; private set; } - } -} diff --git a/tests/Dapper.Tests.Contrib/TestSuite.Async.cs b/tests/Dapper.Tests.Contrib/TestSuite.Async.cs deleted file mode 100644 index fcd11802a..000000000 --- a/tests/Dapper.Tests.Contrib/TestSuite.Async.cs +++ /dev/null @@ -1,476 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; - -using Dapper.Contrib.Extensions; -using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute; -using Xunit; - -namespace Dapper.Tests.Contrib -{ - public abstract partial class TestSuite - { - [Fact] - public async Task TypeWithGenericParameterCanBeInsertedAsync() - { - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync>(); - var objectToInsert = new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "something" - }; - await connection.InsertAsync(objectToInsert); - - Assert.Single(connection.GetAll>()); - - var objectsToInsert = new List>(2) - { - new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "1", - }, - new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "2", - } - }; - - await connection.InsertAsync(objectsToInsert); - var list = connection.GetAll>(); - Assert.Equal(3, list.Count()); - } - } - - [Fact] - public async Task TypeWithGenericParameterCanBeUpdatedAsync() - { - using (var connection = GetOpenConnection()) - { - var objectToInsert = new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "something" - }; - await connection.InsertAsync(objectToInsert); - - objectToInsert.Name = "somethingelse"; - await connection.UpdateAsync(objectToInsert); - - var updatedObject = connection.Get>(objectToInsert.Id); - Assert.Equal(objectToInsert.Name, updatedObject.Name); - } - } - - [Fact] - public async Task TypeWithGenericParameterCanBeDeletedAsync() - { - using (var connection = GetOpenConnection()) - { - var objectToInsert = new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "something" - }; - await connection.InsertAsync(objectToInsert); - - bool deleted = await connection.DeleteAsync(objectToInsert); - Assert.True(deleted); - } - } - - [Fact] - public async Task GetAsyncSucceedsAfterDeleteAsyncWhenExplicitKeyPresent() - { - using (var connection = GetOpenConnection()) - { - await connection.DeleteAsync(new ObjectX { ObjectXId = Guid.NewGuid().ToString() }).ConfigureAwait(false); - var retrieved = await connection.GetAsync(Guid.NewGuid().ToString()).ConfigureAwait(false); - Assert.Null(retrieved); - } - } - - /// - /// Tests for issue #351 - /// - [Fact] - public async Task InsertGetUpdateDeleteWithExplicitKeyAsync() - { - using (var connection = GetOpenConnection()) - { - var guid = Guid.NewGuid().ToString(); - var o1 = new ObjectX { ObjectXId = guid, Name = "Foo" }; - var originalxCount = (await connection.QueryAsync("Select Count(*) From ObjectX").ConfigureAwait(false)).First(); - await connection.InsertAsync(o1).ConfigureAwait(false); - var list1 = (await connection.QueryAsync("select * from ObjectX").ConfigureAwait(false)).ToList(); - Assert.Equal(list1.Count, originalxCount + 1); - o1 = await connection.GetAsync(guid).ConfigureAwait(false); - Assert.Equal(o1.ObjectXId, guid); - o1.Name = "Bar"; - await connection.UpdateAsync(o1).ConfigureAwait(false); - o1 = await connection.GetAsync(guid).ConfigureAwait(false); - Assert.Equal("Bar", o1.Name); - await connection.DeleteAsync(o1).ConfigureAwait(false); - o1 = await connection.GetAsync(guid).ConfigureAwait(false); - Assert.Null(o1); - - const int id = 42; - var o2 = new ObjectY { ObjectYId = id, Name = "Foo" }; - var originalyCount = connection.Query("Select Count(*) From ObjectY").First(); - await connection.InsertAsync(o2).ConfigureAwait(false); - var list2 = (await connection.QueryAsync("select * from ObjectY").ConfigureAwait(false)).ToList(); - Assert.Equal(list2.Count, originalyCount + 1); - o2 = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Equal(o2.ObjectYId, id); - o2.Name = "Bar"; - await connection.UpdateAsync(o2).ConfigureAwait(false); - o2 = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Equal("Bar", o2.Name); - await connection.DeleteAsync(o2).ConfigureAwait(false); - o2 = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Null(o2); - } - } - - [Fact] - public async Task TableNameAsync() - { - using (var connection = GetOpenConnection()) - { - // tests against "Automobiles" table (Table attribute) - var id = await connection.InsertAsync(new Car { Name = "VolvoAsync" }).ConfigureAwait(false); - var car = await connection.GetAsync(id).ConfigureAwait(false); - Assert.NotNull(car); - Assert.Equal("VolvoAsync", car.Name); - Assert.True(await connection.UpdateAsync(new Car { Id = id, Name = "SaabAsync" }).ConfigureAwait(false)); - Assert.Equal("SaabAsync", (await connection.GetAsync(id).ConfigureAwait(false)).Name); - Assert.True(await connection.DeleteAsync(new Car { Id = id }).ConfigureAwait(false)); - Assert.Null(await connection.GetAsync(id).ConfigureAwait(false)); - } - } - - [Fact] - public async Task TestSimpleGetAsync() - { - using (var connection = GetOpenConnection()) - { - var id = await connection.InsertAsync(new User { Name = "Adama", Age = 10 }).ConfigureAwait(false); - var user = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Equal(id, user.Id); - Assert.Equal("Adama", user.Name); - await connection.DeleteAsync(user).ConfigureAwait(false); - } - } - - [Fact] - public async Task InsertGetUpdateAsync() - { - using (var connection = GetOpenConnection()) - { - Assert.Null(await connection.GetAsync(30).ConfigureAwait(false)); - - var originalCount = (await connection.QueryAsync("select Count(*) from Users").ConfigureAwait(false)).First(); - - var id = await connection.InsertAsync(new User { Name = "Adam", Age = 10 }).ConfigureAwait(false); - - //get a user with "isdirty" tracking - var user = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Equal("Adam", user.Name); - Assert.False(await connection.UpdateAsync(user).ConfigureAwait(false)); //returns false if not updated, based on tracking - user.Name = "Bob"; - Assert.True(await connection.UpdateAsync(user).ConfigureAwait(false)); //returns true if updated, based on tracking - user = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Equal("Bob", user.Name); - - //get a user with no tracking - var notrackedUser = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Equal("Bob", notrackedUser.Name); - Assert.True(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false)); - //returns true, even though user was not changed - notrackedUser.Name = "Cecil"; - Assert.True(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false)); - Assert.Equal("Cecil", (await connection.GetAsync(id).ConfigureAwait(false)).Name); - - Assert.Equal((await connection.QueryAsync("select * from Users").ConfigureAwait(false)).Count(), originalCount + 1); - Assert.True(await connection.DeleteAsync(user).ConfigureAwait(false)); - Assert.Equal((await connection.QueryAsync("select * from Users").ConfigureAwait(false)).Count(), originalCount); - - Assert.False(await connection.UpdateAsync(notrackedUser).ConfigureAwait(false)); //returns false, user not found - - Assert.True(await connection.InsertAsync(new User { Name = "Adam", Age = 10 }).ConfigureAwait(false) > originalCount + 1); - } - } - - [Fact] - public async Task InsertCheckKeyAsync() - { - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - Assert.Null(await connection.GetAsync(3).ConfigureAwait(false)); - var user = new User { Name = "Adamb", Age = 10 }; - var id = await connection.InsertAsync(user).ConfigureAwait(false); - Assert.Equal(user.Id, id); - } - } - - [Fact] - public async Task BuilderSelectClauseAsync() - { - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - var rand = new Random(8675309); - var data = new List(100); - for (var i = 0; i < 100; i++) - { - var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; - data.Add(nU); - nU.Id = await connection.InsertAsync(nU).ConfigureAwait(false); - } - - var builder = new SqlBuilder(); - var justId = builder.AddTemplate("SELECT /**select**/ FROM Users"); - var all = builder.AddTemplate("SELECT Name, /**select**/, Age FROM Users"); - - builder.Select("Id"); - - var ids = await connection.QueryAsync(justId.RawSql, justId.Parameters).ConfigureAwait(false); - var users = await connection.QueryAsync(all.RawSql, all.Parameters).ConfigureAwait(false); - - foreach (var u in data) - { - if (!ids.Any(i => u.Id == i)) throw new Exception("Missing ids in select"); - if (!users.Any(a => a.Id == u.Id && a.Name == u.Name && a.Age == u.Age)) - throw new Exception("Missing users in select"); - } - } - } - - [Fact] - public async Task BuilderTemplateWithoutCompositionAsync() - { - var builder = new SqlBuilder(); - var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new { age = 5 }); - - if (template.RawSql == null) throw new Exception("RawSql null"); - if (template.Parameters == null) throw new Exception("Parameters null"); - - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - await connection.InsertAsync(new User { Age = 5, Name = "Testy McTestington" }).ConfigureAwait(false); - - if ((await connection.QueryAsync(template.RawSql, template.Parameters).ConfigureAwait(false)).Single() != 1) - throw new Exception("Query failed"); - } - } - - [Fact] - public async Task InsertEnumerableAsync() - { - await InsertHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); - } - - [Fact] - public async Task InsertArrayAsync() - { - await InsertHelperAsync(src => src.ToArray()).ConfigureAwait(false); - } - - [Fact] - public async Task InsertListAsync() - { - await InsertHelperAsync(src => src.ToList()).ConfigureAwait(false); - } - - private async Task InsertHelperAsync(Func, T> helper) - where T : class - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false); - Assert.Equal(total, numberOfEntities); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities); - } - } - - [Fact] - public async Task UpdateEnumerableAsync() - { - await UpdateHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); - } - - [Fact] - public async Task UpdateArrayAsync() - { - await UpdateHelperAsync(src => src.ToArray()).ConfigureAwait(false); - } - - [Fact] - public async Task UpdateListAsync() - { - await UpdateHelperAsync(src => src.ToList()).ConfigureAwait(false); - } - - private async Task UpdateHelperAsync(Func, T> helper) - where T : class - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false); - Assert.Equal(total, numberOfEntities); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities); - foreach (var user in users) - { - user.Name += " updated"; - } - await connection.UpdateAsync(helper(users)).ConfigureAwait(false); - var name = connection.Query("select * from Users").First().Name; - Assert.Contains("updated", name); - } - } - - [Fact] - public async Task DeleteEnumerableAsync() - { - await DeleteHelperAsync(src => src.AsEnumerable()).ConfigureAwait(false); - } - - [Fact] - public async Task DeleteArrayAsync() - { - await DeleteHelperAsync(src => src.ToArray()).ConfigureAwait(false); - } - - [Fact] - public async Task DeleteListAsync() - { - await DeleteHelperAsync(src => src.ToList()).ConfigureAwait(false); - } - - private async Task DeleteHelperAsync(Func, T> helper) - where T : class - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - var total = await connection.InsertAsync(helper(users)).ConfigureAwait(false); - Assert.Equal(total, numberOfEntities); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities); - - var usersToDelete = users.Take(10).ToList(); - await connection.DeleteAsync(helper(usersToDelete)).ConfigureAwait(false); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities - 10); - } - } - - [Fact] - public async Task GetAllAsync() - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - var total = await connection.InsertAsync(users).ConfigureAwait(false); - Assert.Equal(total, numberOfEntities); - users = (List)await connection.GetAllAsync().ConfigureAwait(false); - Assert.Equal(users.Count, numberOfEntities); - var iusers = await connection.GetAllAsync().ConfigureAwait(false); - Assert.Equal(iusers.ToList().Count, numberOfEntities); - } - } - - /// - /// Test for issue #933 - /// - [Fact] - public async void GetAsyncAndGetAllAsyncWithNullableValues() - { - using (var connection = GetOpenConnection()) - { - var id1 = connection.Insert(new NullableDate { DateValue = new DateTime(2011, 07, 14) }); - var id2 = connection.Insert(new NullableDate { DateValue = null }); - - var value1 = await connection.GetAsync(id1).ConfigureAwait(false); - Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value); - - var value2 = await connection.GetAsync(id2).ConfigureAwait(false); - Assert.True(value2.DateValue == null); - - var value3 = await connection.GetAllAsync().ConfigureAwait(false); - var valuesList = value3.ToList(); - Assert.Equal(new DateTime(2011, 07, 14), valuesList[0].DateValue.Value); - Assert.True(valuesList[1].DateValue == null); - } - } - - [Fact] - public async Task InsertFieldWithReservedNameAsync() - { - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - var id = await connection.InsertAsync(new Result { Name = "Adam", Order = 1 }).ConfigureAwait(false); - - var result = await connection.GetAsync(id).ConfigureAwait(false); - Assert.Equal(1, result.Order); - } - } - - [Fact] - public async Task DeleteAllAsync() - { - using (var connection = GetOpenConnection()) - { - await connection.DeleteAllAsync().ConfigureAwait(false); - - var id1 = await connection.InsertAsync(new User { Name = "Alice", Age = 32 }).ConfigureAwait(false); - var id2 = await connection.InsertAsync(new User { Name = "Bob", Age = 33 }).ConfigureAwait(false); - await connection.DeleteAllAsync().ConfigureAwait(false); - Assert.Null(await connection.GetAsync(id1).ConfigureAwait(false)); - Assert.Null(await connection.GetAsync(id2).ConfigureAwait(false)); - } - } - } -} diff --git a/tests/Dapper.Tests.Contrib/TestSuite.cs b/tests/Dapper.Tests.Contrib/TestSuite.cs deleted file mode 100644 index 71dbd8adf..000000000 --- a/tests/Dapper.Tests.Contrib/TestSuite.cs +++ /dev/null @@ -1,757 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using Dapper.Contrib.Extensions; -using Xunit; - -using FactAttribute = Dapper.Tests.Contrib.SkippableFactAttribute; - -namespace Dapper.Tests.Contrib -{ - [Table("ObjectX")] - public class ObjectX - { - [ExplicitKey] - public string ObjectXId { get; set; } - public string Name { get; set; } - } - - [Table("ObjectY")] - public class ObjectY - { - [ExplicitKey] - public int ObjectYId { get; set; } - public string Name { get; set; } - } - - [Table("ObjectZ")] - public class ObjectZ - { - [ExplicitKey] - public int Id { get; set; } - public string Name { get; set; } - } - - public interface IUser - { - [Key] - int Id { get; set; } - string Name { get; set; } - int Age { get; set; } - } - - public class User : IUser - { - public int Id { get; set; } - public string Name { get; set; } - public int Age { get; set; } - } - - public interface INullableDate - { - [Key] - int Id { get; set; } - DateTime? DateValue { get; set; } - } - - public class NullableDate : INullableDate - { - public int Id { get; set; } - public DateTime? DateValue { get; set; } - } - - public class Person - { - public int Id { get; set; } - public string Name { get; set; } - } - - [Table("Stuff")] - public class Stuff - { - [Key] - public short TheId { get; set; } - public string Name { get; set; } - public DateTime? Created { get; set; } - } - - [Table("Automobiles")] - public class Car - { - public int Id { get; set; } - public string Name { get; set; } - [Computed] - public string Computed { get; set; } - } - - [Table("Results")] - public class Result - { - public int Id { get; set; } - public string Name { get; set; } - public int Order { get; set; } - } - - [Table("GenericType")] - public class GenericType - { - [ExplicitKey] - public string Id { get; set; } - public string Name { get; set; } - } - - public abstract partial class TestSuite - { - public abstract IDbConnection GetConnection(); - - protected static string GetConnectionString(string name, string defaultConnectionString) => - Environment.GetEnvironmentVariable(name) ?? defaultConnectionString; - - private IDbConnection GetOpenConnection() - { - var connection = GetConnection(); - connection.Open(); - return connection; - } - - [Fact] - public void TypeWithGenericParameterCanBeInserted() - { - using (var connection = GetOpenConnection()) - { - connection.DeleteAll>(); - var objectToInsert = new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "something" - }; - connection.Insert(objectToInsert); - - Assert.Single(connection.GetAll>()); - - var objectsToInsert = new List>(2) - { - new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "1", - }, - new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "2", - } - }; - - connection.Insert(objectsToInsert); - var list = connection.GetAll>(); - Assert.Equal(3, list.Count()); - } - } - - [Fact] - public void TypeWithGenericParameterCanBeUpdated() - { - using (var connection = GetOpenConnection()) - { - var objectToInsert = new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "something" - }; - connection.Insert(objectToInsert); - - objectToInsert.Name = "somethingelse"; - connection.Update(objectToInsert); - - var updatedObject = connection.Get>(objectToInsert.Id); - Assert.Equal(objectToInsert.Name, updatedObject.Name); - } - } - - [Fact] - public void TypeWithGenericParameterCanBeDeleted() - { - using (var connection = GetOpenConnection()) - { - var objectToInsert = new GenericType - { - Id = Guid.NewGuid().ToString(), - Name = "something" - }; - connection.Insert(objectToInsert); - - bool deleted = connection.Delete(objectToInsert); - Assert.True(deleted); - } - } - - [Fact] - public void Issue418() - { - using (var connection = GetOpenConnection()) - { - //update first (will fail) then insert - //added for bug #418 - var updateObject = new ObjectX - { - ObjectXId = Guid.NewGuid().ToString(), - Name = "Someone" - }; - var updates = connection.Update(updateObject); - Assert.False(updates); - - connection.DeleteAll(); - - var objectXId = Guid.NewGuid().ToString(); - var insertObject = new ObjectX - { - ObjectXId = objectXId, - Name = "Someone else" - }; - connection.Insert(insertObject); - var list = connection.GetAll(); - Assert.Single(list); - } - } - - /// - /// Tests for issue #351 - /// - [Fact] - public void InsertGetUpdateDeleteWithExplicitKey() - { - using (var connection = GetOpenConnection()) - { - var guid = Guid.NewGuid().ToString(); - var o1 = new ObjectX { ObjectXId = guid, Name = "Foo" }; - var originalxCount = connection.Query("Select Count(*) From ObjectX").First(); - connection.Insert(o1); - var list1 = connection.Query("select * from ObjectX").ToList(); - Assert.Equal(list1.Count, originalxCount + 1); - o1 = connection.Get(guid); - Assert.Equal(o1.ObjectXId, guid); - o1.Name = "Bar"; - connection.Update(o1); - o1 = connection.Get(guid); - Assert.Equal("Bar", o1.Name); - connection.Delete(o1); - o1 = connection.Get(guid); - Assert.Null(o1); - - const int id = 42; - var o2 = new ObjectY { ObjectYId = id, Name = "Foo" }; - var originalyCount = connection.Query("Select Count(*) From ObjectY").First(); - connection.Insert(o2); - var list2 = connection.Query("select * from ObjectY").ToList(); - Assert.Equal(list2.Count, originalyCount + 1); - o2 = connection.Get(id); - Assert.Equal(o2.ObjectYId, id); - o2.Name = "Bar"; - connection.Update(o2); - o2 = connection.Get(id); - Assert.Equal("Bar", o2.Name); - connection.Delete(o2); - o2 = connection.Get(id); - Assert.Null(o2); - } - } - - [Fact] - public void GetAllWithExplicitKey() - { - using (var connection = GetOpenConnection()) - { - var guid = Guid.NewGuid().ToString(); - var o1 = new ObjectX { ObjectXId = guid, Name = "Foo" }; - connection.Insert(o1); - - var objectXs = connection.GetAll().ToList(); - Assert.True(objectXs.Count > 0); - Assert.Equal(1, objectXs.Count(x => x.ObjectXId == guid)); - } - } - - [Fact] - public void InsertGetUpdateDeleteWithExplicitKeyNamedId() - { - using (var connection = GetOpenConnection()) - { - const int id = 42; - var o2 = new ObjectZ { Id = id, Name = "Foo" }; - connection.Insert(o2); - var list2 = connection.Query("select * from ObjectZ").ToList(); - Assert.Single(list2); - o2 = connection.Get(id); - Assert.Equal(o2.Id, id); - } - } - - [Fact] - public void ShortIdentity() - { - using (var connection = GetOpenConnection()) - { - const string name = "First item"; - var id = connection.Insert(new Stuff { Name = name }); - Assert.True(id > 0); // 1-n are valid here, due to parallel tests - var item = connection.Get(id); - Assert.Equal(item.TheId, (short)id); - Assert.Equal(item.Name, name); - } - } - - [Fact] - public void NullDateTime() - { - using (var connection = GetOpenConnection()) - { - connection.Insert(new Stuff { Name = "First item" }); - connection.Insert(new Stuff { Name = "Second item", Created = DateTime.Now }); - var stuff = connection.Query("select * from Stuff").ToList(); - Assert.Null(stuff[0].Created); - Assert.NotNull(stuff.Last().Created); - } - } - - [Fact] - public void TableName() - { - using (var connection = GetOpenConnection()) - { - // tests against "Automobiles" table (Table attribute) - var id = connection.Insert(new Car { Name = "Volvo" }); - var car = connection.Get(id); - Assert.NotNull(car); - Assert.Equal("Volvo", car.Name); - Assert.Equal("Volvo", connection.Get(id).Name); - Assert.True(connection.Update(new Car { Id = (int)id, Name = "Saab" })); - Assert.Equal("Saab", connection.Get(id).Name); - Assert.True(connection.Delete(new Car { Id = (int)id })); - Assert.Null(connection.Get(id)); - } - } - - [Fact] - public void TestSimpleGet() - { - using (var connection = GetOpenConnection()) - { - var id = connection.Insert(new User { Name = "Adama", Age = 10 }); - var user = connection.Get(id); - Assert.Equal(user.Id, (int)id); - Assert.Equal("Adama", user.Name); - connection.Delete(user); - } - } - - [Fact] - public void TestClosedConnection() - { - using (var connection = GetConnection()) - { - Assert.True(connection.Insert(new User { Name = "Adama", Age = 10 }) > 0); - var users = connection.GetAll(); - Assert.NotEmpty(users); - } - } - - [Fact] - public void InsertEnumerable() - { - InsertHelper(src => src.AsEnumerable()); - } - - [Fact] - public void InsertArray() - { - InsertHelper(src => src.ToArray()); - } - - [Fact] - public void InsertList() - { - InsertHelper(src => src.ToList()); - } - - private void InsertHelper(Func, T> helper) - where T : class - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - - var total = connection.Insert(helper(users)); - Assert.Equal(total, numberOfEntities); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities); - } - } - - [Fact] - public void UpdateEnumerable() - { - UpdateHelper(src => src.AsEnumerable()); - } - - [Fact] - public void UpdateArray() - { - UpdateHelper(src => src.ToArray()); - } - - [Fact] - public void UpdateList() - { - UpdateHelper(src => src.ToList()); - } - - private void UpdateHelper(Func, T> helper) - where T : class - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - - var total = connection.Insert(helper(users)); - Assert.Equal(total, numberOfEntities); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities); - foreach (var user in users) - { - user.Name += " updated"; - } - connection.Update(helper(users)); - var name = connection.Query("select * from Users").First().Name; - Assert.Contains("updated", name); - } - } - - [Fact] - public void DeleteEnumerable() - { - DeleteHelper(src => src.AsEnumerable()); - } - - [Fact] - public void DeleteArray() - { - DeleteHelper(src => src.ToArray()); - } - - [Fact] - public void DeleteList() - { - DeleteHelper(src => src.ToList()); - } - - private void DeleteHelper(Func, T> helper) - where T : class - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - - var total = connection.Insert(helper(users)); - Assert.Equal(total, numberOfEntities); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities); - - var usersToDelete = users.Take(10).ToList(); - connection.Delete(helper(usersToDelete)); - users = connection.Query("select * from Users").ToList(); - Assert.Equal(users.Count, numberOfEntities - 10); - } - } - - [Fact] - public void InsertGetUpdate() - { - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - Assert.Null(connection.Get(3)); - - //insert with computed attribute that should be ignored - connection.Insert(new Car { Name = "Volvo", Computed = "this property should be ignored" }); - - var id = connection.Insert(new User { Name = "Adam", Age = 10 }); - - //get a user with "isdirty" tracking - var user = connection.Get(id); - Assert.Equal("Adam", user.Name); - Assert.False(connection.Update(user)); //returns false if not updated, based on tracking - user.Name = "Bob"; - Assert.True(connection.Update(user)); //returns true if updated, based on tracking - user = connection.Get(id); - Assert.Equal("Bob", user.Name); - - //get a user with no tracking - var notrackedUser = connection.Get(id); - Assert.Equal("Bob", notrackedUser.Name); - Assert.True(connection.Update(notrackedUser)); //returns true, even though user was not changed - notrackedUser.Name = "Cecil"; - Assert.True(connection.Update(notrackedUser)); - Assert.Equal("Cecil", connection.Get(id).Name); - - Assert.Single(connection.Query("select * from Users")); - Assert.True(connection.Delete(user)); - Assert.Empty(connection.Query("select * from Users")); - - Assert.False(connection.Update(notrackedUser)); //returns false, user not found - } - } - -#if SQLCE - [Fact(Skip = "Not parallel friendly - thinking about how to test this")] - public void InsertWithCustomDbType() - { - SqlMapperExtensions.GetDatabaseType = conn => "SQLiteConnection"; - - bool sqliteCodeCalled = false; - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - Assert.IsNull(connection.Get(3)); - try - { - connection.Insert(new User { Name = "Adam", Age = 10 }); - } - catch (SqlCeException ex) - { - sqliteCodeCalled = ex.Message.IndexOf("There was an error parsing the query", StringComparison.OrdinalIgnoreCase) >= 0; - } - // ReSharper disable once EmptyGeneralCatchClause - catch (Exception) - { - } - } - SqlMapperExtensions.GetDatabaseType = null; - - if (!sqliteCodeCalled) - { - throw new Exception("Was expecting sqlite code to be called"); - } - } -#endif - - [Fact] - public void InsertWithCustomTableNameMapper() - { - SqlMapperExtensions.TableNameMapper = type => - { - switch (type.Name) - { - case "Person": - return "People"; - default: - var tableattr = type.GetCustomAttributes(false).SingleOrDefault(attr => attr.GetType().Name == "TableAttribute") as dynamic; - if (tableattr != null) - return tableattr.Name; - - var name = type.Name + "s"; - if (type.IsInterface && name.StartsWith("I")) - return name.Substring(1); - return name; - } - }; - - using (var connection = GetOpenConnection()) - { - var id = connection.Insert(new Person { Name = "Mr Mapper" }); - Assert.Equal(1, id); - connection.GetAll(); - } - } - - [Fact] - public void GetAll() - { - const int numberOfEntities = 10; - - var users = new List(numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - users.Add(new User { Name = "User " + i, Age = i }); - - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - - var total = connection.Insert(users); - Assert.Equal(total, numberOfEntities); - users = connection.GetAll().ToList(); - Assert.Equal(users.Count, numberOfEntities); - var iusers = connection.GetAll().ToList(); - Assert.Equal(iusers.Count, numberOfEntities); - for (var i = 0; i < numberOfEntities; i++) - Assert.Equal(iusers[i].Age, i); - } - } - - /// - /// Test for issue #933 - /// - [Fact] - public void GetAndGetAllWithNullableValues() - { - using (var connection = GetOpenConnection()) - { - var id1 = connection.Insert(new NullableDate { DateValue = new DateTime(2011, 07, 14) }); - var id2 = connection.Insert(new NullableDate { DateValue = null }); - - var value1 = connection.Get(id1); - Assert.Equal(new DateTime(2011, 07, 14), value1.DateValue.Value); - - var value2 = connection.Get(id2); - Assert.True(value2.DateValue == null); - - var value3 = connection.GetAll().ToList(); - Assert.Equal(new DateTime(2011, 07, 14), value3[0].DateValue.Value); - Assert.True(value3[1].DateValue == null); - } - } - - [Fact] - public void Transactions() - { - using (var connection = GetOpenConnection()) - { - var id = connection.Insert(new Car { Name = "one car" }); //insert outside transaction - - var tran = connection.BeginTransaction(); - var car = connection.Get(id, tran); - var orgName = car.Name; - car.Name = "Another car"; - connection.Update(car, tran); - tran.Rollback(); - - car = connection.Get(id); //updates should have been rolled back - Assert.Equal(car.Name, orgName); - } - } -#if TRANSCOPE - [Fact] - public void TransactionScope() - { - using (var txscope = new TransactionScope()) - { - using (var connection = GetOpenConnection()) - { - var id = connection.Insert(new Car { Name = "one car" }); //inser car within transaction - - txscope.Dispose(); //rollback - - Assert.Null(connection.Get(id)); //returns null - car with that id should not exist - } - } - } -#endif - - [Fact] - public void InsertCheckKey() - { - using (var connection = GetOpenConnection()) - { - Assert.Null(connection.Get(3)); - User user = new User { Name = "Adamb", Age = 10 }; - int id = (int)connection.Insert(user); - Assert.Equal(user.Id, id); - } - } - - [Fact] - public void BuilderSelectClause() - { - using (var connection = GetOpenConnection()) - { - var rand = new Random(8675309); - var data = new List(100); - for (int i = 0; i < 100; i++) - { - var nU = new User { Age = rand.Next(70), Id = i, Name = Guid.NewGuid().ToString() }; - data.Add(nU); - nU.Id = (int)connection.Insert(nU); - } - - var builder = new SqlBuilder(); - var justId = builder.AddTemplate("SELECT /**select**/ FROM Users"); - var all = builder.AddTemplate("SELECT Name, /**select**/, Age FROM Users"); - - builder.Select("Id"); - - var ids = connection.Query(justId.RawSql, justId.Parameters); - var users = connection.Query(all.RawSql, all.Parameters); - - foreach (var u in data) - { - if (!ids.Any(i => u.Id == i)) throw new Exception("Missing ids in select"); - if (!users.Any(a => a.Id == u.Id && a.Name == u.Name && a.Age == u.Age)) throw new Exception("Missing users in select"); - } - } - } - - [Fact] - public void BuilderTemplateWithoutComposition() - { - var builder = new SqlBuilder(); - var template = builder.AddTemplate("SELECT COUNT(*) FROM Users WHERE Age = @age", new { age = 5 }); - - if (template.RawSql == null) throw new Exception("RawSql null"); - if (template.Parameters == null) throw new Exception("Parameters null"); - - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - connection.Insert(new User { Age = 5, Name = "Testy McTestington" }); - - if (connection.Query(template.RawSql, template.Parameters).Single() != 1) - throw new Exception("Query failed"); - } - } - - [Fact] - public void InsertFieldWithReservedName() - { - using (var connection = GetOpenConnection()) - { - connection.DeleteAll(); - var id = connection.Insert(new Result() { Name = "Adam", Order = 1 }); - - var result = connection.Get(id); - Assert.Equal(1, result.Order); - } - } - - [Fact] - public void DeleteAll() - { - using (var connection = GetOpenConnection()) - { - var id1 = connection.Insert(new User { Name = "Alice", Age = 32 }); - var id2 = connection.Insert(new User { Name = "Bob", Age = 33 }); - Assert.True(connection.DeleteAll()); - Assert.Null(connection.Get(id1)); - Assert.Null(connection.Get(id2)); - } - } - } -} diff --git a/tests/Dapper.Tests.Contrib/TestSuites.cs b/tests/Dapper.Tests.Contrib/TestSuites.cs deleted file mode 100644 index 55d4ef5cf..000000000 --- a/tests/Dapper.Tests.Contrib/TestSuites.cs +++ /dev/null @@ -1,178 +0,0 @@ -using Microsoft.Data.Sqlite; -using MySqlConnector; -using System; -using System.Data; -using System.Data.SqlClient; -using System.IO; -using Xunit; -using Xunit.Sdk; - -namespace Dapper.Tests.Contrib -{ - // The test suites here implement TestSuiteBase so that each provider runs - // the entire set of tests without declarations per method - // If we want to support a new provider, they need only be added here - not in multiple places - - [XunitTestCaseDiscoverer("Dapper.Tests.SkippableFactDiscoverer", "Dapper.Tests.Contrib")] - [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] - public class SkippableFactAttribute : FactAttribute - { - } - - public class SqlServerTestSuite : TestSuite - { - private const string DbName = "tempdb"; - public static string ConnectionString => - GetConnectionString("SqlServerConnectionString", $"Data Source=.;Initial Catalog={DbName};Integrated Security=True"); - - public override IDbConnection GetConnection() => new SqlConnection(ConnectionString); - - static SqlServerTestSuite() - { - using (var connection = new SqlConnection(ConnectionString)) - { - // ReSharper disable once AccessToDisposedClosure - void dropTable(string name) => connection.Execute($"IF OBJECT_ID('{name}', 'U') IS NOT NULL DROP TABLE [{name}]; "); - connection.Open(); - dropTable("Stuff"); - connection.Execute("CREATE TABLE Stuff (TheId int IDENTITY(1,1) not null, Name nvarchar(100) not null, Created DateTime null);"); - dropTable("People"); - connection.Execute("CREATE TABLE People (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null);"); - dropTable("Users"); - connection.Execute("CREATE TABLE Users (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, Age int not null);"); - dropTable("Automobiles"); - connection.Execute("CREATE TABLE Automobiles (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null);"); - dropTable("Results"); - connection.Execute("CREATE TABLE Results (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, [Order] int not null);"); - dropTable("ObjectX"); - connection.Execute("CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null);"); - dropTable("ObjectY"); - connection.Execute("CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null);"); - dropTable("ObjectZ"); - connection.Execute("CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); - dropTable("GenericType"); - connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null);"); - dropTable("NullableDates"); - connection.Execute("CREATE TABLE NullableDates (Id int IDENTITY(1,1) not null, DateValue DateTime null);"); - } - } - } - - public class MySqlServerTestSuite : TestSuite - { - public static string ConnectionString { get; } = - GetConnectionString("MySqlConnectionString", "Server=localhost;Database=tests;Uid=test;Pwd=pass;UseAffectedRows=false;"); - - public override IDbConnection GetConnection() - { - if (_skip) Skip.Inconclusive("Skipping MySQL Tests - no server."); - return new MySqlConnection(ConnectionString); - } - - private static readonly bool _skip; - - static MySqlServerTestSuite() - { - try - { - using (var connection = new MySqlConnection(ConnectionString)) - { - // ReSharper disable once AccessToDisposedClosure - void dropTable(string name) => connection.Execute($"DROP TABLE IF EXISTS `{name}`;"); - connection.Open(); - dropTable("Stuff"); - connection.Execute("CREATE TABLE Stuff (TheId int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null, Created DateTime null);"); - dropTable("People"); - connection.Execute("CREATE TABLE People (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null);"); - dropTable("Users"); - connection.Execute("CREATE TABLE Users (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null, Age int not null);"); - dropTable("Automobiles"); - connection.Execute("CREATE TABLE Automobiles (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null);"); - dropTable("Results"); - connection.Execute("CREATE TABLE Results (Id int not null AUTO_INCREMENT PRIMARY KEY, Name nvarchar(100) not null, `Order` int not null);"); - dropTable("ObjectX"); - connection.Execute("CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null);"); - dropTable("ObjectY"); - connection.Execute("CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null);"); - dropTable("ObjectZ"); - connection.Execute("CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null);"); - dropTable("GenericType"); - connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null);"); - dropTable("NullableDates"); - connection.Execute("CREATE TABLE NullableDates (Id int not null AUTO_INCREMENT PRIMARY KEY, DateValue DateTime);"); - } - } - catch (MySqlException e) - { - if (e.Message.Contains("Unable to connect")) - _skip = true; - else - throw; - } - } - } - - public class SQLiteTestSuite : TestSuite - { - private const string FileName = "Test.DB.sqlite"; - public static string ConnectionString => $"Filename=./{FileName};Mode=ReadWriteCreate;"; - public override IDbConnection GetConnection() => new SqliteConnection(ConnectionString); - - static SQLiteTestSuite() - { - if (File.Exists(FileName)) - { - File.Delete(FileName); - } - using (var connection = new SqliteConnection(ConnectionString)) - { - connection.Open(); - connection.Execute("CREATE TABLE Stuff (TheId integer primary key autoincrement not null, Name nvarchar(100) not null, Created DateTime null) "); - connection.Execute("CREATE TABLE People (Id integer primary key autoincrement not null, Name nvarchar(100) not null) "); - connection.Execute("CREATE TABLE Users (Id integer primary key autoincrement not null, Name nvarchar(100) not null, Age int not null) "); - connection.Execute("CREATE TABLE Automobiles (Id integer primary key autoincrement not null, Name nvarchar(100) not null) "); - connection.Execute("CREATE TABLE Results (Id integer primary key autoincrement not null, Name nvarchar(100) not null, [Order] int not null) "); - connection.Execute("CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null) "); - connection.Execute("CREATE TABLE ObjectY (ObjectYId integer not null, Name nvarchar(100) not null) "); - connection.Execute("CREATE TABLE ObjectZ (Id integer not null, Name nvarchar(100) not null) "); - connection.Execute("CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null) "); - connection.Execute("CREATE TABLE NullableDates (Id integer primary key autoincrement not null, DateValue DateTime) "); - } - } - } - - -#if SQLCE - public class SqlCETestSuite : TestSuite - { - const string FileName = "Test.DB.sdf"; - public static string ConnectionString => $"Data Source={FileName};"; - public override IDbConnection GetConnection() => new SqlCeConnection(ConnectionString); - - static SqlCETestSuite() - { - if (File.Exists(FileName)) - { - File.Delete(FileName); - } - var engine = new SqlCeEngine(ConnectionString); - engine.CreateDatabase(); - using (var connection = new SqlCeConnection(ConnectionString)) - { - connection.Open(); - connection.Execute(@"CREATE TABLE Stuff (TheId int IDENTITY(1,1) not null, Name nvarchar(100) not null, Created DateTime null) "); - connection.Execute(@"CREATE TABLE People (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null) "); - connection.Execute(@"CREATE TABLE Users (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, Age int not null) "); - connection.Execute(@"CREATE TABLE Automobiles (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null) "); - connection.Execute(@"CREATE TABLE Results (Id int IDENTITY(1,1) not null, Name nvarchar(100) not null, [Order] int not null) "); - connection.Execute(@"CREATE TABLE ObjectX (ObjectXId nvarchar(100) not null, Name nvarchar(100) not null) "); - connection.Execute(@"CREATE TABLE ObjectY (ObjectYId int not null, Name nvarchar(100) not null) "); - connection.Execute(@"CREATE TABLE ObjectZ (Id int not null, Name nvarchar(100) not null) "); - connection.Execute(@"CREATE TABLE GenericType (Id nvarchar(100) not null, Name nvarchar(100) not null) "); - connection.Execute(@"CREATE TABLE NullableDates (Id int IDENTITY(1,1) not null, DateValue DateTime null) "); - } - Console.WriteLine("Created database"); - } - } -#endif -} diff --git a/tests/Dapper.Tests.Contrib/xunit.runner.json b/tests/Dapper.Tests.Contrib/xunit.runner.json deleted file mode 100644 index f23143776..000000000 --- a/tests/Dapper.Tests.Contrib/xunit.runner.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", - "shadowCopy": false -} \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 5c3d2a119..5fc9b77e5 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -14,7 +14,6 @@ - From 52335550292b0ab018e3466328dbbefc60208db2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vedran=20Bilopavlovi=C4=87?= Date: Sun, 9 May 2021 12:04:43 +0200 Subject: [PATCH 178/312] make bechmark test description shorter --- benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs index 163821f42..4a637341b 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs @@ -15,21 +15,21 @@ public void Setup() BaseSetup(); } - [Benchmark(Description = "Read (class)")] + [Benchmark(Description = "Read<> (class)")] public Post Read() { Step(); return _connection.Read("select * from Posts where Id = @Id", i).First(); } - [Benchmark(Description = "Read (simple values in a tuple)")] + [Benchmark(Description = "Read<> (tuples)")] public (int, string, DateTime, DateTime, int?, int?, int?, int?, int?, int?, int?, int?) ReadSimpleValues() { Step(); return _connection.Read("select * from Posts where Id = @Id", i).First(); } - [Benchmark(Description = "Read<(T1, T2, ...)> (named tuple)")] + [Benchmark(Description = "Read<()> (named tuples)")] public (int Id, string Text, DateTime CreationDate, DateTime LastChangeDate, int? Counter1, int? Counter2, int? Counter3, int? Counter4, int? Counter5, int? Counter6, int? Counter7, int? Counter8) ReadTuple() { Step(); From ae65ef10d082df6ab56e3d0710a57d1584d735b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vedran=20Bilopavlovi=C4=87?= Date: Sun, 9 May 2021 12:16:01 +0200 Subject: [PATCH 179/312] Use ItemGroup Condition != 'net462' in poject #if !NET4X in the file itself --- .../Dapper.Tests.Performance/Benchmarks.Norm.cs | 5 +++-- .../Dapper.Tests.Performance.csproj | 11 +++-------- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs index 4a637341b..34fed76d8 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Norm.cs @@ -1,4 +1,5 @@ -using BenchmarkDotNet.Attributes; +#if !NET4X +using BenchmarkDotNet.Attributes; using System.ComponentModel; using System.Linq; using Norm; @@ -37,4 +38,4 @@ public Post Read() } } } - +#endif diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 08c9c1865..1e480867d 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -47,13 +47,8 @@ - - - - 3.1.0 - - - - + + 3.2.0 + From 60d5a0591943039fd056c43d0e9787a377630e71 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 06:57:02 -0500 Subject: [PATCH 180/312] Update benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj --- .../Dapper.Tests.Performance/Dapper.Tests.Performance.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 1e480867d..20dd75c55 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -47,8 +47,6 @@ - - 3.2.0 - + From f13b1e6c0d3e113f89ad8424095b4ea98acb42a6 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 08:49:48 -0400 Subject: [PATCH 181/312] Adding #952 to release notes --- docs/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.md b/docs/index.md index 28738b70b..1bafb72c0 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,6 +24,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) +- Parameters can now be re-used on subsequent commands (#952 via jamescrowley) + ### 2.0.90 - logo added; license updated to mention logo usage (via mgravell) From dffb443e017a8e8abc7f7f3312d00b54426d78f8 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 09:10:22 -0400 Subject: [PATCH 182/312] Add #1598 to release notes --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 1bafb72c0..8ca820589 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,6 +25,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) - Parameters can now be re-used on subsequent commands (#952 via jamescrowley) +- Array query support (`.Query`) on supported platforms (e.g. Postgres) (#1598 via DarkWanderer) ### 2.0.90 From 1f8e09e0f6f0f027b27dad85f64994c0ccf73386 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 09:37:13 -0400 Subject: [PATCH 183/312] Whitespace cleanup --- Dapper.Rainbow/Database.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index 0217ddf13..670f22631 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -472,7 +472,7 @@ public SqlMapper.GridReader QueryMultiple(string sql, dynamic param = null, IDbT /// Disposes the current database, rolling back current transactions. /// public virtual void Dispose() - { + { var connection = _connection; if (connection.State != ConnectionState.Closed) { From 9ad28954442847b94e9718bb6f2c70eb8a2a950e Mon Sep 17 00:00:00 2001 From: Brendan Gooden Date: Mon, 10 May 2021 06:55:58 +0930 Subject: [PATCH 184/312] XML love for SqlMapper.HasTypeHandler --- Dapper/SqlMapper.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 518441b1c..9e232d364 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -263,8 +263,8 @@ public static void RemoveTypeMap(Type type) /// /// Determine if the specified type will be processed by a custom handler. /// - /// - /// + /// The type to handle. + /// Boolean value specifying whether the type will be processed by a custom handler. public static bool HasTypeHandler(Type type) => typeHandlers.ContainsKey(type); /// From 54b1e4e7a273d4d49eca7693a54e4c23fb8a47b6 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 19:09:29 -0400 Subject: [PATCH 185/312] Add 1405 to release notes --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 8ca820589..95caa11db 100644 --- a/docs/index.md +++ b/docs/index.md @@ -26,6 +26,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command - Parameters can now be re-used on subsequent commands (#952 via jamescrowley) - Array query support (`.Query`) on supported platforms (e.g. Postgres) (#1598 via DarkWanderer) +- `SqlMapper.HasTypeHandler` is made public for consumers (#1405 via brendangooden) ### 2.0.90 From fca49ebccee6953569ecb3713798c64b4a5e3bcb Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 21:51:20 -0400 Subject: [PATCH 186/312] SplitOn: improve error message Fixes #866. This provides a more informative error, saying _which_ splitOn column couldn't be found in the result set. --- Dapper/SqlMapper.cs | 14 ++++++++------ tests/Dapper.Tests/MultiMapTests.cs | 7 +++++++ 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 858cf75f4..2edee8c6c 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1679,7 +1679,7 @@ private static int GetNextSplitDynamic(int startIdx, string splitOn, IDataReader { if (startIdx == reader.FieldCount) { - throw MultiMapException(reader); + throw MultiMapException(reader, splitOn); } if (splitOn == "*") @@ -1713,7 +1713,7 @@ private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader } } - throw MultiMapException(reader); + throw MultiMapException(reader, splitOn); } private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache) @@ -1831,16 +1831,18 @@ private static Func GetHandlerDeserializer(ITypeHandler han return reader => handler.Parse(type, reader.GetValue(startBound)); } - private static Exception MultiMapException(IDataRecord reader) + private static Exception MultiMapException(IDataRecord reader, string splitOnColumnName = null) { bool hasFields = false; try { hasFields = reader != null && reader.FieldCount != 0; } catch { /* don't throw when trying to throw */ } if (hasFields) { -#pragma warning disable CA2208 // Instantiate argument exceptions correctly - return new ArgumentException("When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id", "splitOn"); -#pragma warning restore CA2208 // Instantiate argument exceptions correctly + return new ArgumentException( + string.IsNullOrEmpty(splitOnColumnName) + ? "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id" + : $"Multi-map error: splitOn column '{splitOnColumnName}' was not found - please ensure your splitOn parameter is set and in the correct order", + "splitOn"); } else { diff --git a/tests/Dapper.Tests/MultiMapTests.cs b/tests/Dapper.Tests/MultiMapTests.cs index 9c072a340..097d95cd4 100644 --- a/tests/Dapper.Tests/MultiMapTests.cs +++ b/tests/Dapper.Tests/MultiMapTests.cs @@ -194,6 +194,13 @@ public void TestMultiMapperIsNotConfusedWithUnorderedCols() Assert.Equal("a", result.Item2.Name); } + [Fact] + public void TestMultiMapperSplitOnError() + { + var ex = Assert.Throws(() => connection.Query>("select 1 as Id, 2 as BarId", Tuple.Create, splitOn: "DoesntExist").First()); + Assert.StartsWith("Multi-map error: splitOn column 'DoesntExist' was not found - please ensure your splitOn parameter is set and in the correct order", ex.Message); + } + [Fact] public void TestMultiMapDynamic() { From 6af6252eb610e61e08275522b13f3ed9954a37cb Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 21:52:43 -0400 Subject: [PATCH 187/312] Add release notes --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 95caa11db..bb1c5509e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,6 +27,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command - Parameters can now be re-used on subsequent commands (#952 via jamescrowley) - Array query support (`.Query`) on supported platforms (e.g. Postgres) (#1598 via DarkWanderer) - `SqlMapper.HasTypeHandler` is made public for consumers (#1405 via brendangooden) +- Improves multi-mapping error message when a specified column in splitOn can't be found (#1664 by NickCraver) ### 2.0.90 From 566d169a2687893e17beb43a109ae9c740fc2b53 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 22:36:23 -0400 Subject: [PATCH 188/312] Add DbString.ToString() overload Fixes #1538 and #1545 by making DbString.ToString() much more descriptive. --- Dapper/DbString.cs | 7 +++++++ tests/Dapper.Tests/MiscTests.cs | 18 ++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/Dapper/DbString.cs b/Dapper/DbString.cs index 58a7b80c9..320495aac 100644 --- a/Dapper/DbString.cs +++ b/Dapper/DbString.cs @@ -44,6 +44,13 @@ public DbString() /// The value of the string /// public string Value { get; set; } + + /// + /// Gets a string representation of this DbString. + /// + public override string ToString() => + $"Dapper.DbString (Value: '{Value}', Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})"; + /// /// Add the parameter to the command... internal use only /// diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index ae6eb31a6..4d5dd26c4 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -655,6 +655,24 @@ public void TestDbString() Assert.Equal(10, (int)obj.f); } + [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: '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() { From b275d2465289cb6f08e9ff194fb087f428f7647b Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 22:41:16 -0400 Subject: [PATCH 189/312] Release notes --- docs/index.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index bb1c5509e..46fd553b8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,7 +27,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command - Parameters can now be re-used on subsequent commands (#952 via jamescrowley) - Array query support (`.Query`) on supported platforms (e.g. Postgres) (#1598 via DarkWanderer) - `SqlMapper.HasTypeHandler` is made public for consumers (#1405 via brendangooden) -- Improves multi-mapping error message when a specified column in splitOn can't be found (#1664 by NickCraver) +- Improves multi-mapping error message when a specified column in splitOn can't be found (#1664 via NickCraver) +- Improves DbString.ToString() (#1665 via NickCraver) ### 2.0.90 From 600c1aca453942fe2076ee3f0abed23dac9a5997 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sun, 9 May 2021 23:10:31 -0400 Subject: [PATCH 190/312] Add test cases and cleanup last call site. This merges in the work in #1663 and cleans up the convert a bit further. --- Dapper/SqlMapper.GridReader.Async.cs | 2 +- Dapper/SqlMapper.GridReader.cs | 16 ++++++++-------- tests/Dapper.Tests/AsyncTests.cs | 10 ++++++++++ tests/Dapper.Tests/QueryMultipleTests.cs | 10 ++++++++++ 4 files changed, 29 insertions(+), 9 deletions(-) diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index d227ee63c..bec4967be 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -233,7 +233,7 @@ private async Task> ReadBufferedAsync(int index, Func(); while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) { - buffer.Add((T)deserializer(reader)); + buffer.Add(ConvertTo(deserializer(reader))); } return buffer; } diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index 3dcf4252b..15311cf4d 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -3,6 +3,8 @@ using System.Data; using System.Linq; using System.Globalization; +using System.Runtime.CompilerServices; + namespace Dapper { public static partial class SqlMapper @@ -418,15 +420,13 @@ public void Dispose() GC.SuppressFinalize(this); } - private static T ConvertTo(object value) + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static T ConvertTo(object value) => value switch { - if (value is null or DBNull) - return default; - else if (value is T t) - return t; - else - return (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture); - } + T typed => typed, + null or DBNull => default, + _ => (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture), + }; } } } diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index 34990cb40..5d24512be 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -240,6 +240,16 @@ public async Task TestMultiAsync() } } + [Fact] + public async Task TestMultiConversionAsync() + { + using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2").ConfigureAwait(false)) + { + Assert.Equal(1, multi.ReadAsync().Result.Single()); + Assert.Equal(2, multi.ReadAsync().Result.Single()); + } + } + [Fact] public async Task TestMultiAsyncViaFirstOrDefault() { diff --git a/tests/Dapper.Tests/QueryMultipleTests.cs b/tests/Dapper.Tests/QueryMultipleTests.cs index 857525dcf..47b304569 100644 --- a/tests/Dapper.Tests/QueryMultipleTests.cs +++ b/tests/Dapper.Tests/QueryMultipleTests.cs @@ -31,6 +31,16 @@ public void TestQueryMultipleBuffered() } } + [Fact] + public void TestMultiConversion() + { + using (SqlMapper.GridReader multi = connection.QueryMultiple("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2")) + { + Assert.Equal(1, multi.Read().Single()); + Assert.Equal(2, multi.Read().Single()); + } + } + [Fact] public void TestQueryMultipleNonBufferedIncorrectOrder() { From 069df56ec181f76fabbad91e9e76dfe14e82d970 Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Mon, 10 May 2021 17:50:16 +0600 Subject: [PATCH 191/312] Update version of SqlMarshal to fix warnings This address feedback in #1646 --- .../Dapper.Tests.Performance/Dapper.Tests.Performance.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index ff3615507..b34e486fd 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -24,7 +24,7 @@ - + From 31981fdf518c4765d21ca3cacab4cefda4644a41 Mon Sep 17 00:00:00 2001 From: Wei Date: Sun, 4 Jul 2021 20:12:52 +0800 Subject: [PATCH 192/312] Add Documentation for Dapper.SqlBuilder (Issue #688, #480) (#1679) Add Documentation for Dapper.SqlBuilder (Issue #688, #480) --- Dapper.SqlBuilder/Readme.md | 107 ++++++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 Dapper.SqlBuilder/Readme.md diff --git a/Dapper.SqlBuilder/Readme.md b/Dapper.SqlBuilder/Readme.md new file mode 100644 index 000000000..ea69d9b80 --- /dev/null +++ b/Dapper.SqlBuilder/Readme.md @@ -0,0 +1,107 @@ +Dapper.SqlBuilder - a simple sql formatter for .Net +======================================== +[![Build status](https://ci.appveyor.com/api/projects/status/1w448i6nfxd14w75?svg=true)](https://ci.appveyor.com/project/StackExchange/dapper-SqlBuilder) + +Packages +-------- + +MyGet Pre-release feed: https://www.myget.org/gallery/dapper + +| Package | NuGet Stable | NuGet Pre-release | Downloads | MyGet | +| ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | ------------------------------------------------------------ | +| [Dapper.SqlBuilder](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/v/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/vpre/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/dt/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.SqlBuilder.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.SqlBuilder) | + +Features +-------- + +Dapper.SqlBuilder contains a number of helper methods for generating sql. + +The list of extension methods in Dapper.SqlBuilder right now are: + +```csharp +SqlBuilder AddParameters(dynamic parameters); +SqlBuilder Select(string sql, dynamic parameters = null); +SqlBuilder Where(string sql, dynamic parameters = null); +SqlBuilder OrWhere(string sql, dynamic parameters = null); +SqlBuilder OrderBy(string sql, dynamic parameters = null); +SqlBuilder GroupBy(string sql, dynamic parameters = null); +SqlBuilder Having(string sql, dynamic parameters = null); +SqlBuilder Set(string sql, dynamic parameters = null); +SqlBuilder Join(string sql, dynamic parameters = null); +SqlBuilder InnerJoin(string sql, dynamic parameters = null); +SqlBuilder LeftJoin(string sql, dynamic parameters = null); +SqlBuilder RightJoin(string sql, dynamic parameters = null); +SqlBuilder Intersect(string sql, dynamic parameters = null); +``` + + +Template +-------- + +SqlBuilder allows you to generate N SQL templates from a composed query, it can easily format sql when you are attaching parameters and how, e.g: +```csharp +var builder = new SqlBuilder() + .Where("a = @a", new { a = 1 }) + .Where("b = @b", new { b = 2 }) + .OrderBy("a") + .OrderBy("b"); +var counter = builder.AddTemplate("select count(*) from table /**where**/"); +var selector = builder.AddTemplate("select * from table /**where**/ /**orderby**/"); +var count = cnn.Query(counter.RawSql, counter.Parameters).Single(); +var rows = cnn.Query(selector.RawSql, selector.Parameters); +``` + +it's same as +```csharp +var count = cnn.Query("select count(*) from table where a = @a and b = @b", new { a = 1, b = 1 }); +var rows = cnn.Query("select * from table where a = @a and b = @b order by a, b", new { a = 1, b = 1 }); +``` + +Dynamic Filter Paging Example +---------- + +```csharp +var builder = new SqlBuilder(); +var selectTemplate = builder.AddTemplate(@"select X.* from ( + select us.*, ROW_NUMBER() OVER (/**orderby**/) AS RowNumber + from Users us + /**where**/ + ) as X + where RowNumber between @start and @finish", new { start, finish }); +var countTemplate = builder.AddTemplate(@"select count(*) from Users /**where**/"); + +if (userId.HasValue()) + builder.Where($"t.userId = @{nameof(userId)}", new { userId }); +if (isCancel) + builder.Where($"t.isCancel = @{nameof(isCancel)}", new { isCancel }); + +builder.OrderBy(string.Format("t.id {0}", orderDesc ? "desc" : "asc")); + +var users = conn.Query(selectTemplate.RawSql, selectTemplate.Parameters); +var count = conn.ExecuteScalar(countTemplate.RawSql, countTemplate.Parameters); +//..etc.. +``` + +Limitations and caveats +-------- + +OrWhere use `and` not `or` to concat sql problem + +[Issue 647](https://github.com/DapperLib/Dapper/issues/647) + +```csharp +sql.Where("a = @a1"); +sql.OrWhere("b = @b1"); +sql.Where("a = @a2"); +sql.OrWhere("b = @b2"); +``` + +SqlBuilder will generate sql +```sql= +a = @a1 AND b = @b1 AND a = @a2 AND b = @b2 +``` + +not +```sql +a = @a1 OR b = @b1 AND a = @a2 OR b = @b2 +``` From c62611a4f259a920fd78c123f2c3a14f204021ba Mon Sep 17 00:00:00 2001 From: Sviataslau Hankovich Date: Tue, 27 Jul 2021 11:11:04 +0300 Subject: [PATCH 193/312] Fix signatures in readme (#1683) --- Readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index d5bb250dc..e44f89895 100644 --- a/Readme.md +++ b/Readme.md @@ -30,7 +30,7 @@ Execute a query and map the results to a strongly typed List ------------------------------------------------------------ ```csharp -public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true) +public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) ``` Example usage: @@ -57,7 +57,7 @@ Execute a query and map it to a list of dynamic objects ------------------------------------------------------- ```csharp -public static IEnumerable Query (this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null, bool buffered = true) +public static IEnumerable Query (this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) ``` This method will execute SQL and return a dynamic list. @@ -76,7 +76,7 @@ Execute a Command that returns no results ----------------------------------------- ```csharp -public static int Execute(this IDbConnection cnn, string sql, object param = null, SqlTransaction transaction = null) +public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) ``` Example usage: From 4aac41a2bc160828d7bd2f1c9cfea8e31a1db99e Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 27 Jul 2021 09:11:16 +0100 Subject: [PATCH 194/312] Enables "snowflake" connection (#1689) * snowflake tests #1687 * actually add the tests! --- Dapper/SqlMapper.Settings.cs | 6 ++ Dapper/SqlMapper.cs | 6 ++ tests/Dapper.Tests/Dapper.Tests.csproj | 3 +- .../Dapper.Tests/Providers/SnowflakeTests.cs | 86 +++++++++++++++++++ 4 files changed, 99 insertions(+), 2 deletions(-) create mode 100644 tests/Dapper.Tests/Providers/SnowflakeTests.cs diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index d425d44d5..cc71238b2 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -93,6 +93,12 @@ public static void SetDefaults() /// operation if there are this many elements or more. Note that this feature requires SQL Server 2016 / compatibility level 130 (or above). /// public static int InListStringSplitCount { get; set; } = -1; + + /// + /// If set, pseudo-positional parameters (i.e. ?foo?) are passed using auto-generated incremental names, i.e. "1", "2", "3" + /// instead of the original name; for most scenarios, this is ignored since the name is redundant, but "snowflake" requires this. + /// + public static bool UseIncrementalPseudoPositionalParameterNames { get; set; } } } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 2edee8c6c..00add11e2 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1778,6 +1778,8 @@ private static void PassByPosition(IDbCommand cmd) } HashSet consumed = new HashSet(StringComparer.Ordinal); bool firstMatch = true; + int index = 0; // use this to spoof names; in most pseudo-positional cases, the name is ignored, however: + // for "snowflake", the name needs to be incremental i.e. "1", "2", "3" cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match => { string key = match.Groups[1].Value; @@ -1793,6 +1795,10 @@ private static void PassByPosition(IDbCommand cmd) cmd.Parameters.Clear(); // only clear if we are pretty positive that we've found this pattern successfully } // if found, return the anonymous token "?" + if (Settings.UseIncrementalPseudoPositionalParameterNames) + { + param.ParameterName = (++index).ToString(); + } cmd.Parameters.Add(param); parameters.Remove(key); consumed.Add(key); diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 5f74e4b84..f8402fcaf 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -20,10 +20,9 @@ + - - diff --git a/tests/Dapper.Tests/Providers/SnowflakeTests.cs b/tests/Dapper.Tests/Providers/SnowflakeTests.cs new file mode 100644 index 000000000..0be413670 --- /dev/null +++ b/tests/Dapper.Tests/Providers/SnowflakeTests.cs @@ -0,0 +1,86 @@ +#if !NET462 // platform not supported exception +using System; +using System.Collections.Generic; +using System.IO; +using Snowflake.Data.Client; +using Xunit; +using Xunit.Abstractions; + +namespace Dapper.Tests +{ + public class SnowflakeTests + { + static readonly string s_ConnectionString; + static SnowflakeTests() + { + SqlMapper.Settings.UseIncrementalPseudoPositionalParameterNames = true; + + try + { // this *probably* won't exist (TODO: can we get a test account?) + s_ConnectionString = File.ReadAllText(@"c:\Code\SnowflakeConnectionString.txt").Trim(); + } catch { } + } + + public SnowflakeTests(ITestOutputHelper output) + => Output = output; + + private ITestOutputHelper Output { get; } + + + private static SnowflakeDbConnection GetConnection() + { + if (string.IsNullOrWhiteSpace(s_ConnectionString)) + Skip.Inconclusive("no snowflake connection-string"); + + return new SnowflakeDbConnection + { + ConnectionString = s_ConnectionString + }; + } + + [Fact] + public void Connect() + { + using var connection = GetConnection(); + connection.Open(); + } + + + [Fact] + public void BasicQuery() + { + + using var connection = GetConnection(); + var nations = connection.Query(@"SELECT * FROM NATION").AsList(); + Assert.NotEmpty(nations); + Output.WriteLine($"nations: {nations.Count}"); + foreach (var nation in nations) + { + Output.WriteLine($"{nation.N_NATIONKEY}: {nation.N_NAME} (region: {nation.N_REGIONKEY}), {nation.N_COMMENT}"); + } + } + + [Fact] + public void ParameterizedQuery() + { + using var connection = GetConnection(); + const int region = 1; + var nations = connection.Query(@"SELECT * FROM NATION WHERE N_REGIONKEY=?region?", new { region }).AsList(); + Assert.NotEmpty(nations); + Output.WriteLine($"nations: {nations.Count}"); + foreach (var nation in nations) + { + Output.WriteLine($"{nation.N_NATIONKEY}: {nation.N_NAME} (region: {nation.N_REGIONKEY}), {nation.N_COMMENT}"); + } + } + + public class Nation + { + public int N_NATIONKEY { get; set; } + public string N_NAME{ get; set; } + public int N_REGIONKEY { get; set; } + public string N_COMMENT { get; set; } + } + } +} +#endif From 1235a38e1ba8cb30067080a62ed1b3355bb181a7 Mon Sep 17 00:00:00 2001 From: Pure Krome Date: Tue, 27 Jul 2021 18:11:38 +1000 Subject: [PATCH 195/312] Another example: Execute a command Multiple Times (#1690) Another example for "Execute a command Multiple Times" when we have an existing collection. The existing example (which still remains) I found to be a little bit confusing when I had an existing collection. So hopefully this 2nd example will help a tiny bit. --- Readme.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/Readme.md b/Readme.md index e44f89895..16988e242 100644 --- a/Readme.md +++ b/Readme.md @@ -106,6 +106,20 @@ var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)", ); Assert.Equal(3, count); // 3 rows inserted: "1,1", "2,2" and "3,3" ``` + +Another example usage when you _already_ have an existing collection: +```csharp +var foos = new List +{ + { new Foo { A = 1, B = 1 } } + { new Foo { A = 2, B = 2 } } + { new Foo { A = 3, B = 3 } } +}; + +var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)", foos); +Assert.Equal(foos.Count, count); +``` + This works for any parameter that implements IEnumerable for some T. Performance From a4b1c56a152dec8bf2de8f4d0a84e60626938555 Mon Sep 17 00:00:00 2001 From: derickstinson <59659958+derickstinson@users.noreply.github.com> Date: Tue, 27 Jul 2021 04:13:00 -0400 Subject: [PATCH 196/312] Update SqlMapper.cs (#1675) Fix verbiage for return type documentation comments. --- Dapper/SqlMapper.cs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 00add11e2..5ace2cbe9 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -740,7 +740,7 @@ public static IEnumerable Query(this IDbConnection cnn, string sql, object /// The command timeout (in seconds). /// The type of command to execute. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -760,7 +760,7 @@ public static T QueryFirst(this IDbConnection cnn, string sql, object param = /// The command timeout (in seconds). /// The type of command to execute. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -780,7 +780,7 @@ public static T QueryFirstOrDefault(this IDbConnection cnn, string sql, objec /// The command timeout (in seconds). /// The type of command to execute. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -800,7 +800,7 @@ public static T QuerySingle(this IDbConnection cnn, string sql, object param /// The command timeout (in seconds). /// The type of command to execute. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -822,7 +822,7 @@ public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, obje /// The type of command to execute. /// is null. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static IEnumerable Query(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) @@ -845,7 +845,7 @@ public static IEnumerable Query(this IDbConnection cnn, Type type, strin /// The type of command to execute. /// is null. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static object QueryFirst(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -867,7 +867,7 @@ public static object QueryFirst(this IDbConnection cnn, Type type, string sql, o /// The type of command to execute. /// is null. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static object QueryFirstOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -889,7 +889,7 @@ public static object QueryFirstOrDefault(this IDbConnection cnn, Type type, stri /// The type of command to execute. /// is null. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static object QuerySingle(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -911,7 +911,7 @@ public static object QuerySingle(this IDbConnection cnn, Type type, string sql, /// The type of command to execute. /// is null. /// - /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static object QuerySingleOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) @@ -928,7 +928,7 @@ public static object QuerySingleOrDefault(this IDbConnection cnn, Type type, str /// The connection to query on. /// The command used to query on this connection. /// - /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static IEnumerable Query(this IDbConnection cnn, CommandDefinition command) @@ -944,7 +944,7 @@ public static IEnumerable Query(this IDbConnection cnn, CommandDefinition /// The connection to query on. /// The command used to query on this connection. /// - /// A single instance or null of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A single instance or null of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QueryFirst(this IDbConnection cnn, CommandDefinition command) => @@ -957,7 +957,7 @@ public static T QueryFirst(this IDbConnection cnn, CommandDefinition command) /// The connection to query on. /// The command used to query on this connection. /// - /// A single or null instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A single or null instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition command) => @@ -970,7 +970,7 @@ public static T QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition /// The connection to query on. /// The command used to query on this connection. /// - /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QuerySingle(this IDbConnection cnn, CommandDefinition command) => @@ -983,7 +983,7 @@ public static T QuerySingle(this IDbConnection cnn, CommandDefinition command /// The connection to query on. /// The command used to query on this connection. /// - /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static T QuerySingleOrDefault(this IDbConnection cnn, CommandDefinition command) => From 80719d00f397e7d368fb83da305bb79f3d9eb9f0 Mon Sep 17 00:00:00 2001 From: Sergei Khlebnikov Date: Sun, 10 Oct 2021 16:29:55 +0300 Subject: [PATCH 197/312] Added more use cases RepoDB benchmarks (#1710) --- .../Benchmarks.RepoDB.cs | 36 ++++++++++++++++--- .../Dapper.Tests.Performance.csproj | 4 +-- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs index 58f2d1e75..cb8a62f7c 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs @@ -1,6 +1,6 @@ -using BenchmarkDotNet.Attributes; -using System.ComponentModel; +using System.ComponentModel; using System.Linq; +using BenchmarkDotNet.Attributes; using RepoDb; namespace Dapper.Tests.Performance @@ -16,11 +16,39 @@ public void Setup() ClassMapper.Add("Posts"); } - [Benchmark(Description = "Query")] + [Benchmark(Description = "Query")] public Post Query() { Step(); - return _connection.Query(i).FirstOrDefault(); + return _connection.Query(i).First(); + } + + [Benchmark(Description = "QueryWhere")] + public Post QueryWhere() + { + Step(); + return _connection.Query(x => x.Id == i).First(); + } + + [Benchmark(Description = "QueryDynamic")] + public Post QueryDynamic() + { + Step(); + return _connection.Query(new { Id = i }).First(); + } + + [Benchmark(Description = "QueryField")] + public Post QueryField() + { + Step(); + return _connection.Query(new QueryField[] { new(nameof(Post.Id), i) }).First(); + } + + [Benchmark(Description = "ExecuteQuery")] + public Post ExecuteQuery() + { + Step(); + return _connection.ExecuteQuery("select * from Posts where Id = @Id", new { Id = i }).First(); } } } diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index b34e486fd..914104e50 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -22,10 +22,10 @@ - + - + From b272cc664d933b4b65703d26a79272d549576dff Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 3 Nov 2021 14:01:15 +0000 Subject: [PATCH 198/312] Don't specify dbtype for DateTime et al; fixed npgsql 6 issue (#1723) * context: #1716 https://github.com/DapperLib/Dapper/issues/1716 - change type-map to allow nullable - change DateTime/TimeSpan to null - do not specify DbType for null - semi-breaking change: swap GetDbType to SetDbType, noting that it is marked [Obsolete] and internal-use-only - semi-breaking change: make LookupDbType return nullable (same internal-use/[Obsolete]) * add test for Npgsql 6.0.0-rc.1 * try list parameters (like arrays) * never assign -1 (sentinel) as the .DbType * postgres tests --- Dapper/DynamicParameters.cs | 20 +++---- Dapper/SqlMapper.cs | 56 +++++++++++-------- nuget.config | 1 + tests/Dapper.Tests/Dapper.Tests.csproj | 2 +- .../Dapper.Tests/Providers/PostgresqlTests.cs | 30 +++++++++- 5 files changed, 73 insertions(+), 36 deletions(-) diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index dc4f5a36e..2569a9383 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -155,6 +155,12 @@ void SqlMapper.IDynamicParameters.AddParameters(IDbCommand command, SqlMapper.Id /// public bool RemoveUnused { get; set; } + internal static bool ShouldSetDbType(DbType? dbType) + => dbType.HasValue && dbType.GetValueOrDefault() != EnumerableMultiParameter; + + internal static bool ShouldSetDbType(DbType dbType) + => dbType != EnumerableMultiParameter; // just in case called with non-nullable + /// /// Add all the parameters needed to the command just before it executes /// @@ -262,9 +268,9 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) #pragma warning disable 0618 p.Value = SqlMapper.SanitizeParameterValue(val); #pragma warning restore 0618 - if (dbType != null && p.DbType != dbType) + if (ShouldSetDbType(dbType) && p.DbType != dbType.GetValueOrDefault()) { - p.DbType = dbType.Value; + p.DbType = dbType.GetValueOrDefault(); } var s = val as string; if (s?.Length <= DbString.DefaultLength) @@ -277,7 +283,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) } else { - if (dbType != null) p.DbType = dbType.Value; + if (ShouldSetDbType(dbType)) p.DbType = dbType.GetValueOrDefault(); if (param.Size != null) p.Size = param.Size.Value; if (param.Precision != null) p.Precision = param.Precision.Value; if (param.Scale != null) p.Scale = param.Scale.Value; @@ -468,15 +474,9 @@ static void ThrowInvalidChain() } else { - dbType = (!dbType.HasValue) -#pragma warning disable 618 - ? SqlMapper.LookupDbType(targetMemberType, targetMemberType?.Name, true, out SqlMapper.ITypeHandler handler) -#pragma warning restore 618 - : dbType; - // CameFromTemplate property would not apply here because this new param // Still needs to be added to the command - Add(dynamicParamName, expression.Compile().Invoke(target), null, ParameterDirection.InputOutput, sizeToSet); + Add(dynamicParamName, expression.Compile().Invoke(target), dbType, ParameterDirection.InputOutput, sizeToSet); } parameter = parameters[dynamicParamName]; diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 5ace2cbe9..ee49c2abd 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -163,11 +163,11 @@ where pair.Value > 1 select Tuple.Create(pair.Key, pair.Value); } - private static Dictionary typeMap; + private static Dictionary typeMap; static SqlMapper() { - typeMap = new Dictionary(37) + typeMap = new Dictionary(37) { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, @@ -184,9 +184,9 @@ static SqlMapper() [typeof(string)] = DbType.String, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, - [typeof(DateTime)] = DbType.DateTime, + [typeof(DateTime)] = null, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, - [typeof(TimeSpan)] = DbType.Time, + [typeof(TimeSpan)] = null, [typeof(byte[])] = DbType.Binary, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, @@ -202,9 +202,9 @@ static SqlMapper() [typeof(bool?)] = DbType.Boolean, [typeof(char?)] = DbType.StringFixedLength, [typeof(Guid?)] = DbType.Guid, - [typeof(DateTime?)] = DbType.DateTime, + [typeof(DateTime?)] = null, [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, - [typeof(TimeSpan?)] = DbType.Time, + [typeof(TimeSpan?)] = null, [typeof(object)] = DbType.Object }; ResetTypeHandlers(false); @@ -234,9 +234,9 @@ public static void AddTypeMap(Type type, DbType dbType) // use clone, mutate, replace to avoid threading issues var snapshot = typeMap; - if (snapshot.TryGetValue(type, out DbType oldValue) && oldValue == dbType) return; // nothing to do + if (snapshot.TryGetValue(type, out var oldValue) && oldValue == dbType) return; // nothing to do - typeMap = new Dictionary(snapshot) { [type] = dbType }; + typeMap = new Dictionary(snapshot) { [type] = dbType }; } /// @@ -250,7 +250,7 @@ public static void RemoveTypeMap(Type type) if (!snapshot.ContainsKey(type)) return; // nothing to do - var newCopy = new Dictionary(snapshot); + var newCopy = new Dictionary(snapshot); newCopy.Remove(type); typeMap = newCopy; @@ -336,15 +336,20 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon /// /// Get the DbType that maps to a given value. /// + /// The parameter to configure the value for. /// The object to get a corresponding database type for. [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] - public static DbType GetDbType(object value) + public static void SetDbType(IDataParameter parameter, object value) { - if (value == null || value is DBNull) return DbType.Object; + if (value == null || value is DBNull) return; - return LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _); + var dbType = LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _); + if (DynamicParameters.ShouldSetDbType(dbType)) + { + parameter.DbType = dbType.GetValueOrDefault(); + } } /// @@ -357,7 +362,7 @@ public static DbType GetDbType(object value) [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] - public static DbType LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) + public static DbType? LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) { handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); @@ -366,7 +371,7 @@ public static DbType LookupDbType(Type type, string name, bool demand, out IType { type = Enum.GetUnderlyingType(type); } - if (typeMap.TryGetValue(type, out DbType dbType)) + if (typeMap.TryGetValue(type, out var dbType)) { return dbType; } @@ -2031,7 +2036,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj var count = 0; bool isString = value is IEnumerable; bool isDbString = value is IEnumerable; - DbType dbType = 0; + DbType? dbType = null; int splitAt = SqlMapper.Settings.InListStringSplitCount; bool viaSplit = splitAt >= 0 @@ -2076,9 +2081,9 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj if (tmp != null && !(tmp is DBNull)) lastValue = tmp; // only interested in non-trivial values for padding - if (listParam.DbType != dbType) + if (DynamicParameters.ShouldSetDbType(dbType) && listParam.DbType != dbType.GetValueOrDefault()) { - listParam.DbType = dbType; + listParam.DbType = dbType.GetValueOrDefault(); } command.Parameters.Add(listParam); } @@ -2092,7 +2097,10 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj var padParam = command.CreateParameter(); padParam.ParameterName = namePrefix + count.ToString(); if (isString) padParam.Size = DbString.DefaultLength; - padParam.DbType = dbType; + if (DynamicParameters.ShouldSetDbType(dbType)) + { + padParam.DbType = dbType.GetValueOrDefault(); + } padParam.Value = lastValue; command.Parameters.Add(padParam); } @@ -2528,7 +2536,7 @@ internal static Action CreateParamInfoGenerator(Identity ide continue; } #pragma warning disable 618 - DbType dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler); + DbType? dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler); #pragma warning restore 618 if (dbType == DynamicParameters.EnumerableMultiParameter) { @@ -2563,22 +2571,22 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name] il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.ParameterName)).GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] } - if (dbType != DbType.Time && handler == null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time + if (DynamicParameters.ShouldSetDbType(dbType) && dbType != DbType.Time && handler == null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time { il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] - if (dbType == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic + if (dbType.GetValueOrDefault() == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic { // look it up from the param value il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value] - il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.GetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.SetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] } else { // constant value; nice and simple - EmitInt32(il, (int)dbType);// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + EmitInt32(il, (int)dbType.GetValueOrDefault());// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] } - il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] } il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] diff --git a/nuget.config b/nuget.config index b004e5cc7..00f52b7e2 100644 --- a/nuget.config +++ b/nuget.config @@ -2,6 +2,7 @@ + \ No newline at end of file diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index f8402fcaf..15a6d39e3 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 0f1c4e456..1fc5cb331 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Data; using System.Data.Common; using System.Linq; @@ -44,7 +45,7 @@ public void TestPostgresqlArrayParameters() { IDbTransaction transaction = conn.BeginTransaction(); conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); - conn.Execute("insert into tcat(breed, name) values(:breed, :name) ", Cats); + conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", Cats); var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new[] { 1, 3, 5 } }); Assert.Equal(3, r.Count()); @@ -55,6 +56,24 @@ public void TestPostgresqlArrayParameters() } } + [FactPostgresql] + public void TestPostgresqlListParameters() + { + using (var conn = GetOpenNpgsqlConnection()) + { + IDbTransaction transaction = conn.BeginTransaction(); + conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); + conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", new List(Cats)); + + var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new List { 1, 3, 5 } }); + Assert.Equal(3, r.Count()); + Assert.Equal(1, r.Count(c => c.Id == 1)); + Assert.Equal(1, r.Count(c => c.Id == 3)); + Assert.Equal(1, r.Count(c => c.Id == 5)); + transaction.Rollback(); + } + } + private class CharTable { public int Id { get; set; } @@ -88,6 +107,15 @@ public void TestPostgresqlSelectArray() } } + [FactPostgresql] + public void TestPostgresqlDateTimeUsage() + { + using (var conn = GetOpenNpgsqlConnection()) + { + _ = conn.ExecuteScalar("SELECT @Now", new { Now = DateTime.UtcNow }); + } + } + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactPostgresqlAttribute : FactAttribute { From 2a837ad7d036671ac9a75c9945bb970aa41b7de9 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 3 Nov 2021 14:05:39 +0000 Subject: [PATCH 199/312] Support for snowflake (#1724) * snowflake tests #1687 * actually add the tests! * merge From 296092b73bad522da9eedfce260628c942377f46 Mon Sep 17 00:00:00 2001 From: mgravell Date: Wed, 3 Nov 2021 15:45:34 +0000 Subject: [PATCH 200/312] release notes --- docs/index.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 46fd553b8..e93135e95 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,11 +24,15 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) +### 2.0.123 + - Parameters can now be re-used on subsequent commands (#952 via jamescrowley) - Array query support (`.Query`) on supported platforms (e.g. Postgres) (#1598 via DarkWanderer) - `SqlMapper.HasTypeHandler` is made public for consumers (#1405 via brendangooden) - Improves multi-mapping error message when a specified column in splitOn can't be found (#1664 via NickCraver) -- Improves DbString.ToString() (#1665 via NickCraver) +- Improves `DbString.ToString()` (#1665 via NickCraver) +- `DbType` for date/time types is no longer explicitly specified (resolves `Npgsql` v6 issue) +- add `Settings.UseIncrementalPseudoPositionalParameterNames`, to support "snowflake" parameter naming conventions ### 2.0.90 From 21ce60b523fb9518ec2cbb9137c73dc0706d0330 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 11 Nov 2021 14:05:48 +0000 Subject: [PATCH 201/312] move tests to npgsql 6 (GA); better datetime coverage --- tests/Dapper.Tests/Dapper.Tests.csproj | 2 +- tests/Dapper.Tests/Providers/PostgresqlTests.cs | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 15a6d39e3..b9f8b9347 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -19,7 +19,7 @@ - + diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 1fc5cb331..3dd07225c 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -112,7 +112,9 @@ public void TestPostgresqlDateTimeUsage() { using (var conn = GetOpenNpgsqlConnection()) { - _ = conn.ExecuteScalar("SELECT @Now", new { Now = DateTime.UtcNow }); + DateTime now = DateTime.UtcNow; + DateTime? nilA = now, nilB = null; + _ = conn.ExecuteScalar("SELECT @now, @nilA, @nilB", new { now, nilA, nilB }); } } From 3d405fea35f3eaef430128381c018520c69ae775 Mon Sep 17 00:00:00 2001 From: mgravell Date: Thu, 11 Nov 2021 14:14:25 +0000 Subject: [PATCH 202/312] add type hint --- tests/Dapper.Tests/Providers/PostgresqlTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 3dd07225c..bc90f1721 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -114,7 +114,7 @@ public void TestPostgresqlDateTimeUsage() { DateTime now = DateTime.UtcNow; DateTime? nilA = now, nilB = null; - _ = conn.ExecuteScalar("SELECT @now, @nilA, @nilB", new { now, nilA, nilB }); + _ = conn.ExecuteScalar("SELECT @now, @nilA, @nilB::timestamp", new { now, nilA, nilB }); } } From cb85070a39e7dfebd8f337bc186d535e2b6f4844 Mon Sep 17 00:00:00 2001 From: Raphael Schweizer Date: Tue, 4 Jan 2022 14:54:19 +0100 Subject: [PATCH 203/312] EF Core benchmarks for connections w/ passwords (#1700) --- .../Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs index c76cdb28e..2c8b811b2 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.EntityFrameworkCore.cs @@ -19,7 +19,7 @@ public class EFCoreBenchmarks : BenchmarkBase public void Setup() { BaseSetup(); - Context = new EFCoreContext(_connection.ConnectionString); + Context = new EFCoreContext(ConnectionString); } [Benchmark(Description = "First")] From 17256754c3e03698c3c41ae33cd8283d35566dc4 Mon Sep 17 00:00:00 2001 From: Jon Roberts Date: Tue, 4 Jan 2022 07:56:32 -0600 Subject: [PATCH 204/312] Document usage of IDynamicParameters in more detail (#1707) Considering all of the inquiries on StackOverflow about DynamicParameters, there should probably be a simple overview in the documentation. Examples: - https://stackoverflow.com/questions/12723922/dapper-adddynamicparams-for-in-statement-with-dynamic-parameter-name - https://stackoverflow.com/questions/43412455/dapper-with-sql-query-dynamic-parameters - https://stackoverflow.com/questions/38443739/passing-dynamically-created-sql-parameters-into-dapper-as-an-anonymous-type - https://stackoverflow.com/questions/62448900/how-do-i-loop-through-dapper-dynamic-query-results-using-strings-as-parameters - https://stackoverflow.com/questions/36735189/dapper-dynamicparameters-returning-error --- Readme.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 16988e242..16fc6018e 100644 --- a/Readme.md +++ b/Readme.md @@ -187,12 +187,35 @@ Alternatively, you might prefer Frans Bouma's [RawDataAccessBencher](https://git Parameterized queries --------------------- -Parameters are passed in as anonymous classes. This allow you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in your db platform's Query analyzer. +Parameters are usually passed in as anonymous classes. This allow you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in your db platform's Query analyzer. ```csharp new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B ``` +Parameters can also be built up dynamically using the DynamicParameters class. This allows for building a dynamic SQL statement while still using parameters for safety and performance. +```csharp + var sqlPredicates = new List(); + var queryParams = new DynamicParameters(); + if (boolExpression) + { + sqlPredicates.Add("column1 = @param1"); + queryParams.Add("param1", dynamicValue1, System.Data.DbType.Guid); + } else { + sqlPredicates.Add("column2 = @param2"); + queryParams.Add("param2", dynamicValue2, System.Data.DbType.String); + } +``` + +DynamicParameters also supports copying multiple parameters from existing objects of different types. + +```csharp + var queryParams = new DynamicParameters(objectOfType1); + queryParams.AddDynamicParams(objectOfType2); +``` + +When an object that implements the `IDynamicParameters` interface passed into `Execute` or `Query` functions, parameter values will be extracted via this interface. Obviously, the most likely object class to use for this purpose would be the built-in `DynamicParameters` class. + List Support ------------ Dapper allows you to pass in `IEnumerable` and will automatically parameterize your query. From 60fc296b73bd6f35a30b3d77ded61af73a62485f Mon Sep 17 00:00:00 2001 From: Raphael Schweizer Date: Tue, 4 Jan 2022 14:57:07 +0100 Subject: [PATCH 205/312] Fix NETSDK1174 (#1699) As per https://docs.microsoft.com/en-us/dotnet/core/tools/sdk-errors/netsdk1174 `-p` is deprecated in favor of `--project` This PR fixes the deprecation warning when running the benchmarks as currently described in the README. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 16fc6018e..c09d1a2eb 100644 --- a/Readme.md +++ b/Readme.md @@ -129,7 +129,7 @@ A key feature of Dapper is performance. The following metrics show how long it t The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/DapperLib/Dapper/tree/main/benchmarks/Dapper.Tests.Performance) (contributions welcome!) and can be run via: ```bash -dotnet run -p .\benchmarks\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join +dotnet run --project .\benchmarks\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join ``` Output from the latest run is: ``` ini From 62a526b4116a4571778683e22424f9880f139002 Mon Sep 17 00:00:00 2001 From: GitHubPang <61439577+GitHubPang@users.noreply.github.com> Date: Tue, 4 Jan 2022 22:24:35 +0800 Subject: [PATCH 206/312] Fix grammar and formatting in readme (#1696) Co-authored-by: Nick Craver --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index c09d1a2eb..275de6b46 100644 --- a/Readme.md +++ b/Readme.md @@ -120,7 +120,7 @@ var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)", fo Assert.Equal(foos.Count, count); ``` -This works for any parameter that implements IEnumerable for some T. +This works for any parameter that implements `IEnumerable` for some T. Performance ----------- @@ -187,7 +187,7 @@ Alternatively, you might prefer Frans Bouma's [RawDataAccessBencher](https://git Parameterized queries --------------------- -Parameters are usually passed in as anonymous classes. This allow you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in your db platform's Query analyzer. +Parameters are usually passed in as anonymous classes. This allows you to name your parameters easily and gives you the ability to simply cut-and-paste SQL snippets and run them in your db platform's Query analyzer. ```csharp new {A = 1, B = "b"} // A will be mapped to the param @A, B to the param @B From ca00feeb5fafe5262166689c0bec2b80b53add4e Mon Sep 17 00:00:00 2001 From: Alla Hoffman Date: Wed, 2 Mar 2022 20:04:43 -0500 Subject: [PATCH 207/312] Adding notes on the sub-packages' purpose to the readme (#1718) I just paraphrased the summaries from the sub-packages' Nuget Readmes and added them to the grid of packages in the Readme. Is that a good spot for them, and are those summaries sufficient? PR would close #1488 --- Readme.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 275de6b46..1a6061b8c 100644 --- a/Readme.md +++ b/Readme.md @@ -20,6 +20,18 @@ MyGet Pre-release feed: https://www.myget.org/gallery/dapper | [Dapper.SqlBuilder](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/v/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/vpre/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder](https://img.shields.io/nuget/dt/Dapper.SqlBuilder.svg)](https://www.nuget.org/packages/Dapper.SqlBuilder/) | [![Dapper.SqlBuilder MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.SqlBuilder.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.SqlBuilder) | | [Dapper.StrongName](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/v/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/vpre/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/dt/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.StrongName.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.StrongName) | +Package Purposes: +* Dapper.EntityFramework + * Extension handlers for EntityFramework +* Dapper.EntityFramework.StrongName + * Extension handlers for EntityFramework +* Dapper.Rainbow + * Micro-ORM implemented on Dapper, provides CRUD helpers +* Dapper.SqlBuilder + * Component for building SQL queries dynamically and composably +* Dapper.StrongName + * High-performance micro-ORM supporting MySQL, Sqlite, SqlICE, and Firebird + Features -------- Dapper is a [NuGet library](https://www.nuget.org/packages/Dapper) that you can add in to your project that will extend your `IDbConnection` interface. @@ -113,7 +125,7 @@ var foos = new List { { new Foo { A = 1, B = 1 } } { new Foo { A = 2, B = 2 } } - { new Foo { A = 3, B = 3 } } + { new Foo { A = 3, B = 3 } } }; var count = connection.Execute(@"insert MyTable(colA, colB) values (@a, @b)", foos); From cb5a43bd7b404d682158f5589cc0a891d6e9c09d Mon Sep 17 00:00:00 2001 From: David Pine Date: Sun, 21 Aug 2022 12:26:05 -0500 Subject: [PATCH 208/312] Update index.md (#1806) --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index e93135e95..84f237b51 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,4 +1,4 @@ -# Dapper - a simple object mapper for .Net +# Dapper - a simple object mapper for .NET ## Overview From 70be2bd5e28613383c166b50e86bba5b510cedd1 Mon Sep 17 00:00:00 2001 From: Olivier Spinelli Date: Sun, 21 Aug 2022 19:27:09 +0200 Subject: [PATCH 209/312] Fixed case sensitivity issues. (#1801) --- tests/Dapper.Tests/SqlBuilderTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/Dapper.Tests/SqlBuilderTests.cs b/tests/Dapper.Tests/SqlBuilderTests.cs index 83587ecd4..899a81cea 100644 --- a/tests/Dapper.Tests/SqlBuilderTests.cs +++ b/tests/Dapper.Tests/SqlBuilderTests.cs @@ -52,9 +52,9 @@ public void TestSqlBuilderUpdateSet() var updatetime = DateTime.Parse("2020/01/01"); var sb = new SqlBuilder() - .Set("vip = @vip", new { vip }) - .Set("updatetime = @updatetime", new { updatetime }) - .Where("id = @id", new { id }) + .Set("Vip = @vip", new { vip }) + .Set("Updatetime = @updatetime", new { updatetime }) + .Where("Id = @id", new { id }) ; var template = sb.AddTemplate("update #Users /**set**/ /**where**/"); @@ -69,7 +69,7 @@ public void TestSqlBuilderUpdateSet() var result = connection.QueryFirst("select * from #Users where Id = 1"); - Assert.Equal("update #Users SET vip = @vip , updatetime = @updatetime\n WHERE id = @id\n", template.RawSql); + Assert.Equal("update #Users SET Vip = @vip , Updatetime = @updatetime\n WHERE Id = @id\n", template.RawSql); Assert.True((bool)result.Vip); From 4562cc2fce6512eef8acb4b8712104834d2d5da4 Mon Sep 17 00:00:00 2001 From: Samir Dahal Date: Sun, 21 Aug 2022 23:12:30 +0545 Subject: [PATCH 210/312] fix typos (#1800) --- Dapper/SqlMapper.Async.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 73fbef6de..dba737fc6 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -81,7 +81,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Co /// The command timeout (in seconds). /// The type of command to execute. /// - /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => @@ -276,7 +276,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Typ /// The connection to query on. /// The command used to query on this connection. /// - /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column in assumed, otherwise an instance is + /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command) => From a31dfd3dd4d7f3f2580bd33c877199d7ef3e3ef9 Mon Sep 17 00:00:00 2001 From: Oleksii Holub <1935960+Tyrrrz@users.noreply.github.com> Date: Sun, 21 Aug 2022 20:28:02 +0300 Subject: [PATCH 211/312] Update GitHubActionsTestLogger (#1781) --- tests/Directory.Build.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 5fc9b77e5..4a940120a 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -14,7 +14,7 @@ - + From 01f03ef3e860945d3c7a0caea443403ad85076ee Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 9 Jun 2023 14:02:06 +0100 Subject: [PATCH 212/312] add support for fetching values via GetFieldValue (#1910) add support for fetching values via GetFieldValue; this requires DbDataReader, but in reality the reader is *always* DbDataReader; acknowledge this, and stop pretending to use IDataReader internally (no breaks to public API) --- Dapper/SqlMapper.Async.cs | 4 +- Dapper/SqlMapper.CacheInfo.cs | 3 +- Dapper/SqlMapper.DeserializerState.cs | 5 +- Dapper/SqlMapper.GridReader.Async.cs | 4 +- Dapper/SqlMapper.GridReader.cs | 7 +- Dapper/SqlMapper.IDataReader.cs | 67 ++++- Dapper/SqlMapper.TypeDeserializerCache.cs | 13 +- Dapper/SqlMapper.cs | 316 +++++++++++++++++----- Dapper/WrappedReader.cs | 106 +------- Directory.Build.props | 3 +- docs/index.md | 4 + tests/Dapper.Tests/Dapper.Tests.csproj | 3 +- tests/Dapper.Tests/DataReaderTests.cs | 141 +++++++++- tests/Dapper.Tests/ParameterTests.cs | 118 +++++++- 14 files changed, 586 insertions(+), 208 deletions(-) diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index dba737fc6..68cbe6b90 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -967,7 +967,7 @@ private static async Task> MultiMapAsync(this IDbC } } - private static IEnumerable ExecuteReaderSync(IDataReader reader, Func func, object parameters) + private static IEnumerable ExecuteReaderSync(DbDataReader reader, Func func, object parameters) { using (reader) { @@ -1004,7 +1004,7 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); DbCommand cmd = null; - IDataReader reader = null; + DbDataReader reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { diff --git a/Dapper/SqlMapper.CacheInfo.cs b/Dapper/SqlMapper.CacheInfo.cs index 409ea3411..63540aa3a 100644 --- a/Dapper/SqlMapper.CacheInfo.cs +++ b/Dapper/SqlMapper.CacheInfo.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Data.Common; using System.Threading; namespace Dapper @@ -9,7 +10,7 @@ public static partial class SqlMapper private class CacheInfo { public DeserializerState Deserializer { get; set; } - public Func[] OtherDeserializers { get; set; } + public Func[] OtherDeserializers { get; set; } public Action ParamReader { get; set; } private int hitCount; public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); } diff --git a/Dapper/SqlMapper.DeserializerState.cs b/Dapper/SqlMapper.DeserializerState.cs index 26b176cc1..bf1c2fce1 100644 --- a/Dapper/SqlMapper.DeserializerState.cs +++ b/Dapper/SqlMapper.DeserializerState.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Data.Common; namespace Dapper { @@ -8,9 +9,9 @@ public static partial class SqlMapper private struct DeserializerState { public readonly int Hash; - public readonly Func Func; + public readonly Func Func; - public DeserializerState(int hash, Func func) + public DeserializerState(int hash, Func func) { Hash = hash; Func = func; diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index bec4967be..f1c5a7fb4 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -14,7 +14,7 @@ public static partial class SqlMapper public partial class GridReader { private readonly CancellationToken cancel; - internal GridReader(IDbCommand command, IDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) + internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) : this(command, reader, identity, dynamicParams, addToCache) { this.cancel = cancel; @@ -225,7 +225,7 @@ private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type t return result; } - private async Task> ReadBufferedAsync(int index, Func deserializer) + private async Task> ReadBufferedAsync(int index, Func deserializer) { try { diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index 15311cf4d..7a59050f0 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -4,6 +4,7 @@ using System.Linq; using System.Globalization; using System.Runtime.CompilerServices; +using System.Data.Common; namespace Dapper { @@ -14,11 +15,11 @@ public static partial class SqlMapper /// public partial class GridReader : IDisposable { - private IDataReader reader; + private DbDataReader reader; private readonly Identity identity; private readonly bool addToCache; - internal GridReader(IDbCommand command, IDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache) + internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache) { Command = command; this.reader = reader; @@ -351,7 +352,7 @@ public IEnumerable Read(Type[] types, Func return buffered ? result.ToList() : result; } - private IEnumerable ReadDeferred(int index, Func deserializer, Type effectiveType) + private IEnumerable ReadDeferred(int index, Func deserializer, Type effectiveType) { try { diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs index dca4ebc6d..0fa7e0719 100644 --- a/Dapper/SqlMapper.IDataReader.cs +++ b/Dapper/SqlMapper.IDataReader.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Data; +using System.Data.Common; namespace Dapper { @@ -13,14 +14,15 @@ public static partial class SqlMapper /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { - if (reader.Read()) + var dbReader = GetDbDataReader(reader, false); + if (dbReader.Read()) { var effectiveType = typeof(T); - var deser = GetDeserializer(effectiveType, reader, 0, -1, false); + var deser = GetDeserializer(effectiveType, dbReader, 0, -1, false); var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; do { - object val = deser(reader); + object val = deser(dbReader); if (val == null || val is T) { yield return (T)val; @@ -29,7 +31,7 @@ public static IEnumerable Parse(this IDataReader reader) { yield return (T)Convert.ChangeType(val, convertToType, System.Globalization.CultureInfo.InvariantCulture); } - } while (reader.Read()); + } while (dbReader.Read()); } } @@ -40,13 +42,14 @@ public static IEnumerable Parse(this IDataReader reader) /// The type to parse from the . public static IEnumerable Parse(this IDataReader reader, Type type) { - if (reader.Read()) + var dbReader = GetDbDataReader(reader, false); + if (dbReader.Read()) { - var deser = GetDeserializer(type, reader, 0, -1, false); + var deser = GetDeserializer(type, dbReader, 0, -1, false); do { - yield return deser(reader); - } while (reader.Read()); + yield return deser(dbReader); + } while (dbReader.Read()); } } @@ -56,13 +59,14 @@ public static IEnumerable Parse(this IDataReader reader, Type type) /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { - if (reader.Read()) + var dbReader = GetDbDataReader(reader, false); + if (dbReader.Read()) { - var deser = GetDapperRowDeserializer(reader, 0, -1, false); + var deser = GetDapperRowDeserializer(dbReader, 0, -1, false); do { - yield return deser(reader); - } while (reader.Read()); + yield return deser(dbReader); + } while (dbReader.Read()); } } @@ -76,12 +80,47 @@ public static IEnumerable Parse(this IDataReader reader) /// The length of columns to read (default -1 = all fields following startIndex) /// Return null if we can't find the first column? (default false) /// A parser for this specific object from this row. +#if DEBUG // make sure we're not using this internally + [Obsolete(nameof(DbDataReader) + " API should be preferred")] +#endif public static Func GetRowParser(this IDataReader reader, Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) + { + return WrapObjectReader(GetDeserializer(type, GetDbDataReader(reader, false), startIndex, length, returnNullIfFirstMissing)); + } + + /// + /// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column. + /// You could return a collection of the base type but have each more specific. + /// + /// The data reader to get the parser for the current row from + /// The type to get the parser for + /// The start column index of the object (default 0) + /// The length of columns to read (default -1 = all fields following startIndex) + /// Return null if we can't find the first column? (default false) + /// A parser for this specific object from this row. + public static Func GetRowParser(this DbDataReader reader, Type type, + int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { return GetDeserializer(type, reader, startIndex, length, returnNullIfFirstMissing); } + /// +#if DEBUG // make sure we're not using this internally + [Obsolete(nameof(DbDataReader) + " API should be preferred")] +#endif + public static Func GetRowParser(this IDataReader reader, Type concreteType = null, + int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) + { + concreteType ??= typeof(T); + var func = GetDeserializer(concreteType, GetDbDataReader(reader, false), startIndex, length, returnNullIfFirstMissing); + return Wrap(func); + + // this is just to be very clear about what we're capturing + static Func Wrap(Func func) + => reader => (T)func(GetDbDataReader(reader, false)); + } + /// /// Gets the row parser for a specific row on a data reader. This allows for type switching every row based on, for example, a TypeId column. /// You could return a collection of the base type but have each more specific. @@ -135,7 +174,7 @@ public static Func GetRowParser(this IDataReader reader, Ty /// public override int Type => 2; /// } /// - public static Func GetRowParser(this IDataReader reader, Type concreteType = null, + public static Func GetRowParser(this DbDataReader reader, Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { concreteType ??= typeof(T); @@ -146,7 +185,7 @@ public static Func GetRowParser(this IDataReader reader, Type } else { - return (Func)(Delegate)func; + return (Func)(Delegate)func; } } } diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index 088f464c7..65fd3bf40 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -3,6 +3,7 @@ using System.Collections; using System.Collections.Generic; using System.Text; +using System.Data.Common; namespace Dapper { @@ -33,7 +34,7 @@ internal static void Purge() } } - internal static Func GetReader(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) + internal static Func GetReader(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { var found = (TypeDeserializerCache)byType[type]; if (found == null) @@ -50,18 +51,18 @@ internal static Func GetReader(Type type, IDataReader reade return found.GetReader(reader, startBound, length, returnNullIfFirstMissing); } - private readonly Dictionary> readers = new Dictionary>(); + private readonly Dictionary> readers = new Dictionary>(); private struct DeserializerKey : IEquatable { private readonly int startBound, length; private readonly bool returnNullIfFirstMissing; - private readonly IDataReader reader; + private readonly DbDataReader reader; private readonly string[] names; private readonly Type[] types; private readonly int hashCode; - public DeserializerKey(int hashCode, int startBound, int length, bool returnNullIfFirstMissing, IDataReader reader, bool copyDown) + public DeserializerKey(int hashCode, int startBound, int length, bool returnNullIfFirstMissing, DbDataReader reader, bool copyDown) { this.hashCode = hashCode; this.startBound = startBound; @@ -136,14 +137,14 @@ public bool Equals(DeserializerKey other) } } - private Func GetReader(IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) + private Func GetReader(DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { if (length < 0) length = reader.FieldCount - startBound; int hash = GetColumnHash(reader, startBound, length); if (returnNullIfFirstMissing) hash *= -27; // get a cheap key first: false means don't copy the values down var key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, false); - Func deser; + Func deser; lock (readers) { if (readers.TryGetValue(key, out deser)) return deser; diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index ee49c2abd..f61f89c8b 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -8,6 +8,8 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Data.Common; +using System.Data.SqlTypes; using System.Globalization; using System.Linq; using System.Reflection; @@ -31,7 +33,7 @@ private class PropertyInfoByNameComparer : IComparer { public int Compare(PropertyInfo x, PropertyInfo y) => string.CompareOrdinal(x.Name, y.Name); } - private static int GetColumnHash(IDataReader reader, int startBound = 0, int length = -1) + private static int GetColumnHash(DbDataReader reader, int startBound = 0, int length = -1) { unchecked { @@ -163,11 +165,39 @@ where pair.Value > 1 select Tuple.Create(pair.Key, pair.Value); } - private static Dictionary typeMap; + private static Dictionary typeMap; + + [Flags] + internal enum TypeMapEntryFlags + { + None = 0, + SetType = 1 << 0, + UseGetFieldValue = 1 << 1, + } + internal readonly struct TypeMapEntry : IEquatable + { + public readonly DbType DbType { get; } + public readonly TypeMapEntryFlags Flags; + public TypeMapEntry(DbType dbType, TypeMapEntryFlags flags) + { + DbType = dbType; + Flags = flags; + } + public override int GetHashCode() => (int)DbType ^ (int)Flags; + public override string ToString() => $"{DbType}, {Flags}"; + public override bool Equals(object obj) => obj is TypeMapEntry other && Equals(other); + public bool Equals(TypeMapEntry other) => other.DbType == DbType && other.Flags == Flags; + public static readonly TypeMapEntry + DoNotSet = new TypeMapEntry((DbType)(-2), TypeMapEntryFlags.None), + DecimalFieldValue = new TypeMapEntry(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); + + public static implicit operator TypeMapEntry(DbType dbType) + => new TypeMapEntry(dbType, TypeMapEntryFlags.SetType); + } static SqlMapper() { - typeMap = new Dictionary(37) + typeMap = new Dictionary(41) { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, @@ -184,9 +214,9 @@ static SqlMapper() [typeof(string)] = DbType.String, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, - [typeof(DateTime)] = null, + [typeof(DateTime)] = TypeMapEntry.DoNotSet, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, - [typeof(TimeSpan)] = null, + [typeof(TimeSpan)] = TypeMapEntry.DoNotSet, [typeof(byte[])] = DbType.Binary, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, @@ -202,10 +232,14 @@ static SqlMapper() [typeof(bool?)] = DbType.Boolean, [typeof(char?)] = DbType.StringFixedLength, [typeof(Guid?)] = DbType.Guid, - [typeof(DateTime?)] = null, + [typeof(DateTime?)] = TypeMapEntry.DoNotSet, [typeof(DateTimeOffset?)] = DbType.DateTimeOffset, - [typeof(TimeSpan?)] = null, - [typeof(object)] = DbType.Object + [typeof(TimeSpan?)] = TypeMapEntry.DoNotSet, + [typeof(object)] = DbType.Object, + [typeof(SqlDecimal)] = TypeMapEntry.DecimalFieldValue, + [typeof(SqlDecimal?)] = TypeMapEntry.DecimalFieldValue, + [typeof(SqlMoney)] = TypeMapEntry.DecimalFieldValue, + [typeof(SqlMoney?)] = TypeMapEntry.DecimalFieldValue, }; ResetTypeHandlers(false); } @@ -230,13 +264,42 @@ private static void ResetTypeHandlers(bool clone) /// The type to map from. /// The database type to map to. public static void AddTypeMap(Type type, DbType dbType) + => AddTypeMap(type, dbType, false); + + /// + /// Configure the specified type to be mapped to a given db-type. + /// + /// The type to map from. + /// The database type to map to. + /// Whether to prefer over . + public static void AddTypeMap(Type type, DbType dbType, bool useGetFieldValue) { // use clone, mutate, replace to avoid threading issues var snapshot = typeMap; + var flags = TypeMapEntryFlags.None; + if (dbType >= 0) + { + flags |= TypeMapEntryFlags.SetType; + } + if (useGetFieldValue) + { + flags |= TypeMapEntryFlags.UseGetFieldValue; + } + var value = new TypeMapEntry(dbType, flags); + if (snapshot.TryGetValue(type, out var oldValue) && oldValue.Equals(value)) return; // nothing to do - if (snapshot.TryGetValue(type, out var oldValue) && oldValue == dbType) return; // nothing to do + SetTypeMap(new Dictionary(snapshot) { [type] = value }); + } - typeMap = new Dictionary(snapshot) { [type] = dbType }; + private static void SetTypeMap(Dictionary value) + { + typeMap = value; + + // this cache is predicated on the contents of the type-map; reset it + lock (s_ReadViaGetFieldValueCache) + { + s_ReadViaGetFieldValueCache.Clear(); + } } /// @@ -250,10 +313,10 @@ public static void RemoveTypeMap(Type type) if (!snapshot.ContainsKey(type)) return; // nothing to do - var newCopy = new Dictionary(snapshot); + var newCopy = new Dictionary(snapshot); newCopy.Remove(type); - typeMap = newCopy; + SetTypeMap(newCopy); } /// @@ -371,9 +434,13 @@ public static void SetDbType(IDataParameter parameter, object value) { type = Enum.GetUnderlyingType(type); } - if (typeMap.TryGetValue(type, out var dbType)) + if (typeMap.TryGetValue(type, out var mapEntry)) { - return dbType; + if ((mapEntry.Flags & TypeMapEntryFlags.SetType) == 0) + { + return null; + } + return mapEntry.DbType; } if (type.FullName == LinqBinary) { @@ -1024,7 +1091,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; - IDataReader reader = null; + DbDataReader reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { @@ -1059,18 +1126,18 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD } } - private static IDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool wasClosed, CommandBehavior behavior) + private static DbDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool wasClosed, CommandBehavior behavior) { try { - return cmd.ExecuteReader(GetBehavior(wasClosed, behavior)); + return GetDbDataReader(cmd.ExecuteReader(GetBehavior(wasClosed, behavior))); } catch (ArgumentException ex) { // thanks, Sqlite! if (Settings.DisableCommandBehaviorOptimizations(behavior, ex)) { // we can retry; this time it will have different flags - return cmd.ExecuteReader(GetBehavior(wasClosed, behavior)); + return GetDbDataReader(cmd.ExecuteReader(GetBehavior(wasClosed, behavior))); } throw; } @@ -1083,7 +1150,7 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; - IDataReader reader = null; + DbDataReader reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -1176,7 +1243,7 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand cmd = null; - IDataReader reader = null; + DbDataReader reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -1234,7 +1301,7 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti /// Shared value deserialization path for QueryRowImpl and QueryRowAsync /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T ReadRow(CacheInfo info, Identity identity, ref CommandDefinition command, Type effectiveType, IDataReader reader) + private static T ReadRow(CacheInfo info, Identity identity, ref CommandDefinition command, Type effectiveType, DbDataReader reader) { var tuple = info.Deserializer; int hash = GetColumnHash(reader); @@ -1250,7 +1317,7 @@ private static T ReadRow(CacheInfo info, Identity identity, ref CommandDefini } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T GetValue(IDataReader reader, Type effectiveType, object val) + private static T GetValue(DbDataReader reader, Type effectiveType, object val) { if (val is T tVal) { @@ -1451,14 +1518,14 @@ private static IEnumerable MultiMap MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, IDataReader reader, Identity identity, bool finalize) + private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, DbDataReader reader, Identity identity, bool finalize) { object param = command.Parameters; identity ??= new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null; - IDataReader ownedReader = null; + DbDataReader ownedReader = null; bool wasClosed = cnn?.State == ConnectionState.Closed; try @@ -1471,7 +1538,7 @@ private static IEnumerable MultiMapImpl[] otherDeserializers; + Func[] otherDeserializers; int hash = GetColumnHash(reader); if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) @@ -1482,7 +1549,7 @@ private static IEnumerable MultiMapImpl mapIt = GenerateMapper(deserializer.Func, otherDeserializers, map); + Func mapIt = GenerateMapper(deserializer.Func, otherDeserializers, map); if (mapIt != null) { @@ -1517,7 +1584,7 @@ private static CommandBehavior GetBehavior(bool close, CommandBehavior @default) return (close ? (@default | CommandBehavior.CloseConnection) : @default) & Settings.AllowedCommandBehaviors; } - private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn, IDataReader reader, Identity identity, bool finalize) + private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn, DbDataReader reader, Identity identity, bool finalize) { if (types.Length < 1) { @@ -1529,7 +1596,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand ownedCommand = null; - IDataReader ownedReader = null; + DbDataReader ownedReader = null; bool wasClosed = cnn?.State == ConnectionState.Closed; try @@ -1542,7 +1609,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn reader = ownedReader; } DeserializerState deserializer; - Func[] otherDeserializers; + Func[] otherDeserializers; int hash = GetColumnHash(reader); if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) @@ -1553,7 +1620,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn SetQueryCache(identity, cinfo); } - Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); + Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); if (mapIt != null) { @@ -1583,7 +1650,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn } } - private static Func GenerateMapper(Func deserializer, Func[] otherDeserializers, object map) + private static Func GenerateMapper(Func deserializer, Func[] otherDeserializers, object map) => otherDeserializers.Length switch { 1 => r => ((Func)map)((TFirst)deserializer(r), (TSecond)otherDeserializers[0](r)), @@ -1595,7 +1662,7 @@ private static Func GenerateMapper throw new NotSupportedException(), }; - private static Func GenerateMapper(int length, Func deserializer, Func[] otherDeserializers, Func map) + private static Func GenerateMapper(int length, Func deserializer, Func[] otherDeserializers, Func map) { return r => { @@ -1611,9 +1678,9 @@ private static Func GenerateMapper(int length, Fu }; } - private static Func[] GenerateDeserializers(Identity identity, string splitOn, IDataReader reader) + private static Func[] GenerateDeserializers(Identity identity, string splitOn, DbDataReader reader) { - var deserializers = new List>(); + var deserializers = new List>(); var splits = splitOn.Split(',').Select(s => s.Trim()).ToArray(); bool isMultiSplit = splits.Length > 1; @@ -1680,7 +1747,7 @@ private static Func[] GenerateDeserializers(Identity identi return deserializers.ToArray(); } - private static int GetNextSplitDynamic(int startIdx, string splitOn, IDataReader reader) + private static int GetNextSplitDynamic(int startIdx, string splitOn, DbDataReader reader) { if (startIdx == reader.FieldCount) { @@ -1703,7 +1770,7 @@ private static int GetNextSplitDynamic(int startIdx, string splitOn, IDataReader return reader.FieldCount; } - private static int GetNextSplit(int startIdx, string splitOn, IDataReader reader) + private static int GetNextSplit(int startIdx, string splitOn, DbDataReader reader) { if (splitOn == "*") { @@ -1817,15 +1884,41 @@ private static void PassByPosition(IDbCommand cmd) }); } - private static Func GetDeserializer(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) + static DbDataReader GetDbDataReader(IDataReader reader, bool disposeOnFail = true) + { + return reader as DbDataReader ?? Throw(reader, disposeOnFail); + static DbDataReader Throw(IDataReader reader, bool disposeOnFail) + { + if (reader is null) + { + throw new ArgumentNullException(nameof(reader)); + } + if (disposeOnFail) + { + reader.Dispose(); // don't leak + } + // in reality, all providers have satisfied this since forever; we should have made Dapper target DbConnection, oops! + throw new NotSupportedException("The provided reader is required to be a DbDataReader, and is not"); + } + } + + private static Func GetDeserializer(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { + + // dynamic is passed in as Object ... by c# design if (type == typeof(object) || type == typeof(DapperRow)) { return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } + Type underlyingType = null; - if (!(typeMap.ContainsKey(type) || type.IsEnum || type.IsArray || type.FullName == LinqBinary + bool useGetFieldValue = false; + if (typeMap.TryGetValue(type, out var mapEntry)) + { + useGetFieldValue = (mapEntry.Flags & TypeMapEntryFlags.UseGetFieldValue) != 0; + } + else if (!(type.IsEnum || type.IsArray || type.FullName == LinqBinary || (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum))) { if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) @@ -1834,10 +1927,10 @@ private static Func GetDeserializer(Type type, IDataReader } return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); } - return GetStructDeserializer(type, underlyingType ?? type, startBound); + return GetStructDeserializer(type, underlyingType ?? type, startBound, useGetFieldValue); } - private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) + private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) { return reader => handler.Parse(type, reader.GetValue(startBound)); } @@ -1861,7 +1954,7 @@ private static Exception MultiMapException(IDataRecord reader, string splitOnCol } } - internal static Func GetDapperRowDeserializer(IDataRecord reader, int startBound, int length, bool returnNullIfFirstMissing) + internal static Func GetDapperRowDeserializer(DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { var fieldCount = reader.FieldCount; if (length == -1) @@ -2886,7 +2979,7 @@ private static T ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition c return Parse(result); } - private static IDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand cmd) + private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand cmd) { Action paramReader = GetParameterReader(cnn, ref command); cmd = null; @@ -2932,7 +3025,7 @@ private static Action GetParameterReader(IDbConnection cnn, return paramReader; } - private static Func GetStructDeserializer(Type type, Type effectiveType, int index) + private static Func GetStructDeserializer(Type type, Type effectiveType, int index, bool useGetFieldValue) { // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!) #pragma warning disable 618 @@ -2970,12 +3063,41 @@ private static Func GetStructDeserializer(Type type, Type e return val is DBNull ? null : handler.Parse(type, val); }; } - return r => + return useGetFieldValue ? ReadViaGetFieldValueFactory(type, index) : ReadViaGetValue(index); + + static Func ReadViaGetValue(int index) + => reader => + { + var val = reader.GetValue(index); + return val is DBNull ? null : val; + }; + } + + static Func ReadViaGetFieldValueFactory(Type type, int index) + { + type = Nullable.GetUnderlyingType(type) ?? type; + var factory = (Func>)s_ReadViaGetFieldValueCache[type]; + if (factory is null) { - var val = r.GetValue(index); - return val is DBNull ? null : val; - }; + factory = (Func>)Delegate.CreateDelegate( + typeof(Func>), null, typeof(SqlMapper).GetMethod( + nameof(UnderlyingReadViaGetFieldValueFactory), BindingFlags.Static | BindingFlags.NonPublic) + .MakeGenericMethod(type)); + lock (s_ReadViaGetFieldValueCache) + { + s_ReadViaGetFieldValueCache[type] = factory; + } + } + return factory(index); } + // cache of ReadViaGetFieldValueFactory for per-value T + static readonly Hashtable s_ReadViaGetFieldValueCache = new Hashtable(); + + static Func UnderlyingReadViaGetFieldValueFactory(int index) + => reader => reader.IsDBNull(index) ? null : reader.GetFieldValue(index); + + static bool UseGetFieldValue(Type type) => typeMap.TryGetValue(type, out var mapEntry) + && (mapEntry.Flags & TypeMapEntryFlags.UseGetFieldValue) != 0; private static T Parse(object value) { @@ -3000,9 +3122,13 @@ private static T Parse(object value) private static readonly MethodInfo enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), new Type[] { typeof(Type), typeof(string), typeof(bool) }), - getItem = typeof(IDataRecord).GetProperties(BindingFlags.Instance | BindingFlags.Public) + getItem = typeof(DbDataReader).GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(p => p.GetIndexParameters().Length > 0 && p.GetIndexParameters()[0].ParameterType == typeof(int)) - .Select(p => p.GetGetMethod()).First(); + .Select(p => p.GetGetMethod()).First(), + getFieldValueT = typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetFieldValue), + BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null), + isDbNull = typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull), + BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null); /// /// Gets type-map for the given type @@ -3078,9 +3204,31 @@ public static void SetTypeMap(Type type, ITypeMap map) /// /// /// +#if DEBUG // make sure we're not using this internally + [Obsolete(nameof(DbDataReader) + " API should be preferred")] +#endif public static Func GetTypeDeserializer( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) + { + return WrapObjectReader(GetTypeDeserializer(type, GetDbDataReader(reader, false), startBound, length, returnNullIfFirstMissing)); + } + + private static Func WrapObjectReader(Func dbReader) + => reader => dbReader(GetDbDataReader(reader)); // we'll eat the extra layer here; this is not a core API + + + /// + /// Internal use only + /// + /// + /// + /// + /// + /// + public static Func GetTypeDeserializer( + Type type, DbDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false + ) { return TypeDeserializerCache.GetReader(type, reader, startBound, length, returnNullIfFirstMissing); } @@ -3104,8 +3252,8 @@ private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary GetTypeDeserializerImpl( - Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false + private static Func GetTypeDeserializerImpl( + Type type, DbDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { if (length == -1) @@ -3119,7 +3267,7 @@ private static Func GetTypeDeserializerImpl( } var returnType = type.IsValueType ? typeof(object) : type; - var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(IDataReader) }, type, true); + var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(DbDataReader) }, type, true); var il = dm.GetILGenerator(); if (IsValueTuple(type)) @@ -3131,11 +3279,11 @@ private static Func GetTypeDeserializerImpl( GenerateDeserializerFromMap(type, reader, startBound, length, returnNullIfFirstMissing, il); } - var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(IDataReader), returnType); - return (Func)dm.CreateDelegate(funcType); + var funcType = System.Linq.Expressions.Expression.GetFuncType(typeof(DbDataReader), returnType); + return (Func)dm.CreateDelegate(funcType); } - private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataReader reader, int startBound, int length, ILGenerator il) + private static void GenerateValueTupleDeserializer(Type valueTupleType, DbDataReader reader, int startBound, int length, ILGenerator il) { var nullableUnderlyingType = Nullable.GetUnderlyingType(valueTupleType); var currentValueTupleType = nullableUnderlyingType ?? valueTupleType; @@ -3201,12 +3349,15 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataRea valueCopyLocal: null, reader.GetFieldType(startBound + i), targetType, - out var isDbNullLabel); + out var isDbNullLabel, out bool popWhenNull); var finishLabel = il.DefineLabel(); il.Emit(OpCodes.Br_S, finishLabel); il.MarkLabel(isDbNullLabel); - il.Emit(OpCodes.Pop); + if (popWhenNull) + { + il.Emit(OpCodes.Pop); + } LoadDefaultValue(il, targetType); @@ -3234,7 +3385,7 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, IDataRea il.Emit(OpCodes.Ret); } - private static void GenerateDeserializerFromMap(Type type, IDataReader reader, int startBound, int length, bool returnNullIfFirstMissing, ILGenerator il) + private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing, ILGenerator il) { var currentIndexDiagnosticLocal = il.DeclareLocal(typeof(int)); var returnValueLocal = il.DeclareLocal(type); @@ -3348,7 +3499,7 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i EmitInt32(il, index); il.Emit(OpCodes.Stloc, currentIndexDiagnosticLocal); - LoadReaderValueOrBranchToDBNullLabel(il, index, ref stringEnumLocal, valueCopyDiagnosticLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel); + LoadReaderValueOrBranchToDBNullLabel(il, index, ref stringEnumLocal, valueCopyDiagnosticLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel, out bool popWhenNull); if (specializedConstructor == null) { @@ -3365,15 +3516,14 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i il.Emit(OpCodes.Br_S, finishLabel); // stack is now [target] - il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][value] + il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][(and possibly value)] + if (popWhenNull) il.Emit(OpCodes.Pop); // stack is now [target][target] if (specializedConstructor != null) { - il.Emit(OpCodes.Pop); LoadDefaultValue(il, item.MemberType); } else if (applyNullSetting && (!memberType.IsValueType || Nullable.GetUnderlyingType(memberType) != null)) { - il.Emit(OpCodes.Pop); // stack is now [target][target] // can load a null with this value if (memberType.IsValueType) { // must be Nullable for some T @@ -3397,7 +3547,6 @@ private static void GenerateDeserializerFromMap(Type type, IDataReader reader, i } else { - il.Emit(OpCodes.Pop); // stack is now [target][target] il.Emit(OpCodes.Pop); // stack is now [target] } @@ -3462,11 +3611,50 @@ private static void LoadDefaultValue(ILGenerator il, Type type) } } - private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref LocalBuilder stringEnumLocal, LocalBuilder valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel) + private static void LoadReaderValueViaGetFieldValue(ILGenerator il, int index, Type memberType, LocalBuilder valueCopyLocal, Label isDbNullLabel, out bool popWhenNull) + { + popWhenNull = false; + var underlyingType = Nullable.GetUnderlyingType(memberType) ?? memberType; + + // for consistency, always do a null check (the GetValue approach always tests for DbNull and jumps) + il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] + EmitInt32(il, index); // stack is now [...][reader][index] + il.Emit(OpCodes.Callvirt, isDbNull); // stack is now [...][bool] + il.Emit(OpCodes.Brtrue_S, isDbNullLabel); + + // DB reports not null; read the value + il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] + EmitInt32(il, index); // stack is now [...][reader][index] + il.Emit(OpCodes.Callvirt, getFieldValueT.MakeGenericMethod(underlyingType)); // stack is now [...][T] + if (valueCopyLocal is not null) + { + il.Emit(OpCodes.Dup); // stack is now [...][T][T] + if (underlyingType.IsValueType) + { + il.Emit(OpCodes.Box, underlyingType); // stack is now [...][T][value-as-object] + } + il.Emit(OpCodes.Stloc, valueCopyLocal); // stack is now [...][T] + } + if (underlyingType != memberType) + { + // Nullable; wrap it + il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { underlyingType })); // stack is now [...][T?] + } + } + + private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref LocalBuilder stringEnumLocal, LocalBuilder valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel, out bool popWhenNull) { isDbNullLabel = il.DefineLabel(); + if (UseGetFieldValue(memberType)) + { + LoadReaderValueViaGetFieldValue(il, index, memberType, valueCopyLocal, isDbNullLabel, out popWhenNull); + return; + } + + popWhenNull = true; il.Emit(OpCodes.Ldarg_0); // stack is now [...][reader] EmitInt32(il, index); // stack is now [...][reader][index] + // default impl: use GetValue il.Emit(OpCodes.Callvirt, getItem); // stack is now [...][value-as-object] if (valueCopyLocal != null) diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index f092823f5..ded629b04 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -80,18 +80,7 @@ internal static class WrappedReader { // the purpose of wrapping here is to allow closing a reader to *also* close // the command, without having to explicitly hand the command back to the - // caller; what that actually looks like depends on what we get: if we are - // given a DbDataReader, we will surface a DbDataReader; if we are given - // a raw IDataReader, we will surface that; and if null: null - public static IDataReader Create(IDbCommand cmd, IDataReader reader) - { - if (cmd == null) return reader; // no need to wrap if no command - - if (reader is DbDataReader dbr) return new DbWrappedReader(cmd, dbr); - if (reader != null) return new BasicWrappedReader(cmd, reader); - cmd.Dispose(); - return null; // GIGO - } + // caller public static DbDataReader Create(IDbCommand cmd, DbDataReader reader) { if (cmd == null) return reader; // no need to wrap if no command @@ -210,97 +199,6 @@ public override long GetChars(int i, long fieldoffset, char[] buffer, int buffer public override Task NextResultAsync(CancellationToken cancellationToken) => _reader.NextResultAsync(cancellationToken); public override Task ReadAsync(CancellationToken cancellationToken) => _reader.ReadAsync(cancellationToken); public override int VisibleFieldCount => _reader.VisibleFieldCount; - protected override DbDataReader GetDbDataReader(int ordinal) => (((IDataReader)_reader).GetData(ordinal) as DbDataReader) ?? throw new NotSupportedException(); - } - - internal class BasicWrappedReader : IWrappedDataReader - { - private IDataReader _reader; - private IDbCommand _cmd; - - IDataReader IWrappedDataReader.Reader => _reader; - - IDbCommand IWrappedDataReader.Command => _cmd; - - public BasicWrappedReader(IDbCommand cmd, IDataReader reader) - { - _cmd = cmd; - _reader = reader; - } - - void IDataReader.Close() => _reader.Close(); - - int IDataReader.Depth => _reader.Depth; - - DataTable IDataReader.GetSchemaTable() => _reader.GetSchemaTable(); - - bool IDataReader.IsClosed => _reader.IsClosed; - - bool IDataReader.NextResult() => _reader.NextResult(); - - bool IDataReader.Read() => _reader.Read(); - - int IDataReader.RecordsAffected => _reader.RecordsAffected; - - void IDisposable.Dispose() - { - _reader.Close(); - _reader.Dispose(); - _reader = DisposedReader.Instance; - _cmd?.Dispose(); - _cmd = null; - } - - int IDataRecord.FieldCount => _reader.FieldCount; - - bool IDataRecord.GetBoolean(int i) => _reader.GetBoolean(i); - - byte IDataRecord.GetByte(int i) => _reader.GetByte(i); - - long IDataRecord.GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => - _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); - - char IDataRecord.GetChar(int i) => _reader.GetChar(i); - - long IDataRecord.GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => - _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); - - IDataReader IDataRecord.GetData(int i) => _reader.GetData(i); - - string IDataRecord.GetDataTypeName(int i) => _reader.GetDataTypeName(i); - - DateTime IDataRecord.GetDateTime(int i) => _reader.GetDateTime(i); - - decimal IDataRecord.GetDecimal(int i) => _reader.GetDecimal(i); - - double IDataRecord.GetDouble(int i) => _reader.GetDouble(i); - - Type IDataRecord.GetFieldType(int i) => _reader.GetFieldType(i); - - float IDataRecord.GetFloat(int i) => _reader.GetFloat(i); - - Guid IDataRecord.GetGuid(int i) => _reader.GetGuid(i); - - short IDataRecord.GetInt16(int i) => _reader.GetInt16(i); - - int IDataRecord.GetInt32(int i) => _reader.GetInt32(i); - - long IDataRecord.GetInt64(int i) => _reader.GetInt64(i); - - string IDataRecord.GetName(int i) => _reader.GetName(i); - - int IDataRecord.GetOrdinal(string name) => _reader.GetOrdinal(name); - - string IDataRecord.GetString(int i) => _reader.GetString(i); - - object IDataRecord.GetValue(int i) => _reader.GetValue(i); - - int IDataRecord.GetValues(object[] values) => _reader.GetValues(values); - - bool IDataRecord.IsDBNull(int i) => _reader.IsDBNull(i); - - object IDataRecord.this[string name] => _reader[name]; - - object IDataRecord.this[int i] => _reader[i]; + protected override DbDataReader GetDbDataReader(int ordinal) => _reader.GetData(ordinal); } } diff --git a/Directory.Build.props b/Directory.Build.props index 0cb2bf374..9b3e9cf67 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,8 +21,9 @@ false true - + 9.0 + false diff --git a/docs/index.md b/docs/index.md index 84f237b51..ea0af98bf 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,6 +22,10 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased +- add support for `SqlDecimal` and other types that need to be accessed via `DbDataReader.GetFieldValue` +- add an overload of `AddTypeMap` that supports `DbDataReader.GetFieldValue` for additional types +- acknowledge that in reality we only support `DbDataReader`; this has been true (via `DbConnection`) for `async` forever + (note: new PRs will not be merged until they add release note wording here) ### 2.0.123 diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index b9f8b9347..35af8a509 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,7 +2,7 @@ Dapper.Tests Dapper Core Test Suite - netcoreapp3.1;net462;net472;net5.0 + net462;net472;net6.0 $(DefineConstants);MSSQLCLIENT $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208 @@ -17,7 +17,6 @@ - diff --git a/tests/Dapper.Tests/DataReaderTests.cs b/tests/Dapper.Tests/DataReaderTests.cs index ab28619bc..ea6b9e1c2 100644 --- a/tests/Dapper.Tests/DataReaderTests.cs +++ b/tests/Dapper.Tests/DataReaderTests.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Data.Common; using System.Linq; using Xunit; @@ -14,14 +15,17 @@ public sealed class MicrosoftSqlClientDataReaderTests : DataReaderTests : TestBase where TProvider : DatabaseProvider { [Fact] - public void GetSameReaderForSameShape() + public void GetSameReaderForSameShape_IDataReader() { var origReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); +#pragma warning disable CS0618 // Type or member is obsolete var origParser = origReader.GetRowParser(typeof(HazNameId)); var typedParser = origReader.GetRowParser(); +#pragma warning restore CS0618 // Type or member is obsolete - Assert.True(ReferenceEquals(origParser, typedParser)); + // because wrapped for IDataReader, not same instance each time + Assert.False(ReferenceEquals(origParser, typedParser)); var list = origReader.Parse().ToList(); Assert.Single(list); @@ -30,6 +34,40 @@ public void GetSameReaderForSameShape() origReader.Dispose(); var secondReader = connection.ExecuteReader("select 'abc' as Name, 123 as Id"); +#pragma warning disable CS0618 // Type or member is obsolete + var secondParser = secondReader.GetRowParser(typeof(HazNameId)); + var thirdParser = secondReader.GetRowParser(typeof(HazNameId), 1); +#pragma warning restore CS0618 // Type or member is obsolete + + list = secondReader.Parse().ToList(); + Assert.Single(list); + Assert.Equal("abc", list[0].Name); + Assert.Equal(123, list[0].Id); + secondReader.Dispose(); + + // now: should be different readers, and because wrapped for IDataReader, not same parser + Assert.False(ReferenceEquals(origReader, secondReader)); + Assert.False(ReferenceEquals(origParser, secondParser)); + Assert.False(ReferenceEquals(secondParser, thirdParser)); + } + + [Fact] + public void GetSameReaderForSameShape_DbDataReader() + { + var origReader = Assert.IsAssignableFrom(connection.ExecuteReader("select 'abc' as Name, 123 as Id")); + var origParser = origReader.GetRowParser(typeof(HazNameId)); + + var typedParser = origReader.GetRowParser(); + + Assert.True(ReferenceEquals(origParser, typedParser)); + + var list = origReader.Parse().ToList(); + Assert.Single(list); + Assert.Equal("abc", list[0].Name); + Assert.Equal(123, list[0].Id); + origReader.Dispose(); + + var secondReader = Assert.IsAssignableFrom(connection.ExecuteReader("select 'abc' as Name, 123 as Id")); var secondParser = secondReader.GetRowParser(typeof(HazNameId)); var thirdParser = secondReader.GetRowParser(typeof(HazNameId), 1); @@ -56,7 +94,7 @@ public void TestTreatIntAsABool() } [Fact] - public void DiscriminatedUnion() + public void DiscriminatedUnion_IDataReader() { List result = new List(); using (var reader = connection.ExecuteReader(@" @@ -66,8 +104,10 @@ union all { if (reader.Read()) { +#pragma warning disable CS0618 var toFoo = reader.GetRowParser(typeof(Discriminated_Foo)); var toBar = reader.GetRowParser(typeof(Discriminated_Bar)); +#pragma warning restore CS0618 var col = reader.GetOrdinal("Type"); do @@ -95,7 +135,46 @@ union all } [Fact] - public void DiscriminatedUnionWithMultiMapping() + public void DiscriminatedUnion_DbDataReader() + { + List result = new List(); + using (var reader = Assert.IsAssignableFrom(connection.ExecuteReader(@" +select 'abc' as Name, 1 as Type, 3.0 as Value +union all +select 'def' as Name, 2 as Type, 4.0 as Value"))) + { + if (reader.Read()) + { + var toFoo = reader.GetRowParser(typeof(Discriminated_Foo)); + var toBar = reader.GetRowParser(typeof(Discriminated_Bar)); + + var col = reader.GetOrdinal("Type"); + do + { + switch (reader.GetInt32(col)) + { + case 1: + result.Add(toFoo(reader)); + break; + case 2: + result.Add(toBar(reader)); + break; + } + } while (reader.Read()); + } + } + + Assert.Equal(2, result.Count); + Assert.Equal(1, result[0].Type); + Assert.Equal(2, result[1].Type); + var foo = (Discriminated_Foo)result[0]; + Assert.Equal("abc", foo.Name); + var bar = (Discriminated_Bar)result[1]; + Assert.Equal(bar.Value, (float)4.0); + } + + [Fact] + public void DiscriminatedUnionWithMultiMapping_IDataReader() { var result = new List(); using (var reader = connection.ExecuteReader(@" @@ -108,6 +187,60 @@ union all var col = reader.GetOrdinal("Type"); var splitOn = reader.GetOrdinal("Id"); +#pragma warning disable CS0618 + var toFoo = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Foo), 0, splitOn); + var toBar = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Bar), 0, splitOn); + var toHaz = reader.GetRowParser(typeof(HazNameId), splitOn, reader.FieldCount - splitOn); +#pragma warning restore CS0618 + + do + { + DiscriminatedWithMultiMapping_BaseType obj = null; + switch (reader.GetInt32(col)) + { + case 1: + obj = toFoo(reader); + break; + case 2: + obj = toBar(reader); + break; + } + + Assert.NotNull(obj); + obj.HazNameIdObject = toHaz(reader); + result.Add(obj); + + } while (reader.Read()); + } + } + + Assert.Equal(2, result.Count); + Assert.Equal(1, result[0].Type); + Assert.Equal(2, result[1].Type); + var foo = (DiscriminatedWithMultiMapping_Foo)result[0]; + Assert.Equal("abc", foo.Name); + Assert.Equal(1, foo.HazNameIdObject.Id); + Assert.Equal("zxc", foo.HazNameIdObject.Name); + var bar = (DiscriminatedWithMultiMapping_Bar)result[1]; + Assert.Equal(bar.Value, (float)4.0); + Assert.Equal(2, bar.HazNameIdObject.Id); + Assert.Equal("qwe", bar.HazNameIdObject.Name); + } + + [Fact] + public void DiscriminatedUnionWithMultiMapping_DbDataReader() + { + var result = new List(); + using (var reader = Assert.IsAssignableFrom(connection.ExecuteReader(@" +select 'abc' as Name, 1 as Type, 3.0 as Value, 1 as Id, 'zxc' as Name +union all +select 'def' as Name, 2 as Type, 4.0 as Value, 2 as Id, 'qwe' as Name"))) + { + if (reader.Read()) + { + var col = reader.GetOrdinal("Type"); + var splitOn = reader.GetOrdinal("Id"); + var toFoo = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Foo), 0, splitOn); var toBar = reader.GetRowParser(typeof(DiscriminatedWithMultiMapping_Bar), 0, splitOn); var toHaz = reader.GetRowParser(typeof(HazNameId), splitOn, reader.FieldCount - splitOn); diff --git a/tests/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs index 7179563ad..a6a17e24f 100644 --- a/tests/Dapper.Tests/ParameterTests.cs +++ b/tests/Dapper.Tests/ParameterTests.cs @@ -3,13 +3,14 @@ using System.Collections.Generic; using System.ComponentModel; using System.Data; +using System.Data.Common; using System.Data.SqlTypes; +using System.Diagnostics; using System.Dynamic; -using System.Linq; -using Xunit; using System.Globalization; +using System.Linq; using System.Text.RegularExpressions; -using System.Diagnostics; +using Xunit; #if ENTITY_FRAMEWORK using System.Data.Entity.Spatial; @@ -1593,5 +1594,116 @@ private static int GetExpectedListExpansionCount(int count, bool enabled) if (delta != 0) blocks++; return blocks * padFactor; } + + [Fact] + public void Issue1907_SqlDecimalPreciseValues() + { + bool close = false; + try + { + if (connection.State != ConnectionState.Open) + { + connection.Open(); + close = true; + } + connection.Execute(@" +create table #Issue1907 ( + Id int not null primary key identity(1,1), + Value numeric(30,15) not null);"); + + const string PreciseValue = "999999999999999.999999999999999"; + SqlDecimal sentValue = SqlDecimal.Parse(PreciseValue), recvValue; + connection.Execute("insert #Issue1907 (Value) values (@value)", new { value = sentValue }); + + // access via vendor-specific API; if this fails, nothing else can work + using (var wrappedReader = connection.ExecuteReader("select Id, Value from #Issue1907")) + { + var reader = Assert.IsAssignableFrom(wrappedReader).Reader; + Assert.True(reader.Read()); + if (reader is Microsoft.Data.SqlClient.SqlDataReader msReader) + { + recvValue = msReader.GetSqlDecimal(1); + } + else if (reader is System.Data.SqlClient.SqlDataReader sdReader) + { + recvValue = sdReader.GetSqlDecimal(1); + } + else + { + throw new InvalidOperationException($"unexpected reader type: {reader.GetType().FullName}"); + } + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + + Assert.False(reader.Read()); + Assert.False(reader.NextResult()); + } + + // access via generic API + using (var wrappedReader = connection.ExecuteReader("select Id, Value from #Issue1907")) + { + var reader = Assert.IsAssignableFrom(Assert.IsAssignableFrom(wrappedReader).Reader); + Assert.True(reader.Read()); + recvValue = reader.GetFieldValue(1); + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + + Assert.False(reader.Read()); + Assert.False(reader.NextResult()); + } + + // prove that we **cannot** fix ExecuteScalar, because ADO.NET itself doesn't work for that + Assert.Throws(() => + { + using var cmd = connection.CreateCommand(); + cmd.CommandText = "select Value from #Issue1907"; + cmd.CommandType = CommandType.Text; + _ = cmd.ExecuteScalar(); + }); + + // prove that simple read: works + recvValue = connection.QuerySingle("select Value from #Issue1907"); + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + + recvValue = connection.QuerySingle("select Value from #Issue1907").Value; + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + + // prove that object read: works + recvValue = connection.QuerySingle("select Id, Value from #Issue1907").Value; + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + + recvValue = connection.QuerySingle("select Id, Value from #Issue1907").Value.Value; + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + + // prove that value-tuple read: works + recvValue = connection.QuerySingle<(int Id, SqlDecimal Value)>("select Id, Value from #Issue1907").Value; + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + + recvValue = connection.QuerySingle<(int Id, SqlDecimal? Value)>("select Id, Value from #Issue1907").Value.Value; + Assert.Equal(sentValue, recvValue); + Assert.Equal(recvValue.ToString(), PreciseValue); + } + finally + { + if (close) connection.Close(); + } + + } + class HazSqlDecimal + { + public int Id { get; set; } + public SqlDecimal Value { get; set; } + } + + class HazNullableSqlDecimal + { + public int Id { get; set; } + public SqlDecimal? Value { get; set; } + } } } From 194a0cea6a861bc030a573ea2f54158148b6bec0 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Fri, 9 Jun 2023 09:35:00 -0400 Subject: [PATCH 213/312] [Builds] AppVeyor: use Visual Studio 2022 image (#1911) We want to use latest SDK and ability to run against net6.0 tests. It looks like the pool issue with VS 2022 has been resolved on the AppVeyor side so: yay! That solves everything cleanly with no installs. --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index d91b9d196..122b20fab 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -1,4 +1,4 @@ -image: Visual Studio 2019 +image: Visual Studio 2022 skip_branch_with_pr: true skip_tags: true @@ -35,7 +35,7 @@ init: - git config --global core.autocrlf input - SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%PATH% - net start MSSQL$SQL2019 - + nuget: disable_publish_on_pr: true From d56340b8859ca4270ef12efb03f6e48a95745ee6 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 9 Jun 2023 16:22:25 +0100 Subject: [PATCH 214/312] add missing QueryUnbufferedAsync API (#1912) * impl QueryUnbufferedAsync * implement GridReader.ReadUnbufferedAsync --- Dapper/SqlMapper.Async.cs | 75 ++++++++++++++++++ Dapper/SqlMapper.GridReader.Async.cs | 110 +++++++++++++++++++++------ Dapper/SqlMapper.GridReader.cs | 4 +- docs/index.md | 10 ++- tests/Dapper.Tests/AsyncTests.cs | 88 ++++++++++++++++++++- 5 files changed, 256 insertions(+), 31 deletions(-) diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 68cbe6b90..37fe2e41c 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -5,6 +5,7 @@ using System.Data.Common; using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -1217,5 +1218,79 @@ private static async Task ExecuteScalarImplAsync(IDbConnection cnn, Comman } return Parse(result); } + +#if NET5_0_OR_GREATER + /// + /// Execute a query asynchronously using . + /// + /// The type of results to return. + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is + /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). + /// + public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object param = null, DbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + // note: in many cases of adding a new async method I might add a CancellationToken - however, cancellation is expressed via WithCancellation on iterators + return QueryUnbufferedAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + } + + private static IAsyncEnumerable QueryUnbufferedAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) + { + return Impl(cnn, effectiveType, command, command.CancellationToken); // proxy to allow CT expression + + static async IAsyncEnumerable Impl(IDbConnection cnn, Type effectiveType, CommandDefinition command, + [EnumeratorCancellation] CancellationToken cancel) + { + object param = command.Parameters; + var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); + var info = GetCacheInfo(identity, param, command.AddToCache); + bool wasClosed = cnn.State == ConnectionState.Closed; + using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); + DbDataReader reader = null; + try + { + if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); + reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult, cancel).ConfigureAwait(false); + + var tuple = info.Deserializer; + int hash = GetColumnHash(reader); + if (tuple.Func == null || tuple.Hash != hash) + { + if (reader.FieldCount == 0) + { + yield break; + } + tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); + if (command.AddToCache) SetQueryCache(identity, info); + } + + var func = tuple.Func; + + var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; + while (await reader.ReadAsync(cancel).ConfigureAwait(false)) + { + object val = func(reader); + yield return GetValue(reader, effectiveType, val); + } + while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore subsequent result sets */ } + command.OnCompleted(); + } + finally + { + if (reader is not null) + { + await reader.DisposeAsync(); + } + if (wasClosed) cnn.Close(); + } + } + } +#endif } } diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index f1c5a7fb4..c15aadfe0 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -2,8 +2,8 @@ using System.Collections.Generic; using System.Data; using System.Data.Common; -using System.Globalization; using System.Linq; +using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -12,6 +12,9 @@ namespace Dapper public static partial class SqlMapper { public partial class GridReader +#if NET5_0_OR_GREATER + : IAsyncDisposable +#endif { private readonly CancellationToken cancel; internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) @@ -140,7 +143,7 @@ public Task ReadSingleOrDefaultAsync(Type type) private async Task NextResultAsync() { - if (await ((DbDataReader)reader).NextResultAsync(cancel).ConfigureAwait(false)) + if (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { // readCount++; gridIndex++; @@ -150,14 +153,37 @@ private async Task NextResultAsync() { // happy path; close the reader cleanly - no // need for "Cancel" etc +#if NET5_0_OR_GREATER + await reader.DisposeAsync(); +#else reader.Dispose(); +#endif reader = null; callbacks?.OnCompleted(); +#if NET5_0_OR_GREATER + await DisposeAsync(); +#else Dispose(); +#endif } } private Task> ReadAsyncImpl(Type type, bool buffered) + { + var deserializer = ValidateAndMarkConsumed(type); + if (buffered) + { + return ReadBufferedAsync(gridIndex, deserializer); + } + else + { + var result = ReadDeferred(gridIndex, deserializer, type); + if (buffered) result = result?.ToList(); // for the "not a DbDataReader" scenario + return Task.FromResult(result); + } + } + + private Func ValidateAndMarkConsumed(Type type) { if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); @@ -172,27 +198,10 @@ private Task> ReadAsyncImpl(Type type, bool buffered) cache.Deserializer = deserializer; } IsConsumed = true; - if (buffered && reader is DbDataReader) - { - return ReadBufferedAsync(gridIndex, deserializer.Func); - } - else - { - var result = ReadDeferred(gridIndex, deserializer.Func, type); - if (buffered) result = result?.ToList(); // for the "not a DbDataReader" scenario - return Task.FromResult(result); - } - } - - private Task ReadRowAsyncImpl(Type type, Row row) - { - if (reader is DbDataReader dbReader) return ReadRowAsyncImplViaDbReader(dbReader, type, row); - - // no async API available; use non-async and fake it - return Task.FromResult(ReadRow(type, row)); + return deserializer.Func; } - private async Task ReadRowAsyncImplViaDbReader(DbDataReader reader, Type type, Row row) + private async Task ReadRowAsyncImpl(Type type, Row row) { if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); @@ -229,7 +238,6 @@ private async Task> ReadBufferedAsync(int index, Func(); while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) { @@ -245,6 +253,64 @@ private async Task> ReadBufferedAsync(int index, Func + /// Read the next grid of results. + /// + /// The type to read. + public IAsyncEnumerable ReadUnbufferedAsync() => ReadAsyncUnbufferedImpl(typeof(T)); + + private IAsyncEnumerable ReadAsyncUnbufferedImpl(Type type) + { + var deserializer = ValidateAndMarkConsumed(type); + return ReadUnbufferedAsync(gridIndex, deserializer, cancel); + } + + private async IAsyncEnumerable ReadUnbufferedAsync(int index, Func deserializer, [EnumeratorCancellation] CancellationToken cancel) + { + try + { + while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) + { + yield return ConvertTo(deserializer(reader)); + } + } + finally // finally so that First etc progresses things even when multiple rows + { + if (index == gridIndex) + { + await NextResultAsync().ConfigureAwait(false); + } + } + } + + /// + /// Dispose the grid, closing and disposing both the underlying reader and command. + /// + public async ValueTask DisposeAsync() + { + if (reader != null) + { + if (!reader.IsClosed) Command?.Cancel(); + await reader.DisposeAsync(); + reader = null; + } + if (Command != null) + { + if (Command is DbCommand typed) + { + await typed.DisposeAsync(); + } + else + { + Command.Dispose(); + } + Command = null; + } + GC.SuppressFinalize(this); + } +#endif } } } diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index 7a59050f0..7ecf0d7be 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Generic; using System.Data; -using System.Linq; +using System.Data.Common; using System.Globalization; +using System.Linq; using System.Runtime.CompilerServices; -using System.Data.Common; namespace Dapper { diff --git a/docs/index.md b/docs/index.md index ea0af98bf..e01834c5a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,9 +22,13 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased -- add support for `SqlDecimal` and other types that need to be accessed via `DbDataReader.GetFieldValue` -- add an overload of `AddTypeMap` that supports `DbDataReader.GetFieldValue` for additional types -- acknowledge that in reality we only support `DbDataReader`; this has been true (via `DbConnection`) for `async` forever +- (#1910 via mgravell, fix #1907, #1263) + - add support for `SqlDecimal` and other types that need to be accessed via `DbDataReader.GetFieldValue` + - add an overload of `AddTypeMap` that supports `DbDataReader.GetFieldValue` for additional types + - acknowledge that in reality we only support `DbDataReader`; this has been true (via `DbConnection`) for `async` forever +- (#1912 via mgravell) + - add missing `AsyncEnumerable QueryUnbufferedAsync(...)` and `GridReader.ReadUnbufferedAsync(...)` APIs (.NET 5 and later) + - implement `IAsyncDisposable` on `GridReader` (.NET 5 and later) (note: new PRs will not be merged until they add release note wording here) diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index 5d24512be..b8d1fff88 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -1,11 +1,12 @@ -using System.Linq; +using System; +using System.Collections.Generic; using System.Data; +using System.Data.Common; using System.Diagnostics; -using System; -using System.Threading.Tasks; +using System.Linq; using System.Threading; +using System.Threading.Tasks; using Xunit; -using System.Data.Common; using Xunit.Abstractions; namespace Dapper.Tests @@ -45,6 +46,85 @@ public async Task TestBasicStringUsageAsync() Assert.Equal(new[] { "abc", "def" }, arr); } +#if NET5_0_OR_GREATER + [Fact] + public async Task TestBasicStringUsageUnbufferedAsync() + { + var results = new List(); + await foreach (var value in connection.QueryUnbufferedAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }) + .ConfigureAwait(false)) + { + results.Add(value); + } + var arr = results.ToArray(); + Assert.Equal(new[] { "abc", "def" }, arr); + } + + [Fact] + public async Task TestBasicStringUsageUnbufferedAsync_Cancellation() + { + using var cts = new CancellationTokenSource(); + var results = new List(); + await Assert.ThrowsAnyAsync(async () => + { + await foreach (var value in connection.QueryUnbufferedAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }) + .ConfigureAwait(false).WithCancellation(cts.Token)) + { + results.Add(value); + cts.Cancel(); // cancel after first item + } + }); + var arr = results.ToArray(); + Assert.Equal(new[] { "abc" }, arr); // we don't expect the "def" because of the cancellation + } + + [Fact] + public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync() + { + var results = new List(); + await using (var grid = await connection.QueryMultipleAsync("select 'abc' union select 'def'; select @txt", new { txt = "ghi" }) + .ConfigureAwait(false)) + { + while (!grid.IsConsumed) + { + await foreach (var value in grid.ReadUnbufferedAsync() + .ConfigureAwait(false)) + { + results.Add(value); + } + } + } + var arr = results.ToArray(); + Assert.Equal(new[] { "abc", "def", "ghi" }, arr); + } + + [Fact] + public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync_Cancellation() + { + using var cts = new CancellationTokenSource(); + var results = new List(); + await using (var grid = await connection.QueryMultipleAsync("select 'abc' union select 'def'; select @txt", new { txt = "ghi" }) + .ConfigureAwait(false)) + { + await Assert.ThrowsAnyAsync(async () => + { + while (!grid.IsConsumed) + { + await foreach (var value in grid.ReadUnbufferedAsync() + .ConfigureAwait(false) + .WithCancellation(cts.Token)) + { + results.Add(value); + } + cts.Cancel(); + } + }); + } + var arr = results.ToArray(); + Assert.Equal(new[] { "abc", "def" }, arr); // don't expect the ghi because of cancellation + } +#endif + [Fact] public async Task TestBasicStringUsageQueryFirstAsync() { From a9ef55fae10c0e4eb378d2dc348bc29f36750698 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 9 Jun 2023 16:32:38 +0100 Subject: [PATCH 215/312] update release number in docs --- docs/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index e01834c5a..c5b5ffdfe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,6 +22,10 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased +(note: new PRs will not be merged until they add release note wording here) + +### 2.0.138 + - (#1910 via mgravell, fix #1907, #1263) - add support for `SqlDecimal` and other types that need to be accessed via `DbDataReader.GetFieldValue` - add an overload of `AddTypeMap` that supports `DbDataReader.GetFieldValue` for additional types @@ -30,8 +34,6 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command - add missing `AsyncEnumerable QueryUnbufferedAsync(...)` and `GridReader.ReadUnbufferedAsync(...)` APIs (.NET 5 and later) - implement `IAsyncDisposable` on `GridReader` (.NET 5 and later) -(note: new PRs will not be merged until they add release note wording here) - ### 2.0.123 - Parameters can now be re-used on subsequent commands (#952 via jamescrowley) From da6509a22440250ecb91e1a93bc7074dad3b720d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Tue, 13 Jun 2023 18:06:30 +0200 Subject: [PATCH 216/312] Document how to run docker for db tests (Firebird + MySQL + PostgreSQL) (#1918) --- tests/Dapper.Tests/Providers/FirebirdTests.cs | 6 ++++++ tests/Dapper.Tests/Providers/MySQLTests.cs | 6 ++++++ tests/Dapper.Tests/Providers/PostgresqlTests.cs | 6 ++++++ 3 files changed, 18 insertions(+) diff --git a/tests/Dapper.Tests/Providers/FirebirdTests.cs b/tests/Dapper.Tests/Providers/FirebirdTests.cs index ae383b7fb..c5872c228 100644 --- a/tests/Dapper.Tests/Providers/FirebirdTests.cs +++ b/tests/Dapper.Tests/Providers/FirebirdTests.cs @@ -6,6 +6,12 @@ namespace Dapper.Tests.Providers { + /// + /// If Docker Desktop is installed, run the following command to start a container suitable for the tests. + /// + /// docker run -d -p 3050:3050 --name Dapper.Tests.Firebird -e FIREBIRD_DATABASE=database -e ISC_PASSWORD=masterkey jacobalberty/firebird + /// + /// public class FirebirdProvider : DatabaseProvider { public override DbProviderFactory Factory => FirebirdClientFactory.Instance; diff --git a/tests/Dapper.Tests/Providers/MySQLTests.cs b/tests/Dapper.Tests/Providers/MySQLTests.cs index 4b17ff80b..6986c999d 100644 --- a/tests/Dapper.Tests/Providers/MySQLTests.cs +++ b/tests/Dapper.Tests/Providers/MySQLTests.cs @@ -7,6 +7,12 @@ namespace Dapper.Tests { + /// + /// If Docker Desktop is installed, run the following command to start a container suitable for the tests. + /// + /// docker run -d -p 3306:3306 --name Dapper.Tests.MySQL -e MYSQL_DATABASE=tests -e MYSQL_USER=test -e MYSQL_PASSWORD=pass -e MYSQL_ROOT_PASSWORD=pass mysql + /// + /// public sealed class MySqlProvider : DatabaseProvider { public override DbProviderFactory Factory => MySqlConnector.MySqlConnectorFactory.Instance; diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index bc90f1721..3501daa8d 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -7,6 +7,12 @@ namespace Dapper.Tests { + /// + /// If Docker Desktop is installed, run the following command to start a container suitable for the tests. + /// + /// docker run -d -p 5432:5432 --name Dapper.Tests.PostgreSQL -e POSTGRES_DB=dappertest -e POSTGRES_USER=dappertest -e POSTGRES_PASSWORD=dapperpass postgres + /// + /// public class PostgresProvider : DatabaseProvider { public override DbProviderFactory Factory => Npgsql.NpgsqlFactory.Instance; From 677fee27c6f471babf00b0830822736af1bebee4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Tue, 13 Jun 2023 18:11:13 +0200 Subject: [PATCH 217/312] Enable the `Issue552_SignedUnsignedBooleans` test (#1919) It works fine with MySqlConnector 1.1.0 --- tests/Dapper.Tests/Providers/MySQLTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Dapper.Tests/Providers/MySQLTests.cs b/tests/Dapper.Tests/Providers/MySQLTests.cs index 6986c999d..f850e6cd2 100644 --- a/tests/Dapper.Tests/Providers/MySQLTests.cs +++ b/tests/Dapper.Tests/Providers/MySQLTests.cs @@ -44,7 +44,7 @@ public void DapperEnumValue_Mysql() } } - [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/552, not resolved on the MySQL end.")] + [FactMySql] public void Issue552_SignedUnsignedBooleans() { using (var conn = Provider.GetMySqlConnection(true, false, false)) From d91d90465db3dd9087f2635ee17a1010363c56da Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 20 Jun 2023 13:31:55 +0100 Subject: [PATCH 218/312] reinstate support for `IDataReader` (#1913) * if we stop supporting DbDataReader, someone is going to complain; I'd rather wrap it for now (to make the lib work), and add an analyzer in AOT that shouts at them for using IDbConnection also: compiler nits * release notes --- Dapper/SqlMapper.Async.cs | 2 +- Dapper/SqlMapper.IDataReader.cs | 12 +- Dapper/SqlMapper.cs | 58 ++++----- Dapper/WrappedReader.cs | 172 +++++++++++++++++++++++++- docs/index.md | 2 + tests/Dapper.Tests/DataReaderTests.cs | 4 +- 6 files changed, 199 insertions(+), 51 deletions(-) diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 37fe2e41c..59e37262d 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -1136,7 +1136,7 @@ private static async Task ExecuteWrappedReaderImplAsync(IDbConnect var reader = await ExecuteReaderWithFlagsFallbackAsync(cmd, wasClosed, commandBehavior, command.CancellationToken).ConfigureAwait(false); wasClosed = false; disposeCommand = false; - return WrappedReader.Create(cmd, reader); + return DbWrappedReader.Create(cmd, reader); } finally { diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs index 0fa7e0719..ef292d952 100644 --- a/Dapper/SqlMapper.IDataReader.cs +++ b/Dapper/SqlMapper.IDataReader.cs @@ -14,7 +14,7 @@ public static partial class SqlMapper /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { - var dbReader = GetDbDataReader(reader, false); + var dbReader = GetDbDataReader(reader); if (dbReader.Read()) { var effectiveType = typeof(T); @@ -42,7 +42,7 @@ public static IEnumerable Parse(this IDataReader reader) /// The type to parse from the . public static IEnumerable Parse(this IDataReader reader, Type type) { - var dbReader = GetDbDataReader(reader, false); + var dbReader = GetDbDataReader(reader); if (dbReader.Read()) { var deser = GetDeserializer(type, dbReader, 0, -1, false); @@ -59,7 +59,7 @@ public static IEnumerable Parse(this IDataReader reader, Type type) /// The data reader to parse results from. public static IEnumerable Parse(this IDataReader reader) { - var dbReader = GetDbDataReader(reader, false); + var dbReader = GetDbDataReader(reader); if (dbReader.Read()) { var deser = GetDapperRowDeserializer(dbReader, 0, -1, false); @@ -86,7 +86,7 @@ public static IEnumerable Parse(this IDataReader reader) public static Func GetRowParser(this IDataReader reader, Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { - return WrapObjectReader(GetDeserializer(type, GetDbDataReader(reader, false), startIndex, length, returnNullIfFirstMissing)); + return WrapObjectReader(GetDeserializer(type, GetDbDataReader(reader), startIndex, length, returnNullIfFirstMissing)); } /// @@ -113,12 +113,12 @@ public static Func GetRowParser(this IDataReader reader, Type int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { concreteType ??= typeof(T); - var func = GetDeserializer(concreteType, GetDbDataReader(reader, false), startIndex, length, returnNullIfFirstMissing); + var func = GetDeserializer(concreteType, GetDbDataReader(reader), startIndex, length, returnNullIfFirstMissing); return Wrap(func); // this is just to be very clear about what we're capturing static Func Wrap(Func func) - => reader => (T)func(GetDbDataReader(reader, false)); + => reader => (T)func(GetDbDataReader(reader)); } /// diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index f61f89c8b..daa62a3ec 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -58,7 +58,7 @@ private static void OnQueryCachePurged() handler?.Invoke(null, EventArgs.Empty); } - private static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new System.Collections.Concurrent.ConcurrentDictionary(); + private static readonly System.Collections.Concurrent.ConcurrentDictionary _queryCache = new(); private static void SetQueryCache(Identity key, CacheInfo value) { if (Interlocked.Increment(ref collect) == COLLECT_PER_ITEMS) @@ -188,11 +188,11 @@ public TypeMapEntry(DbType dbType, TypeMapEntryFlags flags) public override bool Equals(object obj) => obj is TypeMapEntry other && Equals(other); public bool Equals(TypeMapEntry other) => other.DbType == DbType && other.Flags == Flags; public static readonly TypeMapEntry - DoNotSet = new TypeMapEntry((DbType)(-2), TypeMapEntryFlags.None), - DecimalFieldValue = new TypeMapEntry(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); + DoNotSet = new((DbType)(-2), TypeMapEntryFlags.None), + DecimalFieldValue = new(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); public static implicit operator TypeMapEntry(DbType dbType) - => new TypeMapEntry(dbType, TypeMapEntryFlags.SetType); + => new(dbType, TypeMapEntryFlags.SetType); } static SqlMapper() @@ -677,7 +677,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, obje { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); - return WrappedReader.Create(dbcmd, reader); + return DbWrappedReader.Create(dbcmd, reader); } /// @@ -693,7 +693,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, obje public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command) { var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); - return WrappedReader.Create(dbcmd, reader); + return DbWrappedReader.Create(dbcmd, reader); } /// @@ -710,7 +710,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand dbcmd); - return WrappedReader.Create(dbcmd, reader); + return DbWrappedReader.Create(dbcmd, reader); } /// @@ -1842,13 +1842,13 @@ private static void PassByPosition(IDbCommand cmd) { if (cmd.Parameters.Count == 0) return; - Dictionary parameters = new Dictionary(StringComparer.Ordinal); + Dictionary parameters = new(StringComparer.Ordinal); foreach (IDbDataParameter param in cmd.Parameters) { if (!string.IsNullOrEmpty(param.ParameterName)) parameters[param.ParameterName] = param; } - HashSet consumed = new HashSet(StringComparer.Ordinal); + var consumed = new HashSet(StringComparer.Ordinal); bool firstMatch = true; int index = 0; // use this to spoof names; in most pseudo-positional cases, the name is ignored, however: // for "snowflake", the name needs to be incremental i.e. "1", "2", "3" @@ -1884,22 +1884,9 @@ private static void PassByPosition(IDbCommand cmd) }); } - static DbDataReader GetDbDataReader(IDataReader reader, bool disposeOnFail = true) + static DbDataReader GetDbDataReader(IDataReader reader) { - return reader as DbDataReader ?? Throw(reader, disposeOnFail); - static DbDataReader Throw(IDataReader reader, bool disposeOnFail) - { - if (reader is null) - { - throw new ArgumentNullException(nameof(reader)); - } - if (disposeOnFail) - { - reader.Dispose(); // don't leak - } - // in reality, all providers have satisfied this since forever; we should have made Dapper target DbConnection, oops! - throw new NotSupportedException("The provided reader is required to be a DbDataReader, and is not"); - } + return reader as DbDataReader ?? new WrappedBasicReader(reader); } private static Func GetDeserializer(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) @@ -2171,7 +2158,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj } var tmp = listParam.Value = SanitizeParameterValue(item); - if (tmp != null && !(tmp is DBNull)) + if (tmp != null && tmp is not DBNull) lastValue = tmp; // only interested in non-trivial values for padding if (DynamicParameters.ShouldSetDbType(dbType) && listParam.DbType != dbType.GetValueOrDefault()) @@ -2277,7 +2264,7 @@ private static bool TryStringSplit(ref IEnumerable list, int splitAt, string nam private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, string colType, bool byPosition, Action append) { - if (!(list is ICollection typed)) + if (list is not ICollection typed) { typed = list.ToList(); list = typed; // because we still need to be able to iterate it, even if we fail here @@ -2371,9 +2358,9 @@ private static IEnumerable FilterParameters(IEnumerable /// Replace all literal tokens with their text form. @@ -2483,7 +2470,7 @@ internal static IList GetLiteralTokens(string sql) var matches = literalTokens.Matches(sql); var found = new HashSet(StringComparer.Ordinal); - List list = new List(matches.Count); + var list = new List(matches.Count); foreach (Match match in matches) { string token = match.Value; @@ -3091,7 +3078,7 @@ static Func ReadViaGetFieldValueFactory(Type type, int ind return factory(index); } // cache of ReadViaGetFieldValueFactory for per-value T - static readonly Hashtable s_ReadViaGetFieldValueCache = new Hashtable(); + static readonly Hashtable s_ReadViaGetFieldValueCache = new(); static Func UnderlyingReadViaGetFieldValueFactory(int index) => reader => reader.IsDBNull(index) ? null : reader.GetFieldValue(index); @@ -3165,7 +3152,7 @@ public static ITypeMap GetTypeMap(Type type) } // use Hashtable to get free lockless reading - private static readonly Hashtable _typeMaps = new Hashtable(); + private static readonly Hashtable _typeMaps = new(); /// /// Set custom mapping for type deserializers @@ -3211,7 +3198,7 @@ public static Func GetTypeDeserializer( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) { - return WrapObjectReader(GetTypeDeserializer(type, GetDbDataReader(reader, false), startBound, length, returnNullIfFirstMissing)); + return WrapObjectReader(GetTypeDeserializer(type, GetDbDataReader(reader), startBound, length, returnNullIfFirstMissing)); } private static Func WrapObjectReader(Func dbReader) @@ -3684,10 +3671,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind Type numericType = Enum.GetUnderlyingType(unboxType); if (colType == typeof(string)) { - if (stringEnumLocal == null) - { - stringEnumLocal = il.DeclareLocal(typeof(string)); - } + stringEnumLocal ??= il.DeclareLocal(typeof(string)); il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index ded629b04..feaa7dc25 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -1,7 +1,9 @@ using System; using System.Collections; +using System.Collections.ObjectModel; using System.Data; using System.Data.Common; +using System.Diagnostics; using System.IO; using System.Runtime.CompilerServices; using System.Threading; @@ -11,7 +13,7 @@ namespace Dapper { internal sealed class DisposedReader : DbDataReader { - internal static readonly DisposedReader Instance = new DisposedReader(); + internal static readonly DisposedReader Instance = new(); private DisposedReader() { } public override int Depth => 0; public override int FieldCount => 0; @@ -76,7 +78,7 @@ protected override void Dispose(bool disposing) { } public override object this[string name] => ThrowDisposed(); } - internal static class WrappedReader + internal sealed class DbWrappedReader : DbDataReader, IWrappedDataReader { // the purpose of wrapping here is to allow closing a reader to *also* close // the command, without having to explicitly hand the command back to the @@ -89,9 +91,7 @@ public static DbDataReader Create(IDbCommand cmd, DbDataReader reader) cmd.Dispose(); return null; // GIGO } - } - internal sealed class DbWrappedReader : DbDataReader, IWrappedDataReader - { + private DbDataReader _reader; private IDbCommand _cmd; @@ -200,5 +200,167 @@ public override long GetChars(int i, long fieldoffset, char[] buffer, int buffer public override Task ReadAsync(CancellationToken cancellationToken) => _reader.ReadAsync(cancellationToken); public override int VisibleFieldCount => _reader.VisibleFieldCount; protected override DbDataReader GetDbDataReader(int ordinal) => _reader.GetData(ordinal); + +#if NET5_0_OR_GREATER + public override Task CloseAsync() => _reader.CloseAsync(); + + public override ValueTask DisposeAsync() => _reader.DisposeAsync(); + + public override Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) => _reader.GetColumnSchemaAsync(cancellationToken); + + public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) => base.GetSchemaTableAsync(cancellationToken); +#endif + } + + internal sealed class WrappedBasicReader : DbDataReader + { + private IDataReader _reader; + + public WrappedBasicReader(IDataReader reader) + { + Debug.Assert(reader is not DbDataReader); // or we wouldn't be here! + _reader = reader ?? throw new ArgumentNullException(nameof(reader)); + } + + public override bool HasRows => true; // have to assume that we do + public override void Close() => _reader.Close(); + public override DataTable GetSchemaTable() => _reader.GetSchemaTable(); + +#if PLAT_NO_REMOTING + [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] +#endif + public override object InitializeLifetimeService() => throw new NotSupportedException(); + + public override int Depth => _reader.Depth; + + public override bool IsClosed => _reader.IsClosed; + + public override bool NextResult() => _reader.NextResult(); + + public override bool Read() => _reader.Read(); + + public override int RecordsAffected => _reader.RecordsAffected; + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _reader.Close(); + _reader.Dispose(); + _reader = DisposedReader.Instance; // all future ops are no-ops + } + } + + public override int FieldCount => _reader.FieldCount; + + public override bool GetBoolean(int i) => _reader.GetBoolean(i); + + public override byte GetByte(int i) => _reader.GetByte(i); + + public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => + _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); + + public override char GetChar(int i) => _reader.GetChar(i); + + public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => + _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); + + public override string GetDataTypeName(int i) => _reader.GetDataTypeName(i); + + public override DateTime GetDateTime(int i) => _reader.GetDateTime(i); + + public override decimal GetDecimal(int i) => _reader.GetDecimal(i); + + public override double GetDouble(int i) => _reader.GetDouble(i); + + public override Type GetFieldType(int i) => _reader.GetFieldType(i); + + public override float GetFloat(int i) => _reader.GetFloat(i); + + public override Guid GetGuid(int i) => _reader.GetGuid(i); + + public override short GetInt16(int i) => _reader.GetInt16(i); + + public override int GetInt32(int i) => _reader.GetInt32(i); + + public override long GetInt64(int i) => _reader.GetInt64(i); + + public override string GetName(int i) => _reader.GetName(i); + + public override int GetOrdinal(string name) => _reader.GetOrdinal(name); + + public override string GetString(int i) => _reader.GetString(i); + + public override object GetValue(int i) => _reader.GetValue(i); + + public override int GetValues(object[] values) => _reader.GetValues(values); + + public override bool IsDBNull(int i) => _reader.IsDBNull(i); + + public override object this[string name] => _reader[name]; + + public override object this[int i] => _reader[i]; + + public override T GetFieldValue(int ordinal) + { + var value = _reader.GetValue(ordinal); + if (value is DBNull) + { + value = null; + } + return (T)value; + } + public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(GetFieldValue(ordinal)); + } + public override IEnumerator GetEnumerator() => _reader is IEnumerable e ? e.GetEnumerator() + : throw new NotImplementedException(); + public override Type GetProviderSpecificFieldType(int ordinal) => _reader.GetFieldType(ordinal); + public override object GetProviderSpecificValue(int ordinal) => _reader.GetValue(ordinal); + public override int GetProviderSpecificValues(object[] values) => _reader.GetValues(values); + public override Stream GetStream(int ordinal) => throw new NotSupportedException(); + public override TextReader GetTextReader(int ordinal) => throw new NotSupportedException(); + public override Task IsDBNullAsync(int ordinal, CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(_reader.IsDBNull(ordinal)); + } + public override Task NextResultAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(_reader.NextResult()); + } + public override Task ReadAsync(CancellationToken cancellationToken) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(_reader.Read()); + } + public override int VisibleFieldCount => _reader.FieldCount; + protected override DbDataReader GetDbDataReader(int ordinal) => throw new NotSupportedException(); + +#if NET5_0_OR_GREATER + public override Task CloseAsync() + { + _reader.Close(); + return Task.CompletedTask; + } + + public override ValueTask DisposeAsync() + { + _reader.Dispose(); + return default; + } + + public override Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) + => throw new NotSupportedException(); + + public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) + { + cancellationToken.ThrowIfCancellationRequested(); + return Task.FromResult(_reader.GetSchemaTable()); + } +#endif } } diff --git a/docs/index.md b/docs/index.md index c5b5ffdfe..2dcceaa0e 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,6 +22,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased +- reinstate fallback support for `IDataReader`, and implement missing `DbDataReader` async APIs + (note: new PRs will not be merged until they add release note wording here) ### 2.0.138 diff --git a/tests/Dapper.Tests/DataReaderTests.cs b/tests/Dapper.Tests/DataReaderTests.cs index ea6b9e1c2..789b9c9ed 100644 --- a/tests/Dapper.Tests/DataReaderTests.cs +++ b/tests/Dapper.Tests/DataReaderTests.cs @@ -96,7 +96,7 @@ public void TestTreatIntAsABool() [Fact] public void DiscriminatedUnion_IDataReader() { - List result = new List(); + var result = new List(); using (var reader = connection.ExecuteReader(@" select 'abc' as Name, 1 as Type, 3.0 as Value union all @@ -137,7 +137,7 @@ union all [Fact] public void DiscriminatedUnion_DbDataReader() { - List result = new List(); + var result = new List(); using (var reader = Assert.IsAssignableFrom(connection.ExecuteReader(@" select 'abc' as Name, 1 as Type, 3.0 as Value union all From 20d81380cac6452ad56d7635f578e3912d577080 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 20 Jun 2023 13:33:43 +0100 Subject: [PATCH 219/312] mark all structs as `readonly`; fix some compiler noise (no functional change) (#1925) --- Dapper/CommandDefinition.cs | 2 +- Dapper/SqlMapper.Async.cs | 20 +++++++++++++++++++- Dapper/SqlMapper.DeserializerState.cs | 2 +- Dapper/SqlMapper.LiteralToken.cs | 2 +- Dapper/SqlMapper.TypeDeserializerCache.cs | 9 ++++----- docs/index.md | 4 +++- tests/Dapper.Tests/AsyncTests.cs | 16 +++++++++++++++- 7 files changed, 44 insertions(+), 11 deletions(-) diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index 8c193cebc..5860cd461 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -9,7 +9,7 @@ namespace Dapper /// /// Represents the key aspects of a sql operation /// - public struct CommandDefinition + public readonly struct CommandDefinition { internal static CommandDefinition ForCallback(object parameters) { diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 59e37262d..78d3e04c5 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -530,7 +530,7 @@ public static Task ExecuteAsync(this IDbConnection cnn, CommandDefinition c } } - private struct AsyncExecState + private readonly struct AsyncExecState { public readonly DbCommand Command; public readonly Task Task; @@ -1220,6 +1220,24 @@ private static async Task ExecuteScalarImplAsync(IDbConnection cnn, Comman } #if NET5_0_OR_GREATER + /// + /// Execute a query asynchronously using . + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of dynamic data + /// + public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object param = null, DbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + // note: in many cases of adding a new async method I might add a CancellationToken - however, cancellation is expressed via WithCancellation on iterators + return QueryUnbufferedAsync(cnn, typeof(object), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + } + /// /// Execute a query asynchronously using . /// diff --git a/Dapper/SqlMapper.DeserializerState.cs b/Dapper/SqlMapper.DeserializerState.cs index bf1c2fce1..4b594e0f5 100644 --- a/Dapper/SqlMapper.DeserializerState.cs +++ b/Dapper/SqlMapper.DeserializerState.cs @@ -6,7 +6,7 @@ namespace Dapper { public static partial class SqlMapper { - private struct DeserializerState + private readonly struct DeserializerState { public readonly int Hash; public readonly Func Func; diff --git a/Dapper/SqlMapper.LiteralToken.cs b/Dapper/SqlMapper.LiteralToken.cs index 5ad399ab8..b5fdff0ec 100644 --- a/Dapper/SqlMapper.LiteralToken.cs +++ b/Dapper/SqlMapper.LiteralToken.cs @@ -8,7 +8,7 @@ public static partial class SqlMapper /// /// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql /// - internal struct LiteralToken + internal readonly struct LiteralToken { /// /// The text in the original command that should be replaced diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index 65fd3bf40..f24e58a6d 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -1,9 +1,8 @@ using System; -using System.Data; using System.Collections; using System.Collections.Generic; -using System.Text; using System.Data.Common; +using System.Text; namespace Dapper { @@ -16,7 +15,7 @@ private TypeDeserializerCache(Type type) this.type = type; } - private static readonly Hashtable byType = new Hashtable(); + private static readonly Hashtable byType = new(); private readonly Type type; internal static void Purge(Type type) { @@ -51,9 +50,9 @@ internal static Func GetReader(Type type, DbDataReader rea return found.GetReader(reader, startBound, length, returnNullIfFirstMissing); } - private readonly Dictionary> readers = new Dictionary>(); + private readonly Dictionary> readers = new(); - private struct DeserializerKey : IEquatable + private readonly struct DeserializerKey : IEquatable { private readonly int startBound, length; private readonly bool returnNullIfFirstMissing; diff --git a/docs/index.md b/docs/index.md index 2dcceaa0e..9170ff21f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,9 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased -- reinstate fallback support for `IDataReader`, and implement missing `DbDataReader` async APIs +- add missing non-generic `AsyncEnumerable QueryUnbufferedAsync(...)` API (#1925 via mgravell, fixes #1922) +- formally mark all `struct` types as `readonly` (#1925 via mgravell) +- reinstate fallback support for `IDataReader`, and implement missing `DbDataReader` async APIs (#1913 via mgravell) (note: new PRs will not be merged until they add release note wording here) diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index b8d1fff88..8309a22ca 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -47,6 +47,20 @@ public async Task TestBasicStringUsageAsync() } #if NET5_0_OR_GREATER + [Fact] + public async Task TestBasicStringUsageUnbufferedDynamicAsync() + { + var results = new List(); + await foreach (var row in connection.QueryUnbufferedAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }) + .ConfigureAwait(false)) + { + string value = row.Value; + results.Add(value); + } + var arr = results.ToArray(); + Assert.Equal(new[] { "abc", "def" }, arr); + } + [Fact] public async Task TestBasicStringUsageUnbufferedAsync() { @@ -192,7 +206,7 @@ public async Task TestBasicStringUsageAsyncNonBuffered() [Fact] public void TestLongOperationWithCancellation() { - CancellationTokenSource cancel = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + CancellationTokenSource cancel = new(TimeSpan.FromSeconds(5)); var task = connection.QueryAsync(new CommandDefinition("waitfor delay '00:00:10';select 1", cancellationToken: cancel.Token)); try { From 1c628bfb74fa822b8ac7806053a43c47c07879a8 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 20 Jun 2023 13:54:38 +0100 Subject: [PATCH 220/312] release notes --- docs/index.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 9170ff21f..2f9b59579 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,12 +22,14 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased +(note: new PRs will not be merged until they add release note wording here) + +### 2.0.138 + - add missing non-generic `AsyncEnumerable QueryUnbufferedAsync(...)` API (#1925 via mgravell, fixes #1922) - formally mark all `struct` types as `readonly` (#1925 via mgravell) - reinstate fallback support for `IDataReader`, and implement missing `DbDataReader` async APIs (#1913 via mgravell) -(note: new PRs will not be merged until they add release note wording here) - ### 2.0.138 - (#1910 via mgravell, fix #1907, #1263) From 02c92d99c4445f37b15b723bdde9ff3180d47950 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 20 Jun 2023 13:55:06 +0100 Subject: [PATCH 221/312] tyop --- docs/index.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 2f9b59579..e2092effc 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,7 +24,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) -### 2.0.138 +### 2.0.143 - add missing non-generic `AsyncEnumerable QueryUnbufferedAsync(...)` API (#1925 via mgravell, fixes #1922) - formally mark all `struct` types as `readonly` (#1925 via mgravell) From 33e0143002b5f170ae0bb9152d15625cd99c8a00 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 8 Jul 2023 10:28:18 -0400 Subject: [PATCH 222/312] Builds: Bump library versions (#1935) --- Directory.Build.props | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Directory.Build.props b/Directory.Build.props index 9b3e9cf67..d7d57bf2a 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -36,9 +36,9 @@ - - - + + + True From 136dc11f0cd1fbab1e23fedbd58d29ca141bfd7d Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Sat, 8 Jul 2023 11:43:58 -0400 Subject: [PATCH 223/312] Tests: Upgrade dependencies for Dependabot (#1936) I'll do a pass at all of these later, but getting CVE versions out of the pipe. Resolving 4 alerts here: https://github.com/DapperLib/Dapper/security/dependabot --- .../Dapper.Tests.Performance.csproj | 2 +- tests/Dapper.Tests/Dapper.Tests.csproj | 9 ++++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 914104e50..a9b419994 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -25,7 +25,7 @@ - + diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 35af8a509..b0e2b5712 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -15,15 +15,18 @@ - + - - + + + + + From a7e708a6185bb5a1bd42da082b65f7e3897fa2ae Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Aug 2023 17:03:01 +0100 Subject: [PATCH 224/312] Create FUNDING.yml --- .github/FUNDING.yml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..92d0356d7 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +# These are supported funding model platforms + +github: [mgravell, dapperlib] From 19355d5c5c869f4de0d0201366ce63ba5c9e69e1 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 14 Aug 2023 17:04:38 +0100 Subject: [PATCH 225/312] Update FUNDING.yml --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 92d0356d7..ab8863289 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,3 +1,4 @@ # These are supported funding model platforms github: [mgravell, dapperlib] +custom: ["https://www.buymeacoffee.com/marcgravell"] From 33090c0218383411c3b25fa6cb4cbee38d0f3270 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 17 Aug 2023 17:30:34 +0100 Subject: [PATCH 226/312] Underscore handling (#1947) * Adds MatchConstructorParametersWithUnderscores option * generalize to single MatchNamesWithUnderscores (pre-existing) * cite 2nd PR --------- Co-authored-by: Jonas Goronczy --- Dapper/DefaultTypeMap.cs | 72 +++++++++++++++++++++----- docs/index.md | 2 + tests/Dapper.Tests/ConstructorTests.cs | 40 ++++++++++++++ tests/Dapper.Tests/MiscTests.cs | 21 ++++++++ 4 files changed, 123 insertions(+), 12 deletions(-) diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index b278250cb..12e431522 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -74,8 +74,16 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) int i = 0; for (; i < ctorParameters.Length; i++) { - if (!string.Equals(ctorParameters[i].Name, names[i], StringComparison.OrdinalIgnoreCase)) + if (EqualsCI(ctorParameters[i].Name, names[i])) + { } // exact match + else if (MatchNamesWithUnderscores && EqualsCIU(ctorParameters[i].Name, names[i])) + { } // match after applying underscores + else + { + // not a name match break; + } + if (types[i] == typeof(byte[]) && ctorParameters[i].ParameterType.FullName == SqlMapper.LinqBinary) continue; var unboxedType = Nullable.GetUnderlyingType(ctorParameters[i].ParameterType) ?? ctorParameters[i].ParameterType; @@ -119,9 +127,8 @@ public ConstructorInfo FindExplicitConstructor() /// Mapping implementation public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) { - var parameters = constructor.GetParameters(); - - return new SimpleMemberMap(columnName, parameters.FirstOrDefault(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase))); + ParameterInfo param = MatchFirstOrDefault(constructor.GetParameters(), columnName, static p => p.Name); + return new SimpleMemberMap(columnName, param); } /// @@ -131,14 +138,7 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, /// Mapping implementation public SqlMapper.IMemberMap GetMember(string columnName) { - var property = Properties.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) - ?? Properties.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)); - - if (property == null && MatchNamesWithUnderscores) - { - property = Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.Ordinal)) - ?? Properties.Find(p => string.Equals(p.Name, columnName.Replace("_", ""), StringComparison.OrdinalIgnoreCase)); - } + var property = MatchFirstOrDefault(Properties, columnName, static p => p.Name); if (property != null) return new SimpleMemberMap(columnName, property); @@ -174,6 +174,54 @@ public SqlMapper.IMemberMap GetMember(string columnName) /// public static bool MatchNamesWithUnderscores { get; set; } + static T MatchFirstOrDefault(IList members, string name, Func selector) where T : class + { + if (members is { Count: > 0 }) + { + // try exact first + foreach (var member in members) + { + if (string.Equals(name, selector(member), StringComparison.Ordinal)) + { + return member; + } + } + // then exact ignoring case + foreach (var member in members) + { + if (string.Equals(name, selector(member), StringComparison.OrdinalIgnoreCase)) + { + return member; + } + } + if (MatchNamesWithUnderscores) + { + // same again, minus underscore delta + name = name?.Replace("_", ""); + foreach (var member in members) + { + if (string.Equals(name, selector(member)?.Replace("_", ""), StringComparison.Ordinal)) + { + return member; + } + } + foreach (var member in members) + { + if (string.Equals(name, selector(member)?.Replace("_", ""), StringComparison.OrdinalIgnoreCase)) + { + return member; + } + } + } + } + return null; + } + + internal static bool EqualsCI(string x, string y) + => string.Equals(x, y, StringComparison.OrdinalIgnoreCase); + internal static bool EqualsCIU(string x, string y) + => string.Equals(x?.Replace("_", ""), y?.Replace("_", ""), StringComparison.OrdinalIgnoreCase); + /// /// The settable properties for this typemap /// diff --git a/docs/index.md b/docs/index.md index e2092effc..326c30cc8 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,6 +22,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased +- add underscore handling with constructors (#1786 via @jo-goro, fixes #818; also #1947 via mgravell) + (note: new PRs will not be merged until they add release note wording here) ### 2.0.143 diff --git a/tests/Dapper.Tests/ConstructorTests.cs b/tests/Dapper.Tests/ConstructorTests.cs index 4c72894ee..af8ae7afa 100644 --- a/tests/Dapper.Tests/ConstructorTests.cs +++ b/tests/Dapper.Tests/ConstructorTests.cs @@ -220,5 +220,45 @@ public void TestWithNonPublicConstructor() var output = connection.Query("select 1 as Foo").First(); Assert.Equal(1, output.Foo); } + + [Fact] + public void CtorWithUnderscores() + { + var obj = connection.QueryFirst("select 'abc' as FIRST_NAME, 'def' as LAST_NAME"); + Assert.NotNull(obj); + Assert.Equal("abc", obj.FirstName); + Assert.Equal("def", obj.LastName); + } + + [Fact] + public void CtorWithoutUnderscores() + { + DefaultTypeMap.MatchNamesWithUnderscores = true; + var obj = connection.QueryFirst("select 'abc' as FIRST_NAME, 'def' as LAST_NAME"); + Assert.NotNull(obj); + Assert.Equal("abc", obj.FirstName); + Assert.Equal("def", obj.LastName); + } + + class Type_ParamsWithUnderscores + { + public string FirstName { get; } + public string LastName { get; } + public Type_ParamsWithUnderscores(string first_name, string last_name) + { + FirstName = first_name; + LastName = last_name; + } + } + class Type_ParamsWithoutUnderscores + { + public string FirstName { get; } + public string LastName { get; } + public Type_ParamsWithoutUnderscores(string firstName, string lastName) + { + FirstName = firstName; + LastName = lastName; + } + } } } diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 4d5dd26c4..608607a0d 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -1273,5 +1273,26 @@ 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; + } + } } } From 8c9b4bde3cdb03e263403bedf0eed83fb6491467 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 17 Aug 2023 20:14:10 +0100 Subject: [PATCH 227/312] Add FetchSize global setting (#1946) * fix #1945 * PR number * docs --- Dapper/CommandDefinition.cs | 22 +++++++++++++++++++--- Dapper/SqlMapper.Link.cs | 2 ++ Dapper/SqlMapper.Settings.cs | 25 ++++++++++++++++++++++++- docs/index.md | 2 ++ 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index 5860cd461..87d0d7119 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -131,6 +131,9 @@ internal IDbCommand SetupCommand(IDbConnection cnn, Action p private static SqlMapper.Link> commandInitCache; + internal static void ResetCommandInitCache() + => SqlMapper.Link>.Clear(ref commandInitCache); + private static Action GetInit(Type commandType) { if (commandType == null) @@ -141,14 +144,15 @@ private static Action GetInit(Type commandType) } var bindByName = GetBasicPropertySetter(commandType, "BindByName", typeof(bool)); var initialLongFetchSize = GetBasicPropertySetter(commandType, "InitialLONGFetchSize", typeof(int)); + var fetchSize = GetBasicPropertySetter(commandType, "FetchSize", typeof(long)); action = null; - if (bindByName != null || initialLongFetchSize != null) + if (bindByName is not null || initialLongFetchSize is not null || fetchSize is not null) { var method = new DynamicMethod(commandType.Name + "_init", null, new Type[] { typeof(IDbCommand) }); var il = method.GetILGenerator(); - if (bindByName != null) + if (bindByName is not null) { // .BindByName = true il.Emit(OpCodes.Ldarg_0); @@ -156,7 +160,7 @@ private static Action GetInit(Type commandType) il.Emit(OpCodes.Ldc_I4_1); il.EmitCall(OpCodes.Callvirt, bindByName, null); } - if (initialLongFetchSize != null) + if (initialLongFetchSize is not null) { // .InitialLONGFetchSize = -1 il.Emit(OpCodes.Ldarg_0); @@ -164,6 +168,18 @@ private static Action GetInit(Type commandType) il.Emit(OpCodes.Ldc_I4_M1); il.EmitCall(OpCodes.Callvirt, initialLongFetchSize, null); } + if (fetchSize is not null) + { + var snapshot = SqlMapper.Settings.FetchSize; + if (snapshot >= 0) + { + // .FetchSize = {withValue} + il.Emit(OpCodes.Ldarg_0); + il.Emit(OpCodes.Castclass, commandType); + il.Emit(OpCodes.Ldc_I8, snapshot); // bake it as a constant + il.EmitCall(OpCodes.Callvirt, fetchSize, null); + } + } il.Emit(OpCodes.Ret); action = (Action)method.CreateDelegate(typeof(Action)); } diff --git a/Dapper/SqlMapper.Link.cs b/Dapper/SqlMapper.Link.cs index 277f2a9e9..fe6de9226 100644 --- a/Dapper/SqlMapper.Link.cs +++ b/Dapper/SqlMapper.Link.cs @@ -13,6 +13,8 @@ public static partial class SqlMapper /// The value type of the cache. internal class Link where TKey : class { + public static void Clear(ref Link head) => Interlocked.Exchange(ref head, null); + public static bool TryGet(Link link, TKey key, out TValue value) { while (link != null) diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index cc71238b2..192cd893d 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -1,5 +1,6 @@ using System; using System.Data; +using System.Threading; namespace Dapper { @@ -63,7 +64,9 @@ static Settings() public static void SetDefaults() { CommandTimeout = null; - ApplyNullValues = false; + ApplyNullValues = PadListExpansions = UseIncrementalPseudoPositionalParameterNames = false; + AllowedCommandBehaviors = DefaultAllowedCommandBehaviors; + FetchSize = InListStringSplitCount = -1; } /// @@ -99,6 +102,26 @@ public static void SetDefaults() /// instead of the original name; for most scenarios, this is ignored since the name is redundant, but "snowflake" requires this. /// public static bool UseIncrementalPseudoPositionalParameterNames { get; set; } + + /// + /// If assigned a non-negative value, then that value is applied to any commands FetchSize property, if it exists; + /// see https://docs.oracle.com/en/database/oracle/oracle-database/18/odpnt/CommandFetchSize.html; note that this value + /// can only be set globally - it is not intended for frequent/contextual changing. + /// + public static long FetchSize + { + get => Volatile.Read(ref s_FetchSize); + set + { + if (Volatile.Read(ref s_FetchSize) != value) + { + Volatile.Write(ref s_FetchSize, value); + CommandDefinition.ResetCommandInitCache(); // if this setting is useful: we've invalidated things + } + } + } + + private static long s_FetchSize = -1; } } } diff --git a/docs/index.md b/docs/index.md index 326c30cc8..2d7a2bffe 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,6 +22,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased + +- add global `FetchSize` setting for use with Oracle (#1946 via mgravell, fixes #1945) (also add some missing logic in `Settings.Reset()`) - add underscore handling with constructors (#1786 via @jo-goro, fixes #818; also #1947 via mgravell) (note: new PRs will not be merged until they add release note wording here) From 63fb7bae11ebf9de02379b7a7b58b663101044ac Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 18 Aug 2023 10:47:57 +0100 Subject: [PATCH 228/312] release notes 2.0.151 --- docs/index.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/index.md b/docs/index.md index 2d7a2bffe..08230f58d 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,12 +22,13 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased +(note: new PRs will not be merged until they add release note wording here) + +### 2.0.151 - add global `FetchSize` setting for use with Oracle (#1946 via mgravell, fixes #1945) (also add some missing logic in `Settings.Reset()`) - add underscore handling with constructors (#1786 via @jo-goro, fixes #818; also #1947 via mgravell) -(note: new PRs will not be merged until they add release note wording here) - ### 2.0.143 - add missing non-generic `AsyncEnumerable QueryUnbufferedAsync(...)` API (#1925 via mgravell, fixes #1922) From 67f7a1fe173545a58ded63961570f5bc8ba73774 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 18 Aug 2023 11:47:37 +0100 Subject: [PATCH 229/312] add Microsoft.CodeAnalysis.PublicApiAnalyzers on the main libs (#1948) --- .../Dapper.EntityFramework.csproj | 5 + Dapper.EntityFramework/PublicAPI.Shipped.txt | 12 + .../PublicAPI.Unshipped.txt | 1 + Dapper.ProviderTools/BulkCopy.cs | 3 + .../Dapper.ProviderTools.csproj | 6 + Dapper.ProviderTools/PublicAPI.Shipped.txt | 30 ++ Dapper.ProviderTools/PublicAPI.Unshipped.txt | 1 + Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 5 + Dapper.SqlBuilder/PublicAPI.Shipped.txt | 21 ++ Dapper.SqlBuilder/PublicAPI.Unshipped.txt | 1 + Dapper/Dapper.csproj | 11 + Dapper/PublicAPI.Shipped.txt | 313 ++++++++++++++++++ Dapper/PublicAPI.Unshipped.txt | 1 + Dapper/PublicAPI/net461/PublicAPI.Shipped.txt | 1 + .../PublicAPI/net461/PublicAPI.Unshipped.txt | 1 + Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt | 4 + .../PublicAPI/net5.0/PublicAPI.Unshipped.txt | 1 + .../netstandard2.0/PublicAPI.Shipped.txt | 1 + .../netstandard2.0/PublicAPI.Unshipped.txt | 1 + Dapper/SqlMapper.Async.cs | 30 ++ Dapper/SqlMapper.IDataReader.cs | 2 + Dapper/SqlMapper.cs | 25 ++ 22 files changed, 476 insertions(+) create mode 100644 Dapper.EntityFramework/PublicAPI.Shipped.txt create mode 100644 Dapper.EntityFramework/PublicAPI.Unshipped.txt create mode 100644 Dapper.ProviderTools/PublicAPI.Shipped.txt create mode 100644 Dapper.ProviderTools/PublicAPI.Unshipped.txt create mode 100644 Dapper.SqlBuilder/PublicAPI.Shipped.txt create mode 100644 Dapper.SqlBuilder/PublicAPI.Unshipped.txt create mode 100644 Dapper/PublicAPI.Shipped.txt create mode 100644 Dapper/PublicAPI.Unshipped.txt create mode 100644 Dapper/PublicAPI/net461/PublicAPI.Shipped.txt create mode 100644 Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt create mode 100644 Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt create mode 100644 Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt create mode 100644 Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt create mode 100644 Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt diff --git a/Dapper.EntityFramework/Dapper.EntityFramework.csproj b/Dapper.EntityFramework/Dapper.EntityFramework.csproj index 13310a116..87b97b8d2 100644 --- a/Dapper.EntityFramework/Dapper.EntityFramework.csproj +++ b/Dapper.EntityFramework/Dapper.EntityFramework.csproj @@ -13,5 +13,10 @@ + + + all + runtime; build; native; contentfiles; analyzers + \ No newline at end of file diff --git a/Dapper.EntityFramework/PublicAPI.Shipped.txt b/Dapper.EntityFramework/PublicAPI.Shipped.txt new file mode 100644 index 000000000..d842ac329 --- /dev/null +++ b/Dapper.EntityFramework/PublicAPI.Shipped.txt @@ -0,0 +1,12 @@ +Dapper.EntityFramework.DbGeographyHandler +Dapper.EntityFramework.DbGeographyHandler.DbGeographyHandler() -> void +Dapper.EntityFramework.DbGeometryHandler +Dapper.EntityFramework.DbGeometryHandler.DbGeometryHandler() -> void +Dapper.EntityFramework.Handlers +override Dapper.EntityFramework.DbGeographyHandler.Parse(object value) -> System.Data.Entity.Spatial.DbGeography +override Dapper.EntityFramework.DbGeographyHandler.SetValue(System.Data.IDbDataParameter parameter, System.Data.Entity.Spatial.DbGeography value) -> void +override Dapper.EntityFramework.DbGeometryHandler.Parse(object value) -> System.Data.Entity.Spatial.DbGeometry +override Dapper.EntityFramework.DbGeometryHandler.SetValue(System.Data.IDbDataParameter parameter, System.Data.Entity.Spatial.DbGeometry value) -> void +static Dapper.EntityFramework.Handlers.Register() -> void +static readonly Dapper.EntityFramework.DbGeographyHandler.Default -> Dapper.EntityFramework.DbGeographyHandler +static readonly Dapper.EntityFramework.DbGeometryHandler.Default -> Dapper.EntityFramework.DbGeometryHandler \ No newline at end of file diff --git a/Dapper.EntityFramework/PublicAPI.Unshipped.txt b/Dapper.EntityFramework/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper.EntityFramework/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs index 3773b48f8..e1327daa0 100644 --- a/Dapper.ProviderTools/BulkCopy.cs +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -98,14 +98,17 @@ private static readonly ConcurrentDictionary?> /// /// Write a set of data to the server /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public abstract Task WriteToServerAsync(DbDataReader source, CancellationToken cancellationToken = default); /// /// Write a set of data to the server /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public abstract Task WriteToServerAsync(DataTable source, CancellationToken cancellationToken = default); /// /// Write a set of data to the server /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public abstract Task WriteToServerAsync(DataRow[] source, CancellationToken cancellationToken = default); /// /// Add a mapping between two columns by name diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index dbc92136d..7d10fe2e5 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -9,6 +9,12 @@ true enable + + + all + runtime; build; native; contentfiles; analyzers + + diff --git a/Dapper.ProviderTools/PublicAPI.Shipped.txt b/Dapper.ProviderTools/PublicAPI.Shipped.txt new file mode 100644 index 000000000..a8329b713 --- /dev/null +++ b/Dapper.ProviderTools/PublicAPI.Shipped.txt @@ -0,0 +1,30 @@ +#nullable enable +abstract Dapper.ProviderTools.BulkCopy.AddColumnMapping(int sourceColumn, int destinationColumn) -> void +abstract Dapper.ProviderTools.BulkCopy.AddColumnMapping(string! sourceColumn, string! destinationColumn) -> void +abstract Dapper.ProviderTools.BulkCopy.DestinationTableName.get -> string! +abstract Dapper.ProviderTools.BulkCopy.DestinationTableName.set -> void +abstract Dapper.ProviderTools.BulkCopy.Dispose(bool disposing) -> void +abstract Dapper.ProviderTools.BulkCopy.Wrapped.get -> object! +abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.DataRow![]! source) -> void +abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.DataTable! source) -> void +abstract Dapper.ProviderTools.BulkCopy.WriteToServer(System.Data.IDataReader! source) -> void +abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.Common.DbDataReader! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.DataRow![]! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +abstract Dapper.ProviderTools.BulkCopy.WriteToServerAsync(System.Data.DataTable! source, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task! +Dapper.ProviderTools.BulkCopy +Dapper.ProviderTools.BulkCopy.BatchSize.get -> int +Dapper.ProviderTools.BulkCopy.BatchSize.set -> void +Dapper.ProviderTools.BulkCopy.BulkCopy() -> void +Dapper.ProviderTools.BulkCopy.BulkCopyTimeout.get -> int +Dapper.ProviderTools.BulkCopy.BulkCopyTimeout.set -> void +Dapper.ProviderTools.BulkCopy.Dispose() -> void +Dapper.ProviderTools.BulkCopy.EnableStreaming.get -> bool +Dapper.ProviderTools.BulkCopy.EnableStreaming.set -> void +Dapper.ProviderTools.DbConnectionExtensions +Dapper.ProviderTools.DbExceptionExtensions +static Dapper.ProviderTools.BulkCopy.Create(System.Data.Common.DbConnection! connection) -> Dapper.ProviderTools.BulkCopy! +static Dapper.ProviderTools.BulkCopy.TryCreate(System.Data.Common.DbConnection! connection) -> Dapper.ProviderTools.BulkCopy? +static Dapper.ProviderTools.DbConnectionExtensions.TryClearAllPools(this System.Data.Common.DbConnection! connection) -> bool +static Dapper.ProviderTools.DbConnectionExtensions.TryClearPool(this System.Data.Common.DbConnection! connection) -> bool +static Dapper.ProviderTools.DbConnectionExtensions.TryGetClientConnectionId(this System.Data.Common.DbConnection! connection, out System.Guid clientConnectionId) -> bool +static Dapper.ProviderTools.DbExceptionExtensions.IsNumber(this System.Data.Common.DbException! exception, int number) -> bool \ No newline at end of file diff --git a/Dapper.ProviderTools/PublicAPI.Unshipped.txt b/Dapper.ProviderTools/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..91b0e1a43 --- /dev/null +++ b/Dapper.ProviderTools/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +#nullable enable \ No newline at end of file diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index 74fe251d3..10c2f8672 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -11,6 +11,11 @@ + + + all + runtime; build; native; contentfiles; analyzers + diff --git a/Dapper.SqlBuilder/PublicAPI.Shipped.txt b/Dapper.SqlBuilder/PublicAPI.Shipped.txt new file mode 100644 index 000000000..c96031ed7 --- /dev/null +++ b/Dapper.SqlBuilder/PublicAPI.Shipped.txt @@ -0,0 +1,21 @@ +Dapper.SqlBuilder +Dapper.SqlBuilder.AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false) -> Dapper.SqlBuilder +Dapper.SqlBuilder.AddParameters(dynamic parameters) -> Dapper.SqlBuilder +Dapper.SqlBuilder.AddTemplate(string sql, dynamic parameters = null) -> Dapper.SqlBuilder.Template +Dapper.SqlBuilder.GroupBy(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.Having(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.InnerJoin(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.Intersect(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.Join(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.LeftJoin(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.OrderBy(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.OrWhere(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.RightJoin(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.Select(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.Set(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +Dapper.SqlBuilder.SqlBuilder() -> void +Dapper.SqlBuilder.Template +Dapper.SqlBuilder.Template.Parameters.get -> object +Dapper.SqlBuilder.Template.RawSql.get -> string +Dapper.SqlBuilder.Template.Template(Dapper.SqlBuilder builder, string sql, dynamic parameters) -> void +Dapper.SqlBuilder.Where(string sql, dynamic parameters = null) -> Dapper.SqlBuilder \ No newline at end of file diff --git a/Dapper.SqlBuilder/PublicAPI.Unshipped.txt b/Dapper.SqlBuilder/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper.SqlBuilder/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 25f87ccf6..765065285 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -7,8 +7,19 @@ Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0;net5.0 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers + diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt new file mode 100644 index 000000000..82f27d6cb --- /dev/null +++ b/Dapper/PublicAPI.Shipped.txt @@ -0,0 +1,313 @@ +abstract Dapper.SqlMapper.StringTypeHandler.Format(T xml) -> string +abstract Dapper.SqlMapper.StringTypeHandler.Parse(string xml) -> T +abstract Dapper.SqlMapper.TypeHandler.Parse(object value) -> T +abstract Dapper.SqlMapper.TypeHandler.SetValue(System.Data.IDbDataParameter parameter, T value) -> void +const Dapper.DbString.DefaultLength = 4000 -> int +Dapper.CommandDefinition +Dapper.CommandDefinition.Buffered.get -> bool +Dapper.CommandDefinition.CancellationToken.get -> System.Threading.CancellationToken +Dapper.CommandDefinition.CommandDefinition() -> void +Dapper.CommandDefinition.CommandDefinition(string commandText, object parameters = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null, Dapper.CommandFlags flags = Dapper.CommandFlags.Buffered, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Dapper.CommandDefinition.CommandText.get -> string +Dapper.CommandDefinition.CommandTimeout.get -> int? +Dapper.CommandDefinition.CommandType.get -> System.Data.CommandType? +Dapper.CommandDefinition.Flags.get -> Dapper.CommandFlags +Dapper.CommandDefinition.Parameters.get -> object +Dapper.CommandDefinition.Pipelined.get -> bool +Dapper.CommandDefinition.Transaction.get -> System.Data.IDbTransaction +Dapper.CommandFlags +Dapper.CommandFlags.Buffered = 1 -> Dapper.CommandFlags +Dapper.CommandFlags.NoCache = 4 -> Dapper.CommandFlags +Dapper.CommandFlags.None = 0 -> Dapper.CommandFlags +Dapper.CommandFlags.Pipelined = 2 -> Dapper.CommandFlags +Dapper.CustomPropertyTypeMap +Dapper.CustomPropertyTypeMap.CustomPropertyTypeMap(System.Type type, System.Func propertySelector) -> void +Dapper.CustomPropertyTypeMap.FindConstructor(string[] names, System.Type[] types) -> System.Reflection.ConstructorInfo +Dapper.CustomPropertyTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo +Dapper.CustomPropertyTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.CustomPropertyTypeMap.GetMember(string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.DbString +Dapper.DbString.AddParameter(System.Data.IDbCommand command, string name) -> void +Dapper.DbString.DbString() -> void +Dapper.DbString.IsAnsi.get -> bool +Dapper.DbString.IsAnsi.set -> void +Dapper.DbString.IsFixedLength.get -> bool +Dapper.DbString.IsFixedLength.set -> void +Dapper.DbString.Length.get -> int +Dapper.DbString.Length.set -> void +Dapper.DbString.Value.get -> string +Dapper.DbString.Value.set -> void +Dapper.DefaultTypeMap +Dapper.DefaultTypeMap.DefaultTypeMap(System.Type type) -> void +Dapper.DefaultTypeMap.FindConstructor(string[] names, System.Type[] types) -> System.Reflection.ConstructorInfo +Dapper.DefaultTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo +Dapper.DefaultTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.DefaultTypeMap.GetMember(string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.DefaultTypeMap.Properties.get -> System.Collections.Generic.List +Dapper.DynamicParameters +Dapper.DynamicParameters.Add(string name, object value = null, System.Data.DbType? dbType = null, System.Data.ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) -> void +Dapper.DynamicParameters.Add(string name, object value, System.Data.DbType? dbType, System.Data.ParameterDirection? direction, int? size) -> void +Dapper.DynamicParameters.AddDynamicParams(object param) -> void +Dapper.DynamicParameters.AddParameters(System.Data.IDbCommand command, Dapper.SqlMapper.Identity identity) -> void +Dapper.DynamicParameters.DynamicParameters() -> void +Dapper.DynamicParameters.DynamicParameters(object template) -> void +Dapper.DynamicParameters.Get(string name) -> T +Dapper.DynamicParameters.Output(T target, System.Linq.Expressions.Expression> expression, System.Data.DbType? dbType = null, int? size = null) -> Dapper.DynamicParameters +Dapper.DynamicParameters.ParameterNames.get -> System.Collections.Generic.IEnumerable +Dapper.DynamicParameters.RemoveUnused.get -> bool +Dapper.DynamicParameters.RemoveUnused.set -> void +Dapper.ExplicitConstructorAttribute +Dapper.ExplicitConstructorAttribute.ExplicitConstructorAttribute() -> void +Dapper.IWrappedDataReader +Dapper.IWrappedDataReader.Command.get -> System.Data.IDbCommand +Dapper.IWrappedDataReader.Reader.get -> System.Data.IDataReader +Dapper.SqlMapper +Dapper.SqlMapper.GridReader +Dapper.SqlMapper.GridReader.Command.get -> System.Data.IDbCommand +Dapper.SqlMapper.GridReader.Command.set -> void +Dapper.SqlMapper.GridReader.Dispose() -> void +Dapper.SqlMapper.GridReader.IsConsumed.get -> bool +Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Type type, bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.Read(System.Type[] types, System.Func map, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable +Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task> +Dapper.SqlMapper.GridReader.ReadAsync(System.Type type, bool buffered = true) -> System.Threading.Tasks.Task> +Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task> +Dapper.SqlMapper.GridReader.ReadFirst() -> dynamic +Dapper.SqlMapper.GridReader.ReadFirst(System.Type type) -> object +Dapper.SqlMapper.GridReader.ReadFirst() -> T +Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadFirstAsync(System.Type type) -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> dynamic +Dapper.SqlMapper.GridReader.ReadFirstOrDefault(System.Type type) -> object +Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> T +Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync(System.Type type) -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadSingle() -> dynamic +Dapper.SqlMapper.GridReader.ReadSingle(System.Type type) -> object +Dapper.SqlMapper.GridReader.ReadSingle() -> T +Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadSingleAsync(System.Type type) -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> dynamic +Dapper.SqlMapper.GridReader.ReadSingleOrDefault(System.Type type) -> object +Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> T +Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync(System.Type type) -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.ICustomQueryParameter +Dapper.SqlMapper.ICustomQueryParameter.AddParameter(System.Data.IDbCommand command, string name) -> void +Dapper.SqlMapper.Identity +Dapper.SqlMapper.Identity.Equals(Dapper.SqlMapper.Identity other) -> bool +Dapper.SqlMapper.Identity.ForDynamicParameters(System.Type type) -> Dapper.SqlMapper.Identity +Dapper.SqlMapper.IDynamicParameters +Dapper.SqlMapper.IDynamicParameters.AddParameters(System.Data.IDbCommand command, Dapper.SqlMapper.Identity identity) -> void +Dapper.SqlMapper.IMemberMap +Dapper.SqlMapper.IMemberMap.ColumnName.get -> string +Dapper.SqlMapper.IMemberMap.Field.get -> System.Reflection.FieldInfo +Dapper.SqlMapper.IMemberMap.MemberType.get -> System.Type +Dapper.SqlMapper.IMemberMap.Parameter.get -> System.Reflection.ParameterInfo +Dapper.SqlMapper.IMemberMap.Property.get -> System.Reflection.PropertyInfo +Dapper.SqlMapper.IParameterCallbacks +Dapper.SqlMapper.IParameterCallbacks.OnCompleted() -> void +Dapper.SqlMapper.IParameterLookup +Dapper.SqlMapper.IParameterLookup.this[string name].get -> object +Dapper.SqlMapper.ITypeHandler +Dapper.SqlMapper.ITypeHandler.Parse(System.Type destinationType, object value) -> object +Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter parameter, object value) -> void +Dapper.SqlMapper.ITypeMap +Dapper.SqlMapper.ITypeMap.FindConstructor(string[] names, System.Type[] types) -> System.Reflection.ConstructorInfo +Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo +Dapper.SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.SqlMapper.ITypeMap.GetMember(string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.SqlMapper.Settings +Dapper.SqlMapper.StringTypeHandler +Dapper.SqlMapper.StringTypeHandler.StringTypeHandler() -> void +Dapper.SqlMapper.TypeHandler +Dapper.SqlMapper.TypeHandler.TypeHandler() -> void +Dapper.SqlMapper.TypeHandlerCache +Dapper.SqlMapper.UdtTypeHandler +Dapper.SqlMapper.UdtTypeHandler.UdtTypeHandler(string udtTypeName) -> void +override Dapper.DbString.ToString() -> string +override Dapper.SqlMapper.Identity.Equals(object obj) -> bool +override Dapper.SqlMapper.Identity.GetHashCode() -> int +override Dapper.SqlMapper.Identity.ToString() -> string +override Dapper.SqlMapper.StringTypeHandler.Parse(object value) -> T +override Dapper.SqlMapper.StringTypeHandler.SetValue(System.Data.IDbDataParameter parameter, T value) -> void +readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType? +readonly Dapper.SqlMapper.Identity.connectionString -> string +readonly Dapper.SqlMapper.Identity.gridIndex -> int +readonly Dapper.SqlMapper.Identity.hashCode -> int +readonly Dapper.SqlMapper.Identity.parametersType -> System.Type +readonly Dapper.SqlMapper.Identity.sql -> string +readonly Dapper.SqlMapper.Identity.type -> System.Type +static Dapper.DbString.IsAnsiDefault.get -> bool +static Dapper.DbString.IsAnsiDefault.set -> void +static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.get -> bool +static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.set -> void +static Dapper.SqlMapper.AddTypeHandler(System.Type type, Dapper.SqlMapper.ITypeHandler handler) -> void +static Dapper.SqlMapper.AddTypeHandler(Dapper.SqlMapper.TypeHandler handler) -> void +static Dapper.SqlMapper.AddTypeHandlerImpl(System.Type type, Dapper.SqlMapper.ITypeHandler handler, bool clone) -> void +static Dapper.SqlMapper.AddTypeMap(System.Type type, System.Data.DbType dbType) -> void +static Dapper.SqlMapper.AddTypeMap(System.Type type, System.Data.DbType dbType, bool useGetFieldValue) -> void +static Dapper.SqlMapper.AsList(this System.Collections.Generic.IEnumerable source) -> System.Collections.Generic.List +static Dapper.SqlMapper.AsTableValuedParameter(this System.Data.DataTable table, string typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter +static Dapper.SqlMapper.AsTableValuedParameter(this System.Collections.Generic.IEnumerable list, string typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter +static Dapper.SqlMapper.ConnectionStringComparer.get -> System.Collections.Generic.IEqualityComparer +static Dapper.SqlMapper.ConnectionStringComparer.set -> void +static Dapper.SqlMapper.CreateParamInfoGenerator(Dapper.SqlMapper.Identity identity, bool checkForDuplicates, bool removeUnused) -> System.Action +static Dapper.SqlMapper.Execute(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> int +static Dapper.SqlMapper.Execute(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> int +static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Data.IDataReader +static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Data.IDataReader +static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Data.IDataReader +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> object +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.FindOrAddParameter(System.Data.IDataParameterCollection parameters, System.Data.IDbCommand command, string name) -> System.Data.IDbDataParameter +static Dapper.SqlMapper.Format(object value) -> string +static Dapper.SqlMapper.GetCachedSQL(int ignoreHitCountAbove = 2147483647) -> System.Collections.Generic.IEnumerable> +static Dapper.SqlMapper.GetCachedSQLCount() -> int +static Dapper.SqlMapper.GetHashCollissions() -> System.Collections.Generic.IEnumerable> +static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader reader, System.Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func +static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader reader, System.Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func +static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader reader, System.Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func +static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader reader, System.Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func +static Dapper.SqlMapper.GetTypeDeserializer(System.Type type, System.Data.Common.DbDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func +static Dapper.SqlMapper.GetTypeDeserializer(System.Type type, System.Data.IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func +static Dapper.SqlMapper.GetTypeMap(System.Type type) -> Dapper.SqlMapper.ITypeMap +static Dapper.SqlMapper.GetTypeName(this System.Data.DataTable table) -> string +static Dapper.SqlMapper.HasTypeHandler(System.Type type) -> bool +static Dapper.SqlMapper.LookupDbType(System.Type type, string name, bool demand, out Dapper.SqlMapper.ITypeHandler handler) -> System.Data.DbType? +static Dapper.SqlMapper.PackListParameters(System.Data.IDbCommand command, string namePrefix, object value) -> void +static Dapper.SqlMapper.Parse(this System.Data.IDataReader reader) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Parse(this System.Data.IDataReader reader, System.Type type) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Parse(this System.Data.IDataReader reader) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.PurgeQueryCache() -> void +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Type[] types, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Type[] types, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> +static Dapper.SqlMapper.QueryCachePurged -> System.EventHandler +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> Dapper.SqlMapper.GridReader +static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> Dapper.SqlMapper.GridReader +static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task +static Dapper.SqlMapper.ReadChar(object value) -> char +static Dapper.SqlMapper.ReadNullableChar(object value) -> char? +static Dapper.SqlMapper.RemoveTypeMap(System.Type type) -> void +static Dapper.SqlMapper.ReplaceLiterals(this Dapper.SqlMapper.IParameterLookup parameters, System.Data.IDbCommand command) -> void +static Dapper.SqlMapper.ResetTypeHandlers() -> void +static Dapper.SqlMapper.SanitizeParameterValue(object value) -> object +static Dapper.SqlMapper.SetDbType(System.Data.IDataParameter parameter, object value) -> void +static Dapper.SqlMapper.Settings.ApplyNullValues.get -> bool +static Dapper.SqlMapper.Settings.ApplyNullValues.set -> void +static Dapper.SqlMapper.Settings.CommandTimeout.get -> int? +static Dapper.SqlMapper.Settings.CommandTimeout.set -> void +static Dapper.SqlMapper.Settings.FetchSize.get -> long +static Dapper.SqlMapper.Settings.FetchSize.set -> void +static Dapper.SqlMapper.Settings.InListStringSplitCount.get -> int +static Dapper.SqlMapper.Settings.InListStringSplitCount.set -> void +static Dapper.SqlMapper.Settings.PadListExpansions.get -> bool +static Dapper.SqlMapper.Settings.PadListExpansions.set -> void +static Dapper.SqlMapper.Settings.SetDefaults() -> void +static Dapper.SqlMapper.Settings.UseIncrementalPseudoPositionalParameterNames.get -> bool +static Dapper.SqlMapper.Settings.UseIncrementalPseudoPositionalParameterNames.set -> void +static Dapper.SqlMapper.Settings.UseSingleResultOptimization.get -> bool +static Dapper.SqlMapper.Settings.UseSingleResultOptimization.set -> void +static Dapper.SqlMapper.Settings.UseSingleRowOptimization.get -> bool +static Dapper.SqlMapper.Settings.UseSingleRowOptimization.set -> void +static Dapper.SqlMapper.SetTypeMap(System.Type type, Dapper.SqlMapper.ITypeMap map) -> void +static Dapper.SqlMapper.SetTypeName(this System.Data.DataTable table, string typeName) -> void +static Dapper.SqlMapper.ThrowDataException(System.Exception ex, int index, System.Data.IDataReader reader, object value) -> void +static Dapper.SqlMapper.TypeHandlerCache.Parse(object value) -> T +static Dapper.SqlMapper.TypeHandlerCache.SetValue(System.Data.IDbDataParameter parameter, object value) -> void +static Dapper.SqlMapper.TypeMapProvider -> System.Func \ No newline at end of file diff --git a/Dapper/PublicAPI.Unshipped.txt b/Dapper/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper/PublicAPI/net461/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net461/PublicAPI.Shipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper/PublicAPI/net461/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..0a026495a --- /dev/null +++ b/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt @@ -0,0 +1,4 @@ +Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection cnn, string sql, object param = null, System.Data.Common.DbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection cnn, string sql, object param = null, System.Data.Common.DbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable \ No newline at end of file diff --git a/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..5f282702b --- /dev/null +++ b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 78d3e04c5..25b7e7125 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -23,6 +23,7 @@ public static partial class SqlMapper /// The command timeout (in seconds). /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); @@ -85,6 +86,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Co /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); @@ -98,6 +100,7 @@ public static Task> QueryAsync(this IDbConnection cnn, string /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -111,6 +114,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, obj /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -124,6 +128,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -137,6 +142,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, ob /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -149,6 +155,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, strin /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -161,6 +168,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -173,6 +181,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, str /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -185,6 +194,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, /// The transaction to use, if any. /// The command timeout (in seconds). /// The type of command to execute. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -199,6 +209,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, st /// The command timeout (in seconds). /// The type of command to execute. /// is null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -216,6 +227,7 @@ public static Task> QueryAsync(this IDbConnection cnn, Type /// The command timeout (in seconds). /// The type of command to execute. /// is null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -232,6 +244,7 @@ public static Task QueryFirstAsync(this IDbConnection cnn, Type type, st /// The command timeout (in seconds). /// The type of command to execute. /// is null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -248,6 +261,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type /// The command timeout (in seconds). /// The type of command to execute. /// is null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -264,6 +278,7 @@ public static Task QuerySingleAsync(this IDbConnection cnn, Type type, s /// The command timeout (in seconds). /// The type of command to execute. /// is null. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -671,6 +686,7 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -687,6 +703,7 @@ public static Task> QueryAsync(th /// The command to execute. /// The function to map row types to the return type. /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); @@ -708,6 +725,7 @@ public static Task> QueryAsync(th /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -725,6 +743,7 @@ public static Task> QueryAsyncThe command to execute. /// The function to map row types to the return type. /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); @@ -747,6 +766,7 @@ public static Task> QueryAsyncNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -765,6 +785,7 @@ public static Task> QueryAsyncThe command to execute. /// The function to map row types to the return type. /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); @@ -788,6 +809,7 @@ public static Task> QueryAsyncNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -807,6 +829,7 @@ public static Task> QueryAsyncThe command to execute. /// The function to map row types to the return type. /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); @@ -831,6 +854,7 @@ public static Task> QueryAsyncNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -851,6 +875,7 @@ public static Task> QueryAsyncThe command to execute. /// The function to map row types to the return type. /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); @@ -876,6 +901,7 @@ public static Task> QueryAsyncNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -897,6 +923,7 @@ public static Task> QueryAsyncThe command to execute. /// The function to map row types to the return type. /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, CommandDefinition command, Func map, string splitOn = "Id") => MultiMapAsync(cnn, command, map, splitOn); @@ -937,6 +964,7 @@ private static async Task> MultiMapAsyncNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default); @@ -1064,6 +1092,7 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, /// ]]> /// /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default).CastResult(); @@ -1076,6 +1105,7 @@ public static Task ExecuteReaderAsync(this IDbConnection cnn, strin /// The transaction to use for this command. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Task ExecuteReaderAsync(this DbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs index ef292d952..b3780c049 100644 --- a/Dapper/SqlMapper.IDataReader.cs +++ b/Dapper/SqlMapper.IDataReader.cs @@ -83,6 +83,7 @@ public static IEnumerable Parse(this IDataReader reader) #if DEBUG // make sure we're not using this internally [Obsolete(nameof(DbDataReader) + " API should be preferred")] #endif + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Func GetRowParser(this IDataReader reader, Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { @@ -109,6 +110,7 @@ public static Func GetRowParser(this DbDataReader reader, #if DEBUG // make sure we're not using this internally [Obsolete(nameof(DbDataReader) + " API should be preferred")] #endif + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Func GetRowParser(this IDataReader reader, Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index daa62a3ec..369d71e7b 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -724,6 +724,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio /// The command timeout (in seconds). /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => Query(cnn, sql, param, transaction, buffered, commandTimeout, commandType); @@ -737,6 +738,7 @@ public static IEnumerable Query(this IDbConnection cnn, string sql, obj /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryFirst(cnn, sql, param, transaction, commandTimeout, commandType); @@ -750,6 +752,7 @@ public static dynamic QueryFirst(this IDbConnection cnn, string sql, object para /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryFirstOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); @@ -763,6 +766,7 @@ public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, ob /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QuerySingle(cnn, sql, param, transaction, commandTimeout, commandType); @@ -776,6 +780,7 @@ public static dynamic QuerySingle(this IDbConnection cnn, string sql, object par /// The command timeout (in seconds). /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QuerySingleOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); @@ -794,6 +799,7 @@ public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, o /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); @@ -815,6 +821,7 @@ public static IEnumerable Query(this IDbConnection cnn, string sql, object /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); @@ -835,6 +842,7 @@ public static T QueryFirst(this IDbConnection cnn, string sql, object param = /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); @@ -855,6 +863,7 @@ public static T QueryFirstOrDefault(this IDbConnection cnn, string sql, objec /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); @@ -875,6 +884,7 @@ public static T QuerySingle(this IDbConnection cnn, string sql, object param /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); @@ -897,6 +907,7 @@ public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, obje /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -920,6 +931,7 @@ public static IEnumerable Query(this IDbConnection cnn, Type type, strin /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object QueryFirst(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -942,6 +954,7 @@ public static object QueryFirst(this IDbConnection cnn, Type type, string sql, o /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object QueryFirstOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -964,6 +977,7 @@ public static object QueryFirstOrDefault(this IDbConnection cnn, Type type, stri /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object QuerySingle(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -986,6 +1000,7 @@ public static object QuerySingle(this IDbConnection cnn, Type type, string sql, /// A sequence of data of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static object QuerySingleOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) { if (type == null) throw new ArgumentNullException(nameof(type)); @@ -1369,6 +1384,7 @@ private static T GetValue(DbDataReader reader, Type effectiveType, object val /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); @@ -1390,6 +1406,7 @@ public static IEnumerable Query(this IDbConne /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); @@ -1412,6 +1429,7 @@ public static IEnumerable Query(this /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); @@ -1435,6 +1453,7 @@ public static IEnumerable QueryNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); @@ -1459,6 +1478,7 @@ public static IEnumerable QueryNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); @@ -1484,6 +1504,7 @@ public static IEnumerable QueryNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); @@ -1503,6 +1524,7 @@ public static IEnumerable QueryNumber of seconds before command execution timeout. /// Is it a stored proc or a batch? /// An enumerable of . + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static IEnumerable Query(this IDbConnection cnn, string sql, Type[] types, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); @@ -3194,6 +3216,7 @@ public static void SetTypeMap(Type type, ITypeMap map) #if DEBUG // make sure we're not using this internally [Obsolete(nameof(DbDataReader) + " API should be preferred")] #endif + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static Func GetTypeDeserializer( Type type, IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false ) @@ -3929,6 +3952,7 @@ public static IEqualityComparer ConnectionStringComparer /// /// The to create this parameter for. /// The name of the type this parameter is for. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName = null) => new TableValuedParameter(table, typeName); @@ -3960,6 +3984,7 @@ public static string GetTypeName(this DataTable table) => /// /// The list of records to convert to TVPs. /// The sql parameter type name. + [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) where T : IDataRecord => new SqlDataRecordListTVPParameter(list, typeName); From ff98f8dbf1184ea155cce25ea01af94a31fb043c Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 21 Aug 2023 11:29:37 +0100 Subject: [PATCH 230/312] answer #1950 using BDN (#1951) --- .../Benchmarks.Linq2DB.cs | 2 +- .../Benchmarks.Linq2Sql.cs | 2 +- .../Dapper.Tests.Performance.csproj | 2 +- .../DapperCacheImpact.cs | 39 +++++++++++++++++++ 4 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs index ef2610183..cb9aaacd2 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2DB.cs @@ -10,7 +10,7 @@ namespace Dapper.Tests.Performance { [Description("LINQ to DB")] - public class Linq2DBBenchmarks : BenchmarkBase + public class LinqToDBBenchmarks : BenchmarkBase // note To not 2 because the "2" confuses BDN CLI { private Linq2DBContext _dbContext; diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs index dcee70577..d6cc55049 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.Linq2Sql.cs @@ -9,7 +9,7 @@ namespace Dapper.Tests.Performance { [Description("LINQ to SQL")] - public class Linq2SqlBenchmarks : BenchmarkBase + public class LinqToSqlBenchmarks : BenchmarkBase // note To not 2 because the "2" confuses BDN CLI { private DataClassesDataContext Linq2SqlContext; diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index a9b419994..0a7f71582 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -3,7 +3,7 @@ Dapper.Tests.Performance Dapper Core Performance Suite Exe - net462;netcoreapp3.1 + net462;net5.0 false $(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060 diff --git a/benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs b/benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs new file mode 100644 index 000000000..9e17249bf --- /dev/null +++ b/benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs @@ -0,0 +1,39 @@ +using System.ComponentModel; +using BenchmarkDotNet.Attributes; + +namespace Dapper.Tests.Performance +{ + [Description("Dapper cache impact")] + [MemoryDiagnoser] + public class DapperCacheImpact : BenchmarkBase + { + [GlobalSetup] + public void Setup() => BaseSetup(); + + private object args = new { Id = 42, Name = "abc" }; + + public class Foo + { + public int Id { get; set; } + public string Name { get; set; } + } + + // note: custom BDN setup means [Params] is awkward; unroll manually instead + [Benchmark] + public void ExecuteNoParameters_Cache() => _connection.Execute(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.None)); + [Benchmark] + public void ExecuteParameters_Cache() => _connection.Execute(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.None)); + [Benchmark] + public void QueryFirstNoParameters_Cache() => _connection.QueryFirst(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.None)); + [Benchmark] + public void QueryFirstParameters_Cache() => _connection.QueryFirst(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.None)); + [Benchmark] + public void ExecuteNoParameters_NoCache() => _connection.Execute(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.NoCache)); + [Benchmark] + public void ExecuteParameters_NoCache() => _connection.Execute(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.NoCache)); + [Benchmark] + public void QueryFirstNoParameters_NoCache() => _connection.QueryFirst(new CommandDefinition("select '42' as Id, 'abc' as Name", flags: CommandFlags.NoCache)); + [Benchmark] + public void QueryFirstParameters_NoCache() => _connection.QueryFirst(new CommandDefinition("select @id as Id, @name as Name", args, flags: CommandFlags.NoCache)); + } +} From 22adf2488a4e057832e0fa5137ee26ef432f0cce Mon Sep 17 00:00:00 2001 From: William Cole Boren <64283810+williycole@users.noreply.github.com> Date: Thu, 24 Aug 2023 06:56:23 -0500 Subject: [PATCH 231/312] Missing curly brace from code example (#1830) * Update Readme.md * Added additional braces * Update Readme.md --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 1a6061b8c..218c1ed52 100644 --- a/Readme.md +++ b/Readme.md @@ -368,7 +368,7 @@ Ansi Strings and varchar Dapper supports varchar params, if you are executing a where clause on a varchar column using a param be sure to pass it in this way: ```csharp -Query("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }); +Query("select * from Thing where Name = @Name", new {Name = new DbString { Value = "abcde", IsFixedLength = true, Length = 10, IsAnsi = true }}); ``` On SQL Server it is crucial to use the unicode when querying unicode and ANSI when querying non unicode. From ff913be39109a1fdac9caa5a32ff3d32d9acb2a0 Mon Sep 17 00:00:00 2001 From: Lehonti Ramos <17771375+Lehonti@users.noreply.github.com> Date: Thu, 24 Aug 2023 14:12:33 +0200 Subject: [PATCH 232/312] Removed block nesting levels in a few tests by using block-scoped `using` statements (#1939) Co-authored-by: John Doe --- tests/Dapper.Tests/AsyncTests.cs | 148 +++++----- tests/Dapper.Tests/DecimalTests.cs | 8 +- tests/Dapper.Tests/EnumTests.cs | 22 +- tests/Dapper.Tests/Helpers/Attributes.cs | 34 +-- tests/Dapper.Tests/MiscTests.cs | 46 ++- tests/Dapper.Tests/MultiMapTests.cs | 12 +- tests/Dapper.Tests/ProviderTests.cs | 62 ++-- tests/Dapper.Tests/Providers/FirebirdTests.cs | 51 ++-- tests/Dapper.Tests/Providers/MySQLTests.cs | 146 +++++----- tests/Dapper.Tests/Providers/OLDEBTests.cs | 268 ++++++++---------- .../Dapper.Tests/Providers/PostgresqlTests.cs | 93 +++--- tests/Dapper.Tests/Providers/SqliteTests.cs | 81 +++--- tests/Dapper.Tests/QueryMultipleTests.cs | 184 ++++++------ 13 files changed, 521 insertions(+), 634 deletions(-) diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index 8309a22ca..609c16ad6 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -224,12 +224,10 @@ public void TestLongOperationWithCancellation() [Fact] public async Task TestBasicStringUsageClosedAsync() { - using (var conn = GetClosedConnection()) - { - var query = await conn.QueryAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); - var arr = query.ToArray(); - Assert.Equal(new[] { "abc", "def" }, arr); - } + using var conn = GetClosedConnection(); + var query = await conn.QueryAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); + var arr = query.ToArray(); + Assert.Equal(new[] { "abc", "def" }, arr); } [Fact] @@ -258,12 +256,10 @@ public async Task TestExecuteAsync() [Fact] public void TestExecuteClosedConnAsyncInner() { - using (var conn = GetClosedConnection()) - { - var query = conn.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }); - var val = query.Result; - Assert.Equal(1, val); - } + using var conn = GetClosedConnection(); + var query = conn.ExecuteAsync("declare @foo table(id int not null); insert @foo values(@id);", new { id = 1 }); + var val = query.Result; + Assert.Equal(1, val); } [Fact] @@ -307,83 +303,69 @@ public async Task TestMultiMapArbitraryWithSplitAsync() public async Task TestMultiMapWithSplitClosedConnAsync() { const string sql = "select 1 as id, 'abc' as name, 2 as id, 'def' as name"; - using (var conn = GetClosedConnection()) + + using var conn = GetClosedConnection(); + + var productQuery = await conn.QueryAsync(sql, (prod, cat) => { - var productQuery = await conn.QueryAsync(sql, (prod, cat) => - { - prod.Category = cat; - return prod; - }).ConfigureAwait(false); - - var product = productQuery.First(); - // assertions - Assert.Equal(1, product.Id); - Assert.Equal("abc", product.Name); - Assert.Equal(2, product.Category.Id); - Assert.Equal("def", product.Category.Name); - } + prod.Category = cat; + return prod; + }).ConfigureAwait(false); + + var product = productQuery.First(); + // assertions + Assert.Equal(1, product.Id); + Assert.Equal("abc", product.Name); + Assert.Equal(2, product.Category.Id); + Assert.Equal("def", product.Category.Name); } [Fact] public async Task TestMultiAsync() { - using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false)) - { - Assert.Equal(1, multi.ReadAsync().Result.Single()); - Assert.Equal(2, multi.ReadAsync().Result.Single()); - } + using SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false); + Assert.Equal(1, multi.ReadAsync().Result.Single()); + Assert.Equal(2, multi.ReadAsync().Result.Single()); } [Fact] public async Task TestMultiConversionAsync() { - using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2").ConfigureAwait(false)) - { - Assert.Equal(1, multi.ReadAsync().Result.Single()); - Assert.Equal(2, multi.ReadAsync().Result.Single()); - } + using SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2").ConfigureAwait(false); + Assert.Equal(1, multi.ReadAsync().Result.Single()); + Assert.Equal(2, multi.ReadAsync().Result.Single()); } [Fact] public async Task TestMultiAsyncViaFirstOrDefault() { - using (SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5").ConfigureAwait(false)) - { - Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); - Assert.Equal(2, multi.ReadAsync().Result.Single()); - Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); - Assert.Equal(4, multi.ReadAsync().Result.Single()); - Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); - } + using SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5").ConfigureAwait(false); + Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); + Assert.Equal(2, multi.ReadAsync().Result.Single()); + Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); + Assert.Equal(4, multi.ReadAsync().Result.Single()); + Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); } [Fact] public async Task TestMultiClosedConnAsync() { - using (var conn = GetClosedConnection()) - { - using (SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false)) - { - Assert.Equal(1, multi.ReadAsync().Result.Single()); - Assert.Equal(2, multi.ReadAsync().Result.Single()); - } - } + using var conn = GetClosedConnection(); + using SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false); + Assert.Equal(1, multi.ReadAsync().Result.Single()); + Assert.Equal(2, multi.ReadAsync().Result.Single()); } [Fact] public async Task TestMultiClosedConnAsyncViaFirstOrDefault() { - using (var conn = GetClosedConnection()) - { - using (SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5").ConfigureAwait(false)) - { - Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); - Assert.Equal(2, multi.ReadAsync().Result.Single()); - Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); - Assert.Equal(4, multi.ReadAsync().Result.Single()); - Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); - } - } + using var conn = GetClosedConnection(); + using SqlMapper.GridReader multi = await conn.QueryMultipleAsync("select 1; select 2; select 3; select 4; select 5").ConfigureAwait(false); + Assert.Equal(1, multi.ReadFirstOrDefaultAsync().Result); + Assert.Equal(2, multi.ReadAsync().Result.Single()); + Assert.Equal(3, multi.ReadFirstOrDefaultAsync().Result); + Assert.Equal(4, multi.ReadAsync().Result.Single()); + Assert.Equal(5, multi.ReadFirstOrDefaultAsync().Result); } [Fact] @@ -402,17 +384,15 @@ public async Task ExecuteReaderOpenAsync() [Fact] public async Task ExecuteReaderClosedAsync() { - using (var conn = GetClosedConnection()) - { - var dt = new DataTable(); - dt.Load(await conn.ExecuteReaderAsync("select 3 as [three], 4 as [four]").ConfigureAwait(false)); - 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]); - } + using var conn = GetClosedConnection(); + var dt = new DataTable(); + dt.Load(await conn.ExecuteReaderAsync("select 3 as [three], 4 as [four]").ConfigureAwait(false)); + 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] @@ -424,7 +404,8 @@ public async Task LiteralReplacementOpen() [Fact] public async Task LiteralReplacementClosed() { - using (var conn = GetClosedConnection()) await LiteralReplacement(conn).ConfigureAwait(false); + using var conn = GetClosedConnection(); + await LiteralReplacement(conn).ConfigureAwait(false); } private static async Task LiteralReplacement(IDbConnection conn) @@ -453,7 +434,8 @@ public async Task LiteralReplacementDynamicOpen() [Fact] public async Task LiteralReplacementDynamicClosed() { - using (var conn = GetClosedConnection()) await LiteralReplacementDynamic(conn).ConfigureAwait(false); + using var conn = GetClosedConnection(); + await LiteralReplacementDynamic(conn).ConfigureAwait(false); } private static async Task LiteralReplacementDynamic(IDbConnection conn) @@ -917,14 +899,12 @@ select @@Name [Fact] public async Task Issue1281_DataReaderOutOfOrderAsync() { - using (var reader = await connection.ExecuteReaderAsync("Select 0, 1, 2").ConfigureAwait(false)) - { - Assert.True(reader.Read()); - Assert.Equal(2, reader.GetInt32(2)); - Assert.Equal(0, reader.GetInt32(0)); - Assert.Equal(1, reader.GetInt32(1)); - Assert.False(reader.Read()); - } + using var reader = await connection.ExecuteReaderAsync("Select 0, 1, 2").ConfigureAwait(false); + Assert.True(reader.Read()); + Assert.Equal(2, reader.GetInt32(2)); + Assert.Equal(0, reader.GetInt32(0)); + Assert.Equal(1, reader.GetInt32(1)); + Assert.False(reader.Read()); } [Fact] diff --git a/tests/Dapper.Tests/DecimalTests.cs b/tests/Dapper.Tests/DecimalTests.cs index 05b0e4f91..9e19f4ec0 100644 --- a/tests/Dapper.Tests/DecimalTests.cs +++ b/tests/Dapper.Tests/DecimalTests.cs @@ -34,11 +34,9 @@ private void Issue261_Decimals_ADONET(bool setPrecisionScaleViaAbstractApi) { try { - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "create proc #Issue261Direct @c decimal(10,5) OUTPUT as begin set @c=11.884 end"; - cmd.ExecuteNonQuery(); - } + using var cmd = connection.CreateCommand(); + cmd.CommandText = "create proc #Issue261Direct @c decimal(10,5) OUTPUT as begin set @c=11.884 end"; + cmd.ExecuteNonQuery(); } catch { /* we don't care that it already exists */ } diff --git a/tests/Dapper.Tests/EnumTests.cs b/tests/Dapper.Tests/EnumTests.cs index 1698e98d0..2e65353ad 100644 --- a/tests/Dapper.Tests/EnumTests.cs +++ b/tests/Dapper.Tests/EnumTests.cs @@ -92,18 +92,16 @@ private class TestEnumClassNoNull [Fact] public void AdoNetEnumValue() { - using (var cmd = connection.CreateCommand()) - { - cmd.CommandText = "select @foo"; - var p = cmd.CreateParameter(); - p.ParameterName = "@foo"; - p.DbType = DbType.Int32; // it turns out that this is the key piece; setting the DbType - p.Value = AnEnum.B; - cmd.Parameters.Add(p); - object value = cmd.ExecuteScalar(); - AnEnum val = (AnEnum)value; - Assert.Equal(AnEnum.B, val); - } + using var cmd = connection.CreateCommand(); + cmd.CommandText = "select @foo"; + var p = cmd.CreateParameter(); + p.ParameterName = "@foo"; + p.DbType = DbType.Int32; // it turns out that this is the key piece; setting the DbType + p.Value = AnEnum.B; + cmd.Parameters.Add(p); + object value = cmd.ExecuteScalar(); + AnEnum val = (AnEnum)value; + Assert.Equal(AnEnum.B, val); } [Fact] diff --git a/tests/Dapper.Tests/Helpers/Attributes.cs b/tests/Dapper.Tests/Helpers/Attributes.cs index 94a23dc78..d4a376bd2 100644 --- a/tests/Dapper.Tests/Helpers/Attributes.cs +++ b/tests/Dapper.Tests/Helpers/Attributes.cs @@ -59,14 +59,12 @@ public FactRequiredCompatibilityLevelAttribute(int level) : base() public static readonly int DetectedLevel; static FactRequiredCompatibilityLevelAttribute() { - using (var conn = DatabaseProvider.Instance.GetOpenConnection()) + using var conn = DatabaseProvider.Instance.GetOpenConnection(); + try { - try - { - DetectedLevel = conn.QuerySingle("SELECT compatibility_level FROM sys.databases where name = DB_NAME()"); - } - catch { /* don't care */ } + DetectedLevel = conn.QuerySingle("SELECT compatibility_level FROM sys.databases where name = DB_NAME()"); } + catch { /* don't care */ } } } @@ -84,20 +82,18 @@ public FactUnlessCaseSensitiveDatabaseAttribute() : base() public static readonly bool IsCaseSensitive; static FactUnlessCaseSensitiveDatabaseAttribute() { - using (var conn = DatabaseProvider.Instance.GetOpenConnection()) + using var conn = DatabaseProvider.Instance.GetOpenConnection(); + try { - try - { - conn.Execute("declare @i int; set @I = 1;"); - } - catch (Exception ex) when (ex.GetType().Name == "SqlException") - { - int err = ((dynamic)ex).Number; - if (err == 137) - IsCaseSensitive = true; - else - throw; - } + conn.Execute("declare @i int; set @I = 1;"); + } + catch (Exception ex) when (ex.GetType().Name == "SqlException") + { + int err = ((dynamic)ex).Number; + if (err == 137) + IsCaseSensitive = true; + else + throw; } } } diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 608607a0d..59f34519e 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -909,42 +909,34 @@ public Issue40_User() [Fact] public void ExecuteFromClosed() { - using (var conn = GetClosedConnection()) - { - conn.Execute("-- nop"); - Assert.Equal(ConnectionState.Closed, conn.State); - } + 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); - } + 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); - } + 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); - } + using var conn = GetClosedConnection(); + Assert.ThrowsAny(() => conn.Query("select gibberish").Single()); + Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] @@ -1148,13 +1140,11 @@ public void Issue178_SqlServer() 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()); - } + using IDataReader reader1 = sqlCmd.ExecuteReader(); + Assert.True(reader1.Read()); + Assert.Equal(0, reader1.GetInt32(0)); + Assert.False(reader1.Read()); + Assert.False(reader1.NextResult()); } // dapper diff --git a/tests/Dapper.Tests/MultiMapTests.cs b/tests/Dapper.Tests/MultiMapTests.cs index 097d95cd4..0cb5681f7 100644 --- a/tests/Dapper.Tests/MultiMapTests.cs +++ b/tests/Dapper.Tests/MultiMapTests.cs @@ -132,13 +132,11 @@ private class Multi2 [Fact] public void QueryMultimapFromClosed() { - using (var conn = GetClosedConnection()) - { - Assert.Equal(ConnectionState.Closed, conn.State); - var i = conn.Query("select 2 as [Id], 3 as [Id]", (x, y) => x.Id + y.Id).Single(); - Assert.Equal(ConnectionState.Closed, conn.State); - Assert.Equal(5, i); - } + using var conn = GetClosedConnection(); + Assert.Equal(ConnectionState.Closed, conn.State); + var i = conn.Query("select 2 as [Id], 3 as [Id]", (x, y) => x.Id + y.Id).Single(); + Assert.Equal(ConnectionState.Closed, conn.State); + Assert.Equal(5, i); } [Fact] diff --git a/tests/Dapper.Tests/ProviderTests.cs b/tests/Dapper.Tests/ProviderTests.cs index be51af1d0..e3a553c5f 100644 --- a/tests/Dapper.Tests/ProviderTests.cs +++ b/tests/Dapper.Tests/ProviderTests.cs @@ -10,19 +10,15 @@ public class ProviderTests [Fact] public void BulkCopy_SystemDataSqlClient() { - using (var conn = new System.Data.SqlClient.SqlConnection()) - { - Test(conn); - } + using var conn = new System.Data.SqlClient.SqlConnection(); + Test(conn); } [Fact] public void BulkCopy_MicrosoftDataSqlClient() { - using (var conn = new Microsoft.Data.SqlClient.SqlConnection()) - { - Test(conn); - } + using var conn = new Microsoft.Data.SqlClient.SqlConnection(); + Test(conn); } [Fact] @@ -55,41 +51,33 @@ private static void TestClientId() where T : SqlServerDatabaseProvider, new() { var provider = new T(); - using (var conn = provider.GetOpenConnection()) - { - Assert.True(conn.TryGetClientConnectionId(out var id)); - Assert.NotEqual(Guid.Empty, id); - } + using var conn = provider.GetOpenConnection(); + Assert.True(conn.TryGetClientConnectionId(out var id)); + Assert.NotEqual(Guid.Empty, id); } private static void ClearPool() where T : SqlServerDatabaseProvider, new() { var provider = new T(); - using (var conn = provider.GetOpenConnection()) - { - Assert.True(conn.TryClearPool()); - } + using var conn = provider.GetOpenConnection(); + Assert.True(conn.TryClearPool()); } private static void ClearAllPools() where T : SqlServerDatabaseProvider, new() { var provider = new T(); - using (var conn = provider.GetOpenConnection()) - { - Assert.True(conn.TryClearAllPools()); - } + using var conn = provider.GetOpenConnection(); + Assert.True(conn.TryClearAllPools()); } private static void Test(DbConnection connection) { - using (var bcp = BulkCopy.TryCreate(connection)) - { - Assert.NotNull(bcp); - Assert.IsType(bcp.Wrapped); - bcp.EnableStreaming = true; - } + using var bcp = BulkCopy.TryCreate(connection); + Assert.NotNull(bcp); + Assert.IsType(bcp.Wrapped); + bcp.EnableStreaming = true; } [Theory] @@ -110,17 +98,17 @@ private static void Test(int create, int test, bool result) where T : SqlServerDatabaseProvider, new() { var provider = new T(); - using (var conn = provider.GetOpenConnection()) + + using var conn = provider.GetOpenConnection(); + + try + { + conn.Execute("throw @create, 'boom', 1;", new { create }); + Assert.False(true); + } + catch(DbException err) { - try - { - conn.Execute("throw @create, 'boom', 1;", new { create }); - Assert.False(true); - } - catch(DbException err) - { - Assert.Equal(result, err.IsNumber(test)); - } + Assert.Equal(result, err.IsNumber(test)); } } } diff --git a/tests/Dapper.Tests/Providers/FirebirdTests.cs b/tests/Dapper.Tests/Providers/FirebirdTests.cs index c5872c228..f63df6219 100644 --- a/tests/Dapper.Tests/Providers/FirebirdTests.cs +++ b/tests/Dapper.Tests/Providers/FirebirdTests.cs @@ -24,35 +24,34 @@ public class FirebirdTests : TestBase [Fact(Skip = "Bug in Firebird; a PR to fix it has been submitted")] public void Issue178_Firebird() { - using (var connection = GetOpenFirebirdConnection()) - { - const string sql = "select count(*) from Issue178"; - try { connection.Execute("drop table Issue178"); } - catch { /* don't care */ } - connection.Execute("create table Issue178(id int not null)"); - connection.Execute("insert into Issue178(id) values(42)"); - // raw ADO.net - using (var sqlCmd = new FbCommand(sql, connection)) - using (IDataReader reader1 = sqlCmd.ExecuteReader()) - { - Assert.True(reader1.Read()); - Assert.Equal(1, reader1.GetInt32(0)); - Assert.False(reader1.Read()); - Assert.False(reader1.NextResult()); - } + using var connection = GetOpenFirebirdConnection(); - // dapper - using (var reader2 = connection.ExecuteReader(sql)) - { - Assert.True(reader2.Read()); - Assert.Equal(1, reader2.GetInt32(0)); - Assert.False(reader2.Read()); - Assert.False(reader2.NextResult()); - } + const string sql = "select count(*) from Issue178"; + try { connection.Execute("drop table Issue178"); } + catch { /* don't care */ } + connection.Execute("create table Issue178(id int not null)"); + connection.Execute("insert into Issue178(id) values(42)"); + // raw ADO.net + using (var sqlCmd = new FbCommand(sql, connection)) + using (IDataReader reader1 = sqlCmd.ExecuteReader()) + { + Assert.True(reader1.Read()); + Assert.Equal(1, reader1.GetInt32(0)); + Assert.False(reader1.Read()); + Assert.False(reader1.NextResult()); + } - var count = connection.Query(sql).Single(); - Assert.Equal(1, count); + // dapper + using (var reader2 = connection.ExecuteReader(sql)) + { + Assert.True(reader2.Read()); + Assert.Equal(1, reader2.GetInt32(0)); + Assert.False(reader2.Read()); + Assert.False(reader2.NextResult()); } + + var count = connection.Query(sql).Single(); + Assert.Equal(1, count); } } } diff --git a/tests/Dapper.Tests/Providers/MySQLTests.cs b/tests/Dapper.Tests/Providers/MySQLTests.cs index f850e6cd2..f0682919b 100644 --- a/tests/Dapper.Tests/Providers/MySQLTests.cs +++ b/tests/Dapper.Tests/Providers/MySQLTests.cs @@ -38,10 +38,8 @@ public class MySQLTests : TestBase [FactMySql] public void DapperEnumValue_Mysql() { - using (var conn = Provider.GetMySqlConnection()) - { - Common.DapperEnumValue(conn); - } + using var conn = Provider.GetMySqlConnection(); + Common.DapperEnumValue(conn); } [FactMySql] @@ -87,19 +85,15 @@ private class MySqlHasBool [FactMySql] public void Issue295_NullableDateTime_MySql_Default() { - using (var conn = Provider.GetMySqlConnection(true, false, false)) - { - Common.TestDateTime(conn); - } + using var conn = Provider.GetMySqlConnection(true, false, false); + Common.TestDateTime(conn); } [FactMySql] public void Issue295_NullableDateTime_MySql_ConvertZeroDatetime() { - using (var conn = Provider.GetMySqlConnection(true, true, false)) - { - Common.TestDateTime(conn); - } + using var conn = Provider.GetMySqlConnection(true, true, false); + Common.TestDateTime(conn); } [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/295, AllowZeroDateTime=True is not supported")] @@ -114,97 +108,91 @@ public void Issue295_NullableDateTime_MySql_AllowZeroDatetime() [FactMySql(Skip = "See https://github.com/DapperLib/Dapper/issues/295, AllowZeroDateTime=True is not supported")] public void Issue295_NullableDateTime_MySql_ConvertAllowZeroDatetime() { - using (var conn = Provider.GetMySqlConnection(true, true, true)) - { - Common.TestDateTime(conn); - } + using var conn = Provider.GetMySqlConnection(true, true, true); + Common.TestDateTime(conn); } [FactMySql] public void Issue426_SO34439033_DateTimeGainsTicks() { - using (var conn = Provider.GetMySqlConnection(true, true, true)) - { - try { conn.Execute("drop table Issue426_Test"); } catch { /* don't care */ } - try { conn.Execute("create table Issue426_Test (Id int not null, Time time not null)"); } catch { /* don't care */ } - const long ticks = 553440000000; - const int Id = 426; + using var conn = Provider.GetMySqlConnection(true, true, true); - var localObj = new Issue426_Test - { - Id = Id, - Time = TimeSpan.FromTicks(ticks) // from code example - }; - conn.Execute("replace into Issue426_Test values (@Id,@Time)", localObj); - - var dbObj = conn.Query("select * from Issue426_Test where Id = @id", new { id = Id }).Single(); - Assert.Equal(Id, dbObj.Id); - Assert.Equal(ticks, dbObj.Time.Value.Ticks); - } + try { conn.Execute("drop table Issue426_Test"); } catch { /* don't care */ } + try { conn.Execute("create table Issue426_Test (Id int not null, Time time not null)"); } catch { /* don't care */ } + const long ticks = 553440000000; + const int Id = 426; + + var localObj = new Issue426_Test + { + Id = Id, + Time = TimeSpan.FromTicks(ticks) // from code example + }; + conn.Execute("replace into Issue426_Test values (@Id,@Time)", localObj); + + var dbObj = conn.Query("select * from Issue426_Test where Id = @id", new { id = Id }).Single(); + Assert.Equal(Id, dbObj.Id); + Assert.Equal(ticks, dbObj.Time.Value.Ticks); } [FactMySql] public void SO36303462_Tinyint_Bools() { - using (var conn = Provider.GetMySqlConnection(true, true, true)) - { - try { conn.Execute("drop table SO36303462_Test"); } catch { /* don't care */ } - conn.Execute("create table SO36303462_Test (Id int not null, IsBold tinyint not null);"); - conn.Execute("insert SO36303462_Test (Id, IsBold) values (1,1);"); - conn.Execute("insert SO36303462_Test (Id, IsBold) values (2,0);"); - conn.Execute("insert SO36303462_Test (Id, IsBold) values (3,1);"); - - var rows = conn.Query("select * from SO36303462_Test").ToDictionary(x => x.Id); - Assert.Equal(3, rows.Count); - Assert.True(rows[1].IsBold); - Assert.False(rows[2].IsBold); - Assert.True(rows[3].IsBold); - } + using var conn = Provider.GetMySqlConnection(true, true, true); + + try { conn.Execute("drop table SO36303462_Test"); } catch { /* don't care */ } + conn.Execute("create table SO36303462_Test (Id int not null, IsBold tinyint not null);"); + conn.Execute("insert SO36303462_Test (Id, IsBold) values (1,1);"); + conn.Execute("insert SO36303462_Test (Id, IsBold) values (2,0);"); + conn.Execute("insert SO36303462_Test (Id, IsBold) values (3,1);"); + + var rows = conn.Query("select * from SO36303462_Test").ToDictionary(x => x.Id); + Assert.Equal(3, rows.Count); + Assert.True(rows[1].IsBold); + Assert.False(rows[2].IsBold); + Assert.True(rows[3].IsBold); } [FactMySql] public void Issue1277_ReaderSync() { - using (var conn = Provider.GetMySqlConnection()) + using var conn = Provider.GetMySqlConnection(); + + try { conn.Execute("drop table Issue1277_Test"); } catch { /* don't care */ } + conn.Execute("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); + conn.Execute("insert Issue1277_Test (Id, IsBold) values (1,1);"); + conn.Execute("insert Issue1277_Test (Id, IsBold) values (2,0);"); + conn.Execute("insert Issue1277_Test (Id, IsBold) values (3,1);"); + + using (var reader = conn.ExecuteReader( + "select * from Issue1277_Test where Id < @id", + new { id = 42 })) { - try { conn.Execute("drop table Issue1277_Test"); } catch { /* don't care */ } - conn.Execute("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); - conn.Execute("insert Issue1277_Test (Id, IsBold) values (1,1);"); - conn.Execute("insert Issue1277_Test (Id, IsBold) values (2,0);"); - conn.Execute("insert Issue1277_Test (Id, IsBold) values (3,1);"); - - using (var reader = conn.ExecuteReader( - "select * from Issue1277_Test where Id < @id", - new { id = 42 })) - { - var table = new DataTable(); - table.Load(reader); - Assert.Equal(2, table.Columns.Count); - Assert.Equal(3, table.Rows.Count); - } + var table = new DataTable(); + table.Load(reader); + Assert.Equal(2, table.Columns.Count); + Assert.Equal(3, table.Rows.Count); } } [FactMySql] public async Task Issue1277_ReaderAsync() { - using (var conn = Provider.GetMySqlConnection()) + using var conn = Provider.GetMySqlConnection(); + + try { await conn.ExecuteAsync("drop table Issue1277_Test"); } catch { /* don't care */ } + await conn.ExecuteAsync("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); + await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (1,1);"); + await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (2,0);"); + await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (3,1);"); + + using (var reader = await conn.ExecuteReaderAsync( + "select * from Issue1277_Test where Id < @id", + new { id = 42 })) { - try { await conn.ExecuteAsync("drop table Issue1277_Test"); } catch { /* don't care */ } - await conn.ExecuteAsync("create table Issue1277_Test (Id int not null, IsBold tinyint not null);"); - await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (1,1);"); - await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (2,0);"); - await conn.ExecuteAsync("insert Issue1277_Test (Id, IsBold) values (3,1);"); - - using (var reader = await conn.ExecuteReaderAsync( - "select * from Issue1277_Test where Id < @id", - new { id = 42 })) - { - var table = new DataTable(); - table.Load(reader); - Assert.Equal(2, table.Columns.Count); - Assert.Equal(3, table.Rows.Count); - } + var table = new DataTable(); + table.Load(reader); + Assert.Equal(2, table.Columns.Count); + Assert.Equal(3, table.Rows.Count); } } diff --git a/tests/Dapper.Tests/Providers/OLDEBTests.cs b/tests/Dapper.Tests/Providers/OLDEBTests.cs index 5fdbe472e..5126d5a10 100644 --- a/tests/Dapper.Tests/Providers/OLDEBTests.cs +++ b/tests/Dapper.Tests/Providers/OLDEBTests.cs @@ -22,61 +22,52 @@ public class OLDEBTests : TestBase [Fact] public void TestOleDbParameters() { - using (var conn = GetOleDbConnection()) - { - var row = conn.Query("select Id = ?, Age = ?", - new { foo = 12, bar = 23 } // these names DO NOT MATTER!!! - ).Single(); - int age = row.Age; - int id = row.Id; - Assert.Equal(23, age); - Assert.Equal(12, id); - } + using var conn = GetOleDbConnection(); + + var row = conn.Query("select Id = ?, Age = ?", + new { foo = 12, bar = 23 } // these names DO NOT MATTER!!! + ).Single(); + int age = row.Age; + int id = row.Id; + Assert.Equal(23, age); + Assert.Equal(12, id); } [Fact] public void PseudoPositionalParameters_Simple() { - using (var connection = GetOleDbConnection()) - { - int value = connection.Query("select ?x? + ?y_2? + ?z?", new { x = 1, y_2 = 3, z = 5, z2 = 24 }).Single(); - Assert.Equal(9, value); - } + using var connection = GetOleDbConnection(); + int value = connection.Query("select ?x? + ?y_2? + ?z?", new { x = 1, y_2 = 3, z = 5, z2 = 24 }).Single(); + Assert.Equal(9, value); } [Fact] public void Issue601_InternationalParameterNamesWork_OleDb() { // pseudo-positional - using (var connection = GetOleDbConnection()) - { - int value = connection.QuerySingle("select ?æøå٦?", new { æøå٦ = 42 }); - } + using var connection = GetOleDbConnection(); + int value = connection.QuerySingle("select ?æøå٦?", new { æøå٦ = 42 }); } [Fact] public void PseudoPositionalParameters_Dynamic() { - using (var connection = GetOleDbConnection()) - { - var args = new DynamicParameters(); - args.Add("x", 1); - args.Add("y_2", 3); - args.Add("z", 5); - args.Add("z2", 24); - int value = connection.Query("select ?x? + ?y_2? + ?z?", args).Single(); - Assert.Equal(9, value); - } + using var connection = GetOleDbConnection(); + var args = new DynamicParameters(); + args.Add("x", 1); + args.Add("y_2", 3); + args.Add("z", 5); + args.Add("z2", 24); + int value = connection.Query("select ?x? + ?y_2? + ?z?", args).Single(); + Assert.Equal(9, value); } [Fact] public void PseudoPositionalParameters_ReusedParameter() { - using (var connection = GetOleDbConnection()) - { - var ex = Assert.Throws(() => connection.Query("select ?x? + ?y_2? + ?x?", new { x = 1, y_2 = 3 }).Single()); - Assert.Equal("When passing parameters by position, each parameter can only be referenced once", ex.Message); - } + using var connection = GetOleDbConnection(); + var ex = Assert.Throws(() => connection.Query("select ?x? + ?y_2? + ?x?", new { x = 1, y_2 = 3 }).Single()); + Assert.Equal("When passing parameters by position, each parameter can only be referenced once", ex.Message); } [Fact] @@ -85,87 +76,76 @@ public void Issue569_SO38527197_PseudoPositionalParameters_In_And_Other_Conditio const string sql = @"select s1.value as id, s2.value as score from string_split('1,2,3,4,5',',') s1, string_split('1,2,3,4,5',',') s2 where s1.value in ?ids? and s2.value = ?score?"; - using (var connection = GetOleDbConnection()) - { - const int score = 2; - int[] ids = { 1, 2, 5, 7 }; - var list = connection.Query(sql, new { ids, score }).AsList(); - list.Sort(); - Assert.Equal("1,2,5", string.Join(",", list)); - } + using var connection = GetOleDbConnection(); + + const int score = 2; + int[] ids = { 1, 2, 5, 7 }; + var list = connection.Query(sql, new { ids, score }).AsList(); + list.Sort(); + Assert.Equal("1,2,5", string.Join(",", list)); } [Fact] public void Issue569_SO38527197_PseudoPositionalParameters_In() { - using (var connection = GetOleDbConnection()) - { - int[] ids = { 1, 2, 5, 7 }; - var list = connection.Query("select * from string_split('1,2,3,4,5',',') where value in ?ids?", new { ids }).AsList(); - list.Sort(); - Assert.Equal("1,2,5", string.Join(",", list)); - } + using var connection = GetOleDbConnection(); + int[] ids = { 1, 2, 5, 7 }; + var list = connection.Query("select * from string_split('1,2,3,4,5',',') where value in ?ids?", new { ids }).AsList(); + list.Sort(); + Assert.Equal("1,2,5", string.Join(",", list)); } [Fact] public void PseudoPositional_CanUseVariable() { - using (var connection = GetOleDbConnection()) - { - const int id = 42; - var row = connection.QuerySingle("declare @id int = ?id?; select @id as [A], @id as [B];", new { id }); - int a = (int)row.A; - int b = (int)row.B; - Assert.Equal(42, a); - Assert.Equal(42, b); - } + using var connection = GetOleDbConnection(); + const int id = 42; + var row = connection.QuerySingle("declare @id int = ?id?; select @id as [A], @id as [B];", new { id }); + int a = (int)row.A; + int b = (int)row.B; + Assert.Equal(42, a); + Assert.Equal(42, b); } [Fact] public void PseudoPositional_CannotUseParameterMultipleTimes() { - using (var connection = GetOleDbConnection()) + using var connection = GetOleDbConnection(); + var ex = Assert.Throws(() => { - var ex = Assert.Throws(() => - { - const int id = 42; - connection.QuerySingle("select ?id? as [A], ?id? as [B];", new { id }); - }); - Assert.Equal("When passing parameters by position, each parameter can only be referenced once", ex.Message); - } + const int id = 42; + connection.QuerySingle("select ?id? as [A], ?id? as [B];", new { id }); + }); + Assert.Equal("When passing parameters by position, each parameter can only be referenced once", ex.Message); } [Fact] public void PseudoPositionalParameters_ExecSingle() { - using (var connection = GetOleDbConnection()) - { - var data = new { x = 6 }; - connection.Execute("create table #named_single(val int not null)"); - int count = connection.Execute("insert #named_single (val) values (?x?)", data); - int sum = (int)connection.ExecuteScalar("select sum(val) from #named_single"); - Assert.Equal(1, count); - Assert.Equal(6, sum); - } + using var connection = GetOleDbConnection(); + var data = new { x = 6 }; + connection.Execute("create table #named_single(val int not null)"); + int count = connection.Execute("insert #named_single (val) values (?x?)", data); + int sum = (int)connection.ExecuteScalar("select sum(val) from #named_single"); + Assert.Equal(1, count); + Assert.Equal(6, sum); } [Fact] public void PseudoPositionalParameters_ExecMulti() { - using (var connection = GetOleDbConnection()) + using var connection = GetOleDbConnection(); + var data = new[] { - var data = new[] - { - new { x = 1, y = 1 }, - new { x = 3, y = 1 }, - new { x = 6, y = 1 }, - }; - connection.Execute("create table #named_multi(val int not null)"); - int count = connection.Execute("insert #named_multi (val) values (?x?)", data); - int sum = (int)connection.ExecuteScalar("select sum(val) from #named_multi"); - Assert.Equal(3, count); - Assert.Equal(10, sum); - } + new { x = 1, y = 1 }, + new { x = 3, y = 1 }, + new { x = 6, y = 1 }, + }; + connection.Execute("create table #named_multi(val int not null)"); + int count = connection.Execute("insert #named_multi (val) values (?x?)", data); + int sum = (int)connection.ExecuteScalar("select sum(val) from #named_multi"); + Assert.Equal(3, count); + Assert.Equal(10, sum); } [Fact] @@ -178,21 +158,20 @@ public void Issue457_NullParameterValues() SELECT @since as [Since], @customerCode as [Code]"; - using (var connection = GetOleDbConnection()) + using var connection = GetOleDbConnection(); + + DateTime? since = null; // DateTime.Now.Date; + const string code = null; // "abc"; + var row = connection.QuerySingle(sql, new { - DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; - var row = connection.QuerySingle(sql, new - { - since, - customerCode = code - }); - var a = (DateTime?)row.Since; - var b = (string)row.Code; + since, + customerCode = code + }); + var a = (DateTime?)row.Since; + var b = (string)row.Code; - Assert.Equal(since, a); - Assert.Equal(code, b); - } + Assert.Equal(since, a); + Assert.Equal(code, b); } [Fact] @@ -205,21 +184,20 @@ public void Issue457_NullParameterValues_Named() SELECT @since as [Since], @customerCode as [Code]"; - using (var connection = GetOleDbConnection()) + using var connection = GetOleDbConnection(); + + DateTime? since = null; // DateTime.Now.Date; + const string code = null; // "abc"; + var row = connection.QuerySingle(sql, new { - DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; - var row = connection.QuerySingle(sql, new - { - since, - customerCode = code - }); - var a = (DateTime?)row.Since; - var b = (string)row.Code; + since, + customerCode = code + }); + var a = (DateTime?)row.Since; + var b = (string)row.Code; - Assert.Equal(since, a); - Assert.Equal(code, b); - } + Assert.Equal(since, a); + Assert.Equal(code, b); } [Fact] @@ -232,23 +210,22 @@ public async void Issue457_NullParameterValues_MultiAsync() SELECT @since as [Since], @customerCode as [Code]"; - using (var connection = GetOleDbConnection()) + using var connection = GetOleDbConnection(); + + DateTime? since = null; // DateTime.Now.Date; + const string code = null; // "abc"; + using (var multi = await connection.QueryMultipleAsync(sql, new + { + since, + customerCode = code + }).ConfigureAwait(false)) { - DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; - using (var multi = await connection.QueryMultipleAsync(sql, new - { - since, - customerCode = code - }).ConfigureAwait(false)) - { - var row = await multi.ReadSingleAsync().ConfigureAwait(false); - var a = (DateTime?)row.Since; - var b = (string)row.Code; - - Assert.Equal(a, since); - Assert.Equal(b, code); - } + var row = await multi.ReadSingleAsync().ConfigureAwait(false); + var a = (DateTime?)row.Since; + var b = (string)row.Code; + + Assert.Equal(a, since); + Assert.Equal(b, code); } } @@ -262,23 +239,22 @@ public async void Issue457_NullParameterValues_MultiAsync_Named() SELECT @since as [Since], @customerCode as [Code]"; - using (var connection = GetOleDbConnection()) + using var connection = GetOleDbConnection(); + + DateTime? since = null; // DateTime.Now.Date; + const string code = null; // "abc"; + using (var multi = await connection.QueryMultipleAsync(sql, new + { + since, + customerCode = code + }).ConfigureAwait(false)) { - DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; - using (var multi = await connection.QueryMultipleAsync(sql, new - { - since, - customerCode = code - }).ConfigureAwait(false)) - { - var row = await multi.ReadSingleAsync().ConfigureAwait(false); - var a = (DateTime?)row.Since; - var b = (string)row.Code; - - Assert.Equal(a, since); - Assert.Equal(b, code); - } + var row = await multi.ReadSingleAsync().ConfigureAwait(false); + var a = (DateTime?)row.Since; + var b = (string)row.Code; + + Assert.Equal(a, since); + Assert.Equal(b, code); } } } diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 3501daa8d..113134297 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -47,37 +47,35 @@ private class Cat [FactPostgresql] public void TestPostgresqlArrayParameters() { - using (var conn = GetOpenNpgsqlConnection()) - { - IDbTransaction transaction = conn.BeginTransaction(); - conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); - conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", Cats); - - var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new[] { 1, 3, 5 } }); - Assert.Equal(3, r.Count()); - Assert.Equal(1, r.Count(c => c.Id == 1)); - Assert.Equal(1, r.Count(c => c.Id == 3)); - Assert.Equal(1, r.Count(c => c.Id == 5)); - transaction.Rollback(); - } + using var conn = GetOpenNpgsqlConnection(); + + IDbTransaction transaction = conn.BeginTransaction(); + conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); + conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", Cats); + + var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new[] { 1, 3, 5 } }); + Assert.Equal(3, r.Count()); + Assert.Equal(1, r.Count(c => c.Id == 1)); + Assert.Equal(1, r.Count(c => c.Id == 3)); + Assert.Equal(1, r.Count(c => c.Id == 5)); + transaction.Rollback(); } [FactPostgresql] public void TestPostgresqlListParameters() { - using (var conn = GetOpenNpgsqlConnection()) - { - IDbTransaction transaction = conn.BeginTransaction(); - conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); - conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", new List(Cats)); - - var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new List { 1, 3, 5 } }); - Assert.Equal(3, r.Count()); - Assert.Equal(1, r.Count(c => c.Id == 1)); - Assert.Equal(1, r.Count(c => c.Id == 3)); - Assert.Equal(1, r.Count(c => c.Id == 5)); - transaction.Rollback(); - } + using var conn = GetOpenNpgsqlConnection(); + + IDbTransaction transaction = conn.BeginTransaction(); + conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); + conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", new List(Cats)); + + var r = conn.Query("select * from tcat where id=any(:catids)", new { catids = new List { 1, 3, 5 } }); + Assert.Equal(3, r.Count()); + Assert.Equal(1, r.Count(c => c.Id == 1)); + Assert.Equal(1, r.Count(c => c.Id == 3)); + Assert.Equal(1, r.Count(c => c.Id == 5)); + transaction.Rollback(); } private class CharTable @@ -89,39 +87,36 @@ private class CharTable [FactPostgresql] public void TestPostgresqlChar() { - using (var conn = GetOpenNpgsqlConnection()) - { - var transaction = conn.BeginTransaction(); - conn.Execute("create table chartable (id serial not null, charcolumn \"char\" not null);"); - conn.Execute("insert into chartable(charcolumn) values('a');"); - - var r = conn.Query("select * from chartable"); - Assert.Single(r); - Assert.Equal('a', r.Single().CharColumn); - transaction.Rollback(); - } + using var conn = GetOpenNpgsqlConnection(); + + var transaction = conn.BeginTransaction(); + conn.Execute("create table chartable (id serial not null, charcolumn \"char\" not null);"); + conn.Execute("insert into chartable(charcolumn) values('a');"); + + var r = conn.Query("select * from chartable"); + Assert.Single(r); + Assert.Equal('a', r.Single().CharColumn); + transaction.Rollback(); } [FactPostgresql] public void TestPostgresqlSelectArray() { - using (var conn = GetOpenNpgsqlConnection()) - { - var r = conn.Query("select array[1,2,3]").ToList(); - Assert.Single(r); - Assert.Equal(new[] { 1, 2, 3 }, r.Single()); - } + using var conn = GetOpenNpgsqlConnection(); + + var r = conn.Query("select array[1,2,3]").ToList(); + Assert.Single(r); + Assert.Equal(new[] { 1, 2, 3 }, r.Single()); } [FactPostgresql] public void TestPostgresqlDateTimeUsage() { - using (var conn = GetOpenNpgsqlConnection()) - { - DateTime now = DateTime.UtcNow; - DateTime? nilA = now, nilB = null; - _ = conn.ExecuteScalar("SELECT @now, @nilA, @nilB::timestamp", new { now, nilA, nilB }); - } + using var conn = GetOpenNpgsqlConnection(); + + DateTime now = DateTime.UtcNow; + DateTime? nilA = now, nilB = null; + _ = conn.ExecuteScalar("SELECT @now, @nilA, @nilB::timestamp", new { now, nilA, nilB }); } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] diff --git a/tests/Dapper.Tests/Providers/SqliteTests.cs b/tests/Dapper.Tests/Providers/SqliteTests.cs index a5aeb086f..72c6abf6a 100644 --- a/tests/Dapper.Tests/Providers/SqliteTests.cs +++ b/tests/Dapper.Tests/Providers/SqliteTests.cs @@ -34,9 +34,7 @@ static FactSqliteAttribute() { try { - using (DatabaseProvider.Instance.GetOpenConnection()) - { - } + using var _ = DatabaseProvider.Instance.GetOpenConnection(); } catch (Exception ex) { @@ -52,39 +50,37 @@ public class SqliteTypeHandlerTests : SqliteTypeTestBase [FactSqlite] public void Issue466_SqliteHatesOptimizations() { - using (var connection = GetSQLiteConnection()) - { - SqlMapper.ResetTypeHandlers(); - var row = connection.Query("select 42 as Id").First(); - Assert.Equal(42, row.Id); - row = connection.Query("select 42 as Id").First(); - Assert.Equal(42, row.Id); - - SqlMapper.ResetTypeHandlers(); - row = connection.QueryFirst("select 42 as Id"); - Assert.Equal(42, row.Id); - row = connection.QueryFirst("select 42 as Id"); - Assert.Equal(42, row.Id); - } + using var connection = GetSQLiteConnection(); + + SqlMapper.ResetTypeHandlers(); + var row = connection.Query("select 42 as Id").First(); + Assert.Equal(42, row.Id); + row = connection.Query("select 42 as Id").First(); + Assert.Equal(42, row.Id); + + SqlMapper.ResetTypeHandlers(); + row = connection.QueryFirst("select 42 as Id"); + Assert.Equal(42, row.Id); + row = connection.QueryFirst("select 42 as Id"); + Assert.Equal(42, row.Id); } [FactSqlite] public async Task Issue466_SqliteHatesOptimizations_Async() { - using (var connection = GetSQLiteConnection()) - { - SqlMapper.ResetTypeHandlers(); - var row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); - Assert.Equal(42, row.Id); - row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); - Assert.Equal(42, row.Id); - - SqlMapper.ResetTypeHandlers(); - row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); - Assert.Equal(42, row.Id); - row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); - Assert.Equal(42, row.Id); - } + using var connection = GetSQLiteConnection(); + + SqlMapper.ResetTypeHandlers(); + var row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); + Assert.Equal(42, row.Id); + row = (await connection.QueryAsync("select 42 as Id").ConfigureAwait(false)).First(); + Assert.Equal(42, row.Id); + + SqlMapper.ResetTypeHandlers(); + row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); + Assert.Equal(42, row.Id); + row = await connection.QueryFirstAsync("select 42 as Id").ConfigureAwait(false); + Assert.Equal(42, row.Id); } } @@ -93,10 +89,8 @@ public class SqliteTests : SqliteTypeTestBase [FactSqlite] public void DapperEnumValue_Sqlite() { - using (var connection = GetSQLiteConnection()) - { - Common.DapperEnumValue(connection); - } + using var connection = GetSQLiteConnection(); + Common.DapperEnumValue(connection); } @@ -115,15 +109,14 @@ public void Isse467_SqliteLikesParametersWithoutPrefix() private void Isse467_SqliteParameterNaming(bool prefix) { - using (var connection = GetSQLiteConnection()) - { - var cmd = connection.CreateCommand(); - cmd.CommandText = "select @foo"; - const SqliteType type = SqliteType.Integer; - cmd.Parameters.Add(prefix ? "@foo" : "foo", type).Value = 42; - var i = Convert.ToInt32(cmd.ExecuteScalar()); - Assert.Equal(42, i); - } + using var connection = GetSQLiteConnection(); + + var cmd = connection.CreateCommand(); + cmd.CommandText = "select @foo"; + const SqliteType type = SqliteType.Integer; + cmd.Parameters.Add(prefix ? "@foo" : "foo", type).Value = 42; + var i = Convert.ToInt32(cmd.ExecuteScalar()); + Assert.Equal(42, i); } [FactSqlite] diff --git a/tests/Dapper.Tests/QueryMultipleTests.cs b/tests/Dapper.Tests/QueryMultipleTests.cs index 47b304569..8e3015649 100644 --- a/tests/Dapper.Tests/QueryMultipleTests.cs +++ b/tests/Dapper.Tests/QueryMultipleTests.cs @@ -17,63 +17,58 @@ public abstract class QueryMultipleTests : TestBase where [Fact] public void TestQueryMultipleBuffered() { - using (var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 })) - { - var a = grid.Read(); - var b = grid.Read(); - var c = grid.Read(); - var d = grid.Read(); - - Assert.Equal(1, a.Single()); - Assert.Equal(2, b.Single()); - Assert.Equal(3, c.Single()); - Assert.Equal(4, d.Single()); - } + using var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 }); + + var a = grid.Read(); + var b = grid.Read(); + var c = grid.Read(); + var d = grid.Read(); + + Assert.Equal(1, a.Single()); + Assert.Equal(2, b.Single()); + Assert.Equal(3, c.Single()); + Assert.Equal(4, d.Single()); } [Fact] public void TestMultiConversion() { - using (SqlMapper.GridReader multi = connection.QueryMultiple("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2")) - { - Assert.Equal(1, multi.Read().Single()); - Assert.Equal(2, multi.Read().Single()); - } + using SqlMapper.GridReader multi = connection.QueryMultiple("select Cast(1 as BigInt) Col1; select Cast(2 as BigInt) Col2"); + Assert.Equal(1, multi.Read().Single()); + Assert.Equal(2, multi.Read().Single()); } [Fact] public void TestQueryMultipleNonBufferedIncorrectOrder() { - using (var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 })) + using var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 }); + + var a = grid.Read(false); + try + { + var b = grid.Read(false); + throw new InvalidOperationException(); // should have thrown + } + catch (InvalidOperationException) { - var a = grid.Read(false); - try - { - var b = grid.Read(false); - throw new InvalidOperationException(); // should have thrown - } - catch (InvalidOperationException) - { - // that's expected - } + // that's expected } } [Fact] public void TestQueryMultipleNonBufferedCorrectOrder() { - using (var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 })) - { - var a = grid.Read(false).Single(); - var b = grid.Read(false).Single(); - var c = grid.Read(false).Single(); - var d = grid.Read(false).Single(); - - Assert.Equal(1, a); - Assert.Equal(2, b); - Assert.Equal(3, c); - Assert.Equal(4, d); - } + using var grid = connection.QueryMultiple("select 1; select 2; select @x; select 4", new { x = 3 }); + + var a = grid.Read(false).Single(); + var b = grid.Read(false).Single(); + var c = grid.Read(false).Single(); + var d = grid.Read(false).Single(); + + Assert.Equal(1, a); + Assert.Equal(2, b); + Assert.Equal(3, c); + Assert.Equal(4, d); } [Fact] @@ -161,68 +156,62 @@ public void Issue524_QueryMultiple_Cast() Assert.Equal(42, connection.QuerySingle("select cast(42 as bigint)")); // using multi-reader API - using (var reader = connection.QueryMultiple("select cast(42 as bigint); select cast(42 as bigint)")) - { - Assert.Equal(42, reader.Read().Single()); - Assert.Equal(42, reader.ReadSingle()); - } + using var reader = connection.QueryMultiple("select cast(42 as bigint); select cast(42 as bigint)"); + + Assert.Equal(42, reader.Read().Single()); + Assert.Equal(42, reader.ReadSingle()); } [Fact] public void QueryMultipleFromClosed() { - using (var conn = GetClosedConnection()) + using var conn = GetClosedConnection(); + using (var multi = conn.QueryMultiple("select 1; select 'abc';")) { - using (var multi = conn.QueryMultiple("select 1; select 'abc';")) - { - Assert.Equal(1, multi.Read().Single()); - Assert.Equal("abc", multi.Read().Single()); - } - Assert.Equal(ConnectionState.Closed, conn.State); + Assert.Equal(1, multi.Read().Single()); + Assert.Equal("abc", multi.Read().Single()); } + Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void QueryMultiple2FromClosed() { - using (var conn = GetClosedConnection()) + using var conn = GetClosedConnection(); + + Assert.Equal(ConnectionState.Closed, conn.State); + + using (var multi = conn.QueryMultiple("select 1 select 2 select 3")) { - Assert.Equal(ConnectionState.Closed, conn.State); - using (var multi = conn.QueryMultiple("select 1 select 2 select 3")) - { - Assert.Equal(1, multi.Read().Single()); - Assert.Equal(2, multi.Read().Single()); - // not reading 3 is intentional here - } - Assert.Equal(ConnectionState.Closed, conn.State); + Assert.Equal(1, multi.Read().Single()); + Assert.Equal(2, multi.Read().Single()); + // not reading 3 is intentional here } + Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] public void SO35554284_QueryMultipleUntilConsumed() { - using (var reader = connection.QueryMultiple("select 1 as Id; select 2 as Id; select 3 as Id;")) + using var reader = connection.QueryMultiple("select 1 as Id; select 2 as Id; select 3 as Id;"); + + var items = new List(); + while (!reader.IsConsumed) { - var items = new List(); - while (!reader.IsConsumed) - { - items.AddRange(reader.Read()); - } - Assert.Equal(3, items.Count); - Assert.Equal(1, items[0].Id); - Assert.Equal(2, items[1].Id); - Assert.Equal(3, items[2].Id); + items.AddRange(reader.Read()); } + Assert.Equal(3, items.Count); + Assert.Equal(1, items[0].Id); + Assert.Equal(2, items[1].Id); + Assert.Equal(3, items[2].Id); } [Fact] public void QueryMultipleInvalidFromClosed() { - using (var conn = GetClosedConnection()) - { - Assert.ThrowsAny(() => conn.QueryMultiple("select gibberish")); - Assert.Equal(ConnectionState.Closed, conn.State); - } + using var conn = GetClosedConnection(); + Assert.ThrowsAny(() => conn.QueryMultiple("select gibberish")); + Assert.Equal(ConnectionState.Closed, conn.State); } [Fact] @@ -233,29 +222,28 @@ public void QueryMultipleInvalidFromClosed() private void TestMultiSelectWithSomeEmptyGrids(bool buffered) { - using (var reader = connection.QueryMultiple("select 1; select 2 where 1 = 0; select 3 where 1 = 0; select 4;")) - { - var one = reader.Read(buffered: buffered).ToArray(); - var two = reader.Read(buffered: buffered).ToArray(); - var three = reader.Read(buffered: buffered).ToArray(); - var four = reader.Read(buffered: buffered).ToArray(); - try - { // only returned four grids; expect a fifth read to fail - reader.Read(buffered: buffered); - throw new InvalidOperationException("this should not have worked!"); - } - catch (ObjectDisposedException ex) - { // expected; success - Assert.Equal("The reader has been disposed; this can happen after all data has been consumed\r\nObject name: 'Dapper.SqlMapper+GridReader'.", ex.Message, ignoreLineEndingDifferences: true); - } - - Assert.Single(one); - Assert.Equal(1, one[0]); - Assert.Empty(two); - Assert.Empty(three); - Assert.Single(four); - Assert.Equal(4, four[0]); + using var reader = connection.QueryMultiple("select 1; select 2 where 1 = 0; select 3 where 1 = 0; select 4;"); + + var one = reader.Read(buffered: buffered).ToArray(); + var two = reader.Read(buffered: buffered).ToArray(); + var three = reader.Read(buffered: buffered).ToArray(); + var four = reader.Read(buffered: buffered).ToArray(); + try + { // only returned four grids; expect a fifth read to fail + reader.Read(buffered: buffered); + throw new InvalidOperationException("this should not have worked!"); } + catch (ObjectDisposedException ex) + { // expected; success + Assert.Equal("The reader has been disposed; this can happen after all data has been consumed\r\nObject name: 'Dapper.SqlMapper+GridReader'.", ex.Message, ignoreLineEndingDifferences: true); + } + + Assert.Single(one); + Assert.Equal(1, one[0]); + Assert.Empty(two); + Assert.Empty(three); + Assert.Single(four); + Assert.Equal(4, four[0]); } [Fact] From 8a68070b8e05e6ed6ff24a4f0d25ca2a156139a6 Mon Sep 17 00:00:00 2001 From: Giorgi Dalakishvili Date: Sun, 3 Sep 2023 17:48:12 +0400 Subject: [PATCH 233/312] Support $ as parameter prefix (#1952) --- Dapper/SqlMapper.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 369d71e7b..583e06ec3 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2108,7 +2108,7 @@ internal static int GetListPaddingExtraCount(int count) private static string GetInListRegex(string name, bool byPosition) => byPosition ? (@"(\?)" + Regex.Escape(name) + @"\?(?!\w)(\s+(?i)unknown(?-i))?") - : ("([?@:]" + Regex.Escape(name) + @")(?!\w)(\s+(?i)unknown(?-i))?"); + : ("([?@:$]" + Regex.Escape(name) + @")(?!\w)(\s+(?i)unknown(?-i))?"); /// /// Internal use only. @@ -2373,14 +2373,14 @@ private static IEnumerable FilterParameters(IEnumerable(16); foreach (var p in parameters) { - if (Regex.IsMatch(sql, @"[?@:]" + p.Name + @"([^\p{L}\p{N}_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)) + if (Regex.IsMatch(sql, @"[?@:$]" + p.Name + @"([^\p{L}\p{N}_]+|$)", RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant)) list.Add(p); } return list; } // look for ? / @ / : *by itself* - private static readonly Regex smellsLikeOleDb = new(@"(? Date: Tue, 12 Sep 2023 15:58:49 +0100 Subject: [PATCH 234/312] attempt NRT makeover (#1928) * attempt NRT makeover - annotate NRTs on GridReader API - add protected access to some of the GridReader internals - switch GridReader callbacks to be more type-independent docs lib updates; deal with yellow warnings and test failures (SQL server certs and cancellation surfacing differently) fix break simplify proposed GridReader changes to protected OnBeforeGrid and OnAfterGrid[Async] Builds: Bump library versions (#1935) Tests: Upgrade dependencies for Dependabot (#1936) I'll do a pass at all of these later, but getting CVE versions out of the pipe. Resolving 4 alerts here: https://github.com/DapperLib/Dapper/security/dependabot merge and lib updates allow Identity to be constructed on-the-fly inside GridReader fix build warnings include readme rev minor - enable [SkipLocalsInit] everywhere - use NET5_0_OR_GREATER for remoting check # Conflicts: # Dapper/CommandDefinition.cs # Dapper/DefaultTypeMap.cs # Dapper/SqlMapper.Async.cs # Dapper/SqlMapper.IDataReader.cs # Dapper/SqlMapper.Link.cs # Dapper/SqlMapper.cs # Directory.Build.props * shipped, not unshipped * fixup SqlBuilder; use is null / is not null * use central package management (#1949) * Nullable test tweaks * Nuget: let's just remove it! * 2 test fixes from review * fix FindExplicitConstructor * add GetPropertySetterOrThrow * fix NRT on FindConstructor * DapperRow: value is object? * use NotNullWhen * make constructor problem more obvious * test fixes --------- Co-authored-by: Nick Craver --- .../Dapper.EntityFramework.StrongName.csproj | 6 +- .../Dapper.EntityFramework.csproj | 8 +- Dapper.EntityFramework/DbGeographyHandler.cs | 10 +- Dapper.EntityFramework/DbGeometryHandler.cs | 10 +- Dapper.EntityFramework/PublicAPI.Shipped.txt | 15 +- .../PublicAPI.Unshipped.txt | 2 +- Dapper.ProviderTools/BulkCopy.cs | 10 +- .../Dapper.ProviderTools.csproj | 4 +- .../DbConnectionExtensions.cs | 18 +- Dapper.ProviderTools/DbExceptionExtensions.cs | 6 +- .../Internal/DynamicBulkCopy.cs | 2 +- Dapper.Rainbow/Dapper.Rainbow.csproj | 2 +- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 5 +- Dapper.SqlBuilder/PublicAPI.Shipped.txt | 39 +- Dapper.SqlBuilder/PublicAPI.Unshipped.txt | 2 +- Dapper.SqlBuilder/SqlBuilder.cs | 85 ++- Dapper.StrongName/Dapper.StrongName.csproj | 8 +- Dapper.sln | 10 +- Dapper/CommandDefinition.cs | 27 +- Dapper/CustomPropertyTypeMap.cs | 10 +- Dapper/Dapper.csproj | 10 +- Dapper/DataTableHandler.cs | 4 +- Dapper/DbString.cs | 4 +- Dapper/DefaultTypeMap.cs | 38 +- Dapper/DynamicParameters.ParamInfo.cs | 10 +- Dapper/DynamicParameters.cs | 90 +-- Dapper/Extensions.cs | 6 +- Dapper/FeatureSupport.cs | 4 +- Dapper/NRT.cs | 12 + Dapper/Properties/AssemblyInfo.cs | 21 +- Dapper/PublicAPI.Shipped.txt | 466 ++++++------ Dapper/PublicAPI.Unshipped.txt | 2 +- Dapper/PublicAPI/net461/PublicAPI.Shipped.txt | 2 +- .../PublicAPI/net461/PublicAPI.Unshipped.txt | 2 +- Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt | 9 +- .../PublicAPI/net5.0/PublicAPI.Unshipped.txt | 2 +- .../netstandard2.0/PublicAPI.Shipped.txt | 2 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 2 +- Dapper/SimpleMemberMap.cs | 8 +- Dapper/SqlDataRecordHandler.cs | 4 +- Dapper/SqlDataRecordListTVPParameter.cs | 32 +- Dapper/SqlMapper.Async.cs | 184 ++--- Dapper/SqlMapper.CacheInfo.cs | 4 +- Dapper/SqlMapper.DapperRow.Descriptor.cs | 22 +- Dapper/SqlMapper.DapperRow.cs | 74 +- Dapper/SqlMapper.DapperRowMetaObject.cs | 4 +- Dapper/SqlMapper.DapperTable.cs | 8 +- Dapper/SqlMapper.GridReader.Async.cs | 112 ++- Dapper/SqlMapper.GridReader.cs | 171 +++-- Dapper/SqlMapper.IDataReader.cs | 8 +- Dapper/SqlMapper.IMemberMap.cs | 6 +- Dapper/SqlMapper.IParameterLookup.cs | 2 +- Dapper/SqlMapper.ITypeHandler.cs | 4 +- Dapper/SqlMapper.ITypeMap.cs | 6 +- Dapper/SqlMapper.Identity.cs | 26 +- Dapper/SqlMapper.Link.cs | 20 +- Dapper/SqlMapper.TypeDeserializerCache.cs | 26 +- Dapper/SqlMapper.TypeHandler.cs | 18 +- Dapper/SqlMapper.TypeHandlerCache.cs | 6 +- Dapper/SqlMapper.cs | 676 +++++++++--------- Dapper/TableValuedParameter.cs | 8 +- Dapper/TypeExtensions.cs | 2 +- Dapper/UdtTypeHandler.cs | 4 +- Dapper/WrappedReader.cs | 42 +- Dapper/XmlHandlers.cs | 2 +- Directory.Build.props | 15 +- Directory.Packages.props | 45 ++ .../Benchmarks.RepoDB.cs | 2 +- .../Dapper.Tests.Performance.csproj | 49 +- benchmarks/Directory.Build.props | 2 +- docs/index.md | 3 + docs/readme.md | 17 + nuget.config | 1 - tests/Dapper.Tests/AsyncTests.cs | 69 +- tests/Dapper.Tests/ConstructorTests.cs | 7 +- tests/Dapper.Tests/Dapper.Tests.csproj | 19 +- tests/Dapper.Tests/DataReaderTests.cs | 30 +- tests/Dapper.Tests/EnumTests.cs | 3 +- tests/Dapper.Tests/Helpers/Attributes.cs | 2 +- tests/Dapper.Tests/Helpers/Common.cs | 4 +- .../Helpers/TransactedConnection.cs | 8 +- tests/Dapper.Tests/Helpers/XunitSkippable.cs | 6 +- tests/Dapper.Tests/LiteralTests.cs | 2 +- tests/Dapper.Tests/MiscTests.cs | 81 ++- tests/Dapper.Tests/MultiMapTests.cs | 46 +- tests/Dapper.Tests/ParameterTests.cs | 69 +- tests/Dapper.Tests/ProcedureTests.cs | 13 +- tests/Dapper.Tests/Providers/Linq2SqlTests.cs | 4 +- tests/Dapper.Tests/Providers/MySQLTests.cs | 10 +- tests/Dapper.Tests/Providers/OLDEBTests.cs | 24 +- .../Dapper.Tests/Providers/PostgresqlTests.cs | 8 +- .../Dapper.Tests/Providers/SnowflakeTests.cs | 6 +- tests/Dapper.Tests/Providers/SqliteTests.cs | 4 +- tests/Dapper.Tests/SharedTypes/Address.cs | 4 +- tests/Dapper.Tests/SharedTypes/Bar1.cs | 2 +- tests/Dapper.Tests/SharedTypes/Category.cs | 4 +- tests/Dapper.Tests/SharedTypes/Comment.cs | 2 +- tests/Dapper.Tests/SharedTypes/Dog.cs | 2 +- tests/Dapper.Tests/SharedTypes/HazNameId.cs | 4 +- tests/Dapper.Tests/SharedTypes/Index.cs | 2 +- tests/Dapper.Tests/SharedTypes/Person.cs | 6 +- tests/Dapper.Tests/SharedTypes/Post.cs | 6 +- tests/Dapper.Tests/SharedTypes/Product.cs | 4 +- tests/Dapper.Tests/SharedTypes/ReviewBoard.cs | 20 +- tests/Dapper.Tests/SharedTypes/SomeType.cs | 2 +- tests/Dapper.Tests/SharedTypes/User.cs | 2 +- tests/Dapper.Tests/TestBase.cs | 32 +- tests/Dapper.Tests/TupleTests.cs | 6 +- tests/Dapper.Tests/TypeHandlerTests.cs | 132 ++-- tests/Dapper.Tests/XmlTests.cs | 12 +- tests/Directory.Build.props | 8 +- version.json | 2 +- 112 files changed, 1736 insertions(+), 1509 deletions(-) create mode 100644 Dapper/NRT.cs create mode 100644 Directory.Packages.props create mode 100644 docs/readme.md diff --git a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj index 4207cd180..f016f490a 100644 --- a/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj +++ b/Dapper.EntityFramework.StrongName/Dapper.EntityFramework.StrongName.csproj @@ -10,14 +10,14 @@ true Dapper.EntityFramework.StrongName orm;sql;micro-orm + enable - - - + + diff --git a/Dapper.EntityFramework/Dapper.EntityFramework.csproj b/Dapper.EntityFramework/Dapper.EntityFramework.csproj index 87b97b8d2..0496d9c8f 100644 --- a/Dapper.EntityFramework/Dapper.EntityFramework.csproj +++ b/Dapper.EntityFramework/Dapper.EntityFramework.csproj @@ -7,14 +7,14 @@ Marc Gravell;Nick Craver net461 orm;sql;micro-orm + enable - - - + + - + all runtime; build; native; contentfiles; analyzers diff --git a/Dapper.EntityFramework/DbGeographyHandler.cs b/Dapper.EntityFramework/DbGeographyHandler.cs index 3722c6f72..67893a386 100644 --- a/Dapper.EntityFramework/DbGeographyHandler.cs +++ b/Dapper.EntityFramework/DbGeographyHandler.cs @@ -27,10 +27,10 @@ public class DbGeographyHandler : SqlMapper.TypeHandler /// /// The parameter to configure. /// Parameter value. - public override void SetValue(IDbDataParameter parameter, DbGeography value) + public override void SetValue(IDbDataParameter parameter, DbGeography? value) { - object parsed = null; - if (value != null) + object? parsed = null; + if (value is not null) { parsed = SqlGeography.STGeomFromWKB(new SqlBytes(value.AsBinary()), value.CoordinateSystemId); } @@ -46,9 +46,9 @@ public override void SetValue(IDbDataParameter parameter, DbGeography value) /// /// The value from the database. /// The typed value. - public override DbGeography Parse(object value) + public override DbGeography? Parse(object? value) { - if (value == null || value is DBNull) return null; + if (value is null || value is DBNull) return null; if (value is SqlGeography geo) { return DbGeography.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value); diff --git a/Dapper.EntityFramework/DbGeometryHandler.cs b/Dapper.EntityFramework/DbGeometryHandler.cs index 2b7c130bd..835ecfacb 100644 --- a/Dapper.EntityFramework/DbGeometryHandler.cs +++ b/Dapper.EntityFramework/DbGeometryHandler.cs @@ -27,10 +27,10 @@ public class DbGeometryHandler : SqlMapper.TypeHandler /// /// The parameter to configure. /// Parameter value. - public override void SetValue(IDbDataParameter parameter, DbGeometry value) + public override void SetValue(IDbDataParameter parameter, DbGeometry? value) { - object parsed = null; - if (value != null) + object? parsed = null; + if (value is not null) { parsed = SqlGeometry.STGeomFromWKB(new SqlBytes(value.AsBinary()), value.CoordinateSystemId); } @@ -46,9 +46,9 @@ public override void SetValue(IDbDataParameter parameter, DbGeometry value) /// /// The value from the database. /// The typed value. - public override DbGeometry Parse(object value) + public override DbGeometry? Parse(object? value) { - if (value == null || value is DBNull) return null; + if (value is null || value is DBNull) return null; if (value is SqlGeometry geo) { return DbGeometry.FromBinary(geo.STAsBinary().Value, geo.STSrid.Value); diff --git a/Dapper.EntityFramework/PublicAPI.Shipped.txt b/Dapper.EntityFramework/PublicAPI.Shipped.txt index d842ac329..1f97829c4 100644 --- a/Dapper.EntityFramework/PublicAPI.Shipped.txt +++ b/Dapper.EntityFramework/PublicAPI.Shipped.txt @@ -1,12 +1,13 @@ -Dapper.EntityFramework.DbGeographyHandler +#nullable enable +Dapper.EntityFramework.DbGeographyHandler Dapper.EntityFramework.DbGeographyHandler.DbGeographyHandler() -> void Dapper.EntityFramework.DbGeometryHandler Dapper.EntityFramework.DbGeometryHandler.DbGeometryHandler() -> void Dapper.EntityFramework.Handlers -override Dapper.EntityFramework.DbGeographyHandler.Parse(object value) -> System.Data.Entity.Spatial.DbGeography -override Dapper.EntityFramework.DbGeographyHandler.SetValue(System.Data.IDbDataParameter parameter, System.Data.Entity.Spatial.DbGeography value) -> void -override Dapper.EntityFramework.DbGeometryHandler.Parse(object value) -> System.Data.Entity.Spatial.DbGeometry -override Dapper.EntityFramework.DbGeometryHandler.SetValue(System.Data.IDbDataParameter parameter, System.Data.Entity.Spatial.DbGeometry value) -> void +override Dapper.EntityFramework.DbGeographyHandler.Parse(object? value) -> System.Data.Entity.Spatial.DbGeography? +override Dapper.EntityFramework.DbGeographyHandler.SetValue(System.Data.IDbDataParameter! parameter, System.Data.Entity.Spatial.DbGeography? value) -> void +override Dapper.EntityFramework.DbGeometryHandler.Parse(object? value) -> System.Data.Entity.Spatial.DbGeometry? +override Dapper.EntityFramework.DbGeometryHandler.SetValue(System.Data.IDbDataParameter! parameter, System.Data.Entity.Spatial.DbGeometry? value) -> void static Dapper.EntityFramework.Handlers.Register() -> void -static readonly Dapper.EntityFramework.DbGeographyHandler.Default -> Dapper.EntityFramework.DbGeographyHandler -static readonly Dapper.EntityFramework.DbGeometryHandler.Default -> Dapper.EntityFramework.DbGeometryHandler \ No newline at end of file +static readonly Dapper.EntityFramework.DbGeographyHandler.Default -> Dapper.EntityFramework.DbGeographyHandler! +static readonly Dapper.EntityFramework.DbGeometryHandler.Default -> Dapper.EntityFramework.DbGeometryHandler! \ No newline at end of file diff --git a/Dapper.EntityFramework/PublicAPI.Unshipped.txt b/Dapper.EntityFramework/PublicAPI.Unshipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper.EntityFramework/PublicAPI.Unshipped.txt +++ b/Dapper.EntityFramework/PublicAPI.Unshipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper.ProviderTools/BulkCopy.cs b/Dapper.ProviderTools/BulkCopy.cs index e1327daa0..71f1b92c9 100644 --- a/Dapper.ProviderTools/BulkCopy.cs +++ b/Dapper.ProviderTools/BulkCopy.cs @@ -20,7 +20,7 @@ public abstract class BulkCopy : IDisposable /// public static BulkCopy? TryCreate(DbConnection connection) { - if (connection == null) return null; + if (connection is null) return null; var type = connection.GetType(); if (!s_bcpFactory.TryGetValue(type, out var func)) { @@ -36,9 +36,9 @@ public abstract class BulkCopy : IDisposable public static BulkCopy Create(DbConnection connection) { var bcp = TryCreate(connection); - if (bcp == null) + if (bcp is null) { - if (connection == null) throw new ArgumentNullException(nameof(connection)); + if (connection is null) throw new ArgumentNullException(nameof(connection)); throw new NotSupportedException("Unable to create BulkCopy for " + connection.GetType().FullName); } return bcp; @@ -64,10 +64,10 @@ private static readonly ConcurrentDictionary?> { var prefix = match.Groups[1].Value; var bcpType = connectionType.Assembly.GetType($"{connectionType.Namespace}.{prefix}BulkCopy"); - if (bcpType != null) + if (bcpType is not null) { var ctor = bcpType.GetConstructor(new[] { connectionType }); - if (ctor == null) return null; + if (ctor is null) return null; var p = Expression.Parameter(typeof(DbConnection), "conn"); var body = Expression.New(ctor, Expression.Convert(p, connectionType)); diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index 7d10fe2e5..61e8b4c58 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -10,13 +10,13 @@ enable - + all runtime; build; native; contentfiles; analyzers - + diff --git a/Dapper.ProviderTools/DbConnectionExtensions.cs b/Dapper.ProviderTools/DbConnectionExtensions.cs index 0b6729d95..5960fdef5 100644 --- a/Dapper.ProviderTools/DbConnectionExtensions.cs +++ b/Dapper.ProviderTools/DbConnectionExtensions.cs @@ -17,7 +17,7 @@ public static class DbConnectionExtensions public static bool TryGetClientConnectionId(this DbConnection connection, out Guid clientConnectionId) { clientConnectionId = default; - return connection != null && ByTypeHelpers.Get(connection.GetType()).TryGetClientConnectionId( + return connection is not null && ByTypeHelpers.Get(connection.GetType()).TryGetClientConnectionId( connection, out clientConnectionId); } @@ -25,13 +25,13 @@ public static bool TryGetClientConnectionId(this DbConnection connection, out Gu /// Clear all pools associated with the provided connection type /// public static bool TryClearAllPools(this DbConnection connection) - => connection != null && ByTypeHelpers.Get(connection.GetType()).TryClearAllPools(); + => connection is not null && ByTypeHelpers.Get(connection.GetType()).TryClearAllPools(); /// /// Clear the pools associated with the provided connection /// public static bool TryClearPool(this DbConnection connection) - => connection != null && ByTypeHelpers.Get(connection.GetType()).TryClearPool(connection); + => connection is not null && ByTypeHelpers.Get(connection.GetType()).TryClearPool(connection); private sealed class ByTypeHelpers { @@ -44,7 +44,7 @@ private static readonly ConcurrentDictionary s_byType public bool TryGetClientConnectionId(DbConnection connection, out Guid clientConnectionId) { - if (_getClientConnectionId == null) + if (_getClientConnectionId is null) { clientConnectionId = default; return false; @@ -55,14 +55,14 @@ public bool TryGetClientConnectionId(DbConnection connection, out Guid clientCon public bool TryClearPool(DbConnection connection) { - if (_clearPool == null) return false; + if (_clearPool is null) return false; _clearPool(connection); return true; } public bool TryClearAllPools() { - if (_clearAllPools == null) return false; + if (_clearAllPools is null) return false; _clearAllPools(); return true; } @@ -84,7 +84,7 @@ private ByTypeHelpers(Type type) { var clearAllPools = type.GetMethod("ClearAllPools", BindingFlags.Public | BindingFlags.Static, null, Type.EmptyTypes, null); - if (clearAllPools != null) + if (clearAllPools is not null) { _clearAllPools = (Action)Delegate.CreateDelegate(typeof(Action), clearAllPools); } @@ -95,7 +95,7 @@ private ByTypeHelpers(Type type) { var clearPool = type.GetMethod("ClearPool", BindingFlags.Public | BindingFlags.Static, null, new[] { type }, null); - if (clearPool != null) + if (clearPool is not null) { var p = Expression.Parameter(typeof(DbConnection), "connection"); var body = Expression.Call(clearPool, Expression.Convert(p, type)); @@ -111,7 +111,7 @@ private ByTypeHelpers(Type type) try { var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); - if (prop == null || !prop.CanRead) return null; + if (prop is null || !prop.CanRead) return null; if (prop.PropertyType != typeof(T)) return null; var p = Expression.Parameter(typeof(DbConnection), "connection"); diff --git a/Dapper.ProviderTools/DbExceptionExtensions.cs b/Dapper.ProviderTools/DbExceptionExtensions.cs index e69b25764..d1b6d8e2b 100644 --- a/Dapper.ProviderTools/DbExceptionExtensions.cs +++ b/Dapper.ProviderTools/DbExceptionExtensions.cs @@ -15,7 +15,7 @@ public static class DbExceptionExtensions /// Indicates whether the provided exception has an integer Number property with the supplied value /// public static bool IsNumber(this DbException exception, int number) - => exception != null && ByTypeHelpers.Get(exception.GetType()).IsNumber(exception, number); + => exception is not null && ByTypeHelpers.Get(exception.GetType()).IsNumber(exception, number); private sealed class ByTypeHelpers @@ -25,7 +25,7 @@ private static readonly ConcurrentDictionary s_byType private readonly Func? _getNumber; public bool IsNumber(DbException exception, int number) - => _getNumber != null && _getNumber(exception) == number; + => _getNumber is not null && _getNumber(exception) == number; public static ByTypeHelpers Get(Type type) { @@ -46,7 +46,7 @@ private ByTypeHelpers(Type type) try { var prop = type.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); - if (prop == null || !prop.CanRead) return null; + if (prop is null || !prop.CanRead) return null; if (prop.PropertyType != typeof(T)) return null; var p = Expression.Parameter(typeof(DbException), "exception"); diff --git a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs index 2505b4119..d8fd7b5d8 100644 --- a/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs +++ b/Dapper.ProviderTools/Internal/DynamicBulkCopy.cs @@ -9,7 +9,7 @@ namespace Dapper.ProviderTools.Internal internal sealed class DynamicBulkCopy : BulkCopy { internal static BulkCopy? Create(object? wrapped) - => wrapped == null ? null : new DynamicBulkCopy(wrapped); + => wrapped is null ? null : new DynamicBulkCopy(wrapped); private DynamicBulkCopy(object wrapped) => _wrapped = wrapped; diff --git a/Dapper.Rainbow/Dapper.Rainbow.csproj b/Dapper.Rainbow/Dapper.Rainbow.csproj index dcf0f4e40..7d5f3ff5a 100644 --- a/Dapper.Rainbow/Dapper.Rainbow.csproj +++ b/Dapper.Rainbow/Dapper.Rainbow.csproj @@ -14,7 +14,7 @@ - + diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index 10c2f8672..b597bd9eb 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -8,17 +8,18 @@ net461;netstandard2.0;net5.0 false false + enable - + all runtime; build; native; contentfiles; analyzers - + diff --git a/Dapper.SqlBuilder/PublicAPI.Shipped.txt b/Dapper.SqlBuilder/PublicAPI.Shipped.txt index c96031ed7..9baefbb74 100644 --- a/Dapper.SqlBuilder/PublicAPI.Shipped.txt +++ b/Dapper.SqlBuilder/PublicAPI.Shipped.txt @@ -1,21 +1,22 @@ -Dapper.SqlBuilder -Dapper.SqlBuilder.AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false) -> Dapper.SqlBuilder -Dapper.SqlBuilder.AddParameters(dynamic parameters) -> Dapper.SqlBuilder -Dapper.SqlBuilder.AddTemplate(string sql, dynamic parameters = null) -> Dapper.SqlBuilder.Template -Dapper.SqlBuilder.GroupBy(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.Having(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.InnerJoin(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.Intersect(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.Join(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.LeftJoin(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.OrderBy(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.OrWhere(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.RightJoin(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.Select(string sql, dynamic parameters = null) -> Dapper.SqlBuilder -Dapper.SqlBuilder.Set(string sql, dynamic parameters = null) -> Dapper.SqlBuilder +#nullable enable +Dapper.SqlBuilder +Dapper.SqlBuilder.AddClause(string! name, string! sql, object? parameters, string! joiner, string! prefix = "", string! postfix = "", bool isInclusive = false) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.AddParameters(dynamic! parameters) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.AddTemplate(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder.Template! +Dapper.SqlBuilder.GroupBy(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.Having(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.InnerJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.Intersect(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.Join(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.LeftJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.OrderBy(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.OrWhere(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.RightJoin(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.Select(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! +Dapper.SqlBuilder.Set(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! Dapper.SqlBuilder.SqlBuilder() -> void Dapper.SqlBuilder.Template -Dapper.SqlBuilder.Template.Parameters.get -> object -Dapper.SqlBuilder.Template.RawSql.get -> string -Dapper.SqlBuilder.Template.Template(Dapper.SqlBuilder builder, string sql, dynamic parameters) -> void -Dapper.SqlBuilder.Where(string sql, dynamic parameters = null) -> Dapper.SqlBuilder \ No newline at end of file +Dapper.SqlBuilder.Template.Parameters.get -> object? +Dapper.SqlBuilder.Template.RawSql.get -> string! +Dapper.SqlBuilder.Template.Template(Dapper.SqlBuilder! builder, string! sql, dynamic? parameters) -> void +Dapper.SqlBuilder.Where(string! sql, dynamic? parameters = null) -> Dapper.SqlBuilder! \ No newline at end of file diff --git a/Dapper.SqlBuilder/PublicAPI.Unshipped.txt b/Dapper.SqlBuilder/PublicAPI.Unshipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper.SqlBuilder/PublicAPI.Unshipped.txt +++ b/Dapper.SqlBuilder/PublicAPI.Unshipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper.SqlBuilder/SqlBuilder.cs b/Dapper.SqlBuilder/SqlBuilder.cs index ad7b1602f..5726fdc6e 100644 --- a/Dapper.SqlBuilder/SqlBuilder.cs +++ b/Dapper.SqlBuilder/SqlBuilder.cs @@ -1,4 +1,5 @@ using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Text.RegularExpressions; @@ -11,9 +12,15 @@ public class SqlBuilder private class Clause { - public string Sql { get; set; } - public object Parameters { get; set; } - public bool IsInclusive { get; set; } + public Clause(string sql, object? parameters, bool isInclusive) + { + Sql = sql; + Parameters = parameters; + IsInclusive = isInclusive; + } + public string Sql { get; } + public object? Parameters { get; } + public bool IsInclusive { get; } } private class Clauses : List @@ -52,10 +59,10 @@ public class Template { private readonly string _sql; private readonly SqlBuilder _builder; - private readonly object _initParams; + private readonly object? _initParams; private int _dataSeq = -1; // Unresolved - public Template(SqlBuilder builder, string sql, dynamic parameters) + public Template(SqlBuilder builder, string sql, dynamic? parameters) { _initParams = parameters; _sql = sql; @@ -85,73 +92,73 @@ private void ResolveSql() } } - private string rawSql; - private object parameters; + private string? rawSql; + private object? parameters; public string RawSql { - get { ResolveSql(); return rawSql; } + get { ResolveSql(); return rawSql!; } } - public object Parameters + public object? Parameters { get { ResolveSql(); return parameters; } } } - public Template AddTemplate(string sql, dynamic parameters = null) => - new Template(this, sql, parameters); + public Template AddTemplate(string sql, dynamic? parameters = null) => + new Template(this, sql, (object?)parameters); - protected SqlBuilder AddClause(string name, string sql, object parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false) + protected SqlBuilder AddClause(string name, string sql, object? parameters, string joiner, string prefix = "", string postfix = "", bool isInclusive = false) { - if (!_data.TryGetValue(name, out Clauses clauses)) + if (!_data.TryGetValue(name, out var clauses)) { clauses = new Clauses(joiner, prefix, postfix); _data[name] = clauses; } - clauses.Add(new Clause { Sql = sql, Parameters = parameters, IsInclusive = isInclusive }); + clauses.Add(new Clause(sql, parameters, isInclusive)); _seq++; return this; } - public SqlBuilder Intersect(string sql, dynamic parameters = null) => - AddClause("intersect", sql, parameters, "\nINTERSECT\n ", "\n ", "\n", false); + public SqlBuilder Intersect(string sql, dynamic? parameters = null) => + AddClause("intersect", sql, (object?)parameters, "\nINTERSECT\n ", "\n ", "\n", false); - public SqlBuilder InnerJoin(string sql, dynamic parameters = null) => - AddClause("innerjoin", sql, parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n", false); + public SqlBuilder InnerJoin(string sql, dynamic? parameters = null) => + AddClause("innerjoin", sql, (object?)parameters, "\nINNER JOIN ", "\nINNER JOIN ", "\n", false); - public SqlBuilder LeftJoin(string sql, dynamic parameters = null) => - AddClause("leftjoin", sql, parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n", false); + public SqlBuilder LeftJoin(string sql, dynamic? parameters = null) => + AddClause("leftjoin", sql, (object?)parameters, "\nLEFT JOIN ", "\nLEFT JOIN ", "\n", false); - public SqlBuilder RightJoin(string sql, dynamic parameters = null) => - AddClause("rightjoin", sql, parameters, "\nRIGHT JOIN ", "\nRIGHT JOIN ", "\n", false); + public SqlBuilder RightJoin(string sql, dynamic? parameters = null) => + AddClause("rightjoin", sql, (object?)parameters, "\nRIGHT JOIN ", "\nRIGHT JOIN ", "\n", false); - public SqlBuilder Where(string sql, dynamic parameters = null) => - AddClause("where", sql, parameters, " AND ", "WHERE ", "\n", false); + public SqlBuilder Where(string sql, dynamic? parameters = null) => + AddClause("where", sql, (object?)parameters, " AND ", "WHERE ", "\n", false); - public SqlBuilder OrWhere(string sql, dynamic parameters = null) => - AddClause("where", sql, parameters, " OR ", "WHERE ", "\n", true); + public SqlBuilder OrWhere(string sql, dynamic? parameters = null) => + AddClause("where", sql, (object?)parameters, " OR ", "WHERE ", "\n", true); - public SqlBuilder OrderBy(string sql, dynamic parameters = null) => - AddClause("orderby", sql, parameters, " , ", "ORDER BY ", "\n", false); + public SqlBuilder OrderBy(string sql, dynamic? parameters = null) => + AddClause("orderby", sql, (object?)parameters, " , ", "ORDER BY ", "\n", false); - public SqlBuilder Select(string sql, dynamic parameters = null) => - AddClause("select", sql, parameters, " , ", "", "\n", false); + public SqlBuilder Select(string sql, dynamic? parameters = null) => + AddClause("select", sql, (object?)parameters, " , ", "", "\n", false); public SqlBuilder AddParameters(dynamic parameters) => - AddClause("--parameters", "", parameters, "", "", "", false); + AddClause("--parameters", "", (object?)parameters, "", "", "", false); - public SqlBuilder Join(string sql, dynamic parameters = null) => - AddClause("join", sql, parameters, "\nJOIN ", "\nJOIN ", "\n", false); + public SqlBuilder Join(string sql, dynamic? parameters = null) => + AddClause("join", sql, (object?)parameters, "\nJOIN ", "\nJOIN ", "\n", false); - public SqlBuilder GroupBy(string sql, dynamic parameters = null) => - AddClause("groupby", sql, parameters, " , ", "\nGROUP BY ", "\n", false); + public SqlBuilder GroupBy(string sql, dynamic? parameters = null) => + AddClause("groupby", sql, (object?)parameters, " , ", "\nGROUP BY ", "\n", false); - public SqlBuilder Having(string sql, dynamic parameters = null) => - AddClause("having", sql, parameters, "\nAND ", "HAVING ", "\n", false); + public SqlBuilder Having(string sql, dynamic? parameters = null) => + AddClause("having", sql, (object?)parameters, "\nAND ", "HAVING ", "\n", false); - public SqlBuilder Set(string sql, dynamic parameters = null) => - AddClause("set", sql, parameters, " , ", "SET ", "\n", false); + public SqlBuilder Set(string sql, dynamic? parameters = null) => + AddClause("set", sql, (object?)parameters, " , ", "SET ", "\n", false); } } diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 023a4eae4..dec6f7f0c 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -8,17 +8,15 @@ net461;netstandard2.0;net5.0 true true + enable + true - - $(DefineConstants);PLAT_NO_REMOTING;PLAT_SKIP_LOCALS_INIT - true - - + diff --git a/Dapper.sln b/Dapper.sln index 4be17ed03..7a610ba6a 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -1,15 +1,17 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.28917.182 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33906.173 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig appveyor.yml = appveyor.yml + Build.csproj = Build.csproj build.ps1 = build.ps1 Dapper.png = Dapper.png Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props global.json = global.json docs\index.md = docs\index.md License.txt = License.txt @@ -31,9 +33,6 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{8A74F0B6-188F-45D2-8A4B-51E4F211805A}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" - ProjectSection(SolutionItems) = preProject - Directory.Build.props = Directory.Build.props - EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" ProjectSection(SolutionItems) = preProject @@ -51,6 +50,7 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" ProjectSection(SolutionItems) = preProject docs\index.md = docs\index.md + docs\readme.md = docs\readme.md EndProjectSection EndProject Global diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index 87d0d7119..6112353e7 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -11,7 +11,7 @@ namespace Dapper /// public readonly struct CommandDefinition { - internal static CommandDefinition ForCallback(object parameters) + internal static CommandDefinition ForCallback(object? parameters) { if (parameters is DynamicParameters) { @@ -36,12 +36,12 @@ internal void OnCompleted() /// /// The parameters associated with the command /// - public object Parameters { get; } + public object? Parameters { get; } /// /// The active transaction for the command /// - public IDbTransaction Transaction { get; } + public IDbTransaction? Transaction { get; } /// /// The effective timeout for the command @@ -83,7 +83,7 @@ internal void OnCompleted() /// The for this command. /// The behavior flags for this command. /// The cancellation token for this command. - public CommandDefinition(string commandText, object parameters = null, IDbTransaction transaction = null, int? commandTimeout = null, + public CommandDefinition(string commandText, object? parameters = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null, CommandFlags flags = CommandFlags.Buffered , CancellationToken cancellationToken = default ) @@ -97,9 +97,10 @@ public CommandDefinition(string commandText, object parameters = null, IDbTransa CancellationToken = cancellationToken; } - private CommandDefinition(object parameters) : this() + private CommandDefinition(object? parameters) : this() { Parameters = parameters; + CommandText = ""; } /// @@ -107,12 +108,12 @@ private CommandDefinition(object parameters) : this() /// public CancellationToken CancellationToken { get; } - internal IDbCommand SetupCommand(IDbConnection cnn, Action paramReader) + internal IDbCommand SetupCommand(IDbConnection cnn, Action? paramReader) { var cmd = cnn.CreateCommand(); var init = GetInit(cmd.GetType()); init?.Invoke(cmd); - if (Transaction != null) + if (Transaction is not null) cmd.Transaction = Transaction; cmd.CommandText = CommandText; if (CommandTimeout.HasValue) @@ -129,16 +130,16 @@ internal IDbCommand SetupCommand(IDbConnection cnn, Action p return cmd; } - private static SqlMapper.Link> commandInitCache; + private static SqlMapper.Link>? commandInitCache; internal static void ResetCommandInitCache() => SqlMapper.Link>.Clear(ref commandInitCache); - private static Action GetInit(Type commandType) + private static Action? GetInit(Type commandType) { - if (commandType == null) + if (commandType is null) return null; // GIGO - if (SqlMapper.Link>.TryGet(commandInitCache, commandType, out Action action)) + if (SqlMapper.Link>.TryGet(commandInitCache, commandType, out Action? action)) { return action; } @@ -184,11 +185,11 @@ private static Action GetInit(Type commandType) action = (Action)method.CreateDelegate(typeof(Action)); } // cache it - SqlMapper.Link>.TryAdd(ref commandInitCache, commandType, ref action); + SqlMapper.Link>.TryAdd(ref commandInitCache, commandType, ref action!); return action; } - private static MethodInfo GetBasicPropertySetter(Type declaringType, string name, Type expectedType) + private static MethodInfo? GetBasicPropertySetter(Type declaringType, string name, Type expectedType) { var prop = declaringType.GetProperty(name, BindingFlags.Public | BindingFlags.Instance); if (prop?.CanWrite == true && prop.PropertyType == expectedType && prop.GetIndexParameters().Length == 0) diff --git a/Dapper/CustomPropertyTypeMap.cs b/Dapper/CustomPropertyTypeMap.cs index ececeeda9..0d36f7470 100644 --- a/Dapper/CustomPropertyTypeMap.cs +++ b/Dapper/CustomPropertyTypeMap.cs @@ -28,14 +28,14 @@ public CustomPropertyTypeMap(Type type, Func propert /// DataReader column names /// DataReader column types /// Default constructor - public ConstructorInfo FindConstructor(string[] names, Type[] types) => - _type.GetConstructor(Array.Empty()); + public ConstructorInfo? FindConstructor(string[] names, Type[] types) => + _type.GetConstructor(Array.Empty())!; /// /// Always returns null /// /// - public ConstructorInfo FindExplicitConstructor() => null; + public ConstructorInfo? FindExplicitConstructor() => null; /// /// Not implemented as far as default constructor used for all cases @@ -53,10 +53,10 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, /// /// DataReader column name /// Property member map - public SqlMapper.IMemberMap GetMember(string columnName) + public SqlMapper.IMemberMap? GetMember(string columnName) { var prop = _propertySelector(_type, columnName); - return prop != null ? new SimpleMemberMap(columnName, prop) : null; + return prop is not null ? new SimpleMemberMap(columnName, prop) : null; } } } diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 765065285..cf98d5abf 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -6,6 +6,8 @@ A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0;net5.0 + enable + true @@ -16,17 +18,13 @@ - + all runtime; build; native; contentfiles; analyzers - - $(DefineConstants);PLAT_NO_REMOTING;PLAT_SKIP_LOCALS_INIT - true - - + diff --git a/Dapper/DataTableHandler.cs b/Dapper/DataTableHandler.cs index df4dc07a5..3e4bab2c1 100644 --- a/Dapper/DataTableHandler.cs +++ b/Dapper/DataTableHandler.cs @@ -4,12 +4,12 @@ namespace Dapper { internal sealed class DataTableHandler : SqlMapper.ITypeHandler { - public object Parse(Type destinationType, object value) + public object Parse(Type destinationType, object? value) { throw new NotImplementedException(); } - public void SetValue(IDbDataParameter parameter, object value) + public void SetValue(IDbDataParameter parameter, object? value) { TableValuedParameter.Set(parameter, value as DataTable, null); } diff --git a/Dapper/DbString.cs b/Dapper/DbString.cs index 320495aac..14dc4612b 100644 --- a/Dapper/DbString.cs +++ b/Dapper/DbString.cs @@ -43,7 +43,7 @@ public DbString() /// /// The value of the string /// - public string Value { get; set; } + public string? Value { get; set; } /// /// Gets a string representation of this DbString. @@ -76,7 +76,7 @@ public void AddParameter(IDbCommand command, string name) #pragma warning disable 0618 param.Value = SqlMapper.SanitizeParameterValue(Value); #pragma warning restore 0618 - if (Length == -1 && Value != null && Value.Length <= DefaultLength) + if (Length == -1 && Value is not null && Value.Length <= DefaultLength) { param.Size = DefaultLength; } diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index 12e431522..38c3cbd79 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -19,7 +19,7 @@ public sealed class DefaultTypeMap : SqlMapper.ITypeMap /// Entity type public DefaultTypeMap(Type type) { - if (type == null) + if (type is null) throw new ArgumentNullException(nameof(type)); _fields = GetSettableFields(type); @@ -27,24 +27,30 @@ public DefaultTypeMap(Type type) _type = type; } - internal static MethodInfo GetPropertySetter(PropertyInfo propertyInfo, Type type) + internal static MethodInfo GetPropertySetterOrThrow(PropertyInfo propertyInfo, Type type) + { + return GetPropertySetter(propertyInfo, type) ?? Throw(propertyInfo); + + static MethodInfo Throw(PropertyInfo propertyInfo) => throw new InvalidOperationException("Property setting not found for: " + propertyInfo?.Name); + } + internal static MethodInfo? GetPropertySetter(PropertyInfo propertyInfo, Type type) { if (propertyInfo.DeclaringType == type) return propertyInfo.GetSetMethod(true); - return propertyInfo.DeclaringType.GetProperty( + return propertyInfo.DeclaringType!.GetProperty( propertyInfo.Name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, propertyInfo.PropertyType, propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(), - null).GetSetMethod(true); + null)!.GetSetMethod(true); } internal static List GetSettableProps(Type t) { return t .GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance) - .Where(p => GetPropertySetter(p, t) != null) + .Where(p => GetPropertySetter(p, t) is not null) .ToList(); } @@ -59,7 +65,7 @@ internal static List GetSettableFields(Type t) /// DataReader column names /// DataReader column types /// Matching constructor or default one - public ConstructorInfo FindConstructor(string[] names, Type[] types) + public ConstructorInfo? FindConstructor(string[] names, Type[] types) { var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); foreach (ConstructorInfo ctor in constructors.OrderBy(c => c.IsPublic ? 0 : (c.IsPrivate ? 2 : 1)).ThenBy(c => c.GetParameters().Length)) @@ -106,7 +112,7 @@ public ConstructorInfo FindConstructor(string[] names, Type[] types) /// /// Returns the constructor, if any, that has the ExplicitConstructorAttribute on it. /// - public ConstructorInfo FindExplicitConstructor() + public ConstructorInfo? FindExplicitConstructor() { var constructors = _type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); var withAttr = constructors.Where(c => c.GetCustomAttributes(typeof(ExplicitConstructorAttribute), true).Length > 0).ToList(); @@ -127,8 +133,10 @@ public ConstructorInfo FindExplicitConstructor() /// Mapping implementation public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName) { - ParameterInfo param = MatchFirstOrDefault(constructor.GetParameters(), columnName, static p => p.Name); + var param = MatchFirstOrDefault(constructor.GetParameters(), columnName, static p => p.Name) ?? Throw(columnName); return new SimpleMemberMap(columnName, param); + + static ParameterInfo Throw(string name) => throw new ArgumentException("Constructor parameter not found for " + name); } /// @@ -136,11 +144,11 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, /// /// DataReader column name /// Mapping implementation - public SqlMapper.IMemberMap GetMember(string columnName) + public SqlMapper.IMemberMap? GetMember(string columnName) { var property = MatchFirstOrDefault(Properties, columnName, static p => p.Name); - if (property != null) + if (property is not null) return new SimpleMemberMap(columnName, property); // roslyn automatically implemented properties, in particular for get-only properties: <{Name}>k__BackingField; @@ -153,7 +161,7 @@ public SqlMapper.IMemberMap GetMember(string columnName) ?? _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); - if (field == null && MatchNamesWithUnderscores) + if (field is null && MatchNamesWithUnderscores) { var effectiveColumnName = columnName.Replace("_", ""); backingFieldName = "<" + effectiveColumnName + ">k__BackingField"; @@ -164,7 +172,7 @@ public SqlMapper.IMemberMap GetMember(string columnName) ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); } - if (field != null) + if (field is not null) return new SimpleMemberMap(columnName, field); return null; @@ -174,7 +182,7 @@ public SqlMapper.IMemberMap GetMember(string columnName) /// public static bool MatchNamesWithUnderscores { get; set; } - static T MatchFirstOrDefault(IList members, string name, Func selector) where T : class + static T? MatchFirstOrDefault(IList? members, string? name, Func selector) where T : class { if (members is { Count: > 0 }) { @@ -217,9 +225,9 @@ static T MatchFirstOrDefault(IList members, string name, Func s return null; } - internal static bool EqualsCI(string x, string y) + internal static bool EqualsCI(string? x, string? y) => string.Equals(x, y, StringComparison.OrdinalIgnoreCase); - internal static bool EqualsCIU(string x, string y) + internal static bool EqualsCIU(string? x, string? y) => string.Equals(x?.Replace("_", ""), y?.Replace("_", ""), StringComparison.OrdinalIgnoreCase); /// diff --git a/Dapper/DynamicParameters.ParamInfo.cs b/Dapper/DynamicParameters.ParamInfo.cs index 67c9c117e..51c5282b4 100644 --- a/Dapper/DynamicParameters.ParamInfo.cs +++ b/Dapper/DynamicParameters.ParamInfo.cs @@ -7,14 +7,14 @@ public partial class DynamicParameters { private sealed class ParamInfo { - public string Name { get; set; } - public object Value { get; set; } + public string Name { get; set; } = null!; + public object? Value { get; set; } public ParameterDirection ParameterDirection { get; set; } public DbType? DbType { get; set; } public int? Size { get; set; } - public IDbDataParameter AttachedParam { get; set; } - internal Action OutputCallback { get; set; } - internal object OutputTarget { get; set; } + public IDbDataParameter AttachedParam { get; set; } = null!; + internal Action? OutputCallback { get; set; } + internal object OutputTarget { get; set; } = null!; internal bool CameFromTemplate { get; set; } public byte? Precision { get; set; } diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index 2569a9383..d4759a37b 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -16,10 +16,10 @@ public partial class DynamicParameters : SqlMapper.IDynamicParameters, SqlMapper internal const DbType EnumerableMultiParameter = (DbType)(-1); private static readonly Dictionary> paramReaderCache = new Dictionary>(); private readonly Dictionary parameters = new Dictionary(); - private List templates; + private List? templates; - object SqlMapper.IParameterLookup.this[string name] => - parameters.TryGetValue(name, out ParamInfo param) ? param.Value : null; + object? SqlMapper.IParameterLookup.this[string name] => + parameters.TryGetValue(name, out ParamInfo? param) ? param.Value : null; /// /// construct a dynamic parameter bag @@ -33,7 +33,7 @@ public DynamicParameters() /// construct a dynamic parameter bag /// /// can be an anonymous type or a DynamicParameters bag - public DynamicParameters(object template) + public DynamicParameters(object? template) { RemoveUnused = true; AddDynamicParams(template); @@ -44,14 +44,14 @@ public DynamicParameters(object template) /// EG: AddDynamicParams(new {A = 1, B = 2}) // will add property A and B to the dynamic /// /// - public void AddDynamicParams(object param) + public void AddDynamicParams(object? param) { var obj = param; - if (obj != null) + if (obj is not null) { if (obj is DynamicParameters subDynamic) { - if (subDynamic.parameters != null) + if (subDynamic.parameters is not null) { foreach (var kvp in subDynamic.parameters) { @@ -59,7 +59,7 @@ public void AddDynamicParams(object param) } } - if (subDynamic.templates != null) + if (subDynamic.templates is not null) { templates ??= new List(); foreach (var t in subDynamic.templates) @@ -94,7 +94,7 @@ public void AddDynamicParams(object param) /// The type of the parameter. /// The in or out direction of the parameter. /// The size of the parameter. - public void Add(string name, object value, DbType? dbType, ParameterDirection? direction, int? size) + public void Add(string name, object? value, DbType? dbType, ParameterDirection? direction, int? size) { parameters[Clean(name)] = new ParamInfo { @@ -116,7 +116,7 @@ public void Add(string name, object value, DbType? dbType, ParameterDirection? d /// The size of the parameter. /// The precision of the parameter. /// The scale of the parameter. - public void Add(string name, object value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) + public void Add(string name, object? value = null, DbType? dbType = null, ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) { parameters[Clean(name)] = new ParamInfo { @@ -170,7 +170,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) { var literals = SqlMapper.GetLiteralTokens(identity.sql); - if (templates != null) + if (templates is not null) { foreach (var template in templates) { @@ -179,7 +179,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) lock (paramReaderCache) { - if (!paramReaderCache.TryGetValue(newIdent, out appender)) + if (!paramReaderCache.TryGetValue(newIdent, out appender!)) { appender = SqlMapper.CreateParamInfoGenerator(newIdent, true, RemoveUnused, literals); paramReaderCache[newIdent] = appender; @@ -213,7 +213,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) // Now that the parameters are added to the command, let's place our output callbacks var tmp = outputCallbacks; - if (tmp != null) + if (tmp is not null) { foreach (var generator in tmp) { @@ -231,8 +231,8 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) string name = Clean(param.Name); var isCustomQueryParameter = val is SqlMapper.ICustomQueryParameter; - SqlMapper.ITypeHandler handler = null; - if (dbType == null && val != null && !isCustomQueryParameter) + SqlMapper.ITypeHandler? handler = null; + if (dbType is null && val is not null && !isCustomQueryParameter) { #pragma warning disable 618 dbType = SqlMapper.LookupDbType(val.GetType(), name, true, out handler); @@ -240,7 +240,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) } if (isCustomQueryParameter) { - ((SqlMapper.ICustomQueryParameter)val).AddParameter(command, name); + ((SqlMapper.ICustomQueryParameter)val!).AddParameter(command, name); } else if (dbType == EnumerableMultiParameter) { @@ -263,7 +263,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) } p.Direction = param.ParameterDirection; - if (handler == null) + if (handler is null) { #pragma warning disable 0618 p.Value = SqlMapper.SanitizeParameterValue(val); @@ -277,16 +277,16 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) { p.Size = DbString.DefaultLength; } - if (param.Size != null) p.Size = param.Size.Value; - if (param.Precision != null) p.Precision = param.Precision.Value; - if (param.Scale != null) p.Scale = param.Scale.Value; + if (param.Size is not null) p.Size = param.Size.Value; + if (param.Precision is not null) p.Precision = param.Precision.Value; + if (param.Scale is not null) p.Scale = param.Scale.Value; } else { if (ShouldSetDbType(dbType)) p.DbType = dbType.GetValueOrDefault(); - if (param.Size != null) p.Size = param.Size.Value; - if (param.Precision != null) p.Precision = param.Precision.Value; - if (param.Scale != null) p.Scale = param.Scale.Value; + if (param.Size is not null) p.Size = param.Size.Value; + if (param.Precision is not null) p.Precision = param.Precision.Value; + if (param.Scale is not null) p.Scale = param.Scale.Value; handler.SetValue(p, val ?? DBNull.Value); } @@ -317,16 +317,16 @@ public T Get(string name) { var paramInfo = parameters[Clean(name)]; var attachedParam = paramInfo.AttachedParam; - object val = attachedParam == null ? paramInfo.Value : attachedParam.Value; + object? val = attachedParam is null ? paramInfo.Value : attachedParam.Value; if (val == DBNull.Value) { - if (default(T) != null) + if (default(T) is not null) { throw new ApplicationException("Attempting to cast a DBNull to a non nullable type! Note that out/return parameters will not have updated values until the data stream completes (after the 'foreach' for Query(..., buffered: false), or after the GridReader has been disposed for QueryMultiple)"); } - return default; + return default!; } - return (T)val; + return (T)val!; } /// @@ -339,7 +339,7 @@ public T Get(string name) /// /// The size to set on the parameter. Defaults to 0, or DbString.DefaultLength in case of strings. /// The DynamicParameters instance - public DynamicParameters Output(T target, Expression> expression, DbType? dbType = null, int? size = null) + public DynamicParameters Output(T target, Expression> expression, DbType? dbType = null, int? size = null) { static void ThrowInvalidChain() => throw new InvalidOperationException($"Expression must be a property/field chain off of a(n) {typeof(T).Name} instance"); @@ -349,7 +349,7 @@ static void ThrowInvalidChain() var lastMemberAccess = expression.Body as MemberExpression; #pragma warning restore IDE0019 // Use pattern matching - if (lastMemberAccess == null + if (lastMemberAccess is null || (!(lastMemberAccess.Member is PropertyInfo) && !(lastMemberAccess.Member is FieldInfo))) { @@ -367,10 +367,10 @@ static void ThrowInvalidChain() } // Does the chain consist of MemberExpressions leading to a ParameterExpression of type T? - MemberExpression diving = lastMemberAccess; + MemberExpression? diving = lastMemberAccess; // Retain a list of member names and the member expressions so we can rebuild the chain. - List names = new List(); - List chain = new List(); + List names = new List(); + List chain = new List(); do { @@ -384,18 +384,18 @@ static void ThrowInvalidChain() diving = diving?.Expression as MemberExpression; #pragma warning restore IDE0019 // use pattern matching - if (constant is object && constant.Type == typeof(T)) + if (constant is not null && constant.Type == typeof(T)) { break; } - else if (diving == null + else if (diving is null || (!(diving.Member is PropertyInfo) && !(diving.Member is FieldInfo))) { ThrowInvalidChain(); } } - while (diving != null); + while (diving is not null); var dynamicParamName = string.Concat(names.ToArray()); @@ -403,8 +403,8 @@ static void ThrowInvalidChain() var lookup = string.Join("|", names.ToArray()); var cache = CachedOutputSetters.Cache; - var setter = (Action)cache[lookup]; - if (setter != null) goto MAKECALLBACK; + var setter = (Action?)cache[lookup]; + if (setter is not null) goto MAKECALLBACK; // Come on let's build a method, let's build it, let's build it now! var dm = new DynamicMethod("ExpressionParam" + Guid.NewGuid().ToString(), null, new[] { typeof(object), GetType() }, true); @@ -416,20 +416,20 @@ static void ThrowInvalidChain() // Count - 1 to skip the last member access for (var i = 0; i < chain.Count - 1; i++) { - var member = chain[i].Member; + var member = chain[i]?.Member; if (member is PropertyInfo info) { var get = info.GetGetMethod(true); - il.Emit(OpCodes.Callvirt, get); // [Member{i}] + il.Emit(OpCodes.Callvirt, get!); // [Member{i}] } else // Else it must be a field! { - il.Emit(OpCodes.Ldfld, (FieldInfo)member); // [Member{i}] + il.Emit(OpCodes.Ldfld, (FieldInfo)member!); // [Member{i}] } } - var paramGetter = GetType().GetMethod("Get", new Type[] { typeof(string) }).MakeGenericMethod(lastMemberAccess.Type); + var paramGetter = GetType().GetMethod("Get", new Type[] { typeof(string) })!.MakeGenericMethod(lastMemberAccess!.Type); il.Emit(OpCodes.Ldarg_1); // [target] [DynamicParameters] il.Emit(OpCodes.Ldstr, dynamicParamName); // [target] [DynamicParameters] [ParamName] @@ -440,7 +440,7 @@ static void ThrowInvalidChain() if (lastMember is PropertyInfo property) { var set = property.GetSetMethod(true); - il.Emit(OpCodes.Callvirt, set); // SET + il.Emit(OpCodes.Callvirt, set!); // SET } else { @@ -463,7 +463,7 @@ static void ThrowInvalidChain() var targetMemberType = lastMemberAccess?.Type; int sizeToSet = (!size.HasValue && targetMemberType == typeof(string)) ? DbString.DefaultLength : size ?? 0; - if (parameters.TryGetValue(dynamicParamName, out ParamInfo parameter)) + if (parameters.TryGetValue(dynamicParamName, out ParamInfo? parameter)) { parameter.ParameterDirection = parameter.AttachedParam.Direction = ParameterDirection.InputOutput; @@ -481,13 +481,13 @@ static void ThrowInvalidChain() parameter = parameters[dynamicParamName]; parameter.OutputCallback = setter; - parameter.OutputTarget = target; + parameter.OutputTarget = target!; }); return this; } - private List outputCallbacks; + private List? outputCallbacks; void SqlMapper.IParameterCallbacks.OnCompleted() { diff --git a/Dapper/Extensions.cs b/Dapper/Extensions.cs index 6bd91529e..778011ae8 100644 --- a/Dapper/Extensions.cs +++ b/Dapper/Extensions.cs @@ -22,10 +22,10 @@ internal static Task CastResult(this Task task) return source.Task; } - private static void OnTaskCompleted(Task completedTask, object state) + private static void OnTaskCompleted(Task completedTask, object? state) where TFrom : TTo { - var source = (TaskCompletionSource)state; + var source = (TaskCompletionSource)state!; switch (completedTask.Status) { @@ -36,7 +36,7 @@ private static void OnTaskCompleted(Task completedTask, objec source.SetCanceled(); break; case TaskStatus.Faulted: - source.SetException(completedTask.Exception.InnerExceptions); + source.SetException(completedTask.Exception!.InnerExceptions); break; } } diff --git a/Dapper/FeatureSupport.cs b/Dapper/FeatureSupport.cs index 089e846ca..4d7d77e2e 100644 --- a/Dapper/FeatureSupport.cs +++ b/Dapper/FeatureSupport.cs @@ -17,9 +17,9 @@ private static readonly FeatureSupport /// Gets the feature set based on the passed connection /// /// The connection to get supported features for. - public static FeatureSupport Get(IDbConnection connection) + public static FeatureSupport Get(IDbConnection? connection) { - string name = connection?.GetType().Name; + string? name = connection?.GetType().Name; if (string.Equals(name, "npgsqlconnection", StringComparison.OrdinalIgnoreCase)) return Postgres; if (string.Equals(name, "clickhouseconnection", StringComparison.OrdinalIgnoreCase)) return ClickHouse; return Default; diff --git a/Dapper/NRT.cs b/Dapper/NRT.cs new file mode 100644 index 000000000..47c4366e6 --- /dev/null +++ b/Dapper/NRT.cs @@ -0,0 +1,12 @@ +#if !NET5_0_OR_GREATER +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Parameter, Inherited = false)] + internal sealed class NotNullWhenAttribute : Attribute + { + public NotNullWhenAttribute(bool returnValue) => ReturnValue = returnValue; + + public bool ReturnValue { get; } + } +} +#endif diff --git a/Dapper/Properties/AssemblyInfo.cs b/Dapper/Properties/AssemblyInfo.cs index d6c9623b5..06b4b135e 100644 --- a/Dapper/Properties/AssemblyInfo.cs +++ b/Dapper/Properties/AssemblyInfo.cs @@ -1,4 +1,19 @@ - -#if PLAT_SKIP_LOCALS_INIT -[module: System.Runtime.CompilerServices.SkipLocalsInit] +[module: System.Runtime.CompilerServices.SkipLocalsInit] + +#if !NET5_0_OR_GREATER +namespace System.Runtime.CompilerServices +{ + [AttributeUsage(AttributeTargets.Module | AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Constructor | AttributeTargets.Method | AttributeTargets.Property | AttributeTargets.Event | AttributeTargets.Interface, Inherited = false)] + internal sealed class SkipLocalsInitAttribute : Attribute {} +} +namespace System.Diagnostics.CodeAnalysis +{ + [AttributeUsage(AttributeTargets.Method | AttributeTargets.Property, Inherited = false, AllowMultiple = true)] + internal sealed class MemberNotNullAttribute : Attribute + { + public MemberNotNullAttribute(string member) {} + public MemberNotNullAttribute(params string[] members) {} + } +} + #endif diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt index 82f27d6cb..f2438ca4d 100644 --- a/Dapper/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI.Shipped.txt @@ -1,33 +1,34 @@ -abstract Dapper.SqlMapper.StringTypeHandler.Format(T xml) -> string -abstract Dapper.SqlMapper.StringTypeHandler.Parse(string xml) -> T -abstract Dapper.SqlMapper.TypeHandler.Parse(object value) -> T -abstract Dapper.SqlMapper.TypeHandler.SetValue(System.Data.IDbDataParameter parameter, T value) -> void +#nullable enable +abstract Dapper.SqlMapper.StringTypeHandler.Format(T xml) -> string! +abstract Dapper.SqlMapper.StringTypeHandler.Parse(string! xml) -> T +abstract Dapper.SqlMapper.TypeHandler.Parse(object? value) -> T? +abstract Dapper.SqlMapper.TypeHandler.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void const Dapper.DbString.DefaultLength = 4000 -> int Dapper.CommandDefinition Dapper.CommandDefinition.Buffered.get -> bool Dapper.CommandDefinition.CancellationToken.get -> System.Threading.CancellationToken Dapper.CommandDefinition.CommandDefinition() -> void -Dapper.CommandDefinition.CommandDefinition(string commandText, object parameters = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null, Dapper.CommandFlags flags = Dapper.CommandFlags.Buffered, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void -Dapper.CommandDefinition.CommandText.get -> string +Dapper.CommandDefinition.CommandDefinition(string! commandText, object? parameters = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null, Dapper.CommandFlags flags = Dapper.CommandFlags.Buffered, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void +Dapper.CommandDefinition.CommandText.get -> string! Dapper.CommandDefinition.CommandTimeout.get -> int? Dapper.CommandDefinition.CommandType.get -> System.Data.CommandType? Dapper.CommandDefinition.Flags.get -> Dapper.CommandFlags -Dapper.CommandDefinition.Parameters.get -> object +Dapper.CommandDefinition.Parameters.get -> object? Dapper.CommandDefinition.Pipelined.get -> bool -Dapper.CommandDefinition.Transaction.get -> System.Data.IDbTransaction +Dapper.CommandDefinition.Transaction.get -> System.Data.IDbTransaction? Dapper.CommandFlags Dapper.CommandFlags.Buffered = 1 -> Dapper.CommandFlags Dapper.CommandFlags.NoCache = 4 -> Dapper.CommandFlags Dapper.CommandFlags.None = 0 -> Dapper.CommandFlags Dapper.CommandFlags.Pipelined = 2 -> Dapper.CommandFlags Dapper.CustomPropertyTypeMap -Dapper.CustomPropertyTypeMap.CustomPropertyTypeMap(System.Type type, System.Func propertySelector) -> void -Dapper.CustomPropertyTypeMap.FindConstructor(string[] names, System.Type[] types) -> System.Reflection.ConstructorInfo -Dapper.CustomPropertyTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo -Dapper.CustomPropertyTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName) -> Dapper.SqlMapper.IMemberMap -Dapper.CustomPropertyTypeMap.GetMember(string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.CustomPropertyTypeMap.CustomPropertyTypeMap(System.Type! type, System.Func! propertySelector) -> void +Dapper.CustomPropertyTypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? +Dapper.CustomPropertyTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? +Dapper.CustomPropertyTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap! +Dapper.CustomPropertyTypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.DbString -Dapper.DbString.AddParameter(System.Data.IDbCommand command, string name) -> void +Dapper.DbString.AddParameter(System.Data.IDbCommand! command, string! name) -> void Dapper.DbString.DbString() -> void Dapper.DbString.IsAnsi.get -> bool Dapper.DbString.IsAnsi.set -> void @@ -35,100 +36,107 @@ Dapper.DbString.IsFixedLength.get -> bool Dapper.DbString.IsFixedLength.set -> void Dapper.DbString.Length.get -> int Dapper.DbString.Length.set -> void -Dapper.DbString.Value.get -> string +Dapper.DbString.Value.get -> string? Dapper.DbString.Value.set -> void Dapper.DefaultTypeMap -Dapper.DefaultTypeMap.DefaultTypeMap(System.Type type) -> void -Dapper.DefaultTypeMap.FindConstructor(string[] names, System.Type[] types) -> System.Reflection.ConstructorInfo -Dapper.DefaultTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo -Dapper.DefaultTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName) -> Dapper.SqlMapper.IMemberMap -Dapper.DefaultTypeMap.GetMember(string columnName) -> Dapper.SqlMapper.IMemberMap -Dapper.DefaultTypeMap.Properties.get -> System.Collections.Generic.List +Dapper.DefaultTypeMap.DefaultTypeMap(System.Type! type) -> void +Dapper.DefaultTypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? +Dapper.DefaultTypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? +Dapper.DefaultTypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap! +Dapper.DefaultTypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.IMemberMap? +Dapper.DefaultTypeMap.Properties.get -> System.Collections.Generic.List! Dapper.DynamicParameters -Dapper.DynamicParameters.Add(string name, object value = null, System.Data.DbType? dbType = null, System.Data.ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) -> void -Dapper.DynamicParameters.Add(string name, object value, System.Data.DbType? dbType, System.Data.ParameterDirection? direction, int? size) -> void -Dapper.DynamicParameters.AddDynamicParams(object param) -> void -Dapper.DynamicParameters.AddParameters(System.Data.IDbCommand command, Dapper.SqlMapper.Identity identity) -> void +Dapper.DynamicParameters.Add(string! name, object? value = null, System.Data.DbType? dbType = null, System.Data.ParameterDirection? direction = null, int? size = null, byte? precision = null, byte? scale = null) -> void +Dapper.DynamicParameters.Add(string! name, object? value, System.Data.DbType? dbType, System.Data.ParameterDirection? direction, int? size) -> void +Dapper.DynamicParameters.AddDynamicParams(object? param) -> void +Dapper.DynamicParameters.AddParameters(System.Data.IDbCommand! command, Dapper.SqlMapper.Identity! identity) -> void Dapper.DynamicParameters.DynamicParameters() -> void -Dapper.DynamicParameters.DynamicParameters(object template) -> void -Dapper.DynamicParameters.Get(string name) -> T -Dapper.DynamicParameters.Output(T target, System.Linq.Expressions.Expression> expression, System.Data.DbType? dbType = null, int? size = null) -> Dapper.DynamicParameters -Dapper.DynamicParameters.ParameterNames.get -> System.Collections.Generic.IEnumerable +Dapper.DynamicParameters.DynamicParameters(object? template) -> void +Dapper.DynamicParameters.Get(string! name) -> T +Dapper.DynamicParameters.Output(T target, System.Linq.Expressions.Expression!>! expression, System.Data.DbType? dbType = null, int? size = null) -> Dapper.DynamicParameters! +Dapper.DynamicParameters.ParameterNames.get -> System.Collections.Generic.IEnumerable! Dapper.DynamicParameters.RemoveUnused.get -> bool Dapper.DynamicParameters.RemoveUnused.set -> void Dapper.ExplicitConstructorAttribute Dapper.ExplicitConstructorAttribute.ExplicitConstructorAttribute() -> void Dapper.IWrappedDataReader -Dapper.IWrappedDataReader.Command.get -> System.Data.IDbCommand -Dapper.IWrappedDataReader.Reader.get -> System.Data.IDataReader +Dapper.IWrappedDataReader.Command.get -> System.Data.IDbCommand! +Dapper.IWrappedDataReader.Reader.get -> System.Data.IDataReader! Dapper.SqlMapper Dapper.SqlMapper.GridReader -Dapper.SqlMapper.GridReader.Command.get -> System.Data.IDbCommand +Dapper.SqlMapper.GridReader.CancellationToken.get -> System.Threading.CancellationToken +Dapper.SqlMapper.GridReader.Command.get -> System.Data.IDbCommand! Dapper.SqlMapper.GridReader.Command.set -> void Dapper.SqlMapper.GridReader.Dispose() -> void +Dapper.SqlMapper.GridReader.GridReader(System.Data.IDbCommand! command, System.Data.Common.DbDataReader! reader, Dapper.SqlMapper.Identity? identity, System.Action? onCompleted = null, object? state = null, bool addToCache = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Dapper.SqlMapper.GridReader.IsConsumed.get -> bool -Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Type type, bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Func func, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.Read(System.Type[] types, System.Func map, string splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable -Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task> -Dapper.SqlMapper.GridReader.ReadAsync(System.Type type, bool buffered = true) -> System.Threading.Tasks.Task> -Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task> -Dapper.SqlMapper.GridReader.ReadFirst() -> dynamic -Dapper.SqlMapper.GridReader.ReadFirst(System.Type type) -> object +Dapper.SqlMapper.GridReader.OnAfterGrid(int index) -> void +Dapper.SqlMapper.GridReader.OnAfterGridAsync(int index) -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.OnBeforeGrid() -> int +Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Type! type, bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Func! func, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.Read(System.Type![]! types, System.Func! map, string! splitOn = "id", bool buffered = true) -> System.Collections.Generic.IEnumerable! +Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task!>! +Dapper.SqlMapper.GridReader.ReadAsync(System.Type! type, bool buffered = true) -> System.Threading.Tasks.Task!>! +Dapper.SqlMapper.GridReader.ReadAsync(bool buffered = true) -> System.Threading.Tasks.Task!>! +Dapper.SqlMapper.GridReader.Reader.get -> System.Data.Common.DbDataReader! +Dapper.SqlMapper.GridReader.ReadFirst() -> dynamic! +Dapper.SqlMapper.GridReader.ReadFirst(System.Type! type) -> object! Dapper.SqlMapper.GridReader.ReadFirst() -> T -Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadFirstAsync(System.Type type) -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> dynamic -Dapper.SqlMapper.GridReader.ReadFirstOrDefault(System.Type type) -> object -Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> T -Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync(System.Type type) -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadSingle() -> dynamic -Dapper.SqlMapper.GridReader.ReadSingle(System.Type type) -> object +Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadFirstAsync(System.Type! type) -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadFirstAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> dynamic? +Dapper.SqlMapper.GridReader.ReadFirstOrDefault(System.Type! type) -> object? +Dapper.SqlMapper.GridReader.ReadFirstOrDefault() -> T? +Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync(System.Type! type) -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadFirstOrDefaultAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadSingle() -> dynamic! +Dapper.SqlMapper.GridReader.ReadSingle(System.Type! type) -> object! Dapper.SqlMapper.GridReader.ReadSingle() -> T -Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadSingleAsync(System.Type type) -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> dynamic -Dapper.SqlMapper.GridReader.ReadSingleOrDefault(System.Type type) -> object -Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> T -Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync(System.Type type) -> System.Threading.Tasks.Task -Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task +Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadSingleAsync(System.Type! type) -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadSingleAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> dynamic? +Dapper.SqlMapper.GridReader.ReadSingleOrDefault(System.Type! type) -> object? +Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> T? +Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync(System.Type! type) -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ResultIndex.get -> int Dapper.SqlMapper.ICustomQueryParameter -Dapper.SqlMapper.ICustomQueryParameter.AddParameter(System.Data.IDbCommand command, string name) -> void +Dapper.SqlMapper.ICustomQueryParameter.AddParameter(System.Data.IDbCommand! command, string! name) -> void Dapper.SqlMapper.Identity -Dapper.SqlMapper.Identity.Equals(Dapper.SqlMapper.Identity other) -> bool -Dapper.SqlMapper.Identity.ForDynamicParameters(System.Type type) -> Dapper.SqlMapper.Identity +Dapper.SqlMapper.Identity.Equals(Dapper.SqlMapper.Identity? other) -> bool +Dapper.SqlMapper.Identity.ForDynamicParameters(System.Type! type) -> Dapper.SqlMapper.Identity! Dapper.SqlMapper.IDynamicParameters -Dapper.SqlMapper.IDynamicParameters.AddParameters(System.Data.IDbCommand command, Dapper.SqlMapper.Identity identity) -> void +Dapper.SqlMapper.IDynamicParameters.AddParameters(System.Data.IDbCommand! command, Dapper.SqlMapper.Identity! identity) -> void Dapper.SqlMapper.IMemberMap -Dapper.SqlMapper.IMemberMap.ColumnName.get -> string -Dapper.SqlMapper.IMemberMap.Field.get -> System.Reflection.FieldInfo -Dapper.SqlMapper.IMemberMap.MemberType.get -> System.Type -Dapper.SqlMapper.IMemberMap.Parameter.get -> System.Reflection.ParameterInfo -Dapper.SqlMapper.IMemberMap.Property.get -> System.Reflection.PropertyInfo +Dapper.SqlMapper.IMemberMap.ColumnName.get -> string! +Dapper.SqlMapper.IMemberMap.Field.get -> System.Reflection.FieldInfo? +Dapper.SqlMapper.IMemberMap.MemberType.get -> System.Type! +Dapper.SqlMapper.IMemberMap.Parameter.get -> System.Reflection.ParameterInfo? +Dapper.SqlMapper.IMemberMap.Property.get -> System.Reflection.PropertyInfo? Dapper.SqlMapper.IParameterCallbacks Dapper.SqlMapper.IParameterCallbacks.OnCompleted() -> void Dapper.SqlMapper.IParameterLookup -Dapper.SqlMapper.IParameterLookup.this[string name].get -> object +Dapper.SqlMapper.IParameterLookup.this[string! name].get -> object? Dapper.SqlMapper.ITypeHandler -Dapper.SqlMapper.ITypeHandler.Parse(System.Type destinationType, object value) -> object -Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter parameter, object value) -> void +Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object? value) -> object? +Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object? value) -> void Dapper.SqlMapper.ITypeMap -Dapper.SqlMapper.ITypeMap.FindConstructor(string[] names, System.Type[] types) -> System.Reflection.ConstructorInfo -Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo -Dapper.SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo constructor, string columnName) -> Dapper.SqlMapper.IMemberMap -Dapper.SqlMapper.ITypeMap.GetMember(string columnName) -> Dapper.SqlMapper.IMemberMap +Dapper.SqlMapper.ITypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? +Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? +Dapper.SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap! +Dapper.SqlMapper.ITypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.SqlMapper.Settings Dapper.SqlMapper.StringTypeHandler Dapper.SqlMapper.StringTypeHandler.StringTypeHandler() -> void @@ -136,158 +144,158 @@ Dapper.SqlMapper.TypeHandler Dapper.SqlMapper.TypeHandler.TypeHandler() -> void Dapper.SqlMapper.TypeHandlerCache Dapper.SqlMapper.UdtTypeHandler -Dapper.SqlMapper.UdtTypeHandler.UdtTypeHandler(string udtTypeName) -> void -override Dapper.DbString.ToString() -> string -override Dapper.SqlMapper.Identity.Equals(object obj) -> bool +Dapper.SqlMapper.UdtTypeHandler.UdtTypeHandler(string! udtTypeName) -> void +override Dapper.DbString.ToString() -> string! +override Dapper.SqlMapper.Identity.Equals(object? obj) -> bool override Dapper.SqlMapper.Identity.GetHashCode() -> int -override Dapper.SqlMapper.Identity.ToString() -> string -override Dapper.SqlMapper.StringTypeHandler.Parse(object value) -> T -override Dapper.SqlMapper.StringTypeHandler.SetValue(System.Data.IDbDataParameter parameter, T value) -> void +override Dapper.SqlMapper.Identity.ToString() -> string! +override Dapper.SqlMapper.StringTypeHandler.Parse(object? value) -> T +override Dapper.SqlMapper.StringTypeHandler.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType? -readonly Dapper.SqlMapper.Identity.connectionString -> string +readonly Dapper.SqlMapper.Identity.connectionString -> string! readonly Dapper.SqlMapper.Identity.gridIndex -> int readonly Dapper.SqlMapper.Identity.hashCode -> int -readonly Dapper.SqlMapper.Identity.parametersType -> System.Type -readonly Dapper.SqlMapper.Identity.sql -> string -readonly Dapper.SqlMapper.Identity.type -> System.Type +readonly Dapper.SqlMapper.Identity.parametersType -> System.Type? +readonly Dapper.SqlMapper.Identity.sql -> string! +readonly Dapper.SqlMapper.Identity.type -> System.Type? static Dapper.DbString.IsAnsiDefault.get -> bool static Dapper.DbString.IsAnsiDefault.set -> void static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.get -> bool static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.set -> void -static Dapper.SqlMapper.AddTypeHandler(System.Type type, Dapper.SqlMapper.ITypeHandler handler) -> void -static Dapper.SqlMapper.AddTypeHandler(Dapper.SqlMapper.TypeHandler handler) -> void -static Dapper.SqlMapper.AddTypeHandlerImpl(System.Type type, Dapper.SqlMapper.ITypeHandler handler, bool clone) -> void -static Dapper.SqlMapper.AddTypeMap(System.Type type, System.Data.DbType dbType) -> void -static Dapper.SqlMapper.AddTypeMap(System.Type type, System.Data.DbType dbType, bool useGetFieldValue) -> void -static Dapper.SqlMapper.AsList(this System.Collections.Generic.IEnumerable source) -> System.Collections.Generic.List -static Dapper.SqlMapper.AsTableValuedParameter(this System.Data.DataTable table, string typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter -static Dapper.SqlMapper.AsTableValuedParameter(this System.Collections.Generic.IEnumerable list, string typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter -static Dapper.SqlMapper.ConnectionStringComparer.get -> System.Collections.Generic.IEqualityComparer +static Dapper.SqlMapper.AddTypeHandler(System.Type! type, Dapper.SqlMapper.ITypeHandler! handler) -> void +static Dapper.SqlMapper.AddTypeHandler(Dapper.SqlMapper.TypeHandler! handler) -> void +static Dapper.SqlMapper.AddTypeHandlerImpl(System.Type! type, Dapper.SqlMapper.ITypeHandler? handler, bool clone) -> void +static Dapper.SqlMapper.AddTypeMap(System.Type! type, System.Data.DbType dbType) -> void +static Dapper.SqlMapper.AddTypeMap(System.Type! type, System.Data.DbType dbType, bool useGetFieldValue) -> void +static Dapper.SqlMapper.AsList(this System.Collections.Generic.IEnumerable? source) -> System.Collections.Generic.List! +static Dapper.SqlMapper.AsTableValuedParameter(this System.Data.DataTable! table, string? typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter! +static Dapper.SqlMapper.AsTableValuedParameter(this System.Collections.Generic.IEnumerable! list, string? typeName = null) -> Dapper.SqlMapper.ICustomQueryParameter! +static Dapper.SqlMapper.ConnectionStringComparer.get -> System.Collections.Generic.IEqualityComparer! static Dapper.SqlMapper.ConnectionStringComparer.set -> void -static Dapper.SqlMapper.CreateParamInfoGenerator(Dapper.SqlMapper.Identity identity, bool checkForDuplicates, bool removeUnused) -> System.Action -static Dapper.SqlMapper.Execute(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> int -static Dapper.SqlMapper.Execute(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> int -static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Data.IDataReader -static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Data.IDataReader -static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Data.IDataReader -static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> object -static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object -static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T -static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T -static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.FindOrAddParameter(System.Data.IDataParameterCollection parameters, System.Data.IDbCommand command, string name) -> System.Data.IDbDataParameter -static Dapper.SqlMapper.Format(object value) -> string -static Dapper.SqlMapper.GetCachedSQL(int ignoreHitCountAbove = 2147483647) -> System.Collections.Generic.IEnumerable> +static Dapper.SqlMapper.CreateParamInfoGenerator(Dapper.SqlMapper.Identity! identity, bool checkForDuplicates, bool removeUnused) -> System.Action! +static Dapper.SqlMapper.Execute(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> int +static Dapper.SqlMapper.Execute(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> int +static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Data.IDataReader! +static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Data.IDataReader! +static Dapper.SqlMapper.ExecuteReader(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Data.IDataReader! +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection! cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Data.CommandBehavior commandBehavior) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteReaderAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> object? +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object? +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T? +static Dapper.SqlMapper.ExecuteScalar(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T? +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ExecuteScalarAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.FindOrAddParameter(System.Data.IDataParameterCollection! parameters, System.Data.IDbCommand! command, string! name) -> System.Data.IDbDataParameter! +static Dapper.SqlMapper.Format(object? value) -> string! +static Dapper.SqlMapper.GetCachedSQL(int ignoreHitCountAbove = 2147483647) -> System.Collections.Generic.IEnumerable!>! static Dapper.SqlMapper.GetCachedSQLCount() -> int -static Dapper.SqlMapper.GetHashCollissions() -> System.Collections.Generic.IEnumerable> -static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader reader, System.Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func -static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader reader, System.Type type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func -static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader reader, System.Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func -static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader reader, System.Type concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func -static Dapper.SqlMapper.GetTypeDeserializer(System.Type type, System.Data.Common.DbDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func -static Dapper.SqlMapper.GetTypeDeserializer(System.Type type, System.Data.IDataReader reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func -static Dapper.SqlMapper.GetTypeMap(System.Type type) -> Dapper.SqlMapper.ITypeMap -static Dapper.SqlMapper.GetTypeName(this System.Data.DataTable table) -> string -static Dapper.SqlMapper.HasTypeHandler(System.Type type) -> bool -static Dapper.SqlMapper.LookupDbType(System.Type type, string name, bool demand, out Dapper.SqlMapper.ITypeHandler handler) -> System.Data.DbType? -static Dapper.SqlMapper.PackListParameters(System.Data.IDbCommand command, string namePrefix, object value) -> void -static Dapper.SqlMapper.Parse(this System.Data.IDataReader reader) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Parse(this System.Data.IDataReader reader, System.Type type) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Parse(this System.Data.IDataReader reader) -> System.Collections.Generic.IEnumerable +static Dapper.SqlMapper.GetHashCollissions() -> System.Collections.Generic.IEnumerable!>! +static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader! reader, System.Type! type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! +static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader! reader, System.Type! type, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! +static Dapper.SqlMapper.GetRowParser(this System.Data.Common.DbDataReader! reader, System.Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! +static Dapper.SqlMapper.GetRowParser(this System.Data.IDataReader! reader, System.Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! +static Dapper.SqlMapper.GetTypeDeserializer(System.Type! type, System.Data.Common.DbDataReader! reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! +static Dapper.SqlMapper.GetTypeDeserializer(System.Type! type, System.Data.IDataReader! reader, int startBound = 0, int length = -1, bool returnNullIfFirstMissing = false) -> System.Func! +static Dapper.SqlMapper.GetTypeMap(System.Type! type) -> Dapper.SqlMapper.ITypeMap! +static Dapper.SqlMapper.GetTypeName(this System.Data.DataTable! table) -> string? +static Dapper.SqlMapper.HasTypeHandler(System.Type! type) -> bool +static Dapper.SqlMapper.LookupDbType(System.Type! type, string! name, bool demand, out Dapper.SqlMapper.ITypeHandler? handler) -> System.Data.DbType? +static Dapper.SqlMapper.PackListParameters(System.Data.IDbCommand! command, string! namePrefix, object? value) -> void +static Dapper.SqlMapper.Parse(this System.Data.IDataReader! reader) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Parse(this System.Data.IDataReader! reader, System.Type! type) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Parse(this System.Data.IDataReader! reader) -> System.Collections.Generic.IEnumerable! static Dapper.SqlMapper.PurgeQueryCache() -> void -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.Query(this System.Data.IDbConnection cnn, string sql, System.Type[] types, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command, System.Func map, string splitOn = "Id") -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection cnn, string sql, System.Type[] types, System.Func map, object param = null, System.Data.IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task> -static Dapper.SqlMapper.QueryCachePurged -> System.EventHandler -static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic -static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object -static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T -static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T -static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic -static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object -static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T -static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T -static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> Dapper.SqlMapper.GridReader -static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> Dapper.SqlMapper.GridReader -static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic -static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object -static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T -static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T -static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic -static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object -static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> T -static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T -static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, System.Type type, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection cnn, string sql, object param = null, System.Data.IDbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task -static Dapper.SqlMapper.ReadChar(object value) -> char -static Dapper.SqlMapper.ReadNullableChar(object value) -> char? -static Dapper.SqlMapper.RemoveTypeMap(System.Type type) -> void -static Dapper.SqlMapper.ReplaceLiterals(this Dapper.SqlMapper.IParameterLookup parameters, System.Data.IDbCommand command) -> void +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.Query(this System.Data.IDbConnection! cnn, string! sql, System.Type![]! types, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IEnumerable! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command, System.Func! map, string! splitOn = "Id") -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryAsync(this System.Data.IDbConnection! cnn, string! sql, System.Type![]! types, System.Func! map, object? param = null, System.Data.IDbTransaction? transaction = null, bool buffered = true, string! splitOn = "Id", int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task!>! +static Dapper.SqlMapper.QueryCachePurged -> System.EventHandler? +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic! +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object! +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T +static Dapper.SqlMapper.QueryFirst(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic? +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object? +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T? +static Dapper.SqlMapper.QueryFirstOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T? +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryFirstOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> Dapper.SqlMapper.GridReader! +static Dapper.SqlMapper.QueryMultiple(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> Dapper.SqlMapper.GridReader! +static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryMultipleAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic! +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object! +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T +static Dapper.SqlMapper.QuerySingle(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> dynamic? +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> object? +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> T? +static Dapper.SqlMapper.QuerySingleOrDefault(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> T? +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.ReadChar(object! value) -> char +static Dapper.SqlMapper.ReadNullableChar(object! value) -> char? +static Dapper.SqlMapper.RemoveTypeMap(System.Type! type) -> void +static Dapper.SqlMapper.ReplaceLiterals(this Dapper.SqlMapper.IParameterLookup! parameters, System.Data.IDbCommand! command) -> void static Dapper.SqlMapper.ResetTypeHandlers() -> void -static Dapper.SqlMapper.SanitizeParameterValue(object value) -> object -static Dapper.SqlMapper.SetDbType(System.Data.IDataParameter parameter, object value) -> void +static Dapper.SqlMapper.SanitizeParameterValue(object? value) -> object! +static Dapper.SqlMapper.SetDbType(System.Data.IDataParameter! parameter, object! value) -> void static Dapper.SqlMapper.Settings.ApplyNullValues.get -> bool static Dapper.SqlMapper.Settings.ApplyNullValues.set -> void static Dapper.SqlMapper.Settings.CommandTimeout.get -> int? @@ -305,9 +313,9 @@ static Dapper.SqlMapper.Settings.UseSingleResultOptimization.get -> bool static Dapper.SqlMapper.Settings.UseSingleResultOptimization.set -> void static Dapper.SqlMapper.Settings.UseSingleRowOptimization.get -> bool static Dapper.SqlMapper.Settings.UseSingleRowOptimization.set -> void -static Dapper.SqlMapper.SetTypeMap(System.Type type, Dapper.SqlMapper.ITypeMap map) -> void -static Dapper.SqlMapper.SetTypeName(this System.Data.DataTable table, string typeName) -> void -static Dapper.SqlMapper.ThrowDataException(System.Exception ex, int index, System.Data.IDataReader reader, object value) -> void -static Dapper.SqlMapper.TypeHandlerCache.Parse(object value) -> T -static Dapper.SqlMapper.TypeHandlerCache.SetValue(System.Data.IDbDataParameter parameter, object value) -> void -static Dapper.SqlMapper.TypeMapProvider -> System.Func \ No newline at end of file +static Dapper.SqlMapper.SetTypeMap(System.Type! type, Dapper.SqlMapper.ITypeMap? map) -> void +static Dapper.SqlMapper.SetTypeName(this System.Data.DataTable! table, string! typeName) -> void +static Dapper.SqlMapper.ThrowDataException(System.Exception! ex, int index, System.Data.IDataReader! reader, object? value) -> void +static Dapper.SqlMapper.TypeHandlerCache.Parse(object! value) -> T? +static Dapper.SqlMapper.TypeHandlerCache.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void +static Dapper.SqlMapper.TypeMapProvider -> System.Func! \ No newline at end of file diff --git a/Dapper/PublicAPI.Unshipped.txt b/Dapper/PublicAPI.Unshipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper/PublicAPI.Unshipped.txt +++ b/Dapper/PublicAPI.Unshipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper/PublicAPI/net461/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net461/PublicAPI.Shipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper/PublicAPI/net461/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI/net461/PublicAPI.Shipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt +++ b/Dapper/PublicAPI/net461/PublicAPI.Unshipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt index 0a026495a..7e50a2832 100644 --- a/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt @@ -1,4 +1,5 @@ -Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask -Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection cnn, string sql, object param = null, System.Data.Common.DbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection cnn, string sql, object param = null, System.Data.Common.DbTransaction transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable \ No newline at end of file +#nullable enable +Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! \ No newline at end of file diff --git a/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt +++ b/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Shipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt index 5f282702b..91b0e1a43 100644 --- a/Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt +++ b/Dapper/PublicAPI/netstandard2.0/PublicAPI.Unshipped.txt @@ -1 +1 @@ - \ No newline at end of file +#nullable enable \ No newline at end of file diff --git a/Dapper/SimpleMemberMap.cs b/Dapper/SimpleMemberMap.cs index 6c9ede863..46ff5a070 100644 --- a/Dapper/SimpleMemberMap.cs +++ b/Dapper/SimpleMemberMap.cs @@ -49,21 +49,21 @@ public SimpleMemberMap(string columnName, ParameterInfo parameter) /// /// Target member type /// - public Type MemberType => Field?.FieldType ?? Property?.PropertyType ?? Parameter?.ParameterType; + public Type MemberType => Field?.FieldType ?? Property?.PropertyType ?? Parameter?.ParameterType!; /// /// Target property /// - public PropertyInfo Property { get; } + public PropertyInfo? Property { get; } /// /// Target field /// - public FieldInfo Field { get; } + public FieldInfo? Field { get; } /// /// Target constructor parameter /// - public ParameterInfo Parameter { get; } + public ParameterInfo? Parameter { get; } } } diff --git a/Dapper/SqlDataRecordHandler.cs b/Dapper/SqlDataRecordHandler.cs index 2d9976338..ed113dc02 100644 --- a/Dapper/SqlDataRecordHandler.cs +++ b/Dapper/SqlDataRecordHandler.cs @@ -7,12 +7,12 @@ namespace Dapper internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler where T : IDataRecord { - public object Parse(Type destinationType, object value) + public object Parse(Type destinationType, object? value) { throw new NotSupportedException(); } - public void SetValue(IDbDataParameter parameter, object value) + public void SetValue(IDbDataParameter parameter, object? value) { SqlDataRecordListTVPParameter.Set(parameter, value as IEnumerable, null); } diff --git a/Dapper/SqlDataRecordListTVPParameter.cs b/Dapper/SqlDataRecordListTVPParameter.cs index c83542169..28a6ab27c 100644 --- a/Dapper/SqlDataRecordListTVPParameter.cs +++ b/Dapper/SqlDataRecordListTVPParameter.cs @@ -35,9 +35,9 @@ void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string nam command.Parameters.Add(param); } - internal static void Set(IDbDataParameter parameter, IEnumerable data, string typeName) + internal static void Set(IDbDataParameter parameter, IEnumerable? data, string? typeName) { - parameter.Value = data != null && data.Any() ? data : null; + parameter.Value = data is not null && data.Any() ? data : null; StructuredHelper.ConfigureTVP(parameter, typeName); } } @@ -46,16 +46,16 @@ static class StructuredHelper private static readonly Hashtable s_udt = new Hashtable(), s_tvp = new Hashtable(); private static Action GetUDT(Type type) - => (Action)s_udt[type] ?? SlowGetHelper(type, s_udt, "UdtTypeName", 29); // 29 = SqlDbType.Udt (avoiding ref) - private static Action GetTVP(Type type) - => (Action)s_tvp[type] ?? SlowGetHelper(type, s_tvp, "TypeName", 30); // 30 = SqlDbType.Structured (avoiding ref) + => (Action?)s_udt[type] ?? SlowGetHelper(type, s_udt, "UdtTypeName", 29); // 29 = SqlDbType.Udt (avoiding ref) + private static Action GetTVP(Type type) + => (Action?)s_tvp[type] ?? SlowGetHelper(type, s_tvp, "TypeName", 30); // 30 = SqlDbType.Structured (avoiding ref) - static Action SlowGetHelper(Type type, Hashtable hashtable, string nameProperty, int sqlDbType) + static Action SlowGetHelper(Type type, Hashtable hashtable, string nameProperty, int sqlDbType) { lock (hashtable) { - var helper = (Action)hashtable[type]; - if (helper == null) + var helper = (Action?)hashtable[type]; + if (helper is null) { helper = CreateFor(type, nameProperty, sqlDbType); hashtable.Add(type, helper); @@ -64,16 +64,16 @@ static Action SlowGetHelper(Type type, Hashtable hasht } } - static Action CreateFor(Type type, string nameProperty, int sqlDbType) + static Action CreateFor(Type type, string nameProperty, int sqlDbType) { var name = type.GetProperty(nameProperty, BindingFlags.Public | BindingFlags.Instance); - if (name == null || !name.CanWrite) + if (name is null || !name.CanWrite) { return (p, n) => { }; } var dbType = type.GetProperty("SqlDbType", BindingFlags.Public | BindingFlags.Instance); - if (dbType != null && !dbType.CanWrite) dbType = null; + if (dbType is not null && !dbType.CanWrite) dbType = null; var dm = new DynamicMethod(nameof(CreateFor) + "_" + type.Name, null, new[] { typeof(IDbDataParameter), typeof(string) }, true); @@ -81,18 +81,18 @@ static Action CreateFor(Type type, string nameProperty il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Ldarg_1); - il.EmitCall(OpCodes.Callvirt, name.GetSetMethod(), null); + il.EmitCall(OpCodes.Callvirt, name.GetSetMethod()!, null); - if (dbType != null) + if (dbType is not null) { il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Castclass, type); il.Emit(OpCodes.Ldc_I4, sqlDbType); - il.EmitCall(OpCodes.Callvirt, dbType.GetSetMethod(), null); + il.EmitCall(OpCodes.Callvirt, dbType.GetSetMethod()!, null); } il.Emit(OpCodes.Ret); - return (Action)dm.CreateDelegate(typeof(Action)); + return (Action)dm.CreateDelegate(typeof(Action)); } @@ -100,7 +100,7 @@ static Action CreateFor(Type type, string nameProperty // would be a fair option otherwise internal static void ConfigureUDT(IDbDataParameter parameter, string typeName) => GetUDT(parameter.GetType())(parameter, typeName); - internal static void ConfigureTVP(IDbDataParameter parameter, string typeName) + internal static void ConfigureTVP(IDbDataParameter parameter, string? typeName) => GetTVP(parameter.GetType())(parameter, typeName); } } diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 25b7e7125..07f073ee5 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -24,7 +24,7 @@ public static partial class SqlMapper /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryAsync(cnn, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// @@ -51,8 +51,8 @@ public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefin /// The connection to query on. /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => - QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), command); + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), command); /// /// Execute a single-row query asynchronously using Task. @@ -69,8 +69,8 @@ public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefi /// The connection to query on. /// The command used to query on this connection. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => - QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), command); + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), command); /// /// Execute a query asynchronously using Task. @@ -87,7 +87,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Co /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// @@ -101,7 +101,7 @@ public static Task> QueryAsync(this IDbConnection cnn, string /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.First, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// @@ -115,8 +115,8 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, obj /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -129,7 +129,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.Single, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// @@ -143,8 +143,8 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, ob /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -156,7 +156,7 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, strin /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task QueryFirstAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.First, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// @@ -169,8 +169,8 @@ public static Task QueryFirstAsync(this IDbConnection cnn, string sql, /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a single-row query asynchronously using Task. @@ -182,7 +182,7 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, str /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task QuerySingleAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryRowAsync(cnn, Row.Single, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// @@ -195,8 +195,8 @@ public static Task QuerySingleAsync(this IDbConnection cnn, string sql, /// The command timeout (in seconds). /// The type of command to execute. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => - QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(DapperRow), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); /// /// Execute a query asynchronously using Task. @@ -210,9 +210,9 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, st /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static Task> QueryAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return QueryAsync(cnn, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); } @@ -228,9 +228,9 @@ public static Task> QueryAsync(this IDbConnection cnn, Type /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static Task QueryFirstAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return QueryRowAsync(cnn, Row.First, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// @@ -245,10 +245,10 @@ public static Task QueryFirstAsync(this IDbConnection cnn, Type type, st /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); - return QueryRowAsync(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + if (type is null) throw new ArgumentNullException(nameof(type)); + return QueryRowAsync(cnn, Row.FirstOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// /// Execute a single-row query asynchronously using Task. @@ -262,9 +262,9 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static Task QuerySingleAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return QueryRowAsync(cnn, Row.Single, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// @@ -279,10 +279,10 @@ public static Task QuerySingleAsync(this IDbConnection cnn, Type type, s /// The type of command to execute. /// is null. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); - return QueryRowAsync(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + if (type is null) throw new ArgumentNullException(nameof(type)); + return QueryRowAsync(cnn, Row.SingleOrDefault, type, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); } /// @@ -331,8 +331,8 @@ public static Task QueryFirstAsync(this IDbConnection cnn, CommandDefiniti /// The connection to query on. /// The type to return. /// The command used to query on this connection. - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => - QueryRowAsync(cnn, Row.FirstOrDefault, type, command); + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, type, command); /// /// Execute a single-row query asynchronously using Task. @@ -340,8 +340,8 @@ public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, Type /// The type to return. /// The connection to query on. /// The command used to query on this connection. - public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => - QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), command); + public static Task QueryFirstOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.FirstOrDefault, typeof(T), command); /// /// Execute a single-row query asynchronously using Task. @@ -367,8 +367,8 @@ public static Task QuerySingleAsync(this IDbConnection cnn, CommandDefinit /// The connection to query on. /// The type to return. /// The command used to query on this connection. - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => - QueryRowAsync(cnn, Row.SingleOrDefault, type, command); + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Type type, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, type, command); /// /// Execute a single-row query asynchronously using Task. @@ -376,13 +376,13 @@ public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, Typ /// The type to return. /// The connection to query on. /// The command used to query on this connection. - public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => - QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), command); + public static Task QuerySingleOrDefaultAsync(this IDbConnection cnn, CommandDefinition command) => + QueryRowAsync(cnn, Row.SingleOrDefault, typeof(T), command); private static Task ExecuteReaderWithFlagsFallbackAsync(DbCommand cmd, bool wasClosed, CommandBehavior behavior, CancellationToken cancellationToken) { var task = cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); - if (task.Status == TaskStatus.Faulted && Settings.DisableCommandBehaviorOptimizations(behavior, task.Exception.InnerException)) + if (task.Status == TaskStatus.Faulted && Settings.DisableCommandBehaviorOptimizations(behavior, task.Exception!.InnerException!)) { // we can retry; this time it will have different flags return cmd.ExecuteReaderAsync(GetBehavior(wasClosed, behavior), cancellationToken); } @@ -407,7 +407,7 @@ private static Task TryOpenAsync(this IDbConnection cnn, CancellationToken cance /// /// Attempts setup a on a , with a better error message for unsupported usages. /// - private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action paramReader) + private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, IDbConnection cnn, Action? paramReader) { if (command.SetupCommand(cnn, paramReader) is DbCommand dbCommand) { @@ -421,13 +421,13 @@ private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, ID private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); - DbDataReader reader = null; + DbDataReader? reader = null; try { if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); @@ -435,7 +435,7 @@ private static async Task> QueryAsync(this IDbConnection cnn, var tuple = info.Deserializer; int hash = GetColumnHash(reader); - if (tuple.Func == null || tuple.Hash != hash) + if (tuple.Func is null || tuple.Hash != hash) { if (reader.FieldCount == 0) return Enumerable.Empty(); @@ -476,13 +476,13 @@ private static async Task> QueryAsync(this IDbConnection cnn, private static async Task QueryRowAsync(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); - DbDataReader reader = null; + DbDataReader? reader = null; try { if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); @@ -490,7 +490,7 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T ? CommandBehavior.SequentialAccess | CommandBehavior.SingleResult // need to allow multiple rows, to check fail condition : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow, cancel).ConfigureAwait(false); - T result = default; + T result = default!; if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { result = ReadRow(info, identity, ref command, effectiveType, reader); @@ -522,7 +522,7 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The number of rows affected. - public static Task ExecuteAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task ExecuteAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered, default)); /// @@ -533,9 +533,9 @@ public static Task ExecuteAsync(this IDbConnection cnn, string sql, object /// The number of rows affected. public static Task ExecuteAsync(this IDbConnection cnn, CommandDefinition command) { - object param = command.Parameters; - IEnumerable multiExec = GetMultiExec(param); - if (multiExec != null) + object? param = command.Parameters; + IEnumerable? multiExec = GetMultiExec(param); + if (multiExec is not null) { return ExecuteMultiImplAsync(cnn, command, multiExec); } @@ -565,13 +565,13 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD { if (wasClosed) await cnn.TryOpenAsync(command.CancellationToken).ConfigureAwait(false); - CacheInfo info = null; - string masterSql = null; + CacheInfo? info = null; + string? masterSql = null; if ((command.Flags & CommandFlags.Pipelined) != 0) { const int MAX_PENDING = 100; var pending = new Queue(MAX_PENDING); - DbCommand cmd = null; + DbCommand? cmd = null; try { foreach (var obj in multiExec) @@ -596,7 +596,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD { cmd = command.TrySetupAsyncCommand(cnn, null); } - info.ParamReader(cmd, obj); + info!.ParamReader!(cmd, obj); var task = cmd.ExecuteNonQueryAsync(command.CancellationToken); pending.Enqueue(new AsyncExecState(cmd, task)); @@ -636,7 +636,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD cmd.CommandText = masterSql; // because we do magic replaces on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } - info.ParamReader(cmd, obj); + info!.ParamReader!(cmd, obj); total += await cmd.ExecuteNonQueryAsync(command.CancellationToken).ConfigureAwait(false); } } @@ -650,7 +650,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD return total; } - private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object param) + private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object? param) { var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); @@ -687,7 +687,7 @@ private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefini /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -726,7 +726,7 @@ public static Task> QueryAsync(th /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -767,7 +767,7 @@ public static Task> QueryAsyncIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -810,7 +810,7 @@ public static Task> QueryAsyncIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -855,7 +855,7 @@ public static Task> QueryAsyncIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -902,7 +902,7 @@ public static Task> QueryAsyncIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static Task> QueryAsync(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMapAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default), map, splitOn); @@ -929,7 +929,7 @@ public static Task> QueryAsync> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; @@ -965,7 +965,7 @@ private static async Task> MultiMapAsyncIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) + public static Task> QueryAsync(this IDbConnection cnn, string sql, Type[] types, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None, default); return MultiMapAsync(cnn, command, types, map, splitOn); @@ -978,7 +978,7 @@ private static async Task> MultiMapAsync(this IDbC throw new ArgumentException("you must provide at least one type to deserialize"); } - object param = command.Parameters; + object? param = command.Parameters; var identity = new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; @@ -996,7 +996,7 @@ private static async Task> MultiMapAsync(this IDbC } } - private static IEnumerable ExecuteReaderSync(DbDataReader reader, Func func, object parameters) + private static IEnumerable ExecuteReaderSync(DbDataReader reader, Func func, object? parameters) { using (reader) { @@ -1018,7 +1018,7 @@ private static IEnumerable ExecuteReaderSync(DbDataReader reader, FuncThe transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? - public static Task QueryMultipleAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task QueryMultipleAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryMultipleAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// @@ -1028,12 +1028,12 @@ public static Task QueryMultipleAsync(this IDbConnection cnn, string /// The command to execute for this query. public static async Task QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); - DbCommand cmd = null; - DbDataReader reader = null; + DbCommand? cmd = null; + DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { @@ -1050,11 +1050,11 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, } catch { - if (reader != null) + if (reader is not null) { if (!reader.IsClosed) { - try { cmd.Cancel(); } + try { cmd?.Cancel(); } catch { /* don't spoil the existing exception */ } @@ -1093,7 +1093,7 @@ public static async Task QueryMultipleAsync(this IDbConnection cnn, /// /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task ExecuteReaderAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default).CastResult(); /// @@ -1106,7 +1106,7 @@ public static Task ExecuteReaderAsync(this IDbConnection cnn, strin /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Task ExecuteReaderAsync(this DbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task ExecuteReaderAsync(this DbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteWrappedReaderImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered), CommandBehavior.Default); /// @@ -1155,9 +1155,9 @@ public static Task ExecuteReaderAsync(this DbConnection cnn, Comma private static async Task ExecuteWrappedReaderImplAsync(IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { - Action paramReader = GetParameterReader(cnn, ref command); + Action? paramReader = GetParameterReader(cnn, ref command); - DbCommand cmd = null; + DbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true; try { @@ -1171,7 +1171,7 @@ private static async Task ExecuteWrappedReaderImplAsync(IDbConnect finally { if (wasClosed) cnn.Close(); - if (cmd != null && disposeCommand) cmd.Dispose(); + if (cmd is not null && disposeCommand) cmd.Dispose(); } } @@ -1185,7 +1185,7 @@ private static async Task ExecuteWrappedReaderImplAsync(IDbConnect /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell returned, as . - public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// @@ -1199,7 +1199,7 @@ public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell returned, as . - public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => ExecuteScalarImplAsync(cnn, new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered)); /// @@ -1208,7 +1208,7 @@ public static Task ExecuteScalarAsync(this IDbConnection cnn, string sql, /// The connection to execute on. /// The command to execute. /// The first cell selected as . - public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => + public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImplAsync(cnn, command); /// @@ -1218,22 +1218,22 @@ public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDef /// The connection to execute on. /// The command to execute. /// The first cell selected as . - public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => + public static Task ExecuteScalarAsync(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImplAsync(cnn, command); - private static async Task ExecuteScalarImplAsync(IDbConnection cnn, CommandDefinition command) + private static async Task ExecuteScalarImplAsync(IDbConnection cnn, CommandDefinition command) { - Action paramReader = null; - object param = command.Parameters; - if (param != null) + Action? paramReader = null; + object? param = command.Parameters; + if (param is not null) { var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } - DbCommand cmd = null; + DbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; - object result; + object? result; try { cmd = command.TrySetupAsyncCommand(cnn, paramReader); @@ -1262,7 +1262,7 @@ private static async Task ExecuteScalarImplAsync(IDbConnection cnn, Comman /// /// A sequence of data of dynamic data /// - public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object param = null, DbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object? param = null, DbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { // note: in many cases of adding a new async method I might add a CancellationToken - however, cancellation is expressed via WithCancellation on iterators return QueryUnbufferedAsync(cnn, typeof(object), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -1282,7 +1282,7 @@ public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection c /// A sequence of data of ; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object param = null, DbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object? param = null, DbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { // note: in many cases of adding a new async method I might add a CancellationToken - however, cancellation is expressed via WithCancellation on iterators return QueryUnbufferedAsync(cnn, typeof(T), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); @@ -1295,12 +1295,12 @@ private static IAsyncEnumerable QueryUnbufferedAsync(this IDbConnection cn static async IAsyncEnumerable Impl(IDbConnection cnn, Type effectiveType, CommandDefinition command, [EnumeratorCancellation] CancellationToken cancel) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); - DbDataReader reader = null; + DbDataReader? reader = null; try { if (wasClosed) await cnn.TryOpenAsync(cancel).ConfigureAwait(false); @@ -1308,7 +1308,7 @@ static async IAsyncEnumerable Impl(IDbConnection cnn, Type effectiveType, Com var tuple = info.Deserializer; int hash = GetColumnHash(reader); - if (tuple.Func == null || tuple.Hash != hash) + if (tuple.Func is null || tuple.Hash != hash) { if (reader.FieldCount == 0) { diff --git a/Dapper/SqlMapper.CacheInfo.cs b/Dapper/SqlMapper.CacheInfo.cs index 63540aa3a..d235beb6f 100644 --- a/Dapper/SqlMapper.CacheInfo.cs +++ b/Dapper/SqlMapper.CacheInfo.cs @@ -10,8 +10,8 @@ public static partial class SqlMapper private class CacheInfo { public DeserializerState Deserializer { get; set; } - public Func[] OtherDeserializers { get; set; } - public Action ParamReader { get; set; } + public Func[]? OtherDeserializers { get; set; } + public Action? ParamReader { get; set; } private int hitCount; public int GetHitCount() { return Interlocked.CompareExchange(ref hitCount, 0, 0); } public void RecordHit() { Interlocked.Increment(ref hitCount); } diff --git a/Dapper/SqlMapper.DapperRow.Descriptor.cs b/Dapper/SqlMapper.DapperRow.Descriptor.cs index 3bb535dbe..11e85c02d 100644 --- a/Dapper/SqlMapper.DapperRow.Descriptor.cs +++ b/Dapper/SqlMapper.DapperRow.Descriptor.cs @@ -25,7 +25,7 @@ public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object // public DapperRowList(DapperTable table) { _table = table; } // PropertyDescriptorCollection ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors) // { - // if (listAccessors != null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty; + // if (listAccessors is not null && listAccessors.Length != 0) return PropertyDescriptorCollection.Empty; // return DapperRowTypeDescriptor.GetProperties(_table); // } @@ -42,32 +42,32 @@ public DapperRowTypeDescriptor(object instance) AttributeCollection ICustomTypeDescriptor.GetAttributes() => AttributeCollection.Empty; - string ICustomTypeDescriptor.GetClassName() => typeof(DapperRow).FullName; + string ICustomTypeDescriptor.GetClassName() => typeof(DapperRow).FullName!; - string ICustomTypeDescriptor.GetComponentName() => null; + string ICustomTypeDescriptor.GetComponentName() => null!; private static readonly TypeConverter s_converter = new ExpandableObjectConverter(); TypeConverter ICustomTypeDescriptor.GetConverter() => s_converter; - EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => null; + EventDescriptor ICustomTypeDescriptor.GetDefaultEvent() => null!; - PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => null; + PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty() => null!; - object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null; + object ICustomTypeDescriptor.GetEditor(Type editorBaseType) => null!; EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty; EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty; internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row); - internal static PropertyDescriptorCollection GetProperties(DapperTable table, IDictionary row = null) + internal static PropertyDescriptorCollection GetProperties(DapperTable? table, IDictionary? row = null) { - string[] names = table?.FieldNames; - if (names == null || names.Length == 0) return PropertyDescriptorCollection.Empty; + string[]? names = table?.FieldNames; + if (names is null || names.Length == 0) return PropertyDescriptorCollection.Empty; var arr = new PropertyDescriptor[names.Length]; for (int i = 0; i < arr.Length; i++) { - var type = row != null && row.TryGetValue(names[i], out var value) && value != null + var type = row is not null && row.TryGetValue(names[i], out var value) && value is not null ? value.GetType() : typeof(object); arr[i] = new RowBoundPropertyDescriptor(type, names[i], i); } @@ -97,7 +97,7 @@ public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name public override Type PropertyType => _type; public override object GetValue(object component) => ((DapperRow)component).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value; - public override void SetValue(object component, object value) + public override void SetValue(object component, object? value) => ((DapperRow)component).SetValue(_index, value is DBNull ? null : value); } } diff --git a/Dapper/SqlMapper.DapperRow.cs b/Dapper/SqlMapper.DapperRow.cs index 8e55aa9c2..82b88d221 100644 --- a/Dapper/SqlMapper.DapperRow.cs +++ b/Dapper/SqlMapper.DapperRow.cs @@ -8,13 +8,13 @@ namespace Dapper public static partial class SqlMapper { private sealed partial class DapperRow - : IDictionary - , IReadOnlyDictionary + : IDictionary + , IReadOnlyDictionary { private readonly DapperTable table; - private object[] values; + private object?[] values; - public DapperRow(DapperTable table, object[] values) + public DapperRow(DapperTable table, object?[] values) { this.table = table ?? throw new ArgumentNullException(nameof(table)); this.values = values ?? throw new ArgumentNullException(nameof(values)); @@ -26,7 +26,7 @@ private sealed class DeadValue private DeadValue() { /* hiding constructor */ } } - int ICollection>.Count + int ICollection>.Count { get { @@ -39,10 +39,10 @@ int ICollection>.Count } } - public bool TryGetValue(string key, out object value) + public bool TryGetValue(string key, out object? value) => TryGetValue(table.IndexOfName(key), out value); - internal bool TryGetValue(int index, out object value) + internal bool TryGetValue(int index, out object? value) { if (index < 0) { // doesn't exist @@ -66,7 +66,7 @@ public override string ToString() { var value = kv.Value; sb.Append(", ").Append(kv.Key); - if (value != null) + if (value is not null) { sb.Append(" = '").Append(kv.Value).Append('\''); } @@ -79,15 +79,15 @@ public override string ToString() return sb.Append('}').ToStringRecycle(); } - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() { var names = table.FieldNames; for (var i = 0; i < names.Length; i++) { - object value = i < values.Length ? values[i] : null; + object? value = i < values.Length ? values[i] : null!; if (!(value is DeadValue)) { - yield return new KeyValuePair(names[i], value); + yield return new KeyValuePair(names[i], value); } } } @@ -99,24 +99,24 @@ IEnumerator IEnumerable.GetEnumerator() #region Implementation of ICollection> - void ICollection>.Add(KeyValuePair item) + void ICollection>.Add(KeyValuePair item) { - IDictionary dic = this; + IDictionary dic = this; dic.Add(item.Key, item.Value); } - void ICollection>.Clear() + void ICollection>.Clear() { // removes values for **this row**, but doesn't change the fundamental table for (int i = 0; i < values.Length; i++) values[i] = DeadValue.Default; } - bool ICollection>.Contains(KeyValuePair item) + bool ICollection>.Contains(KeyValuePair item) { - return TryGetValue(item.Key, out object value) && Equals(value, item.Value); + return TryGetValue(item.Key, out object? value) && Equals(value, item.Value); } - void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) + void ICollection>.CopyTo(KeyValuePair[] array, int arrayIndex) { foreach (var kv in this) { @@ -124,30 +124,30 @@ void ICollection>.CopyTo(KeyValuePair>.Remove(KeyValuePair item) + bool ICollection>.Remove(KeyValuePair item) { - IDictionary dic = this; + IDictionary dic = this; return dic.Remove(item.Key); } - bool ICollection>.IsReadOnly => false; + bool ICollection>.IsReadOnly => false; #endregion #region Implementation of IDictionary - bool IDictionary.ContainsKey(string key) + bool IDictionary.ContainsKey(string key) { int index = table.IndexOfName(key); if (index < 0 || index >= values.Length || values[index] is DeadValue) return false; return true; } - void IDictionary.Add(string key, object value) + void IDictionary.Add(string key, object? value) { SetValue(key, value, true); } - bool IDictionary.Remove(string key) + bool IDictionary.Remove(string key) => Remove(table.IndexOfName(key)); internal bool Remove(int index) @@ -157,20 +157,20 @@ internal bool Remove(int index) return true; } - object IDictionary.this[string key] + object? IDictionary.this[string key] { - get { TryGetValue(key, out object val); return val; } + get { TryGetValue(key, out object? val); return val; } set { SetValue(key, value, false); } } - public object SetValue(string key, object value) + public object? SetValue(string key, object? value) { return SetValue(key, value, false); } - private object SetValue(string key, object value, bool isAdd) + private object? SetValue(string key, object? value, bool isAdd) { - if (key == null) throw new ArgumentNullException(nameof(key)); + if (key is null) throw new ArgumentNullException(nameof(key)); int index = table.IndexOfName(key); if (index < 0) { @@ -183,7 +183,7 @@ private object SetValue(string key, object value, bool isAdd) } return SetValue(index, value); } - internal object SetValue(int index, object value) + internal object? SetValue(int index, object? value) { int oldLength = values.Length; if (oldLength <= index) @@ -199,12 +199,12 @@ internal object SetValue(int index, object value) return values[index] = value; } - ICollection IDictionary.Keys + ICollection IDictionary.Keys { get { return this.Select(kv => kv.Key).ToArray(); } } - ICollection IDictionary.Values + ICollection IDictionary.Values { get { return this.Select(kv => kv.Value).ToArray(); } } @@ -215,7 +215,7 @@ ICollection IDictionary.Values #region Implementation of IReadOnlyDictionary - int IReadOnlyCollection>.Count + int IReadOnlyCollection>.Count { get { @@ -223,23 +223,23 @@ int IReadOnlyCollection>.Count } } - bool IReadOnlyDictionary.ContainsKey(string key) + bool IReadOnlyDictionary.ContainsKey(string key) { int index = table.IndexOfName(key); return index >= 0 && index < values.Length && !(values[index] is DeadValue); } - object IReadOnlyDictionary.this[string key] + object? IReadOnlyDictionary.this[string key] { - get { TryGetValue(key, out object val); return val; } + get { TryGetValue(key, out object? val); return val; } } - IEnumerable IReadOnlyDictionary.Keys + IEnumerable IReadOnlyDictionary.Keys { get { return this.Select(kv => kv.Key); } } - IEnumerable IReadOnlyDictionary.Values + IEnumerable IReadOnlyDictionary.Values { get { return this.Select(kv => kv.Value); } } diff --git a/Dapper/SqlMapper.DapperRowMetaObject.cs b/Dapper/SqlMapper.DapperRowMetaObject.cs index de33fbdd9..75e7a15a6 100644 --- a/Dapper/SqlMapper.DapperRowMetaObject.cs +++ b/Dapper/SqlMapper.DapperRowMetaObject.cs @@ -16,8 +16,8 @@ System.Dynamic.DynamicMetaObject System.Dynamic.IDynamicMetaObjectProvider.GetMe private sealed class DapperRowMetaObject : System.Dynamic.DynamicMetaObject { - private static readonly MethodInfo getValueMethod = typeof(IDictionary).GetProperty("Item").GetGetMethod(); - private static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) }); + private static readonly MethodInfo getValueMethod = typeof(IDictionary).GetProperty("Item")!.GetGetMethod()!; + private static readonly MethodInfo setValueMethod = typeof(DapperRow).GetMethod("SetValue", new Type[] { typeof(string), typeof(object) })!; public DapperRowMetaObject( System.Linq.Expressions.Expression expression, diff --git a/Dapper/SqlMapper.DapperTable.cs b/Dapper/SqlMapper.DapperTable.cs index f827286f9..760192c1b 100644 --- a/Dapper/SqlMapper.DapperTable.cs +++ b/Dapper/SqlMapper.DapperTable.cs @@ -21,18 +21,18 @@ public DapperTable(string[] fieldNames) for (int i = fieldNames.Length - 1; i >= 0; i--) { string key = fieldNames[i]; - if (key != null) fieldNameLookup[key] = i; + if (key is not null) fieldNameLookup[key] = i; } } internal int IndexOfName(string name) { - return (name != null && fieldNameLookup.TryGetValue(name, out int result)) ? result : -1; + return (name is not null && fieldNameLookup.TryGetValue(name, out int result)) ? result : -1; } internal int AddField(string name) { - if (name == null) throw new ArgumentNullException(nameof(name)); + if (name is null) throw new ArgumentNullException(nameof(name)); if (fieldNameLookup.ContainsKey(name)) throw new InvalidOperationException("Field already exists: " + name); int oldLen = fieldNames.Length; Array.Resize(ref fieldNames, oldLen + 1); // yes, this is sub-optimal, but this is not the expected common case @@ -41,7 +41,7 @@ internal int AddField(string name) return oldLen; } - internal bool FieldExists(string key) => key != null && fieldNameLookup.ContainsKey(key); + internal bool FieldExists(string key) => key is not null && fieldNameLookup.ContainsKey(key); public int FieldCount => fieldNames.Length; } diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index c15aadfe0..770a04c12 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Data; using System.Data.Common; using System.Linq; using System.Runtime.CompilerServices; @@ -16,13 +15,6 @@ public partial class GridReader : IAsyncDisposable #endif { - private readonly CancellationToken cancel; - internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, DynamicParameters dynamicParams, bool addToCache, CancellationToken cancel) - : this(command, reader, identity, dynamicParams, addToCache) - { - this.cancel = cancel; - } - /// /// Read the next grid of results, returned as a dynamic object /// @@ -40,7 +32,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.FirstOrDefault); + public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results, returned as a dynamic object @@ -52,7 +44,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// Read an individual row of the next grid of results, returned as a dynamic object /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.SingleOrDefault); + public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(DapperRow), Row.SingleOrDefault); /// /// Read the next grid of results @@ -62,7 +54,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// is null. public Task> ReadAsync(Type type, bool buffered = true) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadAsyncImpl(type, buffered); } @@ -73,7 +65,7 @@ public Task> ReadAsync(Type type, bool buffered = true) /// is null. public Task ReadFirstAsync(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.First); } @@ -82,10 +74,10 @@ public Task ReadFirstAsync(Type type) /// /// The type to read. /// is null. - public Task ReadFirstOrDefaultAsync(Type type) + public Task ReadFirstOrDefaultAsync(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); - return ReadRowAsyncImpl(type, Row.FirstOrDefault); + if (type is null) throw new ArgumentNullException(nameof(type)); + return ReadRowAsyncImpl(type, Row.FirstOrDefault); } /// @@ -95,7 +87,7 @@ public Task ReadFirstOrDefaultAsync(Type type) /// is null. public Task ReadSingleAsync(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRowAsyncImpl(type, Row.Single); } @@ -104,10 +96,10 @@ public Task ReadSingleAsync(Type type) /// /// The type to read. /// is null. - public Task ReadSingleOrDefaultAsync(Type type) + public Task ReadSingleOrDefaultAsync(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); - return ReadRowAsyncImpl(type, Row.SingleOrDefault); + if (type is null) throw new ArgumentNullException(nameof(type)); + return ReadRowAsyncImpl(type, Row.SingleOrDefault); } /// @@ -127,7 +119,7 @@ public Task ReadSingleOrDefaultAsync(Type type) /// Read an individual row of the next grid of results. /// /// The type to read. - public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.FirstOrDefault); + public Task ReadFirstOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results. @@ -139,15 +131,25 @@ public Task ReadSingleOrDefaultAsync(Type type) /// Read an individual row of the next grid of results. /// /// The type to read. - public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.SingleOrDefault); + public Task ReadSingleOrDefaultAsync() => ReadRowAsyncImpl(typeof(T), Row.SingleOrDefault); - private async Task NextResultAsync() + /// + /// Marks the current grid as consumed, and moves to the next result + /// + protected async Task OnAfterGridAsync(int index) { - if (await reader.NextResultAsync(cancel).ConfigureAwait(false)) + if (index != ResultIndex) + { + // not our data + } + else if (reader is null) + { + // nothing to do + } + else if (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { // readCount++; - gridIndex++; - IsConsumed = false; + _resultIndexAndConsumedFlag = index + 1; } else { @@ -158,8 +160,8 @@ private async Task NextResultAsync() #else reader.Dispose(); #endif - reader = null; - callbacks?.OnCompleted(); + reader = null!; + onCompleted?.Invoke(state); #if NET5_0_OR_GREATER await DisposeAsync(); #else @@ -170,52 +172,48 @@ private async Task NextResultAsync() private Task> ReadAsyncImpl(Type type, bool buffered) { - var deserializer = ValidateAndMarkConsumed(type); + var deserializer = ValidateAndMarkConsumed(type, out var index); if (buffered) { - return ReadBufferedAsync(gridIndex, deserializer); + return ReadBufferedAsync(index, deserializer); } else { - var result = ReadDeferred(gridIndex, deserializer, type); - if (buffered) result = result?.ToList(); // for the "not a DbDataReader" scenario + var result = ReadDeferred(index, deserializer, type); + if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario return Task.FromResult(result); } } - private Func ValidateAndMarkConsumed(Type type) + private Func ValidateAndMarkConsumed(Type type, out int index) { - if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); - if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); - var typedIdentity = identity.ForGrid(type, gridIndex); + index = OnBeforeGrid(); + var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); - if (deserializer.Func == null || deserializer.Hash != hash) + if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } - IsConsumed = true; return deserializer.Func; } private async Task ReadRowAsyncImpl(Type type, Row row) { - if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); - if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); + var index = OnBeforeGrid(); - IsConsumed = true; - T result = default; + T result = default!; if (await reader.ReadAsync(cancel).ConfigureAwait(false) && reader.FieldCount != 0) { - var typedIdentity = identity.ForGrid(type, gridIndex); + var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); - if (deserializer.Func == null || deserializer.Hash != hash) + if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; @@ -230,7 +228,7 @@ private async Task ReadRowAsyncImpl(Type type, Row row) { ThrowZeroRows(row); } - await NextResultAsync().ConfigureAwait(false); + await OnAfterGridAsync(index).ConfigureAwait(false); return result; } @@ -239,7 +237,7 @@ private async Task> ReadBufferedAsync(int index, Func(); - while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) + while (index == ResultIndex && await reader!.ReadAsync(cancel).ConfigureAwait(false)) { buffer.Add(ConvertTo(deserializer(reader))); } @@ -247,10 +245,7 @@ private async Task> ReadBufferedAsync(int index, Func> ReadBufferedAsync(int index, Func ReadAsyncUnbufferedImpl(Type type) { - var deserializer = ValidateAndMarkConsumed(type); - return ReadUnbufferedAsync(gridIndex, deserializer, cancel); + var deserializer = ValidateAndMarkConsumed(type, out var index); + return ReadUnbufferedAsync(index, deserializer, cancel); } private async IAsyncEnumerable ReadUnbufferedAsync(int index, Func deserializer, [EnumeratorCancellation] CancellationToken cancel) { try { - while (index == gridIndex && await reader.ReadAsync(cancel).ConfigureAwait(false)) + while (index == ResultIndex && await reader!.ReadAsync(cancel).ConfigureAwait(false)) { yield return ConvertTo(deserializer(reader)); } } finally // finally so that First etc progresses things even when multiple rows { - if (index == gridIndex) - { - await NextResultAsync().ConfigureAwait(false); - } + await OnAfterGridAsync(index).ConfigureAwait(false); } } @@ -290,13 +282,13 @@ private async IAsyncEnumerable ReadUnbufferedAsync(int index, Func public async ValueTask DisposeAsync() { - if (reader != null) + if (reader is not null) { if (!reader.IsClosed) Command?.Cancel(); await reader.DisposeAsync(); - reader = null; + reader = null!; } - if (Command != null) + if (Command is not null) { if (Command is DbCommand typed) { @@ -306,7 +298,7 @@ public async ValueTask DisposeAsync() { Command.Dispose(); } - Command = null; + Command = null!; } GC.SuppressFinalize(this); } diff --git a/Dapper/SqlMapper.GridReader.cs b/Dapper/SqlMapper.GridReader.cs index 7ecf0d7be..1370f7cc9 100644 --- a/Dapper/SqlMapper.GridReader.cs +++ b/Dapper/SqlMapper.GridReader.cs @@ -1,10 +1,12 @@ using System; using System.Collections.Generic; +using System.ComponentModel; using System.Data; using System.Data.Common; using System.Globalization; using System.Linq; using System.Runtime.CompilerServices; +using System.Threading; namespace Dapper { @@ -16,16 +18,43 @@ public static partial class SqlMapper public partial class GridReader : IDisposable { private DbDataReader reader; - private readonly Identity identity; + private Identity? _identity; private readonly bool addToCache; + private readonly Action? onCompleted; + private readonly object? state; + private readonly CancellationToken cancel; - internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, IParameterCallbacks callbacks, bool addToCache) + /// + /// Creates a grid reader over an existing command and reader + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + protected GridReader(IDbCommand command, DbDataReader reader, Identity? identity, Action? onCompleted = null, object? state = null, bool addToCache = false, CancellationToken cancellationToken = default) { Command = command; this.reader = reader; - this.identity = identity; - this.callbacks = callbacks; + _identity = identity; + this.onCompleted = onCompleted; + this.state = state; this.addToCache = addToCache; + cancel = cancellationToken; + } + + internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, IParameterCallbacks? callbacks, bool addToCache, + CancellationToken cancellationToken = default) + : this(command, reader, identity, callbacks is null ? null : static state => ((IParameterCallbacks)state!).OnCompleted(), + callbacks, addToCache, cancellationToken) + { } + + private Identity Identity => _identity ??= CreateIdentity(); + + private Identity CreateIdentity() + { + var cmd = Command; + if (cmd is not null && cmd.Connection is not null) + { + return new Identity(cmd.CommandText, cmd.CommandType, cmd.Connection, null, null); + } + throw new InvalidOperationException("This operation requires an identity or a connected command"); } /// @@ -45,7 +74,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public dynamic ReadFirstOrDefault() => ReadRow(typeof(DapperRow), Row.FirstOrDefault); + public dynamic? ReadFirstOrDefault() => ReadRow(typeof(DapperRow), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results, returned as a dynamic object. @@ -57,7 +86,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// Read an individual row of the next grid of results, returned as a dynamic object. /// /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> - public dynamic ReadSingleOrDefault() => ReadRow(typeof(DapperRow), Row.SingleOrDefault); + public dynamic? ReadSingleOrDefault() => ReadRow(typeof(DapperRow), Row.SingleOrDefault); /// /// Read the next grid of results. @@ -76,7 +105,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// Read an individual row of the next grid of results. /// /// The type to read. - public T ReadFirstOrDefault() => ReadRow(typeof(T), Row.FirstOrDefault); + public T? ReadFirstOrDefault() => ReadRow(typeof(T), Row.FirstOrDefault); /// /// Read an individual row of the next grid of results. @@ -88,7 +117,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// Read an individual row of the next grid of results. /// /// The type to read. - public T ReadSingleOrDefault() => ReadRow(typeof(T), Row.SingleOrDefault); + public T? ReadSingleOrDefault() => ReadRow(typeof(T), Row.SingleOrDefault); /// /// Read the next grid of results. @@ -98,7 +127,7 @@ internal GridReader(IDbCommand command, DbDataReader reader, Identity identity, /// is null. public IEnumerable Read(Type type, bool buffered = true) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadImpl(type, buffered); } @@ -109,7 +138,7 @@ public IEnumerable Read(Type type, bool buffered = true) /// is null. public object ReadFirst(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.First); } @@ -118,9 +147,9 @@ public object ReadFirst(Type type) /// /// The type to read. /// is null. - public object ReadFirstOrDefault(Type type) + public object? ReadFirstOrDefault(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.FirstOrDefault); } @@ -131,7 +160,7 @@ public object ReadFirstOrDefault(Type type) /// is null. public object ReadSingle(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.Single); } @@ -140,46 +169,55 @@ public object ReadSingle(Type type) /// /// The type to read. /// is null. - public object ReadSingleOrDefault(Type type) + public object? ReadSingleOrDefault(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); return ReadRow(type, Row.SingleOrDefault); } - private IEnumerable ReadImpl(Type type, bool buffered) + + /// + /// Validates that data is available, returning the that corresponds to the current grid - and marks the current grid as consumed; + /// this call must be paired with a call to or + /// + protected int OnBeforeGrid() { - if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); + if (reader is null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); - var typedIdentity = identity.ForGrid(type, gridIndex); + _resultIndexAndConsumedFlag |= CONSUMED_FLAG; + return ResultIndex; + } + + private IEnumerable ReadImpl(Type type, bool buffered) + { + var index = OnBeforeGrid(); + var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); - if (deserializer.Func == null || deserializer.Hash != hash) + if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; } - IsConsumed = true; - var result = ReadDeferred(gridIndex, deserializer.Func, type); - return buffered ? result?.ToList() : result; + var result = ReadDeferred(index, deserializer.Func, type); + return buffered ? result.ToList() : result; } private T ReadRow(Type type, Row row) { - if (reader == null) throw new ObjectDisposedException(GetType().FullName, "The reader has been disposed; this can happen after all data has been consumed"); - if (IsConsumed) throw new InvalidOperationException("Query results must be consumed in the correct order, and each result can only be consumed once"); - IsConsumed = true; + var index = OnBeforeGrid(); - T result = default; + T result = default!; if (reader.Read() && reader.FieldCount != 0) { - var typedIdentity = identity.ForGrid(type, gridIndex); + var typedIdentity = Identity.ForGrid(type, index); CacheInfo cache = GetCacheInfo(typedIdentity, null, addToCache); var deserializer = cache.Deserializer; int hash = GetColumnHash(reader); - if (deserializer.Func == null || deserializer.Hash != hash) + if (deserializer.Func is null || deserializer.Hash != hash) { deserializer = new DeserializerState(hash, GetDeserializer(type, reader, 0, -1, false)); cache.Deserializer = deserializer; @@ -194,15 +232,14 @@ private T ReadRow(Type type, Row row) { ThrowZeroRows(row); } - NextResult(); + OnAfterGrid(index); return result; } private IEnumerable MultiReadInternal(Delegate func, string splitOn) { - var identity = this.identity.ForGrid(typeof(TReturn), gridIndex); - - IsConsumed = true; + var index = OnBeforeGrid(); + var identity = Identity.ForGrid(typeof(TReturn), index); try { @@ -213,13 +250,14 @@ private IEnumerable MultiReadInternal MultiReadInternal(Type[] types, Func map, string splitOn) { - var identity = this.identity.ForGrid(typeof(TReturn), types, gridIndex); + var index = OnBeforeGrid(); + var identity = Identity.ForGrid(typeof(TReturn), types, index); try { foreach (var r in MultiMapImpl(null, default, types, map, splitOn, reader, identity, false)) @@ -229,7 +267,7 @@ private IEnumerable MultiReadInternal(Type[] types, Func ReadDeferred(int index, Func des { try { - while (index == gridIndex && reader.Read()) + while (index == ResultIndex && reader?.Read() == true) { yield return ConvertTo(deserializer(reader)); } } finally // finally so that First etc progresses things even when multiple rows { - if (index == gridIndex) - { - NextResult(); - } + OnAfterGrid(index); } } - private int gridIndex; //, readCount; - private readonly IParameterCallbacks callbacks; + const int CONSUMED_FLAG = 1 << 31; + private int _resultIndexAndConsumedFlag; //, readCount; + + /// + /// Indicates the current result index + /// + protected int ResultIndex => _resultIndexAndConsumedFlag & ~CONSUMED_FLAG; /// /// Has the underlying reader been consumed? /// - public bool IsConsumed { get; private set; } + /// This also reports true if the current grid is actively being consumed + public bool IsConsumed => (_resultIndexAndConsumedFlag & CONSUMED_FLAG) != 0; /// /// The command associated with the reader /// public IDbCommand Command { get; set; } - private void NextResult() + /// + /// The underlying reader + /// + protected DbDataReader Reader => reader; + + /// + /// The cancellation token associated with this reader + /// + protected CancellationToken CancellationToken => cancel; + + /// + /// Marks the current grid as consumed, and moves to the next result + /// + protected void OnAfterGrid(int index) { - if (reader.NextResult()) + if (index != ResultIndex) + { + // not our data! + } + else if (reader is null) + { + // nothing to do + } + else if (reader.NextResult()) { // readCount++; - gridIndex++; - IsConsumed = false; + _resultIndexAndConsumedFlag = index + 1; } else { // happy path; close the reader cleanly - no // need for "Cancel" etc reader.Dispose(); - reader = null; - callbacks?.OnCompleted(); + reader = null!; + onCompleted?.Invoke(state); Dispose(); } } @@ -407,25 +468,25 @@ private void NextResult() /// public void Dispose() { - if (reader != null) + if (reader is not null) { if (!reader.IsClosed) Command?.Cancel(); reader.Dispose(); - reader = null; + reader = null!; } - if (Command != null) + if (Command is not null) { Command.Dispose(); - Command = null; + Command = null!; } GC.SuppressFinalize(this); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T ConvertTo(object value) => value switch + private static T ConvertTo(object? value) => value switch { T typed => typed, - null or DBNull => default, + null or DBNull => default!, _ => (T)Convert.ChangeType(value, Nullable.GetUnderlyingType(typeof(T)) ?? typeof(T), CultureInfo.InvariantCulture), }; } diff --git a/Dapper/SqlMapper.IDataReader.cs b/Dapper/SqlMapper.IDataReader.cs index b3780c049..0dd0b8cd2 100644 --- a/Dapper/SqlMapper.IDataReader.cs +++ b/Dapper/SqlMapper.IDataReader.cs @@ -23,9 +23,9 @@ public static IEnumerable Parse(this IDataReader reader) do { object val = deser(dbReader); - if (val == null || val is T) + if (val is null || val is T) { - yield return (T)val; + yield return (T)val!; } else { @@ -111,7 +111,7 @@ public static Func GetRowParser(this DbDataReader reader, [Obsolete(nameof(DbDataReader) + " API should be preferred")] #endif [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static Func GetRowParser(this IDataReader reader, Type concreteType = null, + public static Func GetRowParser(this IDataReader reader, Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { concreteType ??= typeof(T); @@ -176,7 +176,7 @@ static Func Wrap(Func func) /// public override int Type => 2; /// } /// - public static Func GetRowParser(this DbDataReader reader, Type concreteType = null, + public static Func GetRowParser(this DbDataReader reader, Type? concreteType = null, int startIndex = 0, int length = -1, bool returnNullIfFirstMissing = false) { concreteType ??= typeof(T); diff --git a/Dapper/SqlMapper.IMemberMap.cs b/Dapper/SqlMapper.IMemberMap.cs index fcd8eb379..25b39951d 100644 --- a/Dapper/SqlMapper.IMemberMap.cs +++ b/Dapper/SqlMapper.IMemberMap.cs @@ -23,17 +23,17 @@ public interface IMemberMap /// /// Target property /// - PropertyInfo Property { get; } + PropertyInfo? Property { get; } /// /// Target field /// - FieldInfo Field { get; } + FieldInfo? Field { get; } /// /// Target constructor parameter /// - ParameterInfo Parameter { get; } + ParameterInfo? Parameter { get; } } } } diff --git a/Dapper/SqlMapper.IParameterLookup.cs b/Dapper/SqlMapper.IParameterLookup.cs index aecf7bd1d..c097af1be 100644 --- a/Dapper/SqlMapper.IParameterLookup.cs +++ b/Dapper/SqlMapper.IParameterLookup.cs @@ -11,7 +11,7 @@ public interface IParameterLookup : IDynamicParameters /// Get the value of the specified parameter (return null if not found) /// /// The name of the parameter to get. - object this[string name] { get; } + object? this[string name] { get; } } } } diff --git a/Dapper/SqlMapper.ITypeHandler.cs b/Dapper/SqlMapper.ITypeHandler.cs index 31154a645..5cd4a4306 100644 --- a/Dapper/SqlMapper.ITypeHandler.cs +++ b/Dapper/SqlMapper.ITypeHandler.cs @@ -15,7 +15,7 @@ public interface ITypeHandler /// /// The parameter to configure /// Parameter value - void SetValue(IDbDataParameter parameter, object value); + void SetValue(IDbDataParameter parameter, object? value); /// /// Parse a database value back to a typed value @@ -23,7 +23,7 @@ public interface ITypeHandler /// The value from the database /// The type to parse to /// The typed value - object Parse(Type destinationType, object value); + object? Parse(Type destinationType, object? value); } } } diff --git a/Dapper/SqlMapper.ITypeMap.cs b/Dapper/SqlMapper.ITypeMap.cs index dbd04ba61..6a149b1fb 100644 --- a/Dapper/SqlMapper.ITypeMap.cs +++ b/Dapper/SqlMapper.ITypeMap.cs @@ -16,7 +16,7 @@ public interface ITypeMap /// DataReader column names /// DataReader column types /// Matching constructor or default one - ConstructorInfo FindConstructor(string[] names, Type[] types); + ConstructorInfo? FindConstructor(string[] names, Type[] types); /// /// Returns a constructor which should *always* be used. @@ -25,7 +25,7 @@ public interface ITypeMap /// /// Use this class to force object creation away from parameterless constructors you don't control. /// - ConstructorInfo FindExplicitConstructor(); + ConstructorInfo? FindExplicitConstructor(); /// /// Gets mapping for constructor parameter @@ -40,7 +40,7 @@ public interface ITypeMap /// /// DataReader column name /// Mapping implementation - IMemberMap GetMember(string columnName); + IMemberMap? GetMember(string columnName); } } } diff --git a/Dapper/SqlMapper.Identity.cs b/Dapper/SqlMapper.Identity.cs index acaf2989c..cfaab8e5f 100644 --- a/Dapper/SqlMapper.Identity.cs +++ b/Dapper/SqlMapper.Identity.cs @@ -11,10 +11,10 @@ internal sealed class Identity new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex); internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => - (otherTypes == null || otherTypes.Length == 0) + (otherTypes is null || otherTypes.Length == 0) ? new Identity(sql, commandType, connectionString, primaryType, parametersType, 0, gridIndex) : new IdentityWithTypes(sql, commandType, connectionString, primaryType, parametersType, otherTypes, gridIndex); @@ -111,10 +111,10 @@ internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => public Identity ForDynamicParameters(Type type) => new Identity(sql, commandType, connectionString, this.type, type, 0, -1); - internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type type, Type parametersType) + internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type? type, Type? parametersType) : this(sql, commandType, connection.ConnectionString, type, parametersType, 0, 0) { /* base call */ } - private protected Identity(string sql, CommandType? commandType, string connectionString, Type type, Type parametersType, int otherTypesHash, int gridIndex) + private protected Identity(string sql, CommandType? commandType, string connectionString, Type? type, Type? parametersType, int otherTypesHash, int gridIndex) { this.sql = sql; this.commandType = commandType; @@ -130,7 +130,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti hashCode = (hashCode * 23) + (sql?.GetHashCode() ?? 0); hashCode = (hashCode * 23) + (type?.GetHashCode() ?? 0); hashCode = (hashCode * 23) + otherTypesHash; - hashCode = (hashCode * 23) + (connectionString == null ? 0 : connectionStringComparer.GetHashCode(connectionString)); + hashCode = (hashCode * 23) + (connectionString is null ? 0 : connectionStringComparer.GetHashCode(connectionString)); hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0); } } @@ -139,7 +139,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti /// Whether this equals another. /// /// The other to compare to. - public override bool Equals(object obj) => Equals(obj as Identity); + public override bool Equals(object? obj) => Equals(obj as Identity); /// /// The raw SQL command. @@ -164,7 +164,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti /// /// This of this Identity. /// - public readonly Type type; + public readonly Type? type; /// /// The connection string for this Identity. @@ -174,7 +174,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti /// /// The type of the parameters object for this Identity. /// - public readonly Type parametersType; + public readonly Type? parametersType; /// /// Gets the hash code for this identity. @@ -192,7 +192,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti /// /// The other object to compare. /// Whether the two are equal - public bool Equals(Identity other) + public bool Equals(Identity? other) { if (ReferenceEquals(this, other)) return true; if (other is null) return false; diff --git a/Dapper/SqlMapper.Link.cs b/Dapper/SqlMapper.Link.cs index fe6de9226..92b434881 100644 --- a/Dapper/SqlMapper.Link.cs +++ b/Dapper/SqlMapper.Link.cs @@ -1,4 +1,5 @@ -using System.Threading; +using System.Diagnostics.CodeAnalysis; +using System.Threading; namespace Dapper { @@ -13,15 +14,14 @@ public static partial class SqlMapper /// The value type of the cache. internal class Link where TKey : class { - public static void Clear(ref Link head) => Interlocked.Exchange(ref head, null); - - public static bool TryGet(Link link, TKey key, out TValue value) + public static void Clear(ref Link? head) => Interlocked.Exchange(ref head, null); + public static bool TryGet(Link? link, TKey key, [NotNullWhen(true)] out TValue? value) { - while (link != null) + while (link is not null) { if ((object)key == (object)link.Key) { - value = link.Value; + value = link.Value!; return true; } link = link.Tail; @@ -30,13 +30,13 @@ public static bool TryGet(Link link, TKey key, out TValue value) return false; } - public static bool TryAdd(ref Link head, TKey key, ref TValue value) + public static bool TryAdd(ref Link? head, TKey key, ref TValue value) { bool tryAgain; do { var snapshot = Interlocked.CompareExchange(ref head, null, null); - if (TryGet(snapshot, key, out TValue found)) + if (TryGet(snapshot, key, out TValue? found)) { // existing match; report the existing value instead value = found; return false; @@ -48,7 +48,7 @@ public static bool TryAdd(ref Link head, TKey key, ref TValue valu return true; } - private Link(TKey key, TValue value, Link tail) + private Link(TKey key, TValue value, Link? tail) { Key = key; Value = value; @@ -57,7 +57,7 @@ private Link(TKey key, TValue value, Link tail) public TKey Key { get; } public TValue Value { get; } - public Link Tail { get; } + public Link? Tail { get; } } } } diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index f24e58a6d..2c2f646db 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -35,13 +35,13 @@ internal static void Purge() internal static Func GetReader(Type type, DbDataReader reader, int startBound, int length, bool returnNullIfFirstMissing) { - var found = (TypeDeserializerCache)byType[type]; - if (found == null) + var found = (TypeDeserializerCache?)byType[type]; + if (found is null) { lock (byType) { - found = (TypeDeserializerCache)byType[type]; - if (found == null) + found = (TypeDeserializerCache?)byType[type]; + if (found is null) { byType[type] = found = new TypeDeserializerCache(type); } @@ -56,9 +56,9 @@ internal static Func GetReader(Type type, DbDataReader rea { private readonly int startBound, length; private readonly bool returnNullIfFirstMissing; - private readonly DbDataReader reader; - private readonly string[] names; - private readonly Type[] types; + private readonly DbDataReader? reader; + private readonly string[]? names; + private readonly Type[]? types; private readonly int hashCode; public DeserializerKey(int hashCode, int startBound, int length, bool returnNullIfFirstMissing, DbDataReader reader, bool copyDown) @@ -92,11 +92,11 @@ public DeserializerKey(int hashCode, int startBound, int length, bool returnNull public override string ToString() { // only used in the debugger - if (names != null) + if (names is not null) { return string.Join(", ", names); } - if (reader != null) + if (reader is not null) { var sb = new StringBuilder(); int index = startBound; @@ -107,10 +107,10 @@ public override string ToString() } return sb.ToString(); } - return base.ToString(); + return base.ToString() ?? ""; } - public override bool Equals(object obj) + public override bool Equals(object? obj) => obj is DeserializerKey key && Equals(key); public bool Equals(DeserializerKey other) @@ -143,10 +143,10 @@ private Func GetReader(DbDataReader reader, int startBound if (returnNullIfFirstMissing) hash *= -27; // get a cheap key first: false means don't copy the values down var key = new DeserializerKey(hash, startBound, length, returnNullIfFirstMissing, reader, false); - Func deser; + Func? deser; lock (readers) { - if (readers.TryGetValue(key, out deser)) return deser; + if (readers.TryGetValue(key, out deser)) return deser!; } deser = GetTypeDeserializerImpl(type, reader, startBound, length, returnNullIfFirstMissing); // get a more expensive key: true means copy the values down so it can be used as a key later diff --git a/Dapper/SqlMapper.TypeHandler.cs b/Dapper/SqlMapper.TypeHandler.cs index 4e9591d9a..4e12bb812 100644 --- a/Dapper/SqlMapper.TypeHandler.cs +++ b/Dapper/SqlMapper.TypeHandler.cs @@ -16,16 +16,16 @@ public abstract class TypeHandler : ITypeHandler /// /// The parameter to configure /// Parameter value - public abstract void SetValue(IDbDataParameter parameter, T value); + public abstract void SetValue(IDbDataParameter parameter, T? value); /// /// Parse a database value back to a typed value /// /// The value from the database /// The typed value - public abstract T Parse(object value); + public abstract T? Parse(object? value); - void ITypeHandler.SetValue(IDbDataParameter parameter, object value) + void ITypeHandler.SetValue(IDbDataParameter parameter, object? value) { if (value is DBNull) { @@ -33,11 +33,11 @@ void ITypeHandler.SetValue(IDbDataParameter parameter, object value) } else { - SetValue(parameter, (T)value); + SetValue(parameter, (T?)value); } } - object ITypeHandler.Parse(Type destinationType, object value) + object? ITypeHandler.Parse(Type destinationType, object? value) { return Parse(value); } @@ -66,9 +66,9 @@ public abstract class StringTypeHandler : TypeHandler /// /// The parameter to configure /// Parameter value - public override void SetValue(IDbDataParameter parameter, T value) + public override void SetValue(IDbDataParameter parameter, T? value) { - parameter.Value = value == null ? (object)DBNull.Value : Format(value); + parameter.Value = value is null ? (object)DBNull.Value : Format(value); } /// @@ -76,9 +76,9 @@ public override void SetValue(IDbDataParameter parameter, T value) /// /// The value from the database /// The typed value - public override T Parse(object value) + public override T Parse(object? value) { - if (value == null || value is DBNull) return default; + if (value is null || value is DBNull) return default!; return Parse((string)value); } } diff --git a/Dapper/SqlMapper.TypeHandlerCache.cs b/Dapper/SqlMapper.TypeHandlerCache.cs index 106f2c3ce..b3d13764a 100644 --- a/Dapper/SqlMapper.TypeHandlerCache.cs +++ b/Dapper/SqlMapper.TypeHandlerCache.cs @@ -20,7 +20,7 @@ public static class TypeHandlerCache /// /// The object to parse. [Obsolete(ObsoleteInternalUsageOnly, true)] - public static T Parse(object value) => (T)handler.Parse(typeof(T), value); + public static T? Parse(object value) => (T?)handler.Parse(typeof(T), value); /// /// Not intended for direct usage. @@ -32,12 +32,10 @@ public static class TypeHandlerCache internal static void SetHandler(ITypeHandler handler) { -#pragma warning disable 618 TypeHandlerCache.handler = handler; -#pragma warning restore 618 } - private static ITypeHandler handler; + private static ITypeHandler handler = null!; } } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 583e06ec3..5f5974f75 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -10,12 +10,12 @@ using System.Data; using System.Data.Common; using System.Data.SqlTypes; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Linq; using System.Reflection; using System.Reflection.Emit; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using System.Text.RegularExpressions; using System.Threading; @@ -31,7 +31,7 @@ public static partial class SqlMapper { private class PropertyInfoByNameComparer : IComparer { - public int Compare(PropertyInfo x, PropertyInfo y) => string.CompareOrdinal(x.Name, y.Name); + public int Compare(PropertyInfo? x, PropertyInfo? y) => string.CompareOrdinal(x?.Name, y?.Name); } private static int GetColumnHash(DbDataReader reader, int startBound = 0, int length = -1) { @@ -51,7 +51,7 @@ private static int GetColumnHash(DbDataReader reader, int startBound = 0, int le /// /// Called if the query cache is purged via PurgeQueryCache /// - public static event EventHandler QueryCachePurged; + public static event EventHandler? QueryCachePurged; private static void OnQueryCachePurged() { var handler = QueryCachePurged; @@ -76,7 +76,7 @@ private static void CollectCacheGarbage() { if (pair.Value.GetHitCount() <= COLLECT_HIT_COUNT_MIN) { - _queryCache.TryRemove(pair.Key, out CacheInfo _); + _queryCache.TryRemove(pair.Key, out var _); } } } @@ -89,9 +89,10 @@ private static void CollectCacheGarbage() private const int COLLECT_PER_ITEMS = 1000, COLLECT_HIT_COUNT_MIN = 0; private static int collect; - private static bool TryGetQueryCache(Identity key, out CacheInfo value) + + private static bool TryGetQueryCache(Identity key, [NotNullWhen(true)] out CacheInfo? value) { - if (_queryCache.TryGetValue(key, out value)) + if (_queryCache.TryGetValue(key, out value!)) { value.RecordHit(); return true; @@ -115,7 +116,7 @@ private static void PurgeQueryCacheByType(Type type) foreach (var entry in _queryCache) { if (entry.Key.type == type) - _queryCache.TryRemove(entry.Key, out CacheInfo _); + _queryCache.TryRemove(entry.Key, out _); } TypeDeserializerCache.Purge(type); } @@ -185,7 +186,7 @@ public TypeMapEntry(DbType dbType, TypeMapEntryFlags flags) } public override int GetHashCode() => (int)DbType ^ (int)Flags; public override string ToString() => $"{DbType}, {Flags}"; - public override bool Equals(object obj) => obj is TypeMapEntry other && Equals(other); + public override bool Equals(object? obj) => obj is TypeMapEntry other && Equals(other); public bool Equals(TypeMapEntry other) => other.DbType == DbType && other.Flags == Flags; public static readonly TypeMapEntry DoNotSet = new((DbType)(-2), TypeMapEntryFlags.None), @@ -249,6 +250,7 @@ static SqlMapper() /// public static void ResetTypeHandlers() => ResetTypeHandlers(true); + [MemberNotNull(nameof(typeHandlers))] private static void ResetTypeHandlers(bool clone) { typeHandlers = new Dictionary(); @@ -338,15 +340,15 @@ public static void RemoveTypeMap(Type type) /// The type to handle. /// The handler to process the . /// Whether to clone the current type handler map. - public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clone) + public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clone) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); - Type secondary = null; + Type? secondary = null; if (type.IsValueType) { var underlying = Nullable.GetUnderlyingType(type); - if (underlying == null) + if (underlying is null) { secondary = typeof(Nullable<>).MakeGenericType(type); // the Nullable // type is already the T @@ -359,26 +361,26 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon } var snapshot = typeHandlers; - if (snapshot.TryGetValue(type, out ITypeHandler oldValue) && handler == oldValue) return; // nothing to do + if (snapshot.TryGetValue(type, out var oldValue) && handler == oldValue) return; // nothing to do var newCopy = clone ? new Dictionary(snapshot) : snapshot; #pragma warning disable 618 - typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler }); - if (secondary != null) + typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, new object?[] { handler }); + if (secondary is not null) { - typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { handler }); + typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, new object?[] { handler }); } #pragma warning restore 618 - if (handler == null) + if (handler is null) { newCopy.Remove(type); - if (secondary != null) newCopy.Remove(secondary); + if (secondary is not null) newCopy.Remove(secondary); } else { newCopy[type] = handler; - if (secondary != null) newCopy[secondary] = handler; + if (secondary is not null) newCopy[secondary] = handler; } typeHandlers = newCopy; } @@ -406,9 +408,9 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler handler, bool clon [EditorBrowsable(EditorBrowsableState.Never)] public static void SetDbType(IDataParameter parameter, object value) { - if (value == null || value is DBNull) return; + if (value is null || value is DBNull) return; - var dbType = LookupDbType(value.GetType(), "n/a", false, out ITypeHandler _); + var dbType = LookupDbType(value.GetType(), "n/a", false, out _); if (DynamicParameters.ShouldSetDbType(dbType)) { parameter.DbType = dbType.GetValueOrDefault(); @@ -425,11 +427,11 @@ public static void SetDbType(IDataParameter parameter, object value) [Obsolete(ObsoleteInternalUsageOnly, false)] [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] - public static DbType? LookupDbType(Type type, string name, bool demand, out ITypeHandler handler) + public static DbType? LookupDbType(Type type, string name, bool demand, out ITypeHandler? handler) { handler = null; var nullUnderlyingType = Nullable.GetUnderlyingType(type); - if (nullUnderlyingType != null) type = nullUnderlyingType; + if (nullUnderlyingType is not null) type = nullUnderlyingType; if (type.IsEnum && !typeMap.ContainsKey(type)) { type = Enum.GetUnderlyingType(type); @@ -463,7 +465,7 @@ public static void SetDbType(IDataParameter parameter, object value) try { handler = (ITypeHandler)Activator.CreateInstance( - typeof(SqlDataRecordHandler<>).MakeGenericType(argTypes)); + typeof(SqlDataRecordHandler<>).MakeGenericType(argTypes))!; AddTypeHandlerImpl(type, handler, true); return DbType.Object; } @@ -500,8 +502,12 @@ public static void SetDbType(IDataParameter parameter, object value) /// /// The type of element in the list. /// The enumerable to return as a list. - public static List AsList(this IEnumerable source) => - (source == null || source is List) ? (List)source : source.ToList(); + public static List AsList(this IEnumerable? source) => source switch + { + null => null!, + List list => list, + _ => Enumerable.ToList(source), + }; /// /// Execute parameterized SQL. @@ -513,7 +519,7 @@ public static List AsList(this IEnumerable source) => /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The number of rows affected. - public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static int Execute(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteImpl(cnn, ref command); @@ -537,7 +543,7 @@ public static int Execute(this IDbConnection cnn, string sql, object param = nul /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell selected as . - public static object ExecuteScalar(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static object? ExecuteScalar(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImpl(cnn, ref command); @@ -554,7 +560,7 @@ public static object ExecuteScalar(this IDbConnection cnn, string sql, object pa /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? /// The first cell returned, as . - public static T ExecuteScalar(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static T? ExecuteScalar(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return ExecuteScalarImpl(cnn, ref command); @@ -566,7 +572,7 @@ public static T ExecuteScalar(this IDbConnection cnn, string sql, object para /// The connection to execute on. /// The command to execute. /// The first cell selected as . - public static object ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => + public static object? ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImpl(cnn, ref command); /// @@ -576,10 +582,10 @@ public static object ExecuteScalar(this IDbConnection cnn, CommandDefinition com /// The connection to execute on. /// The command to execute. /// The first cell selected as . - public static T ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => + public static T? ExecuteScalar(this IDbConnection cnn, CommandDefinition command) => ExecuteScalarImpl(cnn, ref command); - private static IEnumerable GetMultiExec(object param) + private static IEnumerable? GetMultiExec(object? param) { #pragma warning disable IDE0038 // Use pattern matching - complicated enough! return (param is IEnumerable @@ -592,11 +598,11 @@ private static IEnumerable GetMultiExec(object param) private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition command) { - object param = command.Parameters; - IEnumerable multiExec = GetMultiExec(param); + object? param = command.Parameters; + IEnumerable? multiExec = GetMultiExec(param); Identity identity; - CacheInfo info = null; - if (multiExec != null) + CacheInfo? info = null; + if (multiExec is not null) { if ((command.Flags & CommandFlags.Pipelined) != 0) { @@ -611,7 +617,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com if (wasClosed) cnn.Open(); using (var cmd = command.SetupCommand(cnn, null)) { - string masterSql = null; + string? masterSql = null; foreach (var obj in multiExec) { if (isFirst) @@ -626,7 +632,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com cmd.CommandText = masterSql; // because we do magic replaces on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } - info.ParamReader(cmd, obj); + info!.ParamReader!(cmd, obj); total += cmd.ExecuteNonQuery(); } } @@ -640,12 +646,12 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com } // nice and simple - if (param != null) + if (param is not null) { identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); } - return ExecuteCommand(cnn, ref command, param == null ? null : info.ParamReader); + return ExecuteCommand(cnn, ref command, param is null ? null : info!.ParamReader); } /// @@ -673,10 +679,10 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com /// ]]> /// /// - public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); - var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); + var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand? dbcmd); return DbWrappedReader.Create(dbcmd, reader); } @@ -692,7 +698,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, string sql, obje /// public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command) { - var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand dbcmd); + var reader = ExecuteReaderImpl(cnn, ref command, CommandBehavior.Default, out IDbCommand? dbcmd); return DbWrappedReader.Create(dbcmd, reader); } @@ -709,7 +715,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio /// public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinition command, CommandBehavior commandBehavior) { - var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand dbcmd); + var reader = ExecuteReaderImpl(cnn, ref command, commandBehavior, out IDbCommand? dbcmd); return DbWrappedReader.Create(dbcmd, reader); } @@ -725,7 +731,7 @@ public static IDataReader ExecuteReader(this IDbConnection cnn, CommandDefinitio /// The type of command to execute. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => + public static IEnumerable Query(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) => Query(cnn, sql, param, transaction, buffered, commandTimeout, commandType); /// @@ -739,7 +745,7 @@ public static IEnumerable Query(this IDbConnection cnn, string sql, obj /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static dynamic QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static dynamic QueryFirst(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryFirst(cnn, sql, param, transaction, commandTimeout, commandType); /// @@ -753,7 +759,7 @@ public static dynamic QueryFirst(this IDbConnection cnn, string sql, object para /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static dynamic? QueryFirstOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QueryFirstOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); /// @@ -767,7 +773,7 @@ public static dynamic QueryFirstOrDefault(this IDbConnection cnn, string sql, ob /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static dynamic QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static dynamic QuerySingle(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QuerySingle(cnn, sql, param, transaction, commandTimeout, commandType); /// @@ -781,7 +787,7 @@ public static dynamic QuerySingle(this IDbConnection cnn, string sql, object par /// The type of command to execute. /// Note: the row can be accessed via "dynamic", or by casting to an IDictionary<string,object> [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) => + public static dynamic? QuerySingleOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) => QuerySingleOrDefault(cnn, sql, param, transaction, commandTimeout, commandType); /// @@ -800,7 +806,7 @@ public static dynamic QuerySingleOrDefault(this IDbConnection cnn, string sql, o /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) + public static IEnumerable Query(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var data = QueryImpl(cnn, command, typeof(T)); @@ -822,7 +828,7 @@ public static IEnumerable Query(this IDbConnection cnn, string sql, object /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static T QueryFirst(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static T QueryFirst(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.First, ref command, typeof(T)); @@ -843,7 +849,7 @@ public static T QueryFirst(this IDbConnection cnn, string sql, object param = /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static T QueryFirstOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static T? QueryFirstOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); @@ -864,7 +870,7 @@ public static T QueryFirstOrDefault(this IDbConnection cnn, string sql, objec /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static T QuerySingle(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static T QuerySingle(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.Single, ref command, typeof(T)); @@ -885,7 +891,7 @@ public static T QuerySingle(this IDbConnection cnn, string sql, object param /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static T? QuerySingleOrDefault(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); @@ -908,9 +914,9 @@ public static T QuerySingleOrDefault(this IDbConnection cnn, string sql, obje /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) + public static IEnumerable Query(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var data = QueryImpl(cnn, command, type); return command.Buffered ? data.ToList() : data; @@ -932,9 +938,9 @@ public static IEnumerable Query(this IDbConnection cnn, Type type, strin /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static object QueryFirst(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static object QueryFirst(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.First, ref command, type); } @@ -955,9 +961,9 @@ public static object QueryFirst(this IDbConnection cnn, Type type, string sql, o /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static object QueryFirstOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static object? QueryFirstOrDefault(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.FirstOrDefault, ref command, type); } @@ -978,9 +984,9 @@ public static object QueryFirstOrDefault(this IDbConnection cnn, Type type, stri /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static object QuerySingle(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static object QuerySingle(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.Single, ref command, type); } @@ -1001,9 +1007,9 @@ public static object QuerySingle(this IDbConnection cnn, Type type, string sql, /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static object QuerySingleOrDefault(this IDbConnection cnn, Type type, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static object? QuerySingleOrDefault(this IDbConnection cnn, Type type, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None); return QueryRowImpl(cnn, Row.SingleOrDefault, ref command, type); } @@ -1047,7 +1053,7 @@ public static T QueryFirst(this IDbConnection cnn, CommandDefinition command) /// A single or null instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition command) => + public static T? QueryFirstOrDefault(this IDbConnection cnn, CommandDefinition command) => QueryRowImpl(cnn, Row.FirstOrDefault, ref command, typeof(T)); /// @@ -1073,7 +1079,7 @@ public static T QuerySingle(this IDbConnection cnn, CommandDefinition command /// A single instance of the supplied type; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). /// - public static T QuerySingleOrDefault(this IDbConnection cnn, CommandDefinition command) => + public static T? QuerySingleOrDefault(this IDbConnection cnn, CommandDefinition command) => QueryRowImpl(cnn, Row.SingleOrDefault, ref command, typeof(T)); /// @@ -1085,7 +1091,7 @@ public static T QuerySingleOrDefault(this IDbConnection cnn, CommandDefinitio /// The transaction to use for this query. /// Number of seconds before command execution timeout. /// Is it a stored proc or a batch? - public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + public static GridReader QueryMultiple(this IDbConnection cnn, string sql, object? param = null, IDbTransaction? transaction = null, int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.Buffered); return QueryMultipleImpl(cnn, ref command); @@ -1101,12 +1107,12 @@ public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); - IDbCommand cmd = null; - DbDataReader reader = null; + IDbCommand? cmd = null; + DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { @@ -1124,7 +1130,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD } catch { - if (reader != null) + if (reader is not null) { if (!reader.IsClosed) { @@ -1160,12 +1166,12 @@ private static DbDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefinition command, Type effectiveType) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); - IDbCommand cmd = null; - DbDataReader reader = null; + IDbCommand? cmd = null; + DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -1180,7 +1186,7 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini // in the connection closing itself var tuple = info.Deserializer; int hash = GetColumnHash(reader); - if (tuple.Func == null || tuple.Hash != hash) + if (tuple.Func is null || tuple.Hash != hash) { if (reader.FieldCount == 0) //https://code.google.com/p/dapper-dot-net/issues/detail?id=57 yield break; @@ -1192,7 +1198,7 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; while (reader.Read()) { - object val = func(reader); + object? val = func(reader); yield return GetValue(reader, effectiveType, val); } while (reader.NextResult()) { /* ignore subsequent result sets */ } @@ -1205,11 +1211,11 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini } finally { - if (reader != null) + if (reader is not null) { if (!reader.IsClosed) { - try { cmd.Cancel(); } + try { cmd?.Cancel(); } catch { /* don't spoil the existing exception */ } } reader.Dispose(); @@ -1253,12 +1259,12 @@ private static void ThrowZeroRows(Row row) private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType) { - object param = command.Parameters; + object? param = command.Parameters; var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); - IDbCommand cmd = null; - DbDataReader reader = null; + IDbCommand? cmd = null; + DbDataReader? reader = null; bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -1271,7 +1277,7 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti : CommandBehavior.SequentialAccess | CommandBehavior.SingleResult | CommandBehavior.SingleRow); wasClosed = false; // *if* the connection was closed and we got this far, then we now have a reader - T result = default; + T result = default!; if (reader.Read() && reader.FieldCount != 0) { // with the CloseConnection flag, so the reader will deal with the connection; we @@ -1297,11 +1303,11 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti } finally { - if (reader != null) + if (reader is not null) { if (!reader.IsClosed) { - try { cmd.Cancel(); } + try { cmd?.Cancel(); } catch { /* don't spoil the existing exception */ } } reader.Dispose(); @@ -1320,31 +1326,31 @@ private static T ReadRow(CacheInfo info, Identity identity, ref CommandDefini { var tuple = info.Deserializer; int hash = GetColumnHash(reader); - if (tuple.Func == null || tuple.Hash != hash) + if (tuple.Func is null || tuple.Hash != hash) { tuple = info.Deserializer = new DeserializerState(hash, GetDeserializer(effectiveType, reader, 0, -1, false)); if (command.AddToCache) SetQueryCache(identity, info); } var func = tuple.Func; - object val = func(reader); + object? val = func(reader); return GetValue(reader, effectiveType, val); } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static T GetValue(DbDataReader reader, Type effectiveType, object val) + private static T GetValue(DbDataReader reader, Type effectiveType, object? val) { if (val is T tVal) { return tVal; } - else if (val == null && (!effectiveType.IsValueType || Nullable.GetUnderlyingType(effectiveType) != null)) + else if (val is null && (!effectiveType.IsValueType || Nullable.GetUnderlyingType(effectiveType) is not null)) { - return default; + return default!; } else if (val is Array array && typeof(T).IsArray) { - var elementType = typeof(T).GetElementType(); + var elementType = typeof(T).GetElementType()!; var result = Array.CreateInstance(elementType, array.Length); for (int i = 0; i < array.Length; i++) result.SetValue(Convert.ChangeType(array.GetValue(i), elementType, CultureInfo.InvariantCulture), i); @@ -1355,14 +1361,14 @@ private static T GetValue(DbDataReader reader, Type effectiveType, object val try { var convertToType = Nullable.GetUnderlyingType(effectiveType) ?? effectiveType; - return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture); + return (T)Convert.ChangeType(val, convertToType, CultureInfo.InvariantCulture)!; } catch (Exception ex) { #pragma warning disable CS0618 // Type or member is obsolete ThrowDataException(ex, 0, reader, val); #pragma warning restore CS0618 // Type or member is obsolete - return default; // For the compiler - we've already thrown + return default!; // For the compiler - we've already thrown } } } @@ -1385,7 +1391,7 @@ private static T GetValue(DbDataReader reader, Type effectiveType, object val /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// @@ -1407,7 +1413,7 @@ public static IEnumerable Query(this IDbConne /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// @@ -1430,7 +1436,7 @@ public static IEnumerable Query(this /// Is it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// @@ -1454,7 +1460,7 @@ public static IEnumerable QueryIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// @@ -1479,7 +1485,7 @@ public static IEnumerable QueryIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// @@ -1505,7 +1511,7 @@ public static IEnumerable QueryIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => + public static IEnumerable Query(this IDbConnection cnn, string sql, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) => MultiMap(cnn, sql, map, param, transaction, buffered, splitOn, commandTimeout, commandType); /// @@ -1525,7 +1531,7 @@ public static IEnumerable QueryIs it a stored proc or a batch? /// An enumerable of . [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static IEnumerable Query(this IDbConnection cnn, string sql, Type[] types, Func map, object param = null, IDbTransaction transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) + public static IEnumerable Query(this IDbConnection cnn, string sql, Type[] types, Func map, object? param = null, IDbTransaction? transaction = null, bool buffered = true, string splitOn = "Id", int? commandTimeout = null, CommandType? commandType = null) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var results = MultiMapImpl(cnn, command, types, map, splitOn, null, null, true); @@ -1533,37 +1539,37 @@ public static IEnumerable Query(this IDbConnection cnn, string } private static IEnumerable MultiMap( - this IDbConnection cnn, string sql, Delegate map, object param, IDbTransaction transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) + this IDbConnection cnn, string sql, Delegate map, object? param, IDbTransaction? transaction, bool buffered, string splitOn, int? commandTimeout, CommandType? commandType) { var command = new CommandDefinition(sql, param, transaction, commandTimeout, commandType, buffered ? CommandFlags.Buffered : CommandFlags.None); var results = MultiMapImpl(cnn, command, map, splitOn, null, null, true); return buffered ? results.ToList() : results; } - private static IEnumerable MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn, DbDataReader reader, Identity identity, bool finalize) + private static IEnumerable MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Delegate map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize) { - object param = command.Parameters; - identity ??= new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); + object? param = command.Parameters; + identity ??= new Identity(command.CommandText, command.CommandType, cnn!, typeof(TFirst), param?.GetType()); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); - IDbCommand ownedCommand = null; - DbDataReader ownedReader = null; + IDbCommand? ownedCommand = null; + DbDataReader? ownedReader = null; bool wasClosed = cnn?.State == ConnectionState.Closed; try { - if (reader == null) + if (reader is null) { - ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader); - if (wasClosed) cnn.Open(); + ownedCommand = command.SetupCommand(cnn!, cinfo.ParamReader); + if (wasClosed) cnn!.Open(); ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); reader = ownedReader; } var deserializer = default(DeserializerState); - Func[] otherDeserializers; + Func[]? otherDeserializers; int hash = GetColumnHash(reader); - if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) + if ((deserializer = cinfo.Deserializer).Func is null || (otherDeserializers = cinfo.OtherDeserializers) is null || hash != deserializer.Hash) { var deserializers = GenerateDeserializers(identity, splitOn, reader); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); @@ -1573,7 +1579,7 @@ private static IEnumerable MultiMapImpl mapIt = GenerateMapper(deserializer.Func, otherDeserializers, map); - if (mapIt != null) + if (mapIt is not null) { while (reader.Read()) { @@ -1596,7 +1602,7 @@ private static IEnumerable MultiMapImpl MultiMapImpl(this IDbConnection cnn, CommandDefinition command, Type[] types, Func map, string splitOn, DbDataReader reader, Identity identity, bool finalize) + private static IEnumerable MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Type[] types, Func map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize) { if (types.Length < 1) { throw new ArgumentException("you must provide at least one type to deserialize"); } - object param = command.Parameters; - identity ??= new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); + object? param = command.Parameters; + identity ??= new IdentityWithTypes(command.CommandText, command.CommandType, cnn!, types[0], param?.GetType(), types); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); - IDbCommand ownedCommand = null; - DbDataReader ownedReader = null; + IDbCommand? ownedCommand = null; + DbDataReader? ownedReader = null; bool wasClosed = cnn?.State == ConnectionState.Closed; try { - if (reader == null) + if (reader is null) { - ownedCommand = command.SetupCommand(cnn, cinfo.ParamReader); - if (wasClosed) cnn.Open(); + ownedCommand = command.SetupCommand(cnn!, cinfo.ParamReader); + if (wasClosed) cnn!.Open(); ownedReader = ExecuteReaderWithFlagsFallback(ownedCommand, wasClosed, CommandBehavior.SequentialAccess | CommandBehavior.SingleResult); reader = ownedReader; } DeserializerState deserializer; - Func[] otherDeserializers; + Func[]? otherDeserializers; int hash = GetColumnHash(reader); - if ((deserializer = cinfo.Deserializer).Func == null || (otherDeserializers = cinfo.OtherDeserializers) == null || hash != deserializer.Hash) + if ((deserializer = cinfo.Deserializer).Func is null || (otherDeserializers = cinfo.OtherDeserializers) is null || hash != deserializer.Hash) { var deserializers = GenerateDeserializers(identity, splitOn, reader); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); @@ -1644,7 +1650,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); - if (mapIt != null) + if (mapIt is not null) { while (reader.Read()) { @@ -1667,7 +1673,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection cnn { ownedCommand?.Parameters.Clear(); ownedCommand?.Dispose(); - if (wasClosed) cnn.Close(); + if (wasClosed) cnn!.Close(); } } } @@ -1810,27 +1816,27 @@ private static int GetNextSplit(int startIdx, string splitOn, DbDataReader reade throw MultiMapException(reader, splitOn); } - private static CacheInfo GetCacheInfo(Identity identity, object exampleParameters, bool addToCache) + private static CacheInfo GetCacheInfo(Identity identity, object? exampleParameters, bool addToCache) { - if (!TryGetQueryCache(identity, out CacheInfo info)) + if (!TryGetQueryCache(identity, out CacheInfo? info)) { - if (GetMultiExec(exampleParameters) != null) + if (GetMultiExec(exampleParameters) is not null) { throw new InvalidOperationException("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context"); } info = new CacheInfo(); - if (identity.parametersType != null) + if (identity.parametersType is not null) { - Action reader; + Action reader; if (exampleParameters is IDynamicParameters) { - reader = (cmd, obj) => ((IDynamicParameters)obj).AddParameters(cmd, identity); + reader = (cmd, obj) => ((IDynamicParameters)obj!).AddParameters(cmd, identity); } else if (exampleParameters is IEnumerable>) { reader = (cmd, obj) => { - IDynamicParameters mapped = new DynamicParameters(obj); + IDynamicParameters mapped = new DynamicParameters(obj!); mapped.AddParameters(cmd, identity); }; } @@ -1839,7 +1845,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object exampleParameter var literals = GetLiteralTokens(identity.sql); reader = CreateParamInfoGenerator(identity, false, true, literals); } - if ((identity.commandType == null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql)) + if ((identity.commandType is null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql)) { var tail = reader; reader = (cmd, obj) => @@ -1881,7 +1887,7 @@ private static void PassByPosition(IDbCommand cmd) { throw new InvalidOperationException("When passing parameters by position, each parameter can only be referenced once"); } - else if (parameters.TryGetValue(key, out IDbDataParameter param)) + else if (parameters.TryGetValue(key, out IDbDataParameter? param)) { if (firstMatch) { @@ -1921,16 +1927,16 @@ private static Func GetDeserializer(Type type, DbDataReade return GetDapperRowDeserializer(reader, startBound, length, returnNullIfFirstMissing); } - Type underlyingType = null; + Type? underlyingType = null; bool useGetFieldValue = false; if (typeMap.TryGetValue(type, out var mapEntry)) { useGetFieldValue = (mapEntry.Flags & TypeMapEntryFlags.UseGetFieldValue) != 0; } else if (!(type.IsEnum || type.IsArray || type.FullName == LinqBinary - || (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) != null && underlyingType.IsEnum))) + || (type.IsValueType && (underlyingType = Nullable.GetUnderlyingType(type)) is not null && underlyingType.IsEnum))) { - if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) + if (typeHandlers.TryGetValue(type, out ITypeHandler? handler)) { return GetHandlerDeserializer(handler, type, startBound); } @@ -1941,21 +1947,21 @@ private static Func GetDeserializer(Type type, DbDataReade private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) { - return reader => handler.Parse(type, reader.GetValue(startBound)); + return reader => handler.Parse(type, reader.GetValue(startBound))!; } - private static Exception MultiMapException(IDataRecord reader, string splitOnColumnName = null) + private static Exception MultiMapException(IDataRecord reader, string? splitOn = null) { bool hasFields = false; - try { hasFields = reader != null && reader.FieldCount != 0; } + try { hasFields = reader is not null && reader.FieldCount != 0; } catch { /* don't throw when trying to throw */ } if (hasFields) { return new ArgumentException( - string.IsNullOrEmpty(splitOnColumnName) + string.IsNullOrEmpty(splitOn) ? "When using the multi-mapping APIs ensure you set the splitOn param if you have keys other than Id" - : $"Multi-map error: splitOn column '{splitOnColumnName}' was not found - please ensure your splitOn parameter is set and in the correct order", - "splitOn"); + : $"Multi-map error: splitOn column '{splitOn}' was not found - please ensure your splitOn parameter is set and in the correct order", + nameof(splitOn)); } else { @@ -1978,12 +1984,12 @@ internal static Func GetDapperRowDeserializer(DbDataReader var effectiveFieldCount = Math.Min(fieldCount - startBound, length); - DapperTable table = null; + DapperTable? table = null; return r => { - if (table == null) + if (table is null) { string[] names = new string[effectiveFieldCount]; for (int i = 0; i < effectiveFieldCount; i++) @@ -2000,7 +2006,7 @@ internal static Func GetDapperRowDeserializer(DbDataReader values[0] = r.GetValue(startBound); if (values[0] is DBNull) { - return null; + return null!; } } @@ -2009,7 +2015,7 @@ internal static Func GetDapperRowDeserializer(DbDataReader for (int i = 0; i < values.Length; i++) { object val = r.GetValue(i); - values[i] = val is DBNull ? null : val; + values[i] = val is DBNull ? null! : val; } } else @@ -2018,7 +2024,7 @@ internal static Func GetDapperRowDeserializer(DbDataReader for (var iter = begin; iter < effectiveFieldCount; ++iter) { object obj = r.GetValue(iter + startBound); - values[iter] = obj is DBNull ? null : obj; + values[iter] = obj is DBNull ? null! : obj; } } return new DapperRow(table, values); @@ -2033,7 +2039,7 @@ internal static Func GetDapperRowDeserializer(DbDataReader [Obsolete(ObsoleteInternalUsageOnly, false)] public static char ReadChar(object value) { - if (value == null || value is DBNull) throw new ArgumentNullException(nameof(value)); + if (value is null || value is DBNull) throw new ArgumentNullException(nameof(value)); if (value is string s && s.Length == 1) return s[0]; if (value is char c) return c; throw new ArgumentException("A single-character was expected", nameof(value)); @@ -2048,7 +2054,7 @@ public static char ReadChar(object value) [Obsolete(ObsoleteInternalUsageOnly, false)] public static char? ReadNullableChar(object value) { - if (value == null || value is DBNull) return null; + if (value is null || value is DBNull) return null; if (value is string s && s.Length == 1) return s[0]; if (value is char c) return c; throw new ArgumentException("A single-character was expected", nameof(value)); @@ -2119,7 +2125,7 @@ private static string GetInListRegex(string name, bool byPosition) => byPosition [Browsable(false)] [EditorBrowsable(EditorBrowsableState.Never)] [Obsolete(ObsoleteInternalUsageOnly, false)] - public static void PackListParameters(IDbCommand command, string namePrefix, object value) + public static void PackListParameters(IDbCommand command, string namePrefix, object? value) { // initially we tried TVP, however it performs quite poorly. // keep in mind SQL support up to 2000 params easily in sp_executesql, needing more is rare @@ -2144,26 +2150,25 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj bool viaSplit = splitAt >= 0 && TryStringSplit(ref list, splitAt, namePrefix, command, byPosition); - if (list != null && !viaSplit) + if (list is not null && !viaSplit) { - object lastValue = null; + object? lastValue = null; foreach (var item in list) { if (++count == 1) // first item: fetch some type info { - if (item == null) + if (item is null) { throw new NotSupportedException("The first item in a list-expansion cannot be null"); } if (!isDbString) { - dbType = LookupDbType(item.GetType(), "", true, out ITypeHandler handler); + dbType = LookupDbType(item.GetType(), "", true, out var handler); } } var nextName = namePrefix + count.ToString(); - if (isDbString && item is DbString) + if (isDbString && item is DbString str) { - var str = item as DbString; str.AddParameter(command, nextName); } else @@ -2173,14 +2178,14 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj if (isString) { listParam.Size = DbString.DefaultLength; - if (item != null && ((string)item).Length > DbString.DefaultLength) + if (item is not null && ((string)item).Length > DbString.DefaultLength) { listParam.Size = -1; } } var tmp = listParam.Value = SanitizeParameterValue(item); - if (tmp != null && tmp is not DBNull) + if (tmp is not null && tmp is not DBNull) lastValue = tmp; // only interested in non-trivial values for padding if (DynamicParameters.ShouldSetDbType(dbType) && listParam.DbType != dbType.GetValueOrDefault()) @@ -2190,7 +2195,7 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj command.Parameters.Add(listParam); } } - if (Settings.PadListExpansions && !isDbString && lastValue != null) + if (Settings.PadListExpansions && !isDbString && lastValue is not null) { int padCount = GetListPaddingExtraCount(count); for (int i = 0; i < padCount; i++) @@ -2270,9 +2275,9 @@ public static void PackListParameters(IDbCommand command, string namePrefix, obj } } - private static bool TryStringSplit(ref IEnumerable list, int splitAt, string namePrefix, IDbCommand command, bool byPosition) + private static bool TryStringSplit(ref IEnumerable? list, int splitAt, string namePrefix, IDbCommand command, bool byPosition) { - if (list == null || splitAt < 0) return false; + if (list is null || splitAt < 0) return false; return list switch { IEnumerable l => TryStringSplit(ref l, splitAt, namePrefix, command, "int", byPosition, (sb, i) => sb.Append(i.ToString(CultureInfo.InvariantCulture))), @@ -2293,7 +2298,7 @@ private static bool TryStringSplit(ref IEnumerable list, int splitAt, stri } if (typed.Count < splitAt) return false; - string varName = null; + string? varName = null; var regexIncludingUnknown = GetInListRegex(namePrefix, byPosition); var sql = Regex.Replace(command.CommandText, regexIncludingUnknown, match => { @@ -2309,7 +2314,7 @@ private static bool TryStringSplit(ref IEnumerable list, int splitAt, stri return "(select cast([value] as " + colType + ") from string_split(" + variableName + ",','))"; } }, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant); - if (varName == null) return false; // couldn't resolve the var! + if (varName is null) return false; // couldn't resolve the var! command.CommandText = sql; var concatenatedParam = command.CreateParameter(); @@ -2344,9 +2349,9 @@ private static bool TryStringSplit(ref IEnumerable list, int splitAt, stri /// /// The value to sanitize. [Obsolete(ObsoleteInternalUsageOnly, false)] - public static object SanitizeParameterValue(object value) + public static object SanitizeParameterValue(object? value) { - if (value == null) return DBNull.Value; + if (value is null) return DBNull.Value; if (value is Enum) { TypeCode typeCode = value is IConvertible convertible @@ -2395,16 +2400,16 @@ public static void ReplaceLiterals(this IParameterLookup parameters, IDbCommand if (tokens.Count != 0) ReplaceLiterals(parameters, command, tokens); } - internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format", BindingFlags.Public | BindingFlags.Static); + internal static readonly MethodInfo format = typeof(SqlMapper).GetMethod("Format", BindingFlags.Public | BindingFlags.Static)!; /// /// Convert numeric values to their string form for SQL literal purposes. /// /// The value to get a string for. [Obsolete(ObsoleteInternalUsageOnly)] - public static string Format(object value) + public static string Format(object? value) { - if (value == null) + if (value is null) { return "null"; } @@ -2440,9 +2445,9 @@ public static string Format(object value) return ((decimal)value).ToString(CultureInfo.InvariantCulture); default: var multiExec = GetMultiExec(value); - if (multiExec != null) + if (multiExec is not null) { - StringBuilder sb = null; + StringBuilder? sb = null; bool first = true; foreach (object subval in multiExec) { @@ -2453,7 +2458,7 @@ public static string Format(object value) } else { - sb.Append(','); + sb!.Append(','); } sb.Append(Format(subval)); } @@ -2463,7 +2468,7 @@ public static string Format(object value) } else { - return sb.Append(')').ToStringRecycle(); + return sb!.Append(')').ToStringRecycle(); } } throw new NotSupportedException($"The type '{value.GetType().Name}' is not supported for SQL literals."); @@ -2476,7 +2481,7 @@ internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand com var sql = command.CommandText; foreach (var token in tokens) { - object value = parameters[token.Member]; + object? value = parameters[token.Member]; #pragma warning disable 0618 string text = Format(value); #pragma warning restore 0618 @@ -2485,6 +2490,7 @@ internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand com command.CommandText = sql; } + [SuppressMessage("Style", "IDE0220:Add explicit cast", Justification = "Regex matches are Match")] internal static IList GetLiteralTokens(string sql) { if (string.IsNullOrEmpty(sql)) return LiteralToken.None; @@ -2513,13 +2519,13 @@ internal static IList GetLiteralTokens(string sql) public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); - private static bool IsValueTuple(Type type) => (type?.IsValueType == true - && type.FullName.StartsWith("System.ValueTuple`", StringComparison.Ordinal)) - || (type != null && IsValueTuple(Nullable.GetUnderlyingType(type))); + private static bool IsValueTuple(Type? type) => (type?.IsValueType == true + && type.FullName?.StartsWith("System.ValueTuple`", StringComparison.Ordinal) == true) + || (type is not null && IsValueTuple(Nullable.GetUnderlyingType(type))); - internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) + internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { - Type type = identity.parametersType; + Type type = identity.parametersType!; if (IsValueTuple(type)) { @@ -2536,7 +2542,7 @@ internal static Action CreateParamInfoGenerator(Identity ide var il = dm.GetILGenerator(); bool isStruct = type.IsValueType; - var _sizeLocal = (LocalBuilder)null; + var _sizeLocal = (LocalBuilder?)null; LocalBuilder GetSizeLocal() => _sizeLocal ??= il.DeclareLocal(typeof(int)); il.Emit(OpCodes.Ldarg_1); // stack is now [untyped-param] @@ -2554,7 +2560,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Stloc, typedParameterLocal); // stack is now empty il.Emit(OpCodes.Ldarg_0); // stack is now [command] - il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty(nameof(IDbCommand.Parameters)).GetGetMethod(), null); // stack is now [parameters] + il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetProperty(nameof(IDbCommand.Parameters))!.GetGetMethod()!, null); // stack is now [parameters] var allTypeProps = type.GetProperties(); var propsList = new List(allTypeProps.Length); @@ -2567,7 +2573,7 @@ internal static Action CreateParamInfoGenerator(Identity ide var ctors = type.GetConstructors(); ParameterInfo[] ctorParams; - IEnumerable props = null; + IEnumerable? props = null; // try to detect tuple patterns, e.g. anon-types, and use that to choose the order // otherwise: alphabetical if (ctors.Length == 1 && propsList.Count == (ctorParams = ctors[0].GetParameters()).Length) @@ -2592,7 +2598,7 @@ internal static Action CreateParamInfoGenerator(Identity ide var positionByName = new Dictionary(ctorParams.Length, StringComparer.OrdinalIgnoreCase); foreach (var param in ctorParams) { - positionByName[param.Name] = param.Position; + positionByName[param.Name!] = param.Position; } if (positionByName.Count == propsList.Count) { @@ -2615,7 +2621,7 @@ internal static Action CreateParamInfoGenerator(Identity ide } } } - if (props == null) + if (props is null) { propsList.Sort(new PropertyInfoByNameComparer()); props = propsList; @@ -2631,14 +2637,14 @@ internal static Action CreateParamInfoGenerator(Identity ide if (typeof(ICustomQueryParameter).IsAssignableFrom(prop.PropertyType)) { il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param] - il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [custom] + il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [custom] il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name] - il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter)), null); // stack is now [parameters] + il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters] continue; } #pragma warning disable 618 - DbType? dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler handler); + DbType? dbType = LookupDbType(prop.PropertyType, prop.Name, true, out ITypeHandler? handler); #pragma warning restore 618 if (dbType == DynamicParameters.EnumerableMultiParameter) { @@ -2646,12 +2652,12 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [command] [name] il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [command] [name] [typed-param] - il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [command] [name] [typed-value] + il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [command] [name] [typed-value] if (prop.PropertyType.IsValueType) { il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [command] [name] [boxed-value] } - il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.PackListParameters)), null); // stack is [parameters] + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.PackListParameters))!, null); // stack is [parameters] continue; } il.Emit(OpCodes.Dup); // stack is now [parameters] [parameters] @@ -2662,42 +2668,42 @@ internal static Action CreateParamInfoGenerator(Identity ide { // need to be a little careful about adding; use a utility method il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [command] [name] - il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.FindOrAddParameter)), null); // stack is [parameters] [parameter] + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.FindOrAddParameter))!, null); // stack is [parameters] [parameter] } else { // no risk of duplicates; just blindly add - il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod(nameof(IDbCommand.CreateParameter)), null);// stack is now [parameters] [parameters] [parameter] + il.EmitCall(OpCodes.Callvirt, typeof(IDbCommand).GetMethod(nameof(IDbCommand.CreateParameter))!, null);// stack is now [parameters] [parameters] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [parameters] [parameter] [parameter] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [parameters] [parameter] [parameter] [name] - il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.ParameterName)).GetSetMethod(), null);// stack is now [parameters] [parameters] [parameter] + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.ParameterName))!.GetSetMethod()!, null);// stack is now [parameters] [parameters] [parameter] } - if (DynamicParameters.ShouldSetDbType(dbType) && dbType != DbType.Time && handler == null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time + if (DynamicParameters.ShouldSetDbType(dbType) && dbType != DbType.Time && handler is null) // https://connect.microsoft.com/VisualStudio/feedback/details/381934/sqlparameter-dbtype-dbtype-time-sets-the-parameter-to-sqldbtype-datetime-instead-of-sqldbtype-time { il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] if (dbType.GetValueOrDefault() == DbType.Object && prop.PropertyType == typeof(object)) // includes dynamic { // look it up from the param value il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] - il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value] - il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.SetDbType), BindingFlags.Static | BindingFlags.Public)); // stack is now [parameters] [[parameters]] [parameter] + il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [[parameters]] [parameter] [parameter] [object-value] + il.Emit(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.SetDbType), BindingFlags.Static | BindingFlags.Public)!); // stack is now [parameters] [[parameters]] [parameter] } else { // constant value; nice and simple EmitInt32(il, (int)dbType.GetValueOrDefault());// stack is now [parameters] [[parameters]] [parameter] [parameter] [db-type] - il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.DbType))!.GetSetMethod()!, null);// stack is now [parameters] [[parameters]] [parameter] } } il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] EmitInt32(il, (int)ParameterDirection.Input);// stack is now [parameters] [[parameters]] [parameter] [parameter] [dir] - il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Direction)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Direction))!.GetSetMethod()!, null);// stack is now [parameters] [[parameters]] [parameter] il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [typed-param] - il.Emit(callOpCode, prop.GetGetMethod()); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] + il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [[parameters]] [parameter] [parameter] [typed-value] bool checkForNull; if (prop.PropertyType.IsValueType) { @@ -2707,7 +2713,7 @@ internal static Action CreateParamInfoGenerator(Identity ide if ((nullType ?? propType).IsEnum) { - if (nullType != null) + if (nullType is not null) { // Nullable; we want to box as the underlying type; that's just *hard*; for // simplicity, box as Nullable and call SanitizeParameterValue @@ -2732,13 +2738,13 @@ internal static Action CreateParamInfoGenerator(Identity ide } else { - checkForNull = nullType != null; + checkForNull = nullType is not null; } il.Emit(OpCodes.Box, propType); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] if (callSanitize) { checkForNull = false; // handled by sanitize - il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SanitizeParameterValue)), null); + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SanitizeParameterValue))!, null); // stack is [parameters] [[parameters]] [parameter] [parameter] [boxed-value] } } @@ -2755,18 +2761,18 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Brtrue_S, notNull); // relative stack [boxed value = null] il.Emit(OpCodes.Pop); // relative stack empty - il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField(nameof(DBNull.Value))); // relative stack [DBNull] + il.Emit(OpCodes.Ldsfld, typeof(DBNull).GetField(nameof(DBNull.Value))!); // relative stack [DBNull] if (dbType == DbType.String || dbType == DbType.AnsiString) { EmitInt32(il, 0); il.Emit(OpCodes.Stloc, GetSizeLocal()); } - if (allDone != null) il.Emit(OpCodes.Br_S, allDone.Value); + if (allDone is not null) il.Emit(OpCodes.Br_S, allDone.Value); il.MarkLabel(notNull); if (prop.PropertyType == typeof(string)) { il.Emit(OpCodes.Dup); // [string] [string] - il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty(nameof(string.Length)).GetGetMethod(), null); // [string] [length] + il.EmitCall(OpCodes.Callvirt, typeof(string).GetProperty(nameof(string.Length))!.GetGetMethod()!, null); // [string] [length] EmitInt32(il, DbString.DefaultLength); // [string] [length] [4000] il.Emit(OpCodes.Cgt); // [string] [0 or 1] Label isLong = il.DefineLabel(), lenDone = il.DefineLabel(); @@ -2780,21 +2786,21 @@ internal static Action CreateParamInfoGenerator(Identity ide } if (prop.PropertyType.FullName == LinqBinary) { - il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance), null); + il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod("ToArray", BindingFlags.Public | BindingFlags.Instance)!, null); } - if (allDone != null) il.MarkLabel(allDone.Value); + if (allDone is not null) il.MarkLabel(allDone.Value); // relative stack [boxed value or DBNull] } - if (handler != null) + if (handler is not null) { #pragma warning disable 618 - il.Emit(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(prop.PropertyType).GetMethod(nameof(TypeHandlerCache.SetValue))); // stack is now [parameters] [[parameters]] [parameter] + il.Emit(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(prop.PropertyType).GetMethod(nameof(TypeHandlerCache.SetValue))!); // stack is now [parameters] [[parameters]] [parameter] #pragma warning restore 618 } else { - il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Value)).GetSetMethod(), null);// stack is now [parameters] [[parameters]] [parameter] + il.EmitCall(OpCodes.Callvirt, typeof(IDataParameter).GetProperty(nameof(IDataParameter.Value))!.GetSetMethod()!, null);// stack is now [parameters] [[parameters]] [parameter] } if (prop.PropertyType == typeof(string)) @@ -2807,7 +2813,7 @@ internal static Action CreateParamInfoGenerator(Identity ide il.Emit(OpCodes.Dup);// stack is now [parameters] [[parameters]] [parameter] [parameter] il.Emit(OpCodes.Ldloc, sizeLocal); // stack is now [parameters] [[parameters]] [parameter] [parameter] [size] - il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty(nameof(IDbDataParameter.Size)).GetSetMethod(), null); // stack is now [parameters] [[parameters]] [parameter] + il.EmitCall(OpCodes.Callvirt, typeof(IDbDataParameter).GetProperty(nameof(IDbDataParameter.Size))!.GetSetMethod()!, null); // stack is now [parameters] [[parameters]] [parameter] il.MarkLabel(endOfSize); } @@ -2820,7 +2826,7 @@ internal static Action CreateParamInfoGenerator(Identity ide { // stack is now [parameters] [parameters] [parameter] // blindly add - il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod(nameof(IList.Add)), null); // stack is now [parameters] + il.EmitCall(OpCodes.Callvirt, typeof(IList).GetMethod(nameof(IList.Add))!, null); // stack is now [parameters] il.Emit(OpCodes.Pop); // IList.Add returns the new index (int); we don't care } } @@ -2828,18 +2834,18 @@ internal static Action CreateParamInfoGenerator(Identity ide // stack is currently [parameters] il.Emit(OpCodes.Pop); // stack is now empty - if (literals.Count != 0 && propsList != null) + if (literals.Count != 0 && propsList is not null) { il.Emit(OpCodes.Ldarg_0); // command il.Emit(OpCodes.Ldarg_0); // command, command - var cmdText = typeof(IDbCommand).GetProperty(nameof(IDbCommand.CommandText)); - il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod(), null); // command, sql - Dictionary locals = null; - LocalBuilder local = null; + var cmdText = typeof(IDbCommand).GetProperty(nameof(IDbCommand.CommandText))!; + il.EmitCall(OpCodes.Callvirt, cmdText.GetGetMethod()!, null); // command, sql + Dictionary? locals = null; + LocalBuilder? local = null; foreach (var literal in literals) { // find the best member, preferring case-sensitive - PropertyInfo exact = null, fallback = null; + PropertyInfo? exact = null, fallback = null; string huntName = literal.Member; for (int i = 0; i < propsList.Count; i++) { @@ -2856,11 +2862,11 @@ internal static Action CreateParamInfoGenerator(Identity ide } var prop = exact ?? fallback; - if (prop != null) + if (prop is not null) { il.Emit(OpCodes.Ldstr, literal.Token); il.Emit(OpCodes.Ldloc, typedParameterLocal); // command, sql, typed parameter - il.EmitCall(callOpCode, prop.GetGetMethod(), null); // command, sql, typed value + il.EmitCall(callOpCode, prop.GetGetMethod()!, null); // command, sql, typed value Type propType = prop.PropertyType; var typeCode = Type.GetTypeCode(propType); switch (typeCode) @@ -2887,10 +2893,10 @@ internal static Action CreateParamInfoGenerator(Identity ide case TypeCode.Decimal: // need to stloc, ldloca, call // re-use existing locals (both the last known, and via a dictionary) - var convert = GetToString(typeCode); - if (local == null || local.LocalType != propType) + var convert = GetToString(typeCode)!; + if (local is null || local.LocalType != propType) { - if (locals == null) + if (locals is null) { locals = new Dictionary(); local = null; @@ -2899,7 +2905,7 @@ internal static Action CreateParamInfoGenerator(Identity ide { if (!locals.TryGetValue(propType, out local)) local = null; } - if (local == null) + if (local is null) { local = il.DeclareLocal(propType); locals.Add(propType, local); @@ -2918,30 +2924,30 @@ internal static Action CreateParamInfoGenerator(Identity ide il.EmitCall(OpCodes.Callvirt, StringReplace, null); } } - il.EmitCall(OpCodes.Callvirt, cmdText.GetSetMethod(), null); // empty + il.EmitCall(OpCodes.Callvirt, cmdText.GetSetMethod()!, null); // empty } il.Emit(OpCodes.Ret); - return (Action)dm.CreateDelegate(typeof(Action)); + return (Action)dm.CreateDelegate(typeof(Action)); } private static readonly Dictionary toStrings = new[] { typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short), typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal) - }.ToDictionary(x => Type.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), new[] { typeof(IFormatProvider) })); + }.ToDictionary(x => Type.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), new[] { typeof(IFormatProvider) })!); - private static MethodInfo GetToString(TypeCode typeCode) + private static MethodInfo? GetToString(TypeCode typeCode) { - return toStrings.TryGetValue(typeCode, out MethodInfo method) ? method : null; + return toStrings.TryGetValue(typeCode, out MethodInfo? method) ? method : null; } - private static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), new Type[] { typeof(string), typeof(string) }), - InvariantCulture = typeof(CultureInfo).GetProperty(nameof(CultureInfo.InvariantCulture), BindingFlags.Public | BindingFlags.Static).GetGetMethod(); + private static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), new Type[] { typeof(string), typeof(string) })!, + InvariantCulture = typeof(CultureInfo).GetProperty(nameof(CultureInfo.InvariantCulture), BindingFlags.Public | BindingFlags.Static)!.GetGetMethod()!; - private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action paramReader) + private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action? paramReader) { - IDbCommand cmd = null; + IDbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; try { @@ -2959,19 +2965,19 @@ private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition comma } } - private static T ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition command) + private static T? ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition command) { - Action paramReader = null; - object param = command.Parameters; - if (param != null) + Action? paramReader = null; + object? param = command.Parameters; + if (param is not null) { var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } - IDbCommand cmd = null; + IDbCommand? cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed; - object result; + object? result; try { cmd = command.SetupCommand(cnn, paramReader); @@ -2988,9 +2994,9 @@ private static T ExecuteScalarImpl(IDbConnection cnn, ref CommandDefinition c return Parse(result); } - private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand cmd) + private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefinition command, CommandBehavior commandBehavior, out IDbCommand? cmd) { - Action paramReader = GetParameterReader(cnn, ref command); + Action? paramReader = GetParameterReader(cnn, ref command); cmd = null; bool wasClosed = cnn.State == ConnectionState.Closed, disposeCommand = true; try @@ -3006,7 +3012,7 @@ private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefi finally { if (wasClosed) cnn.Close(); - if (cmd != null && disposeCommand) + if (cmd is not null && disposeCommand) { cmd.Parameters.Clear(); cmd.Dispose(); @@ -3014,18 +3020,18 @@ private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefi } } - private static Action GetParameterReader(IDbConnection cnn, ref CommandDefinition command) + private static Action? GetParameterReader(IDbConnection cnn, ref CommandDefinition command) { - object param = command.Parameters; - IEnumerable multiExec = GetMultiExec(param); - CacheInfo info = null; - if (multiExec != null) + object? param = command.Parameters; + IEnumerable? multiExec = GetMultiExec(param); + CacheInfo? info = null; + if (multiExec is not null) { throw new NotSupportedException("MultiExec is not supported by ExecuteReader"); } // nice and simple - if (param != null) + if (param is not null) { var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); @@ -3044,11 +3050,11 @@ private static Func GetStructDeserializer(Type type, Type } if (type == typeof(char?)) { - return r => ReadNullableChar(r.GetValue(index)); + return r => ReadNullableChar(r.GetValue(index))!; } if (type.FullName == LinqBinary) { - return r => Activator.CreateInstance(type, r.GetValue(index)); + return r => Activator.CreateInstance(type, r.GetValue(index))!; } #pragma warning restore 618 @@ -3061,15 +3067,15 @@ private static Func GetStructDeserializer(Type type, Type { val = Convert.ChangeType(val, Enum.GetUnderlyingType(effectiveType), CultureInfo.InvariantCulture); } - return val is DBNull ? null : Enum.ToObject(effectiveType, val); + return val is DBNull ? null! : Enum.ToObject(effectiveType, val); }; } - if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) + if (typeHandlers.TryGetValue(type, out var handler)) { return r => { var val = r.GetValue(index); - return val is DBNull ? null : handler.Parse(type, val); + return val is DBNull ? null! : handler.Parse(type, val)!; }; } return useGetFieldValue ? ReadViaGetFieldValueFactory(type, index) : ReadViaGetValue(index); @@ -3078,19 +3084,19 @@ static Func ReadViaGetValue(int index) => reader => { var val = reader.GetValue(index); - return val is DBNull ? null : val; + return val is DBNull ? null! : val; }; } static Func ReadViaGetFieldValueFactory(Type type, int index) { type = Nullable.GetUnderlyingType(type) ?? type; - var factory = (Func>)s_ReadViaGetFieldValueCache[type]; + var factory = (Func>?)s_ReadViaGetFieldValueCache[type]; if (factory is null) { factory = (Func>)Delegate.CreateDelegate( typeof(Func>), null, typeof(SqlMapper).GetMethod( - nameof(UnderlyingReadViaGetFieldValueFactory), BindingFlags.Static | BindingFlags.NonPublic) + nameof(UnderlyingReadViaGetFieldValueFactory), BindingFlags.Static | BindingFlags.NonPublic)! .MakeGenericMethod(type)); lock (s_ReadViaGetFieldValueCache) { @@ -3103,14 +3109,14 @@ static Func ReadViaGetFieldValueFactory(Type type, int ind static readonly Hashtable s_ReadViaGetFieldValueCache = new(); static Func UnderlyingReadViaGetFieldValueFactory(int index) - => reader => reader.IsDBNull(index) ? null : reader.GetFieldValue(index); + => reader => reader.IsDBNull(index) ? null! : reader.GetFieldValue(index)!; static bool UseGetFieldValue(Type type) => typeMap.TryGetValue(type, out var mapEntry) && (mapEntry.Flags & TypeMapEntryFlags.UseGetFieldValue) != 0; - private static T Parse(object value) + private static T Parse(object? value) { - if (value is null || value is DBNull) return default; + if (value is null || value is DBNull) return default!; if (value is T t) return t; var type = typeof(T); type = Nullable.GetUnderlyingType(type) ?? type; @@ -3122,22 +3128,22 @@ private static T Parse(object value) } return (T)Enum.ToObject(type, value); } - if (typeHandlers.TryGetValue(type, out ITypeHandler handler)) + if (typeHandlers.TryGetValue(type, out ITypeHandler? handler)) { - return (T)handler.Parse(type, value); + return (T)handler.Parse(type, value)!; } return (T)Convert.ChangeType(value, type, CultureInfo.InvariantCulture); } private static readonly MethodInfo - enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), new Type[] { typeof(Type), typeof(string), typeof(bool) }), + enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), new Type[] { typeof(Type), typeof(string), typeof(bool) })!, getItem = typeof(DbDataReader).GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(p => p.GetIndexParameters().Length > 0 && p.GetIndexParameters()[0].ParameterType == typeof(int)) - .Select(p => p.GetGetMethod()).First(), + .Select(p => p.GetGetMethod()).First()!, getFieldValueT = typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetFieldValue), - BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null), + BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null)!, isDbNull = typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull), - BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null); + BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null)!; /// /// Gets type-map for the given type @@ -3154,16 +3160,16 @@ private static readonly MethodInfo /// Type map implementation, DefaultTypeMap instance if no override present public static ITypeMap GetTypeMap(Type type) { - if (type == null) throw new ArgumentNullException(nameof(type)); - var map = (ITypeMap)_typeMaps[type]; - if (map == null) + if (type is null) throw new ArgumentNullException(nameof(type)); + var map = (ITypeMap?)_typeMaps[type]; + if (map is null) { lock (_typeMaps) { // double-checked; store this to avoid reflection next time we see this type // since multiple queries commonly use the same domain-entity/DTO/view-model type - map = (ITypeMap)_typeMaps[type]; + map = (ITypeMap?)_typeMaps[type]; - if (map == null) + if (map is null) { map = TypeMapProvider(type); _typeMaps[type] = map; @@ -3181,12 +3187,12 @@ public static ITypeMap GetTypeMap(Type type) /// /// Entity type to override /// Mapping rules implementation, null to remove custom map - public static void SetTypeMap(Type type, ITypeMap map) + public static void SetTypeMap(Type type, ITypeMap? map) { - if (type == null) + if (type is null) throw new ArgumentNullException(nameof(type)); - if (map == null || map is DefaultTypeMap) + if (map is null || map is DefaultTypeMap) { lock (_typeMaps) { @@ -3243,11 +3249,11 @@ public static Func GetTypeDeserializer( return TypeDeserializerCache.GetReader(type, reader, startBound, length, returnNullIfFirstMissing); } - private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary locals, Type type, bool initAndLoad) + private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary? locals, Type type, bool initAndLoad) { - if (type == null) throw new ArgumentNullException(nameof(type)); + if (type is null) throw new ArgumentNullException(nameof(type)); locals ??= new Dictionary(); - if (!locals.TryGetValue(type, out LocalBuilder found)) + if (!locals.TryGetValue(type, out LocalBuilder? found)) { found = il.DeclareLocal(type); locals.Add(type, found); @@ -3305,7 +3311,7 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, DbDataRe { var arity = int.Parse(currentValueTupleType.Name.Substring("ValueTuple`".Length), CultureInfo.InvariantCulture); var constructorParameterTypes = new Type[arity]; - var restField = (FieldInfo)null; + var restField = (FieldInfo?)null; foreach (var field in currentValueTupleType.GetFields(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly)) { @@ -3321,19 +3327,19 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, DbDataRe } var itemFieldCount = constructorParameterTypes.Length; - if (restField != null) itemFieldCount--; + if (restField is not null) itemFieldCount--; for (var i = 0; i < itemFieldCount; i++) { languageTupleElementTypes.Add(constructorParameterTypes[i]); } - if (restField != null) + if (restField is not null) { constructorParameterTypes[constructorParameterTypes.Length - 1] = restField.FieldType; } - constructors.Add(currentValueTupleType.GetConstructor(constructorParameterTypes)); + constructors.Add(currentValueTupleType.GetConstructor(constructorParameterTypes)!); if (restField is null) break; @@ -3344,7 +3350,7 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, DbDataRe } } - var stringEnumLocal = (LocalBuilder)null; + var stringEnumLocal = (LocalBuilder?)null; for (var i = 0; i < languageTupleElementTypes.Count; i++) { @@ -3384,11 +3390,11 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, DbDataRe il.Emit(OpCodes.Newobj, constructors[i]); } - if (nullableUnderlyingType != null) + if (nullableUnderlyingType is not null) { var nullableTupleConstructor = valueTupleType.GetConstructor(new[] { nullableUnderlyingType }); - il.Emit(OpCodes.Newobj, nullableTupleConstructor); + il.Emit(OpCodes.Newobj, nullableTupleConstructor!); } il.Emit(OpCodes.Box, valueTupleType); @@ -3407,10 +3413,10 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, ITypeMap typeMap = GetTypeMap(type); int index = startBound; - ConstructorInfo specializedConstructor = null; + ConstructorInfo? specializedConstructor = null; bool supportInitialize = false; - Dictionary structLocals = null; + Dictionary? structLocals = null; if (type.IsValueType) { il.Emit(OpCodes.Ldloca, returnValueLocal); @@ -3425,7 +3431,7 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, } var explicitConstr = typeMap.FindExplicitConstructor(); - if (explicitConstr != null) + if (explicitConstr is not null) { var consPs = explicitConstr.GetParameters(); foreach (var p in consPs) @@ -3446,13 +3452,13 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); - il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); + il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit))!, null); } } else { var ctor = typeMap.FindConstructor(names, types); - if (ctor == null) + if (ctor is null) { string proposedTypes = "(" + string.Join(", ", types.Select((t, i) => t.FullName + " " + names[i]).ToArray()) + ")"; throw new InvalidOperationException($"A parameterless default constructor or one matching signature {proposedTypes} is required for {type.FullName} materialization"); @@ -3466,7 +3472,7 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); - il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit)), null); + il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.BeginInit))!, null); } } else @@ -3481,26 +3487,26 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, { il.Emit(OpCodes.Ldloca, returnValueLocal); // [target] } - else if (specializedConstructor == null) + else if (specializedConstructor is null) { il.Emit(OpCodes.Ldloc, returnValueLocal); // [target] } - var members = (specializedConstructor != null + var members = (specializedConstructor is not null ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) : names.Select(n => typeMap.GetMember(n))).ToList(); // stack is now [target] bool first = true; var allDone = il.DefineLabel(); - var stringEnumLocal = (LocalBuilder)null; + var stringEnumLocal = (LocalBuilder?)null; var valueCopyDiagnosticLocal = il.DeclareLocal(typeof(object)); bool applyNullSetting = Settings.ApplyNullValues; foreach (var item in members) { - if (item != null) + if (item is not null) { - if (specializedConstructor == null) + if (specializedConstructor is null) il.Emit(OpCodes.Dup); // stack is now [target][target] Label finishLabel = il.DefineLabel(); Type memberType = item.MemberType; @@ -3511,16 +3517,16 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, LoadReaderValueOrBranchToDBNullLabel(il, index, ref stringEnumLocal, valueCopyDiagnosticLocal, reader.GetFieldType(index), memberType, out var isDbNullLabel, out bool popWhenNull); - if (specializedConstructor == null) + if (specializedConstructor is null) { // Store the value in the property/field - if (item.Property != null) + if (item.Property is not null) { - il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); + il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetterOrThrow(item.Property, type)); } else { - il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] + il.Emit(OpCodes.Stfld, item.Field!); // stack is now [target] } } @@ -3528,11 +3534,11 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, il.MarkLabel(isDbNullLabel); // incoming stack: [target][target][(and possibly value)] if (popWhenNull) il.Emit(OpCodes.Pop); // stack is now [target][target] - if (specializedConstructor != null) + if (specializedConstructor is not null) { LoadDefaultValue(il, item.MemberType); } - else if (applyNullSetting && (!memberType.IsValueType || Nullable.GetUnderlyingType(memberType) != null)) + else if (applyNullSetting && (!memberType.IsValueType || Nullable.GetUnderlyingType(memberType) is not null)) { // can load a null with this value if (memberType.IsValueType) @@ -3545,14 +3551,14 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, } // Store the value in the property/field - if (item.Property != null) + if (item.Property is not null) { - il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetter(item.Property, type)); + il.Emit(type.IsValueType ? OpCodes.Call : OpCodes.Callvirt, DefaultTypeMap.GetPropertySetterOrThrow(item.Property, type)); // stack is now [target] } else { - il.Emit(OpCodes.Stfld, item.Field); // stack is now [target] + il.Emit(OpCodes.Stfld, item.Field!); // stack is now [target] } } else @@ -3579,7 +3585,7 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, } else { - if (specializedConstructor != null) + if (specializedConstructor is not null) { il.Emit(OpCodes.Newobj, specializedConstructor); } @@ -3587,7 +3593,7 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, if (supportInitialize) { il.Emit(OpCodes.Ldloc, returnValueLocal); - il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.EndInit)), null); + il.EmitCall(OpCodes.Callvirt, typeof(ISupportInitialize).GetMethod(nameof(ISupportInitialize.EndInit))!, null); } } il.MarkLabel(allDone); @@ -3595,7 +3601,7 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, il.Emit(OpCodes.Ldloc, currentIndexDiagnosticLocal); // stack is Exception, index il.Emit(OpCodes.Ldarg_0); // stack is Exception, index, reader il.Emit(OpCodes.Ldloc, valueCopyDiagnosticLocal); // stack is Exception, index, reader, value - il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.ThrowDataException)), null); + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(SqlMapper.ThrowDataException))!, null); il.EndExceptionBlock(); il.Emit(OpCodes.Ldloc, returnValueLocal); // stack is [rval] @@ -3621,7 +3627,7 @@ private static void LoadDefaultValue(ILGenerator il, Type type) } } - private static void LoadReaderValueViaGetFieldValue(ILGenerator il, int index, Type memberType, LocalBuilder valueCopyLocal, Label isDbNullLabel, out bool popWhenNull) + private static void LoadReaderValueViaGetFieldValue(ILGenerator il, int index, Type memberType, LocalBuilder? valueCopyLocal, Label isDbNullLabel, out bool popWhenNull) { popWhenNull = false; var underlyingType = Nullable.GetUnderlyingType(memberType) ?? memberType; @@ -3648,11 +3654,11 @@ private static void LoadReaderValueViaGetFieldValue(ILGenerator il, int index, T if (underlyingType != memberType) { // Nullable; wrap it - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { underlyingType })); // stack is now [...][T?] + il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { underlyingType })!); // stack is now [...][T?] } } - private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref LocalBuilder stringEnumLocal, LocalBuilder valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel, out bool popWhenNull) + private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int index, ref LocalBuilder? stringEnumLocal, LocalBuilder? valueCopyLocal, Type colType, Type memberType, out Label isDbNullLabel, out bool popWhenNull) { isDbNullLabel = il.DefineLabel(); if (UseGetFieldValue(memberType)) @@ -3667,7 +3673,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind // default impl: use GetValue il.Emit(OpCodes.Callvirt, getItem); // stack is now [...][value-as-object] - if (valueCopyLocal != null) + if (valueCopyLocal is not null) { il.Emit(OpCodes.Dup); // stack is now [...][value-as-object][value-as-object] il.Emit(OpCodes.Stloc, valueCopyLocal); // stack is now [...][value-as-object] @@ -3676,7 +3682,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind if (memberType == typeof(char) || memberType == typeof(char?)) { il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod( - memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.ReadNullableChar), BindingFlags.Static | BindingFlags.Public), null); // stack is now [...][typed-value] + memberType == typeof(char) ? nameof(SqlMapper.ReadChar) : nameof(SqlMapper.ReadNullableChar), BindingFlags.Static | BindingFlags.Public)!, null); // stack is now [...][typed-value] } else { @@ -3698,7 +3704,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] - il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null);// stack is now [...][enum-type] + il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null);// stack is now [...][enum-type] il.Emit(OpCodes.Ldloc, stringEnumLocal); // stack is now [...][enum-type][string] il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] @@ -3709,15 +3715,15 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); } - if (nullUnderlyingType != null) + if (nullUnderlyingType is not null) { - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [...][typed-value] + il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })!); // stack is now [...][typed-value] } } else if (memberType.FullName == LinqBinary) { il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [...][byte-array] - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) }));// stack is now [...][binary] + il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) })!);// stack is now [...][binary] } else { @@ -3728,7 +3734,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind if (hasTypeHandler) { #pragma warning disable 618 - il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse)), null); // stack is now [...][typed-value] + il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse))!, null); // stack is now [...][typed-value] #pragma warning restore 618 } else @@ -3740,23 +3746,23 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind { // not a direct match; need to tweak the unbox FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null); - if (nullUnderlyingType != null) + if (nullUnderlyingType is not null) { - il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })); // stack is now [...][typed-value] + il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })!); // stack is now [...][typed-value] } } } } } - private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type via) + private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type from, Type to, Type? via) { - MethodInfo op; + MethodInfo? op; if (from == (via ?? to)) { il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } - else if ((op = GetOperator(from, to)) != null) + else if ((op = GetOperator(from, to)) is not null) { // this is handy for things like decimal <===> double il.Emit(OpCodes.Unbox_Any, from); // stack is now [target][target][data-typed-value] @@ -3824,17 +3830,17 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro else { il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token] - il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle)), null); // stack is now [target][target][value][member-type] + il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null); // stack is now [target][target][value][member-type] il.EmitCall(OpCodes.Call, InvariantCulture, null); // stack is now [target][target][value][member-type][culture] - il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type), typeof(IFormatProvider) }), null); // stack is now [target][target][boxed-member-type-value] + il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type), typeof(IFormatProvider) })!, null); // stack is now [target][target][boxed-member-type-value] il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } } } - private static MethodInfo GetOperator(Type from, Type to) + private static MethodInfo? GetOperator(Type from, Type to) { - if (to == null) return null; + if (to is null) return null; MethodInfo[] fromMethods, toMethods; return ResolveOperator(fromMethods = from.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit") ?? ResolveOperator(toMethods = to.GetMethods(BindingFlags.Static | BindingFlags.Public), from, to, "op_Implicit") @@ -3842,7 +3848,7 @@ private static MethodInfo GetOperator(Type from, Type to) ?? ResolveOperator(toMethods, from, to, "op_Explicit"); } - private static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type to, string name) + private static MethodInfo? ResolveOperator(MethodInfo[] methods, Type from, Type to, string name) { for (int i = 0; i < methods.Length; i++) { @@ -3862,13 +3868,13 @@ private static MethodInfo ResolveOperator(MethodInfo[] methods, Type from, Type /// The reader the exception occurred in. /// The value that caused the exception. [Obsolete(ObsoleteInternalUsageOnly, false)] - public static void ThrowDataException(Exception ex, int index, IDataReader reader, object value) + public static void ThrowDataException(Exception ex, int index, IDataReader reader, object? value) { Exception toThrow; try { string name = "(n/a)", formattedValue = "(n/a)"; - if (reader != null && index >= 0 && index < reader.FieldCount) + if (reader is not null && index >= 0 && index < reader.FieldCount) { name = reader.GetName(index); if (name == string.Empty) @@ -3878,7 +3884,7 @@ public static void ThrowDataException(Exception ex, int index, IDataReader reade } try { - if (value == null || value is DBNull) + if (value is null || value is DBNull) { formattedValue = ""; } @@ -3953,7 +3959,7 @@ public static IEqualityComparer ConnectionStringComparer /// The to create this parameter for. /// The name of the type this parameter is for. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string typeName = null) => + public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, string? typeName = null) => new TableValuedParameter(table, typeName); /// @@ -3963,7 +3969,7 @@ public static ICustomQueryParameter AsTableValuedParameter(this DataTable table, /// The name of the type this table is for. public static void SetTypeName(this DataTable table, string typeName) { - if (table != null) + if (table is not null) { if (string.IsNullOrEmpty(typeName)) table.ExtendedProperties.Remove(DataTableTypeNameKey); @@ -3976,7 +3982,7 @@ public static void SetTypeName(this DataTable table, string typeName) /// Fetch the type name associated with a . /// /// The that has a type name associated with it. - public static string GetTypeName(this DataTable table) => + public static string? GetTypeName(this DataTable table) => table?.ExtendedProperties[DataTableTypeNameKey] as string; /// @@ -3985,16 +3991,16 @@ public static string GetTypeName(this DataTable table) => /// The list of records to convert to TVPs. /// The sql parameter type name. [System.Diagnostics.CodeAnalysis.SuppressMessage("ApiDesign", "RS0026:Do not add multiple public overloads with optional parameters", Justification = "Grandfathered")] - public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string typeName = null) where T : IDataRecord => - new SqlDataRecordListTVPParameter(list, typeName); + public static ICustomQueryParameter AsTableValuedParameter(this IEnumerable list, string? typeName = null) where T : IDataRecord => + new SqlDataRecordListTVPParameter(list, typeName!); // one per thread [ThreadStatic] - private static StringBuilder perThreadStringBuilderCache; + private static StringBuilder? perThreadStringBuilderCache; private static StringBuilder GetStringBuilder() { var tmp = perThreadStringBuilderCache; - if (tmp != null) + if (tmp is not null) { perThreadStringBuilderCache = null; tmp.Length = 0; @@ -4005,7 +4011,7 @@ private static StringBuilder GetStringBuilder() private static string ToStringRecycle(this StringBuilder obj) { - if (obj == null) return ""; + if (obj is null) return ""; var s = obj.ToString(); perThreadStringBuilderCache ??= obj; return s; diff --git a/Dapper/TableValuedParameter.cs b/Dapper/TableValuedParameter.cs index 78409a40d..0c4e40b71 100644 --- a/Dapper/TableValuedParameter.cs +++ b/Dapper/TableValuedParameter.cs @@ -8,7 +8,7 @@ namespace Dapper internal sealed class TableValuedParameter : SqlMapper.ICustomQueryParameter { private readonly DataTable table; - private readonly string typeName; + private readonly string? typeName; /// /// Create a new instance of . @@ -21,7 +21,7 @@ public TableValuedParameter(DataTable table) : this(table, null) { /* run base * /// /// The to create this parameter for. /// The name of the type this parameter is for. - public TableValuedParameter(DataTable table, string typeName) + public TableValuedParameter(DataTable table, string? typeName) { this.table = table; this.typeName = typeName; @@ -35,12 +35,12 @@ void SqlMapper.ICustomQueryParameter.AddParameter(IDbCommand command, string nam command.Parameters.Add(param); } - internal static void Set(IDbDataParameter parameter, DataTable table, string typeName) + internal static void Set(IDbDataParameter parameter, DataTable? table, string? typeName) { #pragma warning disable 0618 parameter.Value = SqlMapper.SanitizeParameterValue(table); #pragma warning restore 0618 - if (string.IsNullOrEmpty(typeName) && table != null) + if (string.IsNullOrEmpty(typeName) && table is not null) { typeName = table.GetTypeName(); } diff --git a/Dapper/TypeExtensions.cs b/Dapper/TypeExtensions.cs index 6291fe46d..eb0efcbc9 100644 --- a/Dapper/TypeExtensions.cs +++ b/Dapper/TypeExtensions.cs @@ -5,7 +5,7 @@ namespace Dapper { internal static class TypeExtensions { - public static MethodInfo GetPublicInstanceMethod(this Type type, string name, Type[] types) + public static MethodInfo? GetPublicInstanceMethod(this Type type, string name, Type[] types) => type.GetMethod(name, BindingFlags.Instance | BindingFlags.Public, null, types, null); } } diff --git a/Dapper/UdtTypeHandler.cs b/Dapper/UdtTypeHandler.cs index 4662dfe3b..9bb80b61d 100644 --- a/Dapper/UdtTypeHandler.cs +++ b/Dapper/UdtTypeHandler.cs @@ -22,12 +22,12 @@ public UdtTypeHandler(string udtTypeName) this.udtTypeName = udtTypeName; } - object ITypeHandler.Parse(Type destinationType, object value) + object? ITypeHandler.Parse(Type destinationType, object? value) { return value is DBNull ? null : value; } - void ITypeHandler.SetValue(IDbDataParameter parameter, object value) + void ITypeHandler.SetValue(IDbDataParameter parameter, object? value) { #pragma warning disable 0618 parameter.Value = SanitizeParameterValue(value); diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index feaa7dc25..42fd6da93 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -34,18 +34,18 @@ private async static Task ThrowDisposedAsync() public override void Close() { } public override DataTable GetSchemaTable() => ThrowDisposed(); -#if PLAT_NO_REMOTING +#if NET5_0_OR_GREATER [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif public override object InitializeLifetimeService() => ThrowDisposed(); protected override void Dispose(bool disposing) { } public override bool GetBoolean(int ordinal) => ThrowDisposed(); - public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => ThrowDisposed(); + public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) => ThrowDisposed(); public override float GetFloat(int ordinal) => ThrowDisposed(); public override short GetInt16(int ordinal) => ThrowDisposed(); public override byte GetByte(int ordinal) => ThrowDisposed(); public override char GetChar(int ordinal) => ThrowDisposed(); - public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => ThrowDisposed(); + public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) => ThrowDisposed(); public override string GetDataTypeName(int ordinal) => ThrowDisposed(); public override DateTime GetDateTime(int ordinal) => ThrowDisposed(); protected override DbDataReader GetDbDataReader(int ordinal) => ThrowDisposed(); @@ -83,13 +83,13 @@ internal sealed class DbWrappedReader : DbDataReader, IWrappedDataReader // the purpose of wrapping here is to allow closing a reader to *also* close // the command, without having to explicitly hand the command back to the // caller - public static DbDataReader Create(IDbCommand cmd, DbDataReader reader) + public static DbDataReader Create(IDbCommand? cmd, DbDataReader reader) { - if (cmd == null) return reader; // no need to wrap if no command + if (cmd is null) return reader; // no need to wrap if no command - if (reader != null) return new DbWrappedReader(cmd, reader); + if (reader is not null) return new DbWrappedReader(cmd, reader); cmd.Dispose(); - return null; // GIGO + return null!; // GIGO } private DbDataReader _reader; @@ -108,9 +108,9 @@ public DbWrappedReader(IDbCommand cmd, DbDataReader reader) public override bool HasRows => _reader.HasRows; public override void Close() => _reader.Close(); - public override DataTable GetSchemaTable() => _reader.GetSchemaTable(); + public override DataTable? GetSchemaTable() => _reader.GetSchemaTable(); -#if PLAT_NO_REMOTING +#if NET5_0_OR_GREATER [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif public override object InitializeLifetimeService() => _reader.InitializeLifetimeService(); @@ -133,7 +133,7 @@ protected override void Dispose(bool disposing) _reader.Dispose(); _reader = DisposedReader.Instance; // all future ops are no-ops _cmd?.Dispose(); - _cmd = null; + _cmd = null!; } } @@ -143,12 +143,12 @@ protected override void Dispose(bool disposing) public override byte GetByte(int i) => _reader.GetByte(i); - public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => + public override long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); public override char GetChar(int i) => _reader.GetChar(i); - public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => + public override long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); public override string GetDataTypeName(int i) => _reader.GetDataTypeName(i); @@ -208,7 +208,7 @@ public override long GetChars(int i, long fieldoffset, char[] buffer, int buffer public override Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) => _reader.GetColumnSchemaAsync(cancellationToken); - public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) => base.GetSchemaTableAsync(cancellationToken); + public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) => base.GetSchemaTableAsync(cancellationToken); #endif } @@ -224,9 +224,9 @@ public WrappedBasicReader(IDataReader reader) public override bool HasRows => true; // have to assume that we do public override void Close() => _reader.Close(); - public override DataTable GetSchemaTable() => _reader.GetSchemaTable(); + public override DataTable? GetSchemaTable() => _reader.GetSchemaTable(); -#if PLAT_NO_REMOTING +#if NET5_0_OR_GREATER [Obsolete("This Remoting API is not supported and throws PlatformNotSupportedException.", DiagnosticId = "SYSLIB0010", UrlFormat = "https://aka.ms/dotnet-warnings/{0}")] #endif public override object InitializeLifetimeService() => throw new NotSupportedException(); @@ -257,13 +257,13 @@ protected override void Dispose(bool disposing) public override byte GetByte(int i) => _reader.GetByte(i); - public override long GetBytes(int i, long fieldOffset, byte[] buffer, int bufferoffset, int length) => - _reader.GetBytes(i, fieldOffset, buffer, bufferoffset, length); + public override long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => + _reader.GetBytes(i, fieldOffset, buffer!, bufferoffset, length); public override char GetChar(int i) => _reader.GetChar(i); - public override long GetChars(int i, long fieldoffset, char[] buffer, int bufferoffset, int length) => - _reader.GetChars(i, fieldoffset, buffer, bufferoffset, length); + public override long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => + _reader.GetChars(i, fieldoffset, buffer!, bufferoffset, length); public override string GetDataTypeName(int i) => _reader.GetDataTypeName(i); @@ -308,7 +308,7 @@ public override T GetFieldValue(int ordinal) { value = null; } - return (T)value; + return (T)value!; } public override Task GetFieldValueAsync(int ordinal, CancellationToken cancellationToken) { @@ -356,7 +356,7 @@ public override ValueTask DisposeAsync() public override Task> GetColumnSchemaAsync(CancellationToken cancellationToken = default) => throw new NotSupportedException(); - public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) + public override Task GetSchemaTableAsync(CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); return Task.FromResult(_reader.GetSchemaTable()); diff --git a/Dapper/XmlHandlers.cs b/Dapper/XmlHandlers.cs index a29b45dba..e33d64d09 100644 --- a/Dapper/XmlHandlers.cs +++ b/Dapper/XmlHandlers.cs @@ -6,7 +6,7 @@ namespace Dapper { internal abstract class XmlTypeHandler : SqlMapper.StringTypeHandler { - public override void SetValue(IDbDataParameter parameter, T value) + public override void SetValue(IDbDataParameter parameter, T? value) { base.SetValue(parameter, value); parameter.DbType = DbType.Xml; diff --git a/Directory.Build.props b/Directory.Build.props index d7d57bf2a..d858cf494 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -19,11 +19,12 @@ embedded en-US false - + true true - 9.0 false + true + readme.md @@ -36,12 +37,12 @@ - - - - - True + + + + + \ No newline at end of file diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 000000000..e8da7cb83 --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs index cb8a62f7c..6c16d60ce 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs @@ -12,7 +12,7 @@ public class RepoDbBenchmarks : BenchmarkBase public void Setup() { BaseSetup(); - SqlServerBootstrap.Initialize(); + GlobalConfiguration.Setup().UseSqlServer(); ClassMapper.Add("Posts"); } diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 0a7f71582..10ad41cff 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -8,25 +8,26 @@ $(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060 - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + @@ -38,16 +39,16 @@ - - - - + + + + - - + + diff --git a/benchmarks/Directory.Build.props b/benchmarks/Directory.Build.props index 837b3a850..ce39a387e 100644 --- a/benchmarks/Directory.Build.props +++ b/benchmarks/Directory.Build.props @@ -10,7 +10,7 @@ - + diff --git a/docs/index.md b/docs/index.md index 08230f58d..b5481de39 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,6 +22,9 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased +- add NRT annotations +- extend `GridReader` API to allow it to be subclassed by external consumers + (note: new PRs will not be merged until they add release note wording here) ### 2.0.151 diff --git a/docs/readme.md b/docs/readme.md new file mode 100644 index 000000000..19da484b7 --- /dev/null +++ b/docs/readme.md @@ -0,0 +1,17 @@ +# Dapper + +Dapper is a simple micro-ORM used to simplify working with ADO.NET; if you like SQL but dislike the boilerplate of ADO.NET: Dapper is for you! + +As a simple example: + +``` c# +string region = ... +var customers = connection.Query( + "select * from Customers where Region = @region", // SQL + new { region } // parameters + ).AsList(); +``` + +But all the execute/single-row/scalar/async/etc functionality you would expect: is there as extension methods on your `DbConnection`. + +See [GitHub](https://github.com/DapperLib/Dapper) for more information and examples. \ No newline at end of file diff --git a/nuget.config b/nuget.config index 00f52b7e2..b004e5cc7 100644 --- a/nuget.config +++ b/nuget.config @@ -2,7 +2,6 @@ - \ No newline at end of file diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index 609c16ad6..f3a1597a2 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -34,7 +34,7 @@ public MicrosoftSqlClientAsyncQueryCacheTests(ITestOutputHelper log) : base(log) public abstract class AsyncTests : TestBase where TProvider : SqlServerDatabaseProvider { - private DbConnection _marsConnection; + private DbConnection? _marsConnection; private DbConnection MarsConnection => _marsConnection ??= Provider.GetOpenConnection(true); @@ -120,7 +120,7 @@ public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync_Cancellation( await using (var grid = await connection.QueryMultipleAsync("select 'abc' union select 'def'; select @txt", new { txt = "ghi" }) .ConfigureAwait(false)) { - await Assert.ThrowsAnyAsync(async () => + var ex = await Assert.ThrowsAnyAsync(async () => { while (!grid.IsConsumed) { @@ -133,6 +133,7 @@ await Assert.ThrowsAnyAsync(async () => cts.Cancel(); } }); + Assert.True(ex is OperationCanceledException or DbException { Message: "Operation cancelled by user." }); } var arr = results.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); // don't expect the ghi because of cancellation @@ -164,7 +165,7 @@ public async Task TestBasicStringUsageQueryFirstOrDefaultAsync() public async Task TestBasicStringUsageQueryFirstOrDefaultAsyncDynamic() { var str = await connection.QueryFirstOrDefaultAsync("select null as [Value] union all select @txt", new { txt = "def" }).ConfigureAwait(false); - Assert.Null(str.Value); + Assert.Null(str!.Value); } [Fact] @@ -191,7 +192,7 @@ public async Task TestBasicStringUsageQuerySingleOrDefaultAsync() [Fact] public async Task TestBasicStringUsageQuerySingleOrDefaultAsyncDynamic() { - var str = await connection.QuerySingleOrDefaultAsync("select null as [Value]").ConfigureAwait(false); + var str = (await connection.QuerySingleOrDefaultAsync("select null as [Value]").ConfigureAwait(false))!; Assert.Null(str.Value); } @@ -217,7 +218,7 @@ public void TestLongOperationWithCancellation() } catch (AggregateException agg) { - Assert.True(agg.InnerException.GetType().Name == "SqlException"); + Assert.Equal("SqlException", agg.InnerException?.GetType().Name); } } @@ -276,6 +277,7 @@ public async Task TestMultiMapWithSplitAsync() // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); + Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); } @@ -295,6 +297,7 @@ public async Task TestMultiMapArbitraryWithSplitAsync() // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); + Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); } @@ -316,11 +319,12 @@ public async Task TestMultiMapWithSplitClosedConnAsync() // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); + Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); - } + } - [Fact] + [Fact] public async Task TestMultiAsync() { using SqlMapper.GridReader multi = await connection.QueryMultipleAsync("select 1; select 2").ConfigureAwait(false); @@ -422,7 +426,7 @@ private static async Task LiteralReplacement(IDbConnection conn) var count = (await conn.QueryAsync("select count(1) from literal1 where id={=foo}", new { foo = 123 }).ConfigureAwait(false)).Single(); Assert.Equal(1, count); int sum = (await conn.QueryAsync("select sum(id) + sum(foo) from literal1").ConfigureAwait(false)).Single(); - Assert.Equal(sum, 123 + 456 + 1 + 2 + 3 + 4); + Assert.Equal(123 + 456 + 1 + 2 + 3 + 4, sum); } [Fact] @@ -503,7 +507,7 @@ public void RunSequentialVersusParallelSync() private class BasicType { - public string Value { get; set; } + public string? Value { get; set; } } [Fact] @@ -511,7 +515,7 @@ public async Task TypeBasedViaTypeAsync() { Type type = Common.GetSomeType(); - dynamic actual = (await MarsConnection.QueryAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).ConfigureAwait(false)).FirstOrDefault(); + dynamic actual = (await MarsConnection.QueryAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).ConfigureAwait(false)).FirstOrDefault()!; Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; @@ -524,7 +528,7 @@ public async Task TypeBasedViaTypeAsyncFirstOrDefault() { Type type = Common.GetSomeType(); - dynamic actual = await MarsConnection.QueryFirstOrDefaultAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).ConfigureAwait(false); + dynamic actual = (await MarsConnection.QueryFirstOrDefaultAsync(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).ConfigureAwait(false))!; Assert.Equal(((object)actual).GetType(), type); int a = actual.A; string b = actual.B; @@ -568,9 +572,9 @@ public async Task TestSupportForDynamicParametersOutputExpressionsAsync() p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); - p.Output(bob, b => b.Address.Index.Id); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); + p.Output(bob, b => b.Address!.Index!.Id); await connection.ExecuteAsync(@" SET @Occupation = 'grillmaster' @@ -598,8 +602,8 @@ public async Task TestSupportForDynamicParametersOutputExpressions_ScalarAsync() p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); var result = (int)(await connection.ExecuteScalarAsync(@" SET @Occupation = 'grillmaster' @@ -607,7 +611,7 @@ public async Task TestSupportForDynamicParametersOutputExpressions_ScalarAsync() SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId -select 42", p).ConfigureAwait(false)); +select 42", p).ConfigureAwait(false))!; Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); @@ -626,8 +630,8 @@ public async Task TestSupportForDynamicParametersOutputExpressions_Query_Default p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); var result = (await connection.QueryAsync(@" SET @Occupation = 'grillmaster' @@ -654,8 +658,8 @@ public async Task TestSupportForDynamicParametersOutputExpressions_Query_Buffere p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); var result = (await connection.QueryAsync(new CommandDefinition(@" SET @Occupation = 'grillmaster' @@ -682,8 +686,8 @@ public async Task TestSupportForDynamicParametersOutputExpressions_Query_NonBuff p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); var result = (await connection.QueryAsync(new CommandDefinition(@" SET @Occupation = 'grillmaster' @@ -710,8 +714,8 @@ public async Task TestSupportForDynamicParametersOutputExpressions_QueryMultiple p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); int x, y; using (var multi = await connection.QueryMultipleAsync(@" @@ -771,10 +775,12 @@ public async Task TestSchemaChangedViaFirstOrDefaultAsync() try { var d = await connection.QueryFirstOrDefaultAsync("select * from #dog").ConfigureAwait(false); + Assert.NotNull(d); Assert.Equal("Alf", d.Name); Assert.Equal(1, d.Age); connection.Execute("alter table #dog drop column Name"); d = await connection.QueryFirstOrDefaultAsync("select * from #dog").ConfigureAwait(false); + Assert.NotNull(d); Assert.Null(d.Name); Assert.Equal(1, d.Age); } @@ -845,6 +851,15 @@ public async Task TestMultiMapArbitraryMapsAsync() var p = data[0]; Assert.Equal(1, p.Id); Assert.Equal("Review Board 1", p.Name); + Assert.NotNull(p.User1); + Assert.NotNull(p.User2); + Assert.NotNull(p.User3); + Assert.NotNull(p.User4); + Assert.NotNull(p.User5); + Assert.NotNull(p.User6); + Assert.NotNull(p.User7); + Assert.NotNull(p.User8); + Assert.NotNull(p.User9); Assert.Equal(1, p.User1.Id); Assert.Equal(2, p.User2.Id); Assert.Equal(3, p.User3.Id); @@ -913,7 +928,7 @@ public async Task Issue563_QueryAsyncShouldThrowException() try { var data = (await connection.QueryAsync("select 1 union all select 2; RAISERROR('after select', 16, 1);").ConfigureAwait(false)).ToList(); - Assert.True(false, "Expected Exception"); + Assert.Fail("Expected Exception"); } catch (Exception ex) when (ex.GetType().Name == "SqlException" && ex.Message == "after select") { /* swallow only this */ } } @@ -924,7 +939,7 @@ public abstract class AsyncQueryCacheTests : TestBase wher { private readonly ITestOutputHelper _log; public AsyncQueryCacheTests(ITestOutputHelper log) => _log = log; - private DbConnection _marsConnection; + private DbConnection? _marsConnection; private DbConnection MarsConnection => _marsConnection ??= Provider.GetOpenConnection(true); public override void Dispose() diff --git a/tests/Dapper.Tests/ConstructorTests.cs b/tests/Dapper.Tests/ConstructorTests.cs index af8ae7afa..07e7c638a 100644 --- a/tests/Dapper.Tests/ConstructorTests.cs +++ b/tests/Dapper.Tests/ConstructorTests.cs @@ -59,9 +59,9 @@ public void TestNoDefaultConstructorWithChar() const char c1 = 'ą'; const char c3 = 'ó'; NoDefaultConstructorWithChar nodef = connection.Query("select @c1 c1, @c2 c2, @c3 c3", new { c1 = c1, c2 = (char?)null, c3 = c3 }).First(); - Assert.Equal(nodef.Char1, c1); + Assert.Equal(c1, nodef.Char1); Assert.Null(nodef.Char2); - Assert.Equal(nodef.Char3, c3); + Assert.Equal(c3, nodef.Char3); } [Fact] @@ -132,6 +132,7 @@ private class MultipleConstructors { public MultipleConstructors() { + B = default!; } public MultipleConstructors(int a, string b) @@ -157,7 +158,7 @@ public ConstructorsWithAccessModifiers(int a, string b) } public int A { get; set; } - public string B { get; set; } + public string? B { get; set; } } private class NoDefaultConstructor diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index b0e2b5712..f3007f5cc 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,9 +2,10 @@ Dapper.Tests Dapper Core Test Suite - net462;net472;net6.0 + net472;net6.0 $(DefineConstants);MSSQLCLIENT $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208 + enable @@ -14,17 +15,17 @@ - - - - - - - + + + + + + + - + diff --git a/tests/Dapper.Tests/DataReaderTests.cs b/tests/Dapper.Tests/DataReaderTests.cs index 789b9c9ed..f98bfabed 100644 --- a/tests/Dapper.Tests/DataReaderTests.cs +++ b/tests/Dapper.Tests/DataReaderTests.cs @@ -131,7 +131,7 @@ union all var foo = (Discriminated_Foo)result[0]; Assert.Equal("abc", foo.Name); var bar = (Discriminated_Bar)result[1]; - Assert.Equal(bar.Value, (float)4.0); + Assert.Equal((float)4.0, bar.Value); } [Fact] @@ -170,7 +170,7 @@ union all var foo = (Discriminated_Foo)result[0]; Assert.Equal("abc", foo.Name); var bar = (Discriminated_Bar)result[1]; - Assert.Equal(bar.Value, (float)4.0); + Assert.Equal((float)4.0, bar.Value); } [Fact] @@ -195,7 +195,7 @@ union all do { - DiscriminatedWithMultiMapping_BaseType obj = null; + DiscriminatedWithMultiMapping_BaseType? obj = null; switch (reader.GetInt32(col)) { case 1: @@ -207,7 +207,7 @@ union all } Assert.NotNull(obj); - obj.HazNameIdObject = toHaz(reader); + obj!.HazNameIdObject = toHaz(reader); result.Add(obj); } while (reader.Read()); @@ -219,10 +219,12 @@ union all Assert.Equal(2, result[1].Type); var foo = (DiscriminatedWithMultiMapping_Foo)result[0]; Assert.Equal("abc", foo.Name); + Assert.NotNull(foo.HazNameIdObject); Assert.Equal(1, foo.HazNameIdObject.Id); - Assert.Equal("zxc", foo.HazNameIdObject.Name); + Assert.Equal("zxc", foo.HazNameIdObject!.Name); var bar = (DiscriminatedWithMultiMapping_Bar)result[1]; - Assert.Equal(bar.Value, (float)4.0); + Assert.Equal((float)4.0, bar.Value); + Assert.NotNull(bar.HazNameIdObject); Assert.Equal(2, bar.HazNameIdObject.Id); Assert.Equal("qwe", bar.HazNameIdObject.Name); } @@ -247,7 +249,7 @@ union all do { - DiscriminatedWithMultiMapping_BaseType obj = null; + DiscriminatedWithMultiMapping_BaseType? obj = null; switch (reader.GetInt32(col)) { case 1: @@ -271,10 +273,12 @@ union all Assert.Equal(2, result[1].Type); var foo = (DiscriminatedWithMultiMapping_Foo)result[0]; Assert.Equal("abc", foo.Name); + Assert.NotNull(foo.HazNameIdObject); Assert.Equal(1, foo.HazNameIdObject.Id); Assert.Equal("zxc", foo.HazNameIdObject.Name); var bar = (DiscriminatedWithMultiMapping_Bar)result[1]; - Assert.Equal(bar.Value, (float)4.0); + Assert.Equal((float)4.0, bar.Value); + Assert.NotNull(bar.HazNameIdObject); Assert.Equal(2, bar.HazNameIdObject.Id); Assert.Equal("qwe", bar.HazNameIdObject.Name); } @@ -286,7 +290,7 @@ private abstract class Discriminated_BaseType private class Discriminated_Foo : Discriminated_BaseType { - public string Name { get; set; } + public string? Name { get; set; } public override int Type => 1; } @@ -298,19 +302,19 @@ private class Discriminated_Bar : Discriminated_BaseType private abstract class DiscriminatedWithMultiMapping_BaseType : Discriminated_BaseType { - public abstract HazNameId HazNameIdObject { get; set; } + public abstract HazNameId? HazNameIdObject { get; set; } } private class DiscriminatedWithMultiMapping_Foo : DiscriminatedWithMultiMapping_BaseType { - public override HazNameId HazNameIdObject { get; set; } - public string Name { get; set; } + public override HazNameId? HazNameIdObject { get; set; } + public string? Name { get; set; } public override int Type => 1; } private class DiscriminatedWithMultiMapping_Bar : DiscriminatedWithMultiMapping_BaseType { - public override HazNameId HazNameIdObject { get; set; } + public override HazNameId? HazNameIdObject { get; set; } public float Value { get; set; } public override int Type => 2; } diff --git a/tests/Dapper.Tests/EnumTests.cs b/tests/Dapper.Tests/EnumTests.cs index 2e65353ad..b8696049b 100644 --- a/tests/Dapper.Tests/EnumTests.cs +++ b/tests/Dapper.Tests/EnumTests.cs @@ -99,7 +99,8 @@ public void AdoNetEnumValue() p.DbType = DbType.Int32; // it turns out that this is the key piece; setting the DbType p.Value = AnEnum.B; cmd.Parameters.Add(p); - object value = cmd.ExecuteScalar(); + object? value = cmd.ExecuteScalar(); + Assert.NotNull(value); AnEnum val = (AnEnum)value; Assert.Equal(AnEnum.B, val); } diff --git a/tests/Dapper.Tests/Helpers/Attributes.cs b/tests/Dapper.Tests/Helpers/Attributes.cs index d4a376bd2..e41cf624d 100644 --- a/tests/Dapper.Tests/Helpers/Attributes.cs +++ b/tests/Dapper.Tests/Helpers/Attributes.cs @@ -41,7 +41,7 @@ public FactLongRunningAttribute() #endif } - public string Url { get; private set; } + public string? Url { get; private set; } } [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] diff --git a/tests/Dapper.Tests/Helpers/Common.cs b/tests/Dapper.Tests/Helpers/Common.cs index c3fc74c60..301147127 100644 --- a/tests/Dapper.Tests/Helpers/Common.cs +++ b/tests/Dapper.Tests/Helpers/Common.cs @@ -24,14 +24,14 @@ public static void DapperEnumValue(IDbConnection connection) // test passing as int, reading as AnEnum var k = (int)connection.QuerySingle("select @v, @y, @z", new { v = (int)AnEnum.B, y = (int?)(int)AnEnum.B, z = (int?)null }); - Assert.Equal(k, (int)AnEnum.B); + Assert.Equal((int)AnEnum.B, k); args = new DynamicParameters(); args.Add("v", (int)AnEnum.B); args.Add("y", (int)AnEnum.B); args.Add("z", null); k = (int)connection.QuerySingle("select @v, @y, @z", args); - Assert.Equal(k, (int)AnEnum.B); + Assert.Equal((int)AnEnum.B, k); } public static void TestDateTime(DbConnection connection) diff --git a/tests/Dapper.Tests/Helpers/TransactedConnection.cs b/tests/Dapper.Tests/Helpers/TransactedConnection.cs index 7dd785748..6452aa9be 100644 --- a/tests/Dapper.Tests/Helpers/TransactedConnection.cs +++ b/tests/Dapper.Tests/Helpers/TransactedConnection.cs @@ -1,9 +1,5 @@ using System; -using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Dapper.Tests { @@ -20,8 +16,10 @@ public TransactedConnection(IDbConnection conn, IDbTransaction tran) public string ConnectionString { - get { return _conn.ConnectionString; } + get { return _conn?.ConnectionString ?? ""; } +#pragma warning disable CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). set { _conn.ConnectionString = value; } +#pragma warning restore CS8767 // Nullability of reference types in type of parameter doesn't match implicitly implemented member (possibly because of nullability attributes). } public int ConnectionTimeout => _conn.ConnectionTimeout; diff --git a/tests/Dapper.Tests/Helpers/XunitSkippable.cs b/tests/Dapper.Tests/Helpers/XunitSkippable.cs index 8befa9e12..81814e9e9 100644 --- a/tests/Dapper.Tests/Helpers/XunitSkippable.cs +++ b/tests/Dapper.Tests/Helpers/XunitSkippable.cs @@ -14,7 +14,7 @@ public static class Skip public static void Inconclusive(string reason = "inconclusive") => throw new SkipTestException(reason); - public static void If(object obj, string reason = null) + public static void If(object obj, string? reason = null) where T : class { if (obj is T) Skip.Inconclusive(reason ?? $"not valid for {typeof(T).FullName}"); @@ -63,7 +63,7 @@ protected override string GetDisplayName(IAttributeInfo factAttribute, string di [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public SkippableTestCase() { } - public SkippableTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[] testMethodArguments = null) + public SkippableTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, object[]? testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, testMethodArguments) { } @@ -113,7 +113,7 @@ protected override string GetDisplayName(IAttributeInfo factAttribute, string di [Obsolete("Called by the de-serializer; should only be called by deriving classes for de-serialization purposes")] public NamedSkippedDataRowTestCase() { } - public NamedSkippedDataRowTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, string skipReason, object[] testMethodArguments = null) + public NamedSkippedDataRowTestCase(IMessageSink diagnosticMessageSink, TestMethodDisplay defaultMethodDisplay, TestMethodDisplayOptions defaultMethodDisplayOptions, ITestMethod testMethod, string skipReason, object[]? testMethodArguments = null) : base(diagnosticMessageSink, defaultMethodDisplay, defaultMethodDisplayOptions, testMethod, skipReason, testMethodArguments) { } } diff --git a/tests/Dapper.Tests/LiteralTests.cs b/tests/Dapper.Tests/LiteralTests.cs index 445fda96a..2ad080dc5 100644 --- a/tests/Dapper.Tests/LiteralTests.cs +++ b/tests/Dapper.Tests/LiteralTests.cs @@ -86,7 +86,7 @@ public void LiteralReplacement() var count = connection.Query("select count(1) from #literal1 where id={=foo}", new { foo = 123 }).Single(); Assert.Equal(1, count); int sum = connection.Query("select sum(id) + sum(foo) from #literal1").Single(); - Assert.Equal(sum, 123 + 456 + 1 + 2 + 3 + 4); + Assert.Equal(123 + 456 + 1 + 2 + 3 + 4, sum); } [Fact] diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 59f34519e..24f7bf2bd 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -69,7 +69,7 @@ private struct CarWithAllProps public Car.TrapEnum Trap { get; init; } } - private record PositionalCarRecord(int Age, Car.TrapEnum Trap, string Name) + private record PositionalCarRecord(int Age, Car.TrapEnum Trap, string? Name) { public PositionalCarRecord() : this(default, default, default) { } } @@ -78,7 +78,7 @@ private record NominalCarRecord { public int Age { get; init; } public Car.TrapEnum Trap { get; init; } - public string Name { get; init; } + public string? Name { get; init; } } [Fact] @@ -162,10 +162,12 @@ public void TestSchemaChangedViaFirstOrDefault() 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); } @@ -416,7 +418,7 @@ public void TestExecuteMultipleCommand() private class Student { - public string Name { get; set; } + public string? Name { get; set; } public int Age { get; set; } } @@ -558,7 +560,7 @@ public void TestBigIntMember() declare @bar table(Value bigint) insert @bar values (@foo) select * from @bar", new { foo }).Single(); - Assert.Equal(result.Value, foo); + Assert.Equal(foo, result.Value); } private class WithBigInt @@ -600,14 +602,14 @@ private string f private class InheritanceTest1 { - public string Base1 { get; set; } - public string Base2 { get; private set; } + 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; } + public string? Derived1 { get; set; } + public string? Derived2 { get; private set; } } [Fact] @@ -695,6 +697,7 @@ public void TestDefaultDbStringDbType() 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"]); } @@ -704,7 +707,7 @@ public void TestDapperSetsPrivates() { Assert.Equal(1, connection.Query("select 'one' ShadowInDB").First().Shadow); - Assert.Equal(1, connection.QueryFirstOrDefault("select 'one' ShadowInDB").Shadow); + Assert.Equal(1, connection.QueryFirstOrDefault("select 'one' ShadowInDB")?.Shadow); } private class PrivateDan @@ -719,7 +722,7 @@ private string ShadowInDB [Fact] public void TestUnexpectedDataMessage() { - string msg = null; + string? msg = null; try { connection.Query("select count(1) where 1 = @Foo", new WithBizarreData { Foo = new GenericUriParser(GenericUriParserOptions.Default), Bar = 23 }).First(); @@ -741,7 +744,7 @@ public void TestUnexpectedButFilteredDataMessage() private class WithBizarreData { - public GenericUriParser Foo { get; set; } + public GenericUriParser? Foo { get; set; } public int Bar { get; set; } } @@ -757,11 +760,11 @@ public void TestCharInputAndOutput() const char test = '〠'; char c = connection.Query("select @c", new { c = test }).Single(); - Assert.Equal(c, test); + Assert.Equal(test, c); var obj = connection.Query("select @Value as Value", new WithCharValue { Value = c }).Single(); - Assert.Equal(obj.Value, test); + Assert.Equal(test, obj.Value); } [Fact] @@ -805,20 +808,20 @@ private struct CanHazInt [Fact] public void TestInt16Usage() { - Assert.Equal(connection.Query("select cast(42 as smallint)").Single(), (short)42); - Assert.Equal(connection.Query("select cast(42 as smallint)").Single(), (short?)42); - Assert.Equal(connection.Query("select cast(null as smallint)").Single(), (short?)null); + 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(connection.Query("select cast(42 as smallint)").Single(), (ShortEnum)42); - Assert.Equal(connection.Query("select cast(42 as smallint)").Single(), (ShortEnum?)42); - Assert.Equal(connection.Query("select cast(null as smallint)").Single(), (ShortEnum?)null); + 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(row.NonNullableInt16, (short)1); - Assert.Equal(row.NullableInt16, (short)2); + Assert.Equal((short)1, row.NonNullableInt16); + Assert.Equal((short)2, row.NullableInt16); Assert.Equal(ShortEnum.Three, row.NonNullableInt16Enum); Assert.Equal(ShortEnum.Four, row.NullableInt16Enum); @@ -826,29 +829,29 @@ public void TestInt16Usage() 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(row.NonNullableInt16, (short)5); - Assert.Equal(row.NullableInt16, (short?)null); + Assert.Equal((short)5, row.NonNullableInt16); + Assert.Equal((short?)null, row.NullableInt16); Assert.Equal(ShortEnum.Six, row.NonNullableInt16Enum); - Assert.Equal(row.NullableInt16Enum, (ShortEnum?)null); + Assert.Equal((ShortEnum?)null, row.NullableInt16Enum); } [Fact] public void TestInt32Usage() { - Assert.Equal(connection.Query("select cast(42 as int)").Single(), (int)42); - Assert.Equal(connection.Query("select cast(42 as int)").Single(), (int?)42); - Assert.Equal(connection.Query("select cast(null as int)").Single(), (int?)null); + 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(connection.Query("select cast(42 as int)").Single(), (IntEnum)42); - Assert.Equal(connection.Query("select cast(42 as int)").Single(), (IntEnum?)42); - Assert.Equal(connection.Query("select cast(null as int)").Single(), (IntEnum?)null); + 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(row.NonNullableInt32, (int)1); - Assert.Equal(row.NullableInt32, (int)2); + Assert.Equal((int)1, row.NonNullableInt32); + Assert.Equal((int)2, row.NullableInt32); Assert.Equal(IntEnum.Three, row.NonNullableInt32Enum); Assert.Equal(IntEnum.Four, row.NullableInt32Enum); @@ -856,10 +859,10 @@ public void TestInt32Usage() 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(row.NonNullableInt32, (int)5); - Assert.Equal(row.NullableInt32, (int?)null); + Assert.Equal((int)5, row.NonNullableInt32); + Assert.Equal((int?)null, row.NullableInt32); Assert.Equal(IntEnum.Six, row.NonNullableInt32Enum); - Assert.Equal(row.NullableInt32Enum, (IntEnum?)null); + Assert.Equal((IntEnum?)null, row.NullableInt32Enum); } public class WithInt16Values @@ -975,7 +978,7 @@ public void TestIssue131() "SELECT 1 Id, 'Mr' Title, 'John' Surname, 4 AddressCount", (person, addressCount) => person, splitOn: "AddressCount" - ).FirstOrDefault(); + ).First(); var asDict = (IDictionary)results; @@ -1052,7 +1055,7 @@ public void TypeBasedViaDynamic() { Type type = Common.GetSomeType(); - dynamic template = Activator.CreateInstance(type); + 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; @@ -1066,7 +1069,7 @@ public void TypeBasedViaType() { Type type = Common.GetSomeType(); - dynamic actual = connection.Query(type, "select @A as [A], @B as [B]", new { A = 123, B = "abc" }).FirstOrDefault(); + 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; @@ -1076,7 +1079,7 @@ public void TypeBasedViaType() private T CheetViaDynamic(T template, string query, object args) { - return connection.Query(query, args).SingleOrDefault(); + return connection.Query(query, args).Single(); } [Fact] diff --git a/tests/Dapper.Tests/MultiMapTests.cs b/tests/Dapper.Tests/MultiMapTests.cs index 0cb5681f7..510860e84 100644 --- a/tests/Dapper.Tests/MultiMapTests.cs +++ b/tests/Dapper.Tests/MultiMapTests.cs @@ -21,7 +21,7 @@ public void ParentChildIdentityAssociations() var parents = connection.Query("select 1 as [Id], 1 as [Id] union all select 1,2 union all select 2,3 union all select 1,4 union all select 3,5", (parent, child) => { - if (!lookup.TryGetValue(parent.Id, out Parent found)) + if (!lookup.TryGetValue(parent.Id, out Parent? found)) { lookup.Add(parent.Id, found = parent); } @@ -72,6 +72,7 @@ public void TestMultiMap() Assert.Equal("Sams Post1", p.Content); Assert.Equal(1, p.Id); + Assert.NotNull(p.Owner); Assert.Equal("Sam", p.Owner.Name); Assert.Equal(99, p.Owner.Id); @@ -172,7 +173,10 @@ public void TestMultiMapThreeTypesWithGridReader() var post2 = grid.Read((post, user, comment) => { post.Owner = user; post.Comment = comment; return post; }).SingleOrDefault(); + Assert.NotNull(post2); + Assert.NotNull(post2.Comment); Assert.Equal(1, post2.Comment.Id); + Assert.NotNull(post2.Owner); Assert.Equal(99, post2.Owner.Id); } finally @@ -246,6 +250,7 @@ public void TestMultiMapWithSplit() // https://stackoverflow.com/q/6056778/23354 // assertions Assert.Equal(1, product.Id); Assert.Equal("abc", product.Name); + Assert.NotNull(product.Category); Assert.Equal(2, product.Category.Id); Assert.Equal("def", product.Category.Name); } @@ -349,9 +354,9 @@ public PostWithConstructor(int id, int ownerid, string content) } public int Ident { get; set; } - public UserWithConstructor Owner { get; set; } + public UserWithConstructor? Owner { get; set; } public string FullContent { get; set; } - public Comment Comment { get; set; } + public Comment? Comment { get; set; } } [Fact] @@ -378,6 +383,7 @@ public void TestMultiMapWithConstructor() Assert.Equal("Sams Post1", p.FullContent); Assert.Equal(1, p.Ident); + Assert.NotNull(p.Owner); Assert.Equal("Sam", p.Owner.FullName); Assert.Equal(99, p.Owner.Ident); @@ -450,6 +456,15 @@ public void TestMultiMapArbitraryMaps() var p = data[0]; Assert.Equal(1, p.Id); Assert.Equal("Review Board 1", p.Name); + Assert.NotNull(p.User1); + Assert.NotNull(p.User2); + Assert.NotNull(p.User3); + Assert.NotNull(p.User4); + Assert.NotNull(p.User5); + Assert.NotNull(p.User6); + Assert.NotNull(p.User7); + Assert.NotNull(p.User8); + Assert.NotNull(p.User9); Assert.Equal(1, p.User1.Id); Assert.Equal(2, p.User2.Id); Assert.Equal(3, p.User3.Id); @@ -510,6 +525,7 @@ Order by p.Id Assert.Equal("Sams Post1", p.Content); Assert.Equal(1, p.Id); + Assert.NotNull(p.Owner); Assert.Equal(p.Owner.Name, "Sam" + i); Assert.Equal(99, p.Owner.Id); @@ -563,7 +579,7 @@ public void TestMultiMappingWithSplitOnSpaceBetweenCommas() private class Extra { public int Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } } [Fact] @@ -581,6 +597,7 @@ public void TestMultiMappingWithNonReturnedProperty() Assert.Equal(1, postWithBlog.PostId); Assert.Equal("Title", postWithBlog.Title); + Assert.NotNull(postWithBlog.Blog); Assert.Equal(2, postWithBlog.Blog.BlogId); Assert.Equal("Blog", postWithBlog.Blog.Title); } @@ -588,15 +605,15 @@ public void TestMultiMappingWithNonReturnedProperty() private class Post_DupeProp { public int PostId { get; set; } - public string Title { get; set; } + public string? Title { get; set; } public int BlogId { get; set; } - public Blog_DupeProp Blog { get; set; } + public Blog_DupeProp? Blog { get; set; } } private class Blog_DupeProp { public int BlogId { get; set; } - public string Title { get; set; } + public string? Title { get; set; } } // see https://stackoverflow.com/questions/16955357/issue-about-dapper @@ -616,6 +633,7 @@ public void TestSplitWithMissingMembers() Assert.Null(result.Name); Assert.Null(result.Content); + Assert.NotNull(result.Author); Assert.Equal("def", result.Author.Phone); Assert.Equal("ghi", result.Author.Name); Assert.Equal(0, result.Author.ID); @@ -625,22 +643,22 @@ public void TestSplitWithMissingMembers() public class Profile { public int ID { get; set; } - public string Name { get; set; } - public string Phone { get; set; } - public string Address { get; set; } + public string? Name { get; set; } + public string? Phone { get; set; } + public string? Address { get; set; } //public ExtraInfo Extra { get; set; } } public class Topic { public int ID { get; set; } - public string Title { get; set; } + public string? Title { get; set; } public DateTime CreateDate { get; set; } - public string Content { get; set; } + public string? Content { get; set; } public int UID { get; set; } public int TestColum { get; set; } - public string Name { get; set; } - public Profile Author { get; set; } + public string? Name { get; set; } + public Profile? Author { get; set; } //public Attachment Attach { get; set; } } diff --git a/tests/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs index a6a17e24f..cd0d0f0cc 100644 --- a/tests/Dapper.Tests/ParameterTests.cs +++ b/tests/Dapper.Tests/ParameterTests.cs @@ -653,7 +653,7 @@ public void SupportInit() public class WithInit : ISupportInitialize { - public string Value { get; set; } + public string? Value { get; set; } public int Flags { get; set; } void ISupportInitialize.BeginInit() => Flags++; @@ -718,15 +718,15 @@ public SO29596645_OrganisationDTO() private class HazGeo { public int Id { get; set; } - public DbGeography Geo { get; set; } - public DbGeometry Geometry { get; set; } + public DbGeography? Geo { get; set; } + public DbGeometry? Geometry { get; set; } } private class HazSqlGeo { public int Id { get; set; } - public SqlGeography Geo { get; set; } - public SqlGeometry Geometry { get; set; } + public SqlGeography? Geo { get; set; } + public SqlGeometry? Geometry { get; set; } } [Fact] @@ -1057,8 +1057,8 @@ public void TestSupportForDynamicParametersOutputExpressions() p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); connection.Execute(@" SET @Occupation = 'grillmaster' @@ -1085,8 +1085,8 @@ public void TestSupportForDynamicParametersOutputExpressions_Scalar() p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); var result = (int)connection.ExecuteScalar(@" SET @Occupation = 'grillmaster' @@ -1094,7 +1094,7 @@ public void TestSupportForDynamicParametersOutputExpressions_Scalar() SET @NumberOfLegs = @NumberOfLegs - 1 SET @AddressName = 'bobs burgers' SET @AddressPersonId = @PersonId -select 42", p); +select 42", p)!; Assert.Equal("grillmaster", bob.Occupation); Assert.Equal(2, bob.PersonId); @@ -1116,8 +1116,8 @@ public void TestSupportForDynamicParametersOutputExpressions_Query_Buffered() p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); var result = connection.Query(@" SET @Occupation = 'grillmaster' @@ -1147,8 +1147,8 @@ public void TestSupportForDynamicParametersOutputExpressions_Query_NonBuffered() p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); var result = connection.Query(@" SET @Occupation = 'grillmaster' @@ -1178,8 +1178,8 @@ public void TestSupportForDynamicParametersOutputExpressions_QueryMultiple() p.Output(bob, b => b.PersonId); p.Output(bob, b => b.Occupation); p.Output(bob, b => b.NumberOfLegs); - p.Output(bob, b => b.Address.Name); - p.Output(bob, b => b.Address.PersonId); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); int x, y; using (var multi = connection.QueryMultiple(@" @@ -1233,7 +1233,7 @@ public void SO25069578_DynamicParams_Procs() public class HazX { - public string X { get; set; } + public string? X { get; set; } } [Fact] @@ -1309,7 +1309,7 @@ select @A public class ParameterWithIndexer { public int A { get; set; } - public virtual string this[string columnName] + public virtual string? this[string columnName] { get { return null; } set { } @@ -1332,13 +1332,13 @@ public class MultipleParametersWithIndexer : MultipleParametersWithIndexerDeclar public class MultipleParametersWithIndexerDeclaringType { - public object this[object field] + public object? this[object field] { get { return null; } set { } } - public object this[object field, int index] + public object? this[object field, int index] { get { return null; } set { } @@ -1359,15 +1359,16 @@ public void Issue182_BindDynamicObjectParametersAndColumns() var fromDb = connection.Query("select * from #Dyno where Id=@Id", orig).Single(); Assert.Equal((Guid)fromDb.Id, guid); Assert.Equal("T Rex", fromDb.Name); + Assert.NotNull(fromDb.Foo); Assert.Equal(123L, (long)fromDb.Foo); } public class Dyno { - public dynamic Id { get; set; } - public string Name { get; set; } + public dynamic? Id { get; set; } + public string? Name { get; set; } - public object Foo { get; set; } + public object? Foo { get; set; } } [Fact] @@ -1633,7 +1634,7 @@ Id int not null primary key identity(1,1), throw new InvalidOperationException($"unexpected reader type: {reader.GetType().FullName}"); } Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); Assert.False(reader.Read()); Assert.False(reader.NextResult()); @@ -1646,7 +1647,7 @@ Id int not null primary key identity(1,1), Assert.True(reader.Read()); recvValue = reader.GetFieldValue(1); Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); Assert.False(reader.Read()); Assert.False(reader.NextResult()); @@ -1664,29 +1665,29 @@ Id int not null primary key identity(1,1), // prove that simple read: works recvValue = connection.QuerySingle("select Value from #Issue1907"); Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); - recvValue = connection.QuerySingle("select Value from #Issue1907").Value; + recvValue = connection.QuerySingle("select Value from #Issue1907")!.Value; Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); // prove that object read: works recvValue = connection.QuerySingle("select Id, Value from #Issue1907").Value; Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); - recvValue = connection.QuerySingle("select Id, Value from #Issue1907").Value.Value; + recvValue = connection.QuerySingle("select Id, Value from #Issue1907").Value!.Value; Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); // prove that value-tuple read: works recvValue = connection.QuerySingle<(int Id, SqlDecimal Value)>("select Id, Value from #Issue1907").Value; Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); - recvValue = connection.QuerySingle<(int Id, SqlDecimal? Value)>("select Id, Value from #Issue1907").Value.Value; + recvValue = connection.QuerySingle<(int Id, SqlDecimal? Value)>("select Id, Value from #Issue1907").Value!.Value; Assert.Equal(sentValue, recvValue); - Assert.Equal(recvValue.ToString(), PreciseValue); + Assert.Equal(PreciseValue, recvValue.ToString()); } finally { diff --git a/tests/Dapper.Tests/ProcedureTests.cs b/tests/Dapper.Tests/ProcedureTests.cs index 2e320779a..1bd563045 100644 --- a/tests/Dapper.Tests/ProcedureTests.cs +++ b/tests/Dapper.Tests/ProcedureTests.cs @@ -106,14 +106,15 @@ @TaxInvoiceNumber nvarchar(20) TaxInvoiceNumber = InvoiceNumber }, commandType: CommandType.StoredProcedure).FirstOrDefault(); + Assert.NotNull(result); Assert.Equal("INV0000000028PPN", result.TaxInvoiceNumber); } private class PracticeRebateOrders { - public string fTaxInvoiceNumber; + public string? fTaxInvoiceNumber; [System.Xml.Serialization.XmlElement(Form = System.Xml.Schema.XmlSchemaForm.Unqualified)] - public string TaxInvoiceNumber + public string? TaxInvoiceNumber { get { return fTaxInvoiceNumber; } set { fTaxInvoiceNumber = value; } @@ -139,14 +140,14 @@ CREATE PROCEDURE #TestEmptyResults private class Issue327_Person { public int Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } } private class Issue327_Magic { - public string Creature { get; set; } - public string SpiritAnimal { get; set; } - public string Location { get; set; } + public string? Creature { get; set; } + public string? SpiritAnimal { get; set; } + public string? Location { get; set; } } [Fact] diff --git a/tests/Dapper.Tests/Providers/Linq2SqlTests.cs b/tests/Dapper.Tests/Providers/Linq2SqlTests.cs index 42127d82b..dfaa2228a 100644 --- a/tests/Dapper.Tests/Providers/Linq2SqlTests.cs +++ b/tests/Dapper.Tests/Providers/Linq2SqlTests.cs @@ -23,7 +23,7 @@ public void TestLinqBinaryToClass() var output = connection.Query("select @input as [Value]", new { input }).First().Value; - Assert.Equal(orig, output.ToArray()); + Assert.Equal(orig, output?.ToArray()); } [Fact] @@ -40,7 +40,7 @@ public void TestLinqBinaryRaw() private class WithBinary { - public System.Data.Linq.Binary Value { get; set; } + public System.Data.Linq.Binary? Value { get; set; } } private class NoDefaultConstructorWithBinary diff --git a/tests/Dapper.Tests/Providers/MySQLTests.cs b/tests/Dapper.Tests/Providers/MySQLTests.cs index f0682919b..0a6abef69 100644 --- a/tests/Dapper.Tests/Providers/MySQLTests.cs +++ b/tests/Dapper.Tests/Providers/MySQLTests.cs @@ -23,11 +23,11 @@ public DbConnection GetMySqlConnection(bool open = true, bool convertZeroDatetime = false, bool allowZeroDatetime = false) { string cs = GetConnectionString(); - var csb = Factory.CreateConnectionStringBuilder(); + var csb = Factory.CreateConnectionStringBuilder()!; csb.ConnectionString = cs; ((dynamic)csb).AllowZeroDateTime = allowZeroDatetime; ((dynamic)csb).ConvertZeroDateTime = convertZeroDatetime; - var conn = Factory.CreateConnection(); + var conn = Factory.CreateConnection()!; conn.ConnectionString = csb.ConnectionString; if (open) conn.Open(); return conn; @@ -131,7 +131,7 @@ public void Issue426_SO34439033_DateTimeGainsTicks() var dbObj = conn.Query("select * from Issue426_Test where Id = @id", new { id = Id }).Single(); Assert.Equal(Id, dbObj.Id); - Assert.Equal(ticks, dbObj.Time.Value.Ticks); + Assert.Equal(ticks, dbObj.Time?.Ticks); } [FactMySql] @@ -211,13 +211,13 @@ public class Issue426_Test [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactMySqlAttribute : FactAttribute { - public override string Skip + public override string? Skip { get { return unavailable ?? base.Skip; } set { base.Skip = value; } } - private static readonly string unavailable; + private static readonly string? unavailable; static FactMySqlAttribute() { diff --git a/tests/Dapper.Tests/Providers/OLDEBTests.cs b/tests/Dapper.Tests/Providers/OLDEBTests.cs index 5126d5a10..1fa8da3b9 100644 --- a/tests/Dapper.Tests/Providers/OLDEBTests.cs +++ b/tests/Dapper.Tests/Providers/OLDEBTests.cs @@ -126,7 +126,8 @@ public void PseudoPositionalParameters_ExecSingle() var data = new { x = 6 }; connection.Execute("create table #named_single(val int not null)"); int count = connection.Execute("insert #named_single (val) values (?x?)", data); - int sum = (int)connection.ExecuteScalar("select sum(val) from #named_single"); + int? sum = (int?)connection.ExecuteScalar("select sum(val) from #named_single"); + Assert.NotNull(sum); Assert.Equal(1, count); Assert.Equal(6, sum); } @@ -143,8 +144,9 @@ public void PseudoPositionalParameters_ExecMulti() }; connection.Execute("create table #named_multi(val int not null)"); int count = connection.Execute("insert #named_multi (val) values (?x?)", data); - int sum = (int)connection.ExecuteScalar("select sum(val) from #named_multi"); + int? sum = (int?)connection.ExecuteScalar("select sum(val) from #named_multi"); Assert.Equal(3, count); + Assert.NotNull(sum); Assert.Equal(10, sum); } @@ -161,7 +163,7 @@ public void Issue457_NullParameterValues() using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; + const string? code = null; // "abc"; var row = connection.QuerySingle(sql, new { since, @@ -187,7 +189,7 @@ public void Issue457_NullParameterValues_Named() using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; + const string? code = null; // "abc"; var row = connection.QuerySingle(sql, new { since, @@ -213,7 +215,7 @@ public async void Issue457_NullParameterValues_MultiAsync() using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; + const string? code = null; // "abc"; using (var multi = await connection.QueryMultipleAsync(sql, new { since, @@ -222,10 +224,10 @@ public async void Issue457_NullParameterValues_MultiAsync() { var row = await multi.ReadSingleAsync().ConfigureAwait(false); var a = (DateTime?)row.Since; - var b = (string)row.Code; + var b = (string?)row.Code; - Assert.Equal(a, since); - Assert.Equal(b, code); + Assert.Equal(since, a); + Assert.Equal(code, b); } } @@ -242,7 +244,7 @@ public async void Issue457_NullParameterValues_MultiAsync_Named() using var connection = GetOleDbConnection(); DateTime? since = null; // DateTime.Now.Date; - const string code = null; // "abc"; + const string? code = null; // "abc"; using (var multi = await connection.QueryMultipleAsync(sql, new { since, @@ -253,8 +255,8 @@ public async void Issue457_NullParameterValues_MultiAsync_Named() var a = (DateTime?)row.Since; var b = (string)row.Code; - Assert.Equal(a, since); - Assert.Equal(b, code); + Assert.Equal(since, a); + Assert.Equal(code, b); } } } diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 113134297..32114370e 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -26,8 +26,8 @@ public class PostgresqlTests : TestBase private class Cat { public int Id { get; set; } - public string Breed { get; set; } - public string Name { get; set; } + public string? Breed { get; set; } + public string? Name { get; set; } } private readonly Cat[] Cats = @@ -122,13 +122,13 @@ public void TestPostgresqlDateTimeUsage() [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactPostgresqlAttribute : FactAttribute { - public override string Skip + public override string? Skip { get { return unavailable ?? base.Skip; } set { base.Skip = value; } } - private static readonly string unavailable; + private static readonly string? unavailable; static FactPostgresqlAttribute() { diff --git a/tests/Dapper.Tests/Providers/SnowflakeTests.cs b/tests/Dapper.Tests/Providers/SnowflakeTests.cs index 0be413670..64b2d11f7 100644 --- a/tests/Dapper.Tests/Providers/SnowflakeTests.cs +++ b/tests/Dapper.Tests/Providers/SnowflakeTests.cs @@ -10,7 +10,7 @@ namespace Dapper.Tests { public class SnowflakeTests { - static readonly string s_ConnectionString; + static readonly string? s_ConnectionString; static SnowflakeTests() { SqlMapper.Settings.UseIncrementalPseudoPositionalParameterNames = true; @@ -77,9 +77,9 @@ public void ParameterizedQuery() public class Nation { public int N_NATIONKEY { get; set; } - public string N_NAME{ get; set; } + public string? N_NAME{ get; set; } public int N_REGIONKEY { get; set; } - public string N_COMMENT { get; set; } + public string? N_COMMENT { get; set; } } } } diff --git a/tests/Dapper.Tests/Providers/SqliteTests.cs b/tests/Dapper.Tests/Providers/SqliteTests.cs index 72c6abf6a..b0c5e0f1b 100644 --- a/tests/Dapper.Tests/Providers/SqliteTests.cs +++ b/tests/Dapper.Tests/Providers/SqliteTests.cs @@ -22,13 +22,13 @@ protected SqliteConnection GetSQLiteConnection(bool open = true) [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class FactSqliteAttribute : FactAttribute { - public override string Skip + public override string? Skip { get { return unavailable ?? base.Skip; } set { base.Skip = value; } } - private static readonly string unavailable; + private static readonly string? unavailable; static FactSqliteAttribute() { diff --git a/tests/Dapper.Tests/SharedTypes/Address.cs b/tests/Dapper.Tests/SharedTypes/Address.cs index 6c156953a..f679f3c95 100644 --- a/tests/Dapper.Tests/SharedTypes/Address.cs +++ b/tests/Dapper.Tests/SharedTypes/Address.cs @@ -3,8 +3,8 @@ public class Address { public int AddressId { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public int PersonId { get; set; } - public Index Index { get; set; } + public Index? Index { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/Bar1.cs b/tests/Dapper.Tests/SharedTypes/Bar1.cs index 306d6b96c..6628b2ca7 100644 --- a/tests/Dapper.Tests/SharedTypes/Bar1.cs +++ b/tests/Dapper.Tests/SharedTypes/Bar1.cs @@ -3,6 +3,6 @@ public class Bar1 { public int BarId; - public string Name { get; set; } + public string? Name { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/Category.cs b/tests/Dapper.Tests/SharedTypes/Category.cs index e51ef2008..33350f3d2 100644 --- a/tests/Dapper.Tests/SharedTypes/Category.cs +++ b/tests/Dapper.Tests/SharedTypes/Category.cs @@ -3,7 +3,7 @@ public class Category { public int Id { get; set; } - public string Name { get; set; } - public string Description { get; set; } + public string? Name { get; set; } + public string? Description { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/Comment.cs b/tests/Dapper.Tests/SharedTypes/Comment.cs index 568b0a01d..bb2833000 100644 --- a/tests/Dapper.Tests/SharedTypes/Comment.cs +++ b/tests/Dapper.Tests/SharedTypes/Comment.cs @@ -3,6 +3,6 @@ public class Comment { public int Id { get; set; } - public string CommentData { get; set; } + public string? CommentData { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/Dog.cs b/tests/Dapper.Tests/SharedTypes/Dog.cs index 9a5212922..a5668ecfb 100644 --- a/tests/Dapper.Tests/SharedTypes/Dog.cs +++ b/tests/Dapper.Tests/SharedTypes/Dog.cs @@ -6,7 +6,7 @@ public class Dog { public int? Age { get; set; } public Guid Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } public float? Weight { get; set; } public int IgnoredProperty => 1; diff --git a/tests/Dapper.Tests/SharedTypes/HazNameId.cs b/tests/Dapper.Tests/SharedTypes/HazNameId.cs index 7bc44a5a2..7fd2a1080 100644 --- a/tests/Dapper.Tests/SharedTypes/HazNameId.cs +++ b/tests/Dapper.Tests/SharedTypes/HazNameId.cs @@ -2,7 +2,7 @@ { public class HazNameId { - public string Name { get; set; } + public string? Name { get; set; } public int Id { get; set; } } -} \ No newline at end of file +} diff --git a/tests/Dapper.Tests/SharedTypes/Index.cs b/tests/Dapper.Tests/SharedTypes/Index.cs index ed0e10bc4..7769cec19 100644 --- a/tests/Dapper.Tests/SharedTypes/Index.cs +++ b/tests/Dapper.Tests/SharedTypes/Index.cs @@ -2,6 +2,6 @@ { public class Index { - public string Id { get; set; } + public string? Id { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/Person.cs b/tests/Dapper.Tests/SharedTypes/Person.cs index 269eff0ad..34e3dcdc6 100644 --- a/tests/Dapper.Tests/SharedTypes/Person.cs +++ b/tests/Dapper.Tests/SharedTypes/Person.cs @@ -3,9 +3,9 @@ public class Person { public int PersonId { get; set; } - public string Name { get; set; } - public string Occupation { get; private set; } + public string? Name { get; set; } + public string? Occupation { get; private set; } public int NumberOfLegs = 2; - public Address Address { get; set; } + public Address? Address { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/Post.cs b/tests/Dapper.Tests/SharedTypes/Post.cs index 9c017d6d7..118133e4f 100644 --- a/tests/Dapper.Tests/SharedTypes/Post.cs +++ b/tests/Dapper.Tests/SharedTypes/Post.cs @@ -3,8 +3,8 @@ public class Post { public int Id { get; set; } - public User Owner { get; set; } - public string Content { get; set; } - public Comment Comment { get; set; } + public User? Owner { get; set; } + public string? Content { get; set; } + public Comment? Comment { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/Product.cs b/tests/Dapper.Tests/SharedTypes/Product.cs index 0e07da3a4..be0848398 100644 --- a/tests/Dapper.Tests/SharedTypes/Product.cs +++ b/tests/Dapper.Tests/SharedTypes/Product.cs @@ -3,7 +3,7 @@ public class Product { public int Id { get; set; } - public string Name { get; set; } - public Category Category { get; set; } + public string? Name { get; set; } + public Category? Category { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/ReviewBoard.cs b/tests/Dapper.Tests/SharedTypes/ReviewBoard.cs index 06a5473a7..8a44006b3 100644 --- a/tests/Dapper.Tests/SharedTypes/ReviewBoard.cs +++ b/tests/Dapper.Tests/SharedTypes/ReviewBoard.cs @@ -3,15 +3,15 @@ public class ReviewBoard { public int Id { get; set; } - public string Name { get; set; } - public User User1 { get; set; } - public User User2 { get; set; } - public User User3 { get; set; } - public User User4 { get; set; } - public User User5 { get; set; } - public User User6 { get; set; } - public User User7 { get; set; } - public User User8 { get; set; } - public User User9 { get; set; } + public string? Name { get; set; } + public User? User1 { get; set; } + public User? User2 { get; set; } + public User? User3 { get; set; } + public User? User4 { get; set; } + public User? User5 { get; set; } + public User? User6 { get; set; } + public User? User7 { get; set; } + public User? User8 { get; set; } + public User? User9 { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/SomeType.cs b/tests/Dapper.Tests/SharedTypes/SomeType.cs index b86f8371b..5be1bbf46 100644 --- a/tests/Dapper.Tests/SharedTypes/SomeType.cs +++ b/tests/Dapper.Tests/SharedTypes/SomeType.cs @@ -3,6 +3,6 @@ public class SomeType { public int A { get; set; } - public string B { get; set; } + public string? B { get; set; } } } diff --git a/tests/Dapper.Tests/SharedTypes/User.cs b/tests/Dapper.Tests/SharedTypes/User.cs index 84d8d8524..6b5728f48 100644 --- a/tests/Dapper.Tests/SharedTypes/User.cs +++ b/tests/Dapper.Tests/SharedTypes/User.cs @@ -3,6 +3,6 @@ public class User { public int Id { get; set; } - public string Name { get; set; } + public string? Name { get; set; } } } diff --git a/tests/Dapper.Tests/TestBase.cs b/tests/Dapper.Tests/TestBase.cs index afc9a0f47..7c7b2920f 100644 --- a/tests/Dapper.Tests/TestBase.cs +++ b/tests/Dapper.Tests/TestBase.cs @@ -23,7 +23,7 @@ protected static string GetConnectionString(string name, string defaultConnectio public DbConnection GetOpenConnection() { - var conn = Factory.CreateConnection(); + var conn = Factory.CreateConnection()!; conn.ConnectionString = GetConnectionString(); conn.Open(); if (conn.State != ConnectionState.Open) throw new InvalidOperationException("should be open!"); @@ -32,7 +32,7 @@ public DbConnection GetOpenConnection() public DbConnection GetClosedConnection() { - var conn = Factory.CreateConnection(); + var conn = Factory.CreateConnection()!; conn.ConnectionString = GetConnectionString(); if (conn.State != ConnectionState.Closed) throw new InvalidOperationException("should be closed!"); return conn; @@ -40,7 +40,7 @@ public DbConnection GetClosedConnection() public DbParameter CreateRawParameter(string name, object value) { - var p = Factory.CreateParameter(); + var p = Factory.CreateParameter()!; p.ParameterName = name; p.Value = value ?? DBNull.Value; return p; @@ -49,18 +49,24 @@ public DbParameter CreateRawParameter(string name, object value) public abstract class SqlServerDatabaseProvider : DatabaseProvider { - public override string GetConnectionString() => - GetConnectionString("SqlServerConnectionString", "Data Source=.;Initial Catalog=tempdb;Integrated Security=True"); + public override string GetConnectionString() => GetConnectionString(false); - public DbConnection GetOpenConnection(bool mars) + private string GetConnectionString(bool mars) { - if (!mars) return GetOpenConnection(); + var builder = Factory.CreateConnectionStringBuilder()!; + builder.ConnectionString = GetConnectionString("SqlServerConnectionString", "Data Source=.;Initial Catalog=tempdb;Integrated Security=True"); + builder["TrustServerCertificate"] = true; + if (mars) + { + ((dynamic)builder).MultipleActiveResultSets = true; + } + return builder.ConnectionString; + } - var scsb = Factory.CreateConnectionStringBuilder(); - scsb.ConnectionString = GetConnectionString(); - ((dynamic)scsb).MultipleActiveResultSets = true; - var conn = Factory.CreateConnection(); - conn.ConnectionString = scsb.ConnectionString; + public DbConnection GetOpenConnection(bool mars) + { + var conn = Factory.CreateConnection()!; + conn.ConnectionString = GetConnectionString(mars); conn.Open(); if (conn.State != ConnectionState.Open) throw new InvalidOperationException("should be open!"); return conn; @@ -84,7 +90,7 @@ protected void SkipIfMsDataClient() protected DbConnection GetOpenConnection() => Provider.GetOpenConnection(); protected DbConnection GetClosedConnection() => Provider.GetClosedConnection(); - protected DbConnection _connection; + protected DbConnection? _connection; protected DbConnection connection => _connection ??= Provider.GetOpenConnection(); public TProvider Provider { get; } = DatabaseProvider.Instance; diff --git a/tests/Dapper.Tests/TupleTests.cs b/tests/Dapper.Tests/TupleTests.cs index cf709f6e1..a8cf74586 100644 --- a/tests/Dapper.Tests/TupleTests.cs +++ b/tests/Dapper.Tests/TupleTests.cs @@ -45,7 +45,7 @@ public void TupleReturnValue_NullableTuple_Works() { var val = connection.QuerySingleOrDefault<(int id, string name)?>("select 42, 'Fred', 123"); Assert.NotNull(val); - Assert.Equal(42, val.Value.id); + Assert.Equal(42, val!.Value.id); Assert.Equal("Fred", val.Value.name); } @@ -101,7 +101,7 @@ public void Nullable_TupleReturnValue_Works_With8Elements() "select 1, 2, 3, 4, 5, 6, 7, 8"); Assert.NotNull(val); - Assert.Equal(1, val.Value.e1); + Assert.Equal(1, val!.Value.e1); Assert.Equal(2, val.Value.e2); Assert.Equal(3, val.Value.e3); Assert.Equal(4, val.Value.e4); @@ -157,7 +157,7 @@ public void Nullable_TupleReturnValue_Works_With15Elements() "select 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15"); Assert.NotNull(val); - Assert.Equal(1, val.Value.e1); + Assert.Equal(1, val!.Value.e1); Assert.Equal(2, val.Value.e2); Assert.Equal(3, val.Value.e3); Assert.Equal(4, val.Value.e4); diff --git a/tests/Dapper.Tests/TypeHandlerTests.cs b/tests/Dapper.Tests/TypeHandlerTests.cs index 9742fdf38..759cf6c48 100644 --- a/tests/Dapper.Tests/TypeHandlerTests.cs +++ b/tests/Dapper.Tests/TypeHandlerTests.cs @@ -65,7 +65,7 @@ public void TestCustomTypeMap() // custom mapping var map = new CustomPropertyTypeMap(typeof(TypeWithMapping), - (type, columnName) => type.GetProperties().FirstOrDefault(prop => GetDescriptionFromAttribute(prop) == columnName)); + (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(); @@ -79,20 +79,20 @@ public void TestCustomTypeMap() Assert.Equal("BVal", item.B); } - private static string GetDescriptionFromAttribute(MemberInfo member) + private static string? GetDescriptionFromAttribute(MemberInfo member) { if (member == null) return null; - var attrib = (DescriptionAttribute)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false); + var attrib = (DescriptionAttribute?)Attribute.GetCustomAttribute(member, typeof(DescriptionAttribute), false); return attrib?.Description; } public class TypeWithMapping { [Description("B")] - public string A { get; set; } + public string? A { get; set; } [Description("A")] - public string B { get; set; } + public string? B { get; set; } } [Fact] @@ -122,9 +122,9 @@ private LocalDateHandler() { /* private constructor */ } // by mistake. public static readonly SqlMapper.ITypeHandler Default = new LocalDateHandler(); - public override LocalDate Parse(object value) + public override LocalDate Parse(object? value) { - var date = (DateTime)value; + var date = (DateTime)value!; return new LocalDate { Year = date.Year, Month = date.Month, Day = date.Day }; } @@ -234,17 +234,17 @@ private void TestBigIntForEverythingWorks_ByDataType(string dbType) var row = connection.Query(sql).Single(); Assert.True(row.N_Bool); - Assert.Equal(row.N_SByte, (sbyte)1); - Assert.Equal(row.N_Byte, (byte)1); - Assert.Equal(row.N_Int, (int)1); - Assert.Equal(row.N_UInt, (uint)1); - Assert.Equal(row.N_Short, (short)1); - Assert.Equal(row.N_UShort, (ushort)1); - Assert.Equal(row.N_Long, (long)1); - Assert.Equal(row.N_ULong, (ulong)1); - Assert.Equal(row.N_Float, (float)1); - Assert.Equal(row.N_Double, (double)1); - Assert.Equal(row.N_Decimal, (decimal)1); + Assert.Equal((sbyte)1, row.N_SByte); + Assert.Equal((byte)1, row.N_Byte); + Assert.Equal((int)1, row.N_Int); + Assert.Equal((uint)1, row.N_UInt); + Assert.Equal((short)1, row.N_Short); + Assert.Equal((ushort)1, row.N_UShort); + Assert.Equal((long)1, row.N_Long); + Assert.Equal((ulong)1, row.N_ULong); + Assert.Equal((float)1, row.N_Float); + Assert.Equal((double)1, row.N_Double); + Assert.Equal((decimal)1, row.N_Decimal); Assert.Equal(LotsOfNumerics.E_Byte.B, row.P_Byte); Assert.Equal(LotsOfNumerics.E_SByte.B, row.P_SByte); @@ -255,27 +255,27 @@ private void TestBigIntForEverythingWorks_ByDataType(string dbType) Assert.Equal(LotsOfNumerics.E_Long.B, row.P_Long); Assert.Equal(LotsOfNumerics.E_ULong.B, row.P_ULong); - Assert.True(row.N_N_Bool.Value); - Assert.Equal(row.N_N_SByte.Value, (sbyte)1); - Assert.Equal(row.N_N_Byte.Value, (byte)1); - Assert.Equal(row.N_N_Int.Value, (int)1); - Assert.Equal(row.N_N_UInt.Value, (uint)1); - Assert.Equal(row.N_N_Short.Value, (short)1); - Assert.Equal(row.N_N_UShort.Value, (ushort)1); - Assert.Equal(row.N_N_Long.Value, (long)1); - Assert.Equal(row.N_N_ULong.Value, (ulong)1); - Assert.Equal(row.N_N_Float.Value, (float)1); - Assert.Equal(row.N_N_Double.Value, (double)1); - Assert.Equal(row.N_N_Decimal, (decimal)1); - - Assert.Equal(LotsOfNumerics.E_Byte.B, row.N_P_Byte.Value); - Assert.Equal(LotsOfNumerics.E_SByte.B, row.N_P_SByte.Value); - Assert.Equal(LotsOfNumerics.E_Short.B, row.N_P_Short.Value); - Assert.Equal(LotsOfNumerics.E_UShort.B, row.N_P_UShort.Value); - Assert.Equal(LotsOfNumerics.E_Int.B, row.N_P_Int.Value); - Assert.Equal(LotsOfNumerics.E_UInt.B, row.N_P_UInt.Value); - Assert.Equal(LotsOfNumerics.E_Long.B, row.N_P_Long.Value); - Assert.Equal(LotsOfNumerics.E_ULong.B, row.N_P_ULong.Value); + Assert.True(row.N_N_Bool!.Value); + Assert.Equal((sbyte)1, row.N_N_SByte!.Value); + Assert.Equal((byte)1, row.N_N_Byte!.Value); + Assert.Equal((int)1, row.N_N_Int!.Value); + Assert.Equal((uint)1, row.N_N_UInt!.Value); + Assert.Equal((short)1, row.N_N_Short!.Value); + Assert.Equal((ushort)1, row.N_N_UShort!.Value); + Assert.Equal((long)1, row.N_N_Long!.Value); + Assert.Equal((ulong)1, row.N_N_ULong!.Value); + Assert.Equal((float)1, row.N_N_Float!.Value); + Assert.Equal((double)1, row.N_N_Double!.Value); + Assert.Equal((decimal)1, row.N_N_Decimal); + + Assert.Equal(LotsOfNumerics.E_Byte.B, row.N_P_Byte!.Value); + Assert.Equal(LotsOfNumerics.E_SByte.B, row.N_P_SByte!.Value); + Assert.Equal(LotsOfNumerics.E_Short.B, row.N_P_Short!.Value); + Assert.Equal(LotsOfNumerics.E_UShort.B, row.N_P_UShort!.Value); + Assert.Equal(LotsOfNumerics.E_Int.B, row.N_P_Int!.Value); + Assert.Equal(LotsOfNumerics.E_UInt.B, row.N_P_UInt!.Value); + Assert.Equal(LotsOfNumerics.E_Long.B, row.N_P_Long!.Value); + Assert.Equal(LotsOfNumerics.E_ULong.B, row.N_P_ULong!.Value); TestBigIntForEverythingWorksGeneric(true, dbType); TestBigIntForEverythingWorksGeneric((sbyte)1, dbType); @@ -376,7 +376,7 @@ private RatingValueHandler() public static readonly RatingValueHandler Default = new RatingValueHandler(); - public override RatingValue Parse(object value) + public override RatingValue Parse(object? value) { if (value is int i) { @@ -386,11 +386,11 @@ public override RatingValue Parse(object value) throw new FormatException("Invalid conversion to RatingValue"); } - public override void SetValue(IDbDataParameter parameter, RatingValue value) + public override void SetValue(IDbDataParameter parameter, RatingValue? value) { // ... null, range checks etc ... parameter.DbType = System.Data.DbType.Int32; - parameter.Value = value.Value; + parameter.Value = value?.Value; } } @@ -402,8 +402,8 @@ public class RatingValue public class MyResult { - public string CategoryName { get; set; } - public RatingValue CategoryRating { get; set; } + public string? CategoryName { get; set; } + public RatingValue? CategoryRating { get; set; } } [Fact] @@ -413,7 +413,7 @@ public void SO24740733_TestCustomValueHandler() var foo = connection.Query("SELECT 'Foo' AS CategoryName, 200 AS CategoryRating").Single(); Assert.Equal("Foo", foo.CategoryName); - Assert.Equal(200, foo.CategoryRating.Value); + Assert.Equal(200, foo.CategoryRating?.Value); } [Fact] @@ -433,12 +433,12 @@ private StringListTypeHandler() public static readonly StringListTypeHandler Default = new StringListTypeHandler(); //Just a simple List type handler implementation - public override void SetValue(IDbDataParameter parameter, List value) + public override void SetValue(IDbDataParameter parameter, List? value) { - parameter.Value = string.Join(",", value); + parameter.Value = string.Join(",", value ?? new()); } - public override List Parse(object value) + public override List Parse(object? value) { return ((value as string) ?? "").Split(',').ToList(); } @@ -446,7 +446,7 @@ public override List Parse(object value) public class MyObjectWithStringList { - public List Names { get; set; } + public List? Names { get; set; } } [Fact] @@ -470,7 +470,7 @@ public void Issue253_TestIEnumerableTypeHandlerSetParameterValue() 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(); - Assert.Equal(foo, names); + Assert.Equal(names, foo); } finally { @@ -480,16 +480,16 @@ public void Issue253_TestIEnumerableTypeHandlerSetParameterValue() public class RecordingTypeHandler : SqlMapper.TypeHandler { - public override void SetValue(IDbDataParameter parameter, T value) + public override void SetValue(IDbDataParameter parameter, T? value) { SetValueWasCalled = true; parameter.Value = value; } - public override T Parse(object value) + public override T Parse(object? value) { ParseWasCalled = true; - return (T)value; + return (T)value!; } public bool SetValueWasCalled { get; set; } @@ -608,7 +608,9 @@ public void SO24607639_NullableBools() insert @vals (A,B,C) values (1,0,null); select * from @vals").Single(); Assert.NotNull(obj); + Assert.True(obj.A.HasValue); Assert.True(obj.A.Value); + Assert.True(obj.B.HasValue); Assert.False(obj.B.Value); Assert.Null(obj.C); } @@ -644,7 +646,7 @@ public void Issue149_TypeMismatch_SequentialAccess() Assert.Equal("Error parsing column 0 (Id=cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e - Object)", ex.Message); } - public class Issue149_Person { public string Id { get; set; } } + public class Issue149_Person { public string? Id { get; set; } } [Fact] public void Issue295_NullableDateTime_SqlServer() => Common.TestDateTime(connection); @@ -679,13 +681,13 @@ SomeBlargValue nvarchar(200), var parameterlessWorks = connection.QuerySingle("SELECT * FROM #Issue461"); Assert.Equal(1, parameterlessWorks.Id); Assert.Equal("what up?", parameterlessWorks.SomeValue); - Assert.Equal(parameterlessWorks.SomeBlargValue.Value, Expected); + Assert.Equal(Expected, parameterlessWorks.SomeBlargValue?.Value); // test: via constructor var parameterDoesNot = connection.QuerySingle("SELECT * FROM #Issue461"); Assert.Equal(1, parameterDoesNot.Id); Assert.Equal("what up?", parameterDoesNot.SomeValue); - Assert.Equal(parameterDoesNot.SomeBlargValue.Value, Expected); + Assert.Equal(Expected, parameterDoesNot.SomeBlargValue?.Value); } // I would usually expect this to be a struct; using a class @@ -693,24 +695,24 @@ SomeBlargValue nvarchar(200), // to see an InvalidCastException if it is wrong private class Blarg { - public Blarg(string value) { Value = value; } - public string Value { get; } + public Blarg(string? value) { Value = value; } + public string? Value { get; } public override string ToString() { - return Value; + return Value!; } } private class Issue461_BlargHandler : SqlMapper.TypeHandler { - public override void SetValue(IDbDataParameter parameter, Blarg value) + public override void SetValue(IDbDataParameter parameter, Blarg? value) { - parameter.Value = ((object)value.Value) ?? DBNull.Value; + parameter.Value = ((object?)value?.Value) ?? DBNull.Value; } - public override Blarg Parse(object value) + public override Blarg? Parse(object? value) { - string s = (value == null || value is DBNull) ? null : Convert.ToString(value); + string? s = (value == null || value is DBNull) ? null : Convert.ToString(value); return new Blarg(s); } } @@ -719,8 +721,8 @@ private class Issue461_ParameterlessTypeConstructor { public int Id { get; set; } - public string SomeValue { get; set; } - public Blarg SomeBlargValue { get; set; } + public string? SomeValue { get; set; } + public Blarg? SomeBlargValue { get; set; } } private class Issue461_ParameterisedTypeConstructor diff --git a/tests/Dapper.Tests/XmlTests.cs b/tests/Dapper.Tests/XmlTests.cs index e0b3cf289..ee130c4d5 100644 --- a/tests/Dapper.Tests/XmlTests.cs +++ b/tests/Dapper.Tests/XmlTests.cs @@ -25,16 +25,16 @@ public void CommonXmlTypesSupported() C = XElement.Parse("") }; var bar = connection.QuerySingle("select @a as [A], @b as [B], @c as [C]", new { a = foo.A, b = foo.B, c = foo.C }); - Assert.Equal("abc", bar.A.DocumentElement.Name); - Assert.Equal("def", bar.B.Root.Name.LocalName); - Assert.Equal("ghi", bar.C.Name.LocalName); + Assert.Equal("abc", bar.A?.DocumentElement?.Name); + Assert.Equal("def", bar.B?.Root?.Name.LocalName); + Assert.Equal("ghi", bar.C?.Name.LocalName); } public class Foo { - public XmlDocument A { get; set; } - public XDocument B { get; set; } - public XElement C { get; set; } + public XmlDocument? A { get; set; } + public XDocument? B { get; set; } + public XElement? C { get; set; } } } } diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props index 4a940120a..ca3ad8b24 100644 --- a/tests/Directory.Build.props +++ b/tests/Directory.Build.props @@ -14,10 +14,10 @@ - - - - + + + + diff --git a/version.json b/version.json index 9ceaa3b42..5220edbe8 100644 --- a/version.json +++ b/version.json @@ -1,5 +1,5 @@ { - "version": "2.0", + "version": "2.1", "assemblyVersion": "2.0.0.0", "publicReleaseRefSpec": [ "^refs/heads/main$", From a2d539ccd5fcf1744f0eb6fc8fc2fcf6bb9731ec Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 12 Sep 2023 16:12:56 +0100 Subject: [PATCH 235/312] release notes 2.1.1 --- docs/index.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/index.md b/docs/index.md index b5481de39..40f5b0d0a 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,11 +22,15 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased -- add NRT annotations -- extend `GridReader` API to allow it to be subclassed by external consumers - (note: new PRs will not be merged until they add release note wording here) +### 2.1.1 + +- add NRT annotations (#1928 via @mgravell) +- extend `GridReader` API to allow it to be subclassed by external consumers (#1928 via @mgravell) +- support `$` as a parameter prefix (#1952 via @Giorgi) +- add public API tracking (#1948 via @mgravell) + ### 2.0.151 - add global `FetchSize` setting for use with Oracle (#1946 via mgravell, fixes #1945) (also add some missing logic in `Settings.Reset()`) From 61ff85a2b86acb046231ccfcafbcf1276814220b Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 13 Sep 2023 14:58:12 +0100 Subject: [PATCH 236/312] add missing ReadUnbufferedAsync untyped API (#1958) * - add missing ReadUnbufferedAsync untyped API - remove an impossible branch * release notes --- Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt | 1 + Dapper/SqlMapper.GridReader.Async.cs | 7 +++++-- docs/index.md | 2 ++ tests/Dapper.Tests/AsyncTests.cs | 20 +++++++++++++++++++ 4 files changed, 28 insertions(+), 2 deletions(-) diff --git a/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt index 7e50a2832..5da46e604 100644 --- a/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt @@ -1,5 +1,6 @@ #nullable enable Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! \ No newline at end of file diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index 770a04c12..5b5b9b73d 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -1,7 +1,6 @@ using System; using System.Collections.Generic; using System.Data.Common; -using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; @@ -180,7 +179,6 @@ private Task> ReadAsyncImpl(Type type, bool buffered) else { var result = ReadDeferred(index, deserializer, type); - if (buffered) result = result.ToList(); // for the "not a DbDataReader" scenario return Task.FromResult(result); } } @@ -256,6 +254,11 @@ private async Task> ReadBufferedAsync(int index, FuncThe type to read. public IAsyncEnumerable ReadUnbufferedAsync() => ReadAsyncUnbufferedImpl(typeof(T)); + /// + /// Read the next grid of results. + /// + public IAsyncEnumerable ReadUnbufferedAsync() => ReadAsyncUnbufferedImpl(typeof(DapperRow)); + private IAsyncEnumerable ReadAsyncUnbufferedImpl(Type type) { var deserializer = ValidateAndMarkConsumed(type, out var index); diff --git a/docs/index.md b/docs/index.md index 40f5b0d0a..e2273bb74 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,6 +24,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) +- add untyped `GridReader.ReadUnbufferedAsync` API (#1958 via @mgravell) + ### 2.1.1 - add NRT annotations (#1928 via @mgravell) diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index f3a1597a2..7a9abf2d1 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -112,6 +112,26 @@ public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync() Assert.Equal(new[] { "abc", "def", "ghi" }, arr); } + [Fact] + public async Task TestBasicStringUsageViaGridReaderUnbufferedDynamicAsync() + { + var results = new List(); + await using (var grid = await connection.QueryMultipleAsync("select 'abc' as [Foo] union select 'def'; select @txt as [Foo]", new { txt = "ghi" }) + .ConfigureAwait(false)) + { + while (!grid.IsConsumed) + { + await foreach (var value in grid.ReadUnbufferedAsync() + .ConfigureAwait(false)) + { + results.Add((string)value.Foo); + } + } + } + var arr = results.ToArray(); + Assert.Equal(new[] { "abc", "def", "ghi" }, arr); + } + [Fact] public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync_Cancellation() { From a4a55f5b798ea028692432a86edbbe074adbcd06 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 13 Sep 2023 21:43:13 +0100 Subject: [PATCH 237/312] assert that type handlers don't need object? (#1960) * assert that type handlers don't need object? - include test to validate (plus: NRT check was happy) fix #1959 * release notes --- Dapper/PublicAPI.Shipped.txt | 8 +- Dapper/SqlDataRecordHandler.cs | 4 +- Dapper/SqlMapper.ITypeHandler.cs | 4 +- Dapper/SqlMapper.TypeHandler.cs | 8 +- Dapper/UdtTypeHandler.cs | 4 +- docs/index.md | 1 + tests/Dapper.Tests/DecimalTests.cs | 2 +- tests/Dapper.Tests/TypeHandlerTests.cs | 129 ++++++++++++++++++++++++- 8 files changed, 143 insertions(+), 17 deletions(-) diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt index f2438ca4d..36cdc1c78 100644 --- a/Dapper/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI.Shipped.txt @@ -1,7 +1,7 @@ #nullable enable abstract Dapper.SqlMapper.StringTypeHandler.Format(T xml) -> string! abstract Dapper.SqlMapper.StringTypeHandler.Parse(string! xml) -> T -abstract Dapper.SqlMapper.TypeHandler.Parse(object? value) -> T? +abstract Dapper.SqlMapper.TypeHandler.Parse(object! value) -> T? abstract Dapper.SqlMapper.TypeHandler.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void const Dapper.DbString.DefaultLength = 4000 -> int Dapper.CommandDefinition @@ -130,8 +130,8 @@ Dapper.SqlMapper.IParameterCallbacks.OnCompleted() -> void Dapper.SqlMapper.IParameterLookup Dapper.SqlMapper.IParameterLookup.this[string! name].get -> object? Dapper.SqlMapper.ITypeHandler -Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object? value) -> object? -Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object? value) -> void +Dapper.SqlMapper.ITypeHandler.Parse(System.Type! destinationType, object! value) -> object? +Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void Dapper.SqlMapper.ITypeMap Dapper.SqlMapper.ITypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? @@ -149,7 +149,7 @@ override Dapper.DbString.ToString() -> string! override Dapper.SqlMapper.Identity.Equals(object? obj) -> bool override Dapper.SqlMapper.Identity.GetHashCode() -> int override Dapper.SqlMapper.Identity.ToString() -> string! -override Dapper.SqlMapper.StringTypeHandler.Parse(object? value) -> T +override Dapper.SqlMapper.StringTypeHandler.Parse(object! value) -> T override Dapper.SqlMapper.StringTypeHandler.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType? readonly Dapper.SqlMapper.Identity.connectionString -> string! diff --git a/Dapper/SqlDataRecordHandler.cs b/Dapper/SqlDataRecordHandler.cs index ed113dc02..2d9976338 100644 --- a/Dapper/SqlDataRecordHandler.cs +++ b/Dapper/SqlDataRecordHandler.cs @@ -7,12 +7,12 @@ namespace Dapper internal sealed class SqlDataRecordHandler : SqlMapper.ITypeHandler where T : IDataRecord { - public object Parse(Type destinationType, object? value) + public object Parse(Type destinationType, object value) { throw new NotSupportedException(); } - public void SetValue(IDbDataParameter parameter, object? value) + public void SetValue(IDbDataParameter parameter, object value) { SqlDataRecordListTVPParameter.Set(parameter, value as IEnumerable, null); } diff --git a/Dapper/SqlMapper.ITypeHandler.cs b/Dapper/SqlMapper.ITypeHandler.cs index 5cd4a4306..57a4d2153 100644 --- a/Dapper/SqlMapper.ITypeHandler.cs +++ b/Dapper/SqlMapper.ITypeHandler.cs @@ -15,7 +15,7 @@ public interface ITypeHandler /// /// The parameter to configure /// Parameter value - void SetValue(IDbDataParameter parameter, object? value); + void SetValue(IDbDataParameter parameter, object value); /// /// Parse a database value back to a typed value @@ -23,7 +23,7 @@ public interface ITypeHandler /// The value from the database /// The type to parse to /// The typed value - object? Parse(Type destinationType, object? value); + object? Parse(Type destinationType, object value); } } } diff --git a/Dapper/SqlMapper.TypeHandler.cs b/Dapper/SqlMapper.TypeHandler.cs index 4e12bb812..c36a60b4b 100644 --- a/Dapper/SqlMapper.TypeHandler.cs +++ b/Dapper/SqlMapper.TypeHandler.cs @@ -23,9 +23,9 @@ public abstract class TypeHandler : ITypeHandler /// /// The value from the database /// The typed value - public abstract T? Parse(object? value); + public abstract T? Parse(object value); - void ITypeHandler.SetValue(IDbDataParameter parameter, object? value) + void ITypeHandler.SetValue(IDbDataParameter parameter, object value) { if (value is DBNull) { @@ -37,7 +37,7 @@ void ITypeHandler.SetValue(IDbDataParameter parameter, object? value) } } - object? ITypeHandler.Parse(Type destinationType, object? value) + object? ITypeHandler.Parse(Type destinationType, object value) { return Parse(value); } @@ -76,7 +76,7 @@ public override void SetValue(IDbDataParameter parameter, T? value) /// /// The value from the database /// The typed value - public override T Parse(object? value) + public override T Parse(object value) { if (value is null || value is DBNull) return default!; return Parse((string)value); diff --git a/Dapper/UdtTypeHandler.cs b/Dapper/UdtTypeHandler.cs index 9bb80b61d..503485927 100644 --- a/Dapper/UdtTypeHandler.cs +++ b/Dapper/UdtTypeHandler.cs @@ -22,12 +22,12 @@ public UdtTypeHandler(string udtTypeName) this.udtTypeName = udtTypeName; } - object? ITypeHandler.Parse(Type destinationType, object? value) + object? ITypeHandler.Parse(Type destinationType, object value) { return value is DBNull ? null : value; } - void ITypeHandler.SetValue(IDbDataParameter parameter, object? value) + void ITypeHandler.SetValue(IDbDataParameter parameter, object value) { #pragma warning disable 0618 parameter.Value = SanitizeParameterValue(value); diff --git a/docs/index.md b/docs/index.md index e2273bb74..15d68faff 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,6 +25,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) - add untyped `GridReader.ReadUnbufferedAsync` API (#1958 via @mgravell) +- tweak NRT annotations on type-handler API (#1960 via @mgravell, fixes #1959) ### 2.1.1 diff --git a/tests/Dapper.Tests/DecimalTests.cs b/tests/Dapper.Tests/DecimalTests.cs index 9e19f4ec0..fc0b0fce5 100644 --- a/tests/Dapper.Tests/DecimalTests.cs +++ b/tests/Dapper.Tests/DecimalTests.cs @@ -20,7 +20,7 @@ public void Issue261_Decimals() parameters.Add("c", dbType: DbType.Decimal, direction: ParameterDirection.Output, precision: 10, scale: 5); connection.Execute("create proc #Issue261 @c decimal(10,5) OUTPUT as begin set @c=11.884 end"); connection.Execute("#Issue261", parameters, commandType: CommandType.StoredProcedure); - var c = parameters.Get("c"); + var c = parameters.Get("c"); Assert.Equal(11.884M, c); } diff --git a/tests/Dapper.Tests/TypeHandlerTests.cs b/tests/Dapper.Tests/TypeHandlerTests.cs index 759cf6c48..2e754ca46 100644 --- a/tests/Dapper.Tests/TypeHandlerTests.cs +++ b/tests/Dapper.Tests/TypeHandlerTests.cs @@ -374,7 +374,7 @@ private RatingValueHandler() { } - public static readonly RatingValueHandler Default = new RatingValueHandler(); + public static readonly RatingValueHandler Default = new(); public override RatingValue Parse(object? value) { @@ -431,7 +431,7 @@ private StringListTypeHandler() { } - public static readonly StringListTypeHandler Default = new StringListTypeHandler(); + public static readonly StringListTypeHandler Default = new(); //Just a simple List type handler implementation public override void SetValue(IDbDataParameter parameter, List? value) { @@ -739,5 +739,130 @@ public Issue461_ParameterisedTypeConstructor(int id, string someValue, Blarg som public string SomeValue { get; } public Blarg SomeBlargValue { get; } } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Issue1959_TypeHandlerNullability_Subclass(bool isNull) + { + Issue1959_Subclass_Handler.Register(); + Issue1959_Subclass? when = isNull ? null : new(DateTime.Today); + var whenNotNull = when ?? new(new DateTime(1753, 1, 1)); + + var args = new HazIssue1959_Subclass { Id = 42, Nullable = when, NonNullable = whenNotNull }; + var row = connection.QuerySingle( + "select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]", + args); + + Assert.NotNull(row); + Assert.Equal(42, row.Id); + Assert.Equal(when, row.Nullable); + Assert.Equal(whenNotNull, row.NonNullable); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void Issue1959_TypeHandlerNullability_Raw(bool isNull) + { + Issue1959_Raw_Handler.Register(); + Issue1959_Raw? when = isNull ? null : new(DateTime.Today); + var whenNotNull = when ?? new(new DateTime(1753, 1, 1)); + + var args = new HazIssue1959_Raw { Id = 42, Nullable = when, NonNullable = whenNotNull }; + var row = connection.QuerySingle( + "select @Id as [Id], @NonNullable as [NonNullable], @Nullable as [Nullable]", + args); + + Assert.NotNull(row); + Assert.Equal(42, row.Id); + Assert.Equal(when, row.Nullable); + Assert.Equal(whenNotNull, row.NonNullable); + } + + public class HazIssue1959_Subclass + { + public int Id { get; set; } + public Issue1959_Subclass NonNullable { get; set; } + public Issue1959_Subclass? Nullable { get; set; } + } + + public class HazIssue1959_Raw + { + public int Id { get; set; } + public Issue1959_Raw NonNullable { get; set; } + public Issue1959_Raw? Nullable { get; set; } + } + + public class Issue1959_Subclass_Handler : SqlMapper.TypeHandler + { + public static void Register() => SqlMapper.AddTypeHandler(Instance); + private Issue1959_Subclass_Handler() { } + private static readonly Issue1959_Subclass_Handler Instance = new(); + + public override Issue1959_Subclass Parse(object value) + { + Assert.NotNull(value); + Assert.IsType(value); // checking not DbNull etc + return new Issue1959_Subclass((DateTime)value); + } + public override void SetValue(IDbDataParameter parameter, TypeHandlerTests.Issue1959_Subclass value) + => parameter.Value = value.Value; + } + + public class Issue1959_Raw_Handler : SqlMapper.ITypeHandler + { + public static void Register() => SqlMapper.AddTypeHandler(typeof(Issue1959_Raw), Instance); + private Issue1959_Raw_Handler() { } + private static readonly Issue1959_Raw_Handler Instance = new(); + + void SqlMapper.ITypeHandler.SetValue(IDbDataParameter parameter, object value) + { + Assert.NotNull(value); + if (value is DBNull) + { + parameter.Value = value; + } + else + { + Assert.IsType(value); // checking not DbNull etc + parameter.Value = ((Issue1959_Raw)value).Value; + } + } + object? SqlMapper.ITypeHandler.Parse(Type destinationType, object value) + { + Assert.NotNull(value); + Assert.IsType(value); // checking not DbNull etc + return new Issue1959_Raw((DateTime)value); + } + } + +#pragma warning disable CA2231 // Overload operator equals on overriding value type Equals + public readonly struct Issue1959_Subclass : IEquatable +#pragma warning restore CA2231 // Overload operator equals on overriding value type Equals + { + public Issue1959_Subclass(DateTime value) => Value = value; + public readonly DateTime Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object? obj) + => obj is Issue1959_Subclass other && Equals(other); + public bool Equals(Issue1959_Subclass other) + => other.Value == Value; + public override string ToString() => Value.ToString(); + } + +#pragma warning disable CA2231 // Overload operator equals on overriding value type Equals + public readonly struct Issue1959_Raw : IEquatable +#pragma warning restore CA2231 // Overload operator equals on overriding value type Equals + { + public Issue1959_Raw(DateTime value) => Value = value; + public readonly DateTime Value; + public override int GetHashCode() => Value.GetHashCode(); + public override bool Equals(object? obj) + => obj is Issue1959_Raw other && Equals(other); + public bool Equals(Issue1959_Raw other) + => other.Value == Value; + public override string ToString() => Value.ToString(); + } } } From 3c4ae7d74a18ec313cffdf14c0338d47695289c5 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 13 Sep 2023 21:52:03 +0100 Subject: [PATCH 238/312] release notes 2.1.4 --- docs/index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/index.md b/docs/index.md index 15d68faff..9ebb9efbb 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,6 +24,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) +### 2.1.4 + - add untyped `GridReader.ReadUnbufferedAsync` API (#1958 via @mgravell) - tweak NRT annotations on type-handler API (#1960 via @mgravell, fixes #1959) From 47ef1e7100b7ecdb68bcf97432b9674373a6f75c Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 14 Sep 2023 06:25:35 +0100 Subject: [PATCH 239/312] missed an object? type-handler --- Dapper/DataTableHandler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Dapper/DataTableHandler.cs b/Dapper/DataTableHandler.cs index 3e4bab2c1..df4dc07a5 100644 --- a/Dapper/DataTableHandler.cs +++ b/Dapper/DataTableHandler.cs @@ -4,12 +4,12 @@ namespace Dapper { internal sealed class DataTableHandler : SqlMapper.ITypeHandler { - public object Parse(Type destinationType, object? value) + public object Parse(Type destinationType, object value) { throw new NotImplementedException(); } - public void SetValue(IDbDataParameter parameter, object? value) + public void SetValue(IDbDataParameter parameter, object value) { TableValuedParameter.Set(parameter, value as DataTable, null); } From a37b151bb745ed0763441aa50f29c504221aa7d3 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 10 Oct 2023 15:06:21 +0100 Subject: [PATCH 240/312] auto-detect stored procedures as anything without whitespace (#1975) * 1. auto-detect stored procedures as anything without whitespace 2. we have public fields? (hangs head in shame) * release notes * no need to use regex --- Dapper/CommandDefinition.cs | 22 ++++++++-- Dapper/DynamicParameters.cs | 2 +- Dapper/PublicAPI.Shipped.txt | 5 +++ Dapper/SqlMapper.Async.cs | 16 +++---- Dapper/SqlMapper.Identity.cs | 62 +++++++++++++++++++++++++++- Dapper/SqlMapper.cs | 48 ++++++++++----------- docs/index.md | 2 + tests/Dapper.Tests/ProcedureTests.cs | 20 +++++++++ 8 files changed, 140 insertions(+), 37 deletions(-) diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index 6112353e7..1820d86f4 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -48,10 +48,15 @@ internal void OnCompleted() /// public int? CommandTimeout { get; } + internal readonly CommandType CommandTypeDirect; + /// /// The type of command that the command-text represents /// - public CommandType? CommandType { get; } +#if DEBUG // prevent use in our own code + [Obsolete("Prefer " + nameof(CommandTypeDirect), true)] +#endif + public CommandType? CommandType => CommandTypeDirect; /// /// Should data be buffered before returning? @@ -92,11 +97,21 @@ public CommandDefinition(string commandText, object? parameters = null, IDbTrans Parameters = parameters; Transaction = transaction; CommandTimeout = commandTimeout; - CommandType = commandType; + CommandTypeDirect = commandType ?? InferCommandType(commandText); Flags = flags; CancellationToken = cancellationToken; + + static CommandType InferCommandType(string sql) + { + if (sql is null || sql.IndexOfAny(WhitespaceChars) >= 0) return System.Data.CommandType.Text; + return System.Data.CommandType.StoredProcedure; + } } + // if the sql contains any whitespace character (space/tab/cr/lf): interpret as ad-hoc; but "SomeName" should be treated as a stored-proc + // (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway) + private static readonly char[] WhitespaceChars = new char[] { ' ', '\t', '\r', '\n' }; + private CommandDefinition(object? parameters) : this() { Parameters = parameters; @@ -124,8 +139,7 @@ internal IDbCommand SetupCommand(IDbConnection cnn, Action? { cmd.CommandTimeout = SqlMapper.Settings.CommandTimeout.Value; } - if (CommandType.HasValue) - cmd.CommandType = CommandType.Value; + cmd.CommandType = CommandTypeDirect; paramReader?.Invoke(cmd, Parameters); return cmd; } diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index d4759a37b..f6708b5ce 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -168,7 +168,7 @@ internal static bool ShouldSetDbType(DbType dbType) /// Information about the query protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) { - var literals = SqlMapper.GetLiteralTokens(identity.sql); + var literals = SqlMapper.GetLiteralTokens(identity.Sql); if (templates is not null) { diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt index 36cdc1c78..39a8881e4 100644 --- a/Dapper/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI.Shipped.txt @@ -152,12 +152,17 @@ override Dapper.SqlMapper.Identity.ToString() -> string! override Dapper.SqlMapper.StringTypeHandler.Parse(object! value) -> T override Dapper.SqlMapper.StringTypeHandler.SetValue(System.Data.IDbDataParameter! parameter, T? value) -> void readonly Dapper.SqlMapper.Identity.commandType -> System.Data.CommandType? +Dapper.SqlMapper.Identity.CommandType.get -> System.Data.CommandType? readonly Dapper.SqlMapper.Identity.connectionString -> string! readonly Dapper.SqlMapper.Identity.gridIndex -> int +Dapper.SqlMapper.Identity.GridIndex.get -> int readonly Dapper.SqlMapper.Identity.hashCode -> int readonly Dapper.SqlMapper.Identity.parametersType -> System.Type? +Dapper.SqlMapper.Identity.ParametersType.get -> System.Type? readonly Dapper.SqlMapper.Identity.sql -> string! +Dapper.SqlMapper.Identity.Sql.get -> string! readonly Dapper.SqlMapper.Identity.type -> System.Type? +Dapper.SqlMapper.Identity.Type.get -> System.Type? static Dapper.DbString.IsAnsiDefault.get -> bool static Dapper.DbString.IsAnsiDefault.set -> void static Dapper.DefaultTypeMap.MatchNamesWithUnderscores.get -> bool diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 07f073ee5..6fb8ca4c2 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -422,7 +422,7 @@ private static DbCommand TrySetupAsyncCommand(this CommandDefinition command, ID private static async Task> QueryAsync(this IDbConnection cnn, Type effectiveType, CommandDefinition command) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; @@ -477,7 +477,7 @@ private static async Task> QueryAsync(this IDbConnection cnn, private static async Task QueryRowAsync(this IDbConnection cnn, Row row, Type effectiveType, CommandDefinition command) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; var cancel = command.CancellationToken; @@ -652,7 +652,7 @@ private static async Task ExecuteMultiImplAsync(IDbConnection cnn, CommandD private static async Task ExecuteImplAsync(IDbConnection cnn, CommandDefinition command, object? param) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); @@ -930,7 +930,7 @@ public static Task> QueryAsync> MultiMapAsync(this IDbConnection cnn, CommandDefinition command, Delegate map, string splitOn) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(TFirst), param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, typeof(TFirst), param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -979,7 +979,7 @@ private static async Task> MultiMapAsync(this IDbC } object? param = command.Parameters; - var identity = new IdentityWithTypes(command.CommandText, command.CommandType, cnn, types[0], param?.GetType(), types); + var identity = new IdentityWithTypes(command.CommandText, command.CommandTypeDirect, cnn, types[0], param?.GetType(), types); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; try @@ -1029,7 +1029,7 @@ public static Task QueryMultipleAsync(this IDbConnection cnn, string public static async Task QueryMultipleAsync(this IDbConnection cnn, CommandDefinition command) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); DbCommand? cmd = null; @@ -1227,7 +1227,7 @@ private static async Task ExecuteWrappedReaderImplAsync(IDbConnect object? param = command.Parameters; if (param is not null) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } @@ -1296,7 +1296,7 @@ static async IAsyncEnumerable Impl(IDbConnection cnn, Type effectiveType, Com [EnumeratorCancellation] CancellationToken cancel) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); bool wasClosed = cnn.State == ConnectionState.Closed; using var cmd = command.TrySetupAsyncCommand(cnn, info.ParamReader); diff --git a/Dapper/SqlMapper.Identity.cs b/Dapper/SqlMapper.Identity.cs index cfaab8e5f..4871d9c05 100644 --- a/Dapper/SqlMapper.Identity.cs +++ b/Dapper/SqlMapper.Identity.cs @@ -1,4 +1,5 @@ using System; +using System.ComponentModel; using System.Data; using System.Runtime.CompilerServices; @@ -92,6 +93,7 @@ public class Identity : IEquatable internal virtual Type GetType(int index) => throw new IndexOutOfRangeException(nameof(index)); +#pragma warning disable CS0618 // Type or member is obsolete internal Identity ForGrid(Type primaryType, int gridIndex) => new Identity(sql, commandType, connectionString, primaryType, parametersType, gridIndex); @@ -110,12 +112,14 @@ internal Identity ForGrid(Type primaryType, Type[] otherTypes, int gridIndex) => /// public Identity ForDynamicParameters(Type type) => new Identity(sql, commandType, connectionString, this.type, type, 0, -1); +#pragma warning restore CS0618 // Type or member is obsolete internal Identity(string sql, CommandType? commandType, IDbConnection connection, Type? type, Type? parametersType) : this(sql, commandType, connection.ConnectionString, type, parametersType, 0, 0) { /* base call */ } private protected Identity(string sql, CommandType? commandType, string connectionString, Type? type, Type? parametersType, int otherTypesHash, int gridIndex) { +#pragma warning disable CS0618 // Type or member is obsolete this.sql = sql; this.commandType = commandType; this.connectionString = connectionString; @@ -133,6 +137,7 @@ private protected Identity(string sql, CommandType? commandType, string connecti hashCode = (hashCode * 23) + (connectionString is null ? 0 : connectionStringComparer.GetHashCode(connectionString)); hashCode = (hashCode * 23) + (parametersType?.GetHashCode() ?? 0); } +#pragma warning restore CS0618 // Type or member is obsolete } /// @@ -144,48 +149,101 @@ private protected Identity(string sql, CommandType? commandType, string connecti /// /// The raw SQL command. /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Please use " + nameof(Sql) + ". This API may be removed at a later date.")] public readonly string sql; + /// + /// The raw SQL command. + /// +#pragma warning disable CS0618 // Type or member is obsolete + public string Sql => sql; +#pragma warning restore CS0618 // Type or member is obsolete + /// /// The SQL command type. /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Please use " + nameof(CommandType) + ". This API may be removed at a later date.")] public readonly CommandType? commandType; + /// + /// The SQL command type. + /// +#pragma warning disable CS0618 // Type or member is obsolete + public CommandType? CommandType => commandType; +#pragma warning restore CS0618 // Type or member is obsolete + /// /// The hash code of this Identity. /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Please use " + nameof(GetHashCode) + ". This API may be removed at a later date.")] public readonly int hashCode; /// /// The grid index (position in the reader) of this Identity. /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Please use " + nameof(GridIndex) + ". This API may be removed at a later date.")] public readonly int gridIndex; /// - /// This of this Identity. + /// The grid index (position in the reader) of this Identity. /// +#pragma warning disable CS0618 // Type or member is obsolete + public int GridIndex => gridIndex; +#pragma warning restore CS0618 // Type or member is obsolete + + /// + /// The of this Identity. + /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Please use " + nameof(Type) + ". This API may be removed at a later date.")] public readonly Type? type; + /// + /// The of this Identity. + /// +#pragma warning disable CS0618 // Type or member is obsolete + public Type? Type => type; +#pragma warning restore CS0618 // Type or member is obsolete + /// /// The connection string for this Identity. /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("This API may be removed at a later date.")] public readonly string connectionString; /// /// The type of the parameters object for this Identity. /// + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + [Obsolete("Please use " + nameof(ParametersType) + ". This API may be removed at a later date.")] public readonly Type? parametersType; + /// + /// The type of the parameters object for this Identity. + /// +#pragma warning disable CS0618 // Type or member is obsolete + public Type? ParametersType => parametersType; +#pragma warning restore CS0618 // Type or member is obsolete + /// /// Gets the hash code for this identity. /// /// +#pragma warning disable CS0618 // Type or member is obsolete public override int GetHashCode() => hashCode; +#pragma warning restore CS0618 // Type or member is obsolete /// /// See object.ToString() /// +#pragma warning disable CS0618 // Type or member is obsolete public override string ToString() => sql; +#pragma warning restore CS0618 // Type or member is obsolete /// /// Compare 2 Identity objects @@ -198,6 +256,7 @@ public bool Equals(Identity? other) if (other is null) return false; int typeCount; +#pragma warning disable CS0618 // Type or member is obsolete return gridIndex == other.gridIndex && type == other.type && sql == other.sql @@ -206,6 +265,7 @@ public bool Equals(Identity? other) && parametersType == other.parametersType && (typeCount = TypeCount) == other.TypeCount && (typeCount == 0 || TypesEqual(this, other, typeCount)); +#pragma warning restore CS0618 // Type or member is obsolete } [MethodImpl(MethodImplOptions.NoInlining)] diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 5f5974f75..b9f8b09d8 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -115,7 +115,7 @@ private static void PurgeQueryCacheByType(Type type) { foreach (var entry in _queryCache) { - if (entry.Key.type == type) + if (entry.Key.Type == type) _queryCache.TryRemove(entry.Key, out _); } TypeDeserializerCache.Purge(type); @@ -137,7 +137,9 @@ public static int GetCachedSQLCount() /// public static IEnumerable> GetCachedSQL(int ignoreHitCountAbove = int.MaxValue) { - var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.sql, pair.Value.GetHitCount())); +#pragma warning disable CS0618 // Type or member is obsolete + var data = _queryCache.Select(pair => Tuple.Create(pair.Key.connectionString, pair.Key.Sql, pair.Value.GetHitCount())); +#pragma warning restore CS0618 // Type or member is obsolete return (ignoreHitCountAbove < int.MaxValue) ? data.Where(tuple => tuple.Item3 <= ignoreHitCountAbove) : data; @@ -146,19 +148,19 @@ public static IEnumerable> GetCachedSQL(int ignoreHit /// /// Deep diagnostics only: find any hash collisions in the cache /// - /// - public static IEnumerable> GetHashCollissions() + public static IEnumerable> GetHashCollissions() // legacy incorrect spelling, oops { var counts = new Dictionary(); foreach (var key in _queryCache.Keys) { - if (!counts.TryGetValue(key.hashCode, out int count)) + var hash = key.GetHashCode(); + if (!counts.TryGetValue(hash, out int count)) { - counts.Add(key.hashCode, 1); + counts.Add(hash, 1); } else { - counts[key.hashCode] = count + 1; + counts[hash] = count + 1; } } return from pair in counts @@ -648,7 +650,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com // nice and simple if (param is not null) { - identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); + identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); } return ExecuteCommand(cnn, ref command, param is null ? null : info!.ParamReader); @@ -1108,7 +1110,7 @@ public static GridReader QueryMultiple(this IDbConnection cnn, CommandDefinition private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandDefinition command) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, typeof(GridReader), param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, typeof(GridReader), param?.GetType()); CacheInfo info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? cmd = null; @@ -1167,7 +1169,7 @@ private static DbDataReader ExecuteReaderWithFlagsFallback(IDbCommand cmd, bool private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefinition command, Type effectiveType) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? cmd = null; @@ -1260,7 +1262,7 @@ private static void ThrowZeroRows(Row row) private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefinition command, Type effectiveType) { object? param = command.Parameters; - var identity = new Identity(command.CommandText, command.CommandType, cnn, effectiveType, param?.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, effectiveType, param?.GetType()); var info = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? cmd = null; @@ -1549,7 +1551,7 @@ private static IEnumerable MultiMap MultiMapImpl(this IDbConnection? cnn, CommandDefinition command, Delegate map, string splitOn, DbDataReader? reader, Identity? identity, bool finalize) { object? param = command.Parameters; - identity ??= new Identity(command.CommandText, command.CommandType, cnn!, typeof(TFirst), param?.GetType()); + identity ??= new Identity(command.CommandText, command.CommandTypeDirect, cnn!, typeof(TFirst), param?.GetType()); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? ownedCommand = null; @@ -1620,7 +1622,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection? cn } object? param = command.Parameters; - identity ??= new IdentityWithTypes(command.CommandText, command.CommandType, cnn!, types[0], param?.GetType(), types); + identity ??= new IdentityWithTypes(command.CommandText, command.CommandTypeDirect, cnn!, types[0], param?.GetType(), types); CacheInfo cinfo = GetCacheInfo(identity, param, command.AddToCache); IDbCommand? ownedCommand = null; @@ -1825,7 +1827,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object? exampleParamete throw new InvalidOperationException("An enumerable sequence of parameters (arrays, lists, etc) is not allowed in this context"); } info = new CacheInfo(); - if (identity.parametersType is not null) + if (identity.ParametersType is not null) { Action reader; if (exampleParameters is IDynamicParameters) @@ -1842,10 +1844,10 @@ private static CacheInfo GetCacheInfo(Identity identity, object? exampleParamete } else { - var literals = GetLiteralTokens(identity.sql); + var literals = GetLiteralTokens(identity.Sql); reader = CreateParamInfoGenerator(identity, false, true, literals); } - if ((identity.commandType is null || identity.commandType == CommandType.Text) && ShouldPassByPosition(identity.sql)) + if ((identity.CommandType is null || identity.CommandType == CommandType.Text) && ShouldPassByPosition(identity.Sql)) { var tail = reader; reader = (cmd, obj) => @@ -2517,7 +2519,7 @@ internal static IList GetLiteralTokens(string sql) /// Whether to check for duplicates. /// Whether to remove unused parameters. public static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused) => - CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.sql)); + CreateParamInfoGenerator(identity, checkForDuplicates, removeUnused, GetLiteralTokens(identity.Sql)); private static bool IsValueTuple(Type? type) => (type?.IsValueType == true && type.FullName?.StartsWith("System.ValueTuple`", StringComparison.Ordinal) == true) @@ -2525,7 +2527,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true internal static Action CreateParamInfoGenerator(Identity identity, bool checkForDuplicates, bool removeUnused, IList literals) { - Type type = identity.parametersType!; + Type type = identity.ParametersType!; if (IsValueTuple(type)) { @@ -2533,9 +2535,9 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true } bool filterParams = false; - if (removeUnused && identity.commandType.GetValueOrDefault(CommandType.Text) == CommandType.Text) + if (removeUnused && identity.CommandType.GetValueOrDefault(CommandType.Text) == CommandType.Text) { - filterParams = !smellsLikeOleDb.IsMatch(identity.sql); + filterParams = !smellsLikeOleDb.IsMatch(identity.Sql); } var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); @@ -2628,7 +2630,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true } if (filterParams) { - props = FilterParameters(props, identity.sql); + props = FilterParameters(props, identity.Sql); } var callOpCode = isStruct ? OpCodes.Call : OpCodes.Callvirt; @@ -2971,7 +2973,7 @@ private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition comma object? param = command.Parameters; if (param is not null) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); paramReader = GetCacheInfo(identity, command.Parameters, command.AddToCache).ParamReader; } @@ -3033,7 +3035,7 @@ private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefi // nice and simple if (param is not null) { - var identity = new Identity(command.CommandText, command.CommandType, cnn, null, param.GetType()); + var identity = new Identity(command.CommandText, command.CommandTypeDirect, cnn, null, param.GetType()); info = GetCacheInfo(identity, param, command.AddToCache); } var paramReader = info?.ParamReader; diff --git a/docs/index.md b/docs/index.md index 9ebb9efbb..25a22caf7 100644 --- a/docs/index.md +++ b/docs/index.md @@ -24,6 +24,8 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) +- infer command text without any whitespace as stored-procedure (#1975 via @mgravell) + ### 2.1.4 - add untyped `GridReader.ReadUnbufferedAsync` API (#1958 via @mgravell) diff --git a/tests/Dapper.Tests/ProcedureTests.cs b/tests/Dapper.Tests/ProcedureTests.cs index 1bd563045..9d76f44b9 100644 --- a/tests/Dapper.Tests/ProcedureTests.cs +++ b/tests/Dapper.Tests/ProcedureTests.cs @@ -92,6 +92,26 @@ @ErrorDescription varchar(255) OUTPUT Assert.Equal("Completed successfully", p.Get("ErrorDescription")); } + [Theory] + [InlineData(CommandType.StoredProcedure)] + [InlineData(null)] // auto + public void InferProcedure(CommandType? commandType) + { + connection.Execute("CREATE PROCEDURE #InferProcedure @id int AS BEGIN SELECT -@id END"); + var result = connection.QuerySingle("#InferProcedure", new { id = 42 }, commandType: commandType); + Assert.Equal(-42, result); + } + + [Theory] + [InlineData(CommandType.Text)] + [InlineData(null)] // auto + public void InferNotProcedure(CommandType? commandType) + { + connection.Execute("CREATE PROCEDURE #InferNotProcedure @id int AS BEGIN SELECT -@id END"); + var result = connection.QuerySingle("EXEC #InferNotProcedure @id", new { id = 42 }, commandType: commandType); + Assert.Equal(-42, result); + } + [Fact] public void SO24605346_ProcsAndStrings() { From 2837480903bb8b951de4a4eff4a0c2925d42b2cb Mon Sep 17 00:00:00 2001 From: Giorgi Dalakishvili Date: Wed, 11 Oct 2023 15:41:06 +0400 Subject: [PATCH 241/312] Add DuckDB tests (#1970) * Add DuckDB tests * Update DuckDB.NET.Data.Full version --- Directory.Packages.props | 1 + tests/Dapper.Tests/Dapper.Tests.csproj | 1 + tests/Dapper.Tests/Providers/DuckDBTests.cs | 67 +++++++++++++++++++++ 3 files changed, 69 insertions(+) create mode 100644 tests/Dapper.Tests/Providers/DuckDBTests.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index e8da7cb83..06e68e627 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,6 +1,7 @@ + diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index f3007f5cc..f1e808301 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -15,6 +15,7 @@ + diff --git a/tests/Dapper.Tests/Providers/DuckDBTests.cs b/tests/Dapper.Tests/Providers/DuckDBTests.cs new file mode 100644 index 000000000..d06d25d6b --- /dev/null +++ b/tests/Dapper.Tests/Providers/DuckDBTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Data.Common; +using DuckDB.NET.Data; +using Xunit; + +namespace Dapper.Tests +{ + public class DuckDBProvider : DatabaseProvider + { + public override DbProviderFactory Factory => DuckDBClientFactory.Instance; + public override string GetConnectionString() => "Data Source=:memory:"; + } + + public abstract class DuckDBTypeTestBase : TestBase + { + protected DuckDBConnection GetDuckDBConnection(bool open = true) + => (DuckDBConnection)(open ? Provider.GetOpenConnection() : Provider.GetClosedConnection()); + + [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] + public class FactDuckDBAttribute : FactAttribute + { + public override string? Skip + { + get { return unavailable ?? base.Skip; } + set { base.Skip = value; } + } + + private static readonly string? unavailable; + + static FactDuckDBAttribute() + { + try + { + using var _ = DatabaseProvider.Instance.GetOpenConnection(); + } + catch (Exception ex) + { + unavailable = $"DuckDB is unavailable: {ex.Message}"; + } + } + } + } + + public class DuckDBTests : DuckDBTypeTestBase + { + [FactDuckDB] + public void DuckDBNamedParameter() + { + using var connection = GetDuckDBConnection(); + + var result = connection.QueryFirst("Select $foo", new {foo = 42}); + Assert.Equal(42, result); + } + + [FactDuckDB] + public void DuckDBPositionalParameter() + { + using var connection = GetDuckDBConnection(); + + var dp = new DynamicParameters(); + dp.Add("?", 42); + + var result = connection.QueryFirst("Select ?", dp); + Assert.Equal(42, result); + } + } +} From 19193b52bb105ee2935826bd7bf26478b8ac2993 Mon Sep 17 00:00:00 2001 From: Giorgi Dalakishvili Date: Wed, 11 Oct 2023 16:10:00 +0400 Subject: [PATCH 242/312] Add a setting to turn off Ole Db "smell check" (#1974) * Add a setting to turn off Ole Db "smell check" * Rename setting, fix logic * Test for SupportLegacyParameterTokens * Fix exception type * Add release note --- Dapper/PublicAPI.Shipped.txt | 2 ++ Dapper/SqlMapper.Settings.cs | 7 ++++++ Dapper/SqlMapper.cs | 6 +++-- docs/index.md | 1 + tests/Dapper.Tests/ParameterTests.cs | 35 ++++++++++++++++++++++++++++ 5 files changed, 49 insertions(+), 2 deletions(-) diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt index 39a8881e4..adaa7a517 100644 --- a/Dapper/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI.Shipped.txt @@ -303,6 +303,8 @@ static Dapper.SqlMapper.SanitizeParameterValue(object? value) -> object! static Dapper.SqlMapper.SetDbType(System.Data.IDataParameter! parameter, object! value) -> void static Dapper.SqlMapper.Settings.ApplyNullValues.get -> bool static Dapper.SqlMapper.Settings.ApplyNullValues.set -> void +static Dapper.SqlMapper.Settings.SupportLegacyParameterTokens.get -> bool +static Dapper.SqlMapper.Settings.SupportLegacyParameterTokens.set -> void static Dapper.SqlMapper.Settings.CommandTimeout.get -> int? static Dapper.SqlMapper.Settings.CommandTimeout.set -> void static Dapper.SqlMapper.Settings.FetchSize.get -> long diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index 192cd893d..343906b74 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -121,6 +121,13 @@ public static long FetchSize } } + /// + /// Indicates whether single-character parameter tokens (? etc) will be detected and used where possible; + /// this feature is not recommended and will be disabled by default in future versions; + /// where possible, prefer named parameters (@yourParam etc) or Dapper's "pseudo-positional" parameters (?yourParam? etc). + /// + public static bool SupportLegacyParameterTokens { get; set; } = true; + private static long s_FetchSize = -1; } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index b9f8b09d8..f1e761b23 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2534,11 +2534,13 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true throw new NotSupportedException("ValueTuple should not be used for parameters - the language-level names are not available to use as parameter names, and it adds unnecessary boxing"); } - bool filterParams = false; - if (removeUnused && identity.CommandType.GetValueOrDefault(CommandType.Text) == CommandType.Text) + bool filterParams = removeUnused && identity.CommandType.GetValueOrDefault(CommandType.Text) == CommandType.Text; + + if (filterParams && Settings.SupportLegacyParameterTokens) { filterParams = !smellsLikeOleDb.IsMatch(identity.Sql); } + var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); var il = dm.GetILGenerator(); diff --git a/docs/index.md b/docs/index.md index 25a22caf7..36e69bcd1 100644 --- a/docs/index.md +++ b/docs/index.md @@ -25,6 +25,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command (note: new PRs will not be merged until they add release note wording here) - infer command text without any whitespace as stored-procedure (#1975 via @mgravell) +- add global `SupportLegacyParameterTokens` setting to enable or disable single-character parameter tokens (#1974 via @Giorgi) ### 2.1.4 diff --git a/tests/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs index cd0d0f0cc..b6408359e 100644 --- a/tests/Dapper.Tests/ParameterTests.cs +++ b/tests/Dapper.Tests/ParameterTests.cs @@ -1511,6 +1511,36 @@ public void Issue601_InternationalParameterNamesWork() [FactLongRunning] public void TestListExpansionPadding_Disabled() => TestListExpansionPadding(false); + [Theory] + [InlineData(true)] + [InlineData(false)] + public void OleDbParamFilterFails(bool legacyParameterToken) + { + SqlMapper.PurgeQueryCache(); + var oldValue = SqlMapper.Settings.SupportLegacyParameterTokens; + try + { + SqlMapper.Settings.SupportLegacyParameterTokens = legacyParameterToken; + + if (legacyParameterToken) // OLE DB parameter support enabled; can false-positive + { + Assert.Throws(() => GetValue(connection)); + } + else // OLE DB parameter support disabled; more reliable + { + Assert.Equal("this ? could be awkward", GetValue(connection)); + } + } + finally + { + SqlMapper.Settings.SupportLegacyParameterTokens = oldValue; + } + + static string GetValue(DbConnection connection) + => connection.QuerySingle("select 'this ? could be awkward'", + new TypeWithDodgyProperties()); + } + private void TestListExpansionPadding(bool enabled) { bool oldVal = SqlMapper.Settings.PadListExpansions; @@ -1706,5 +1736,10 @@ class HazNullableSqlDecimal public int Id { get; set; } public SqlDecimal? Value { get; set; } } + + class TypeWithDodgyProperties + { + public string Name => throw new NotSupportedException(); + } } } From 8d53acb4011c33a590dcb6fdb35ab6c48a2b456e Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 13 Oct 2023 07:01:56 +0100 Subject: [PATCH 243/312] the $ support added for DuckDB does not need to exacerbate the OLEDB problem (#1979) --- Dapper/SqlMapper.cs | 2 +- docs/index.md | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index f1e761b23..797b833d2 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2387,7 +2387,7 @@ private static IEnumerable FilterParameters(IEnumerable Date: Fri, 13 Oct 2023 07:06:15 +0100 Subject: [PATCH 244/312] NRT tweak on GetConstructorParameter --- Dapper/PublicAPI.Shipped.txt | 2 +- Dapper/SqlMapper.ITypeMap.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt index adaa7a517..4c5417a6c 100644 --- a/Dapper/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI.Shipped.txt @@ -135,7 +135,7 @@ Dapper.SqlMapper.ITypeHandler.SetValue(System.Data.IDbDataParameter! parameter, Dapper.SqlMapper.ITypeMap Dapper.SqlMapper.ITypeMap.FindConstructor(string![]! names, System.Type![]! types) -> System.Reflection.ConstructorInfo? Dapper.SqlMapper.ITypeMap.FindExplicitConstructor() -> System.Reflection.ConstructorInfo? -Dapper.SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap! +Dapper.SqlMapper.ITypeMap.GetConstructorParameter(System.Reflection.ConstructorInfo! constructor, string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.SqlMapper.ITypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.IMemberMap? Dapper.SqlMapper.Settings Dapper.SqlMapper.StringTypeHandler diff --git a/Dapper/SqlMapper.ITypeMap.cs b/Dapper/SqlMapper.ITypeMap.cs index 6a149b1fb..42189a688 100644 --- a/Dapper/SqlMapper.ITypeMap.cs +++ b/Dapper/SqlMapper.ITypeMap.cs @@ -33,7 +33,7 @@ public interface ITypeMap /// Constructor to resolve /// DataReader column name /// Mapping implementation - IMemberMap GetConstructorParameter(ConstructorInfo constructor, string columnName); + IMemberMap? GetConstructorParameter(ConstructorInfo constructor, string columnName); /// /// Gets member mapping for column From d91f993dc6481373b2615f680baaf78252f6dbca Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 13 Oct 2023 07:11:10 +0100 Subject: [PATCH 245/312] change NRT for ITypeMap.GetConstructorParameter (#1980) * release notes for NRT tweak * PR number --- docs/index.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/index.md b/docs/index.md index 992753923..366451451 100644 --- a/docs/index.md +++ b/docs/index.md @@ -27,6 +27,7 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command - infer command text without any whitespace as stored-procedure (#1975 via @mgravell) - add global `SupportLegacyParameterTokens` setting to enable or disable single-character parameter tokens (#1974 via @Giorgi) - revert `$` addition for legacy parameter tokens (#1979 via @mgravell) +- change NRT annotation on `GetConstructorParameter` (#1980 via @mgravell, fixes #1969) ### 2.1.4 From a93678e485b6996dbab7c06b86b5aecbcf3b9f83 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 13 Oct 2023 07:33:27 +0100 Subject: [PATCH 246/312] move release notes to GitHub --- docs/index.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/index.md b/docs/index.md index 366451451..e05950d63 100644 --- a/docs/index.md +++ b/docs/index.md @@ -20,7 +20,13 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ## Release Notes -### unreleased +**RELEASE NOTE TRACKING HAS MOVED TO GITHUB** + +See: https://github.com/DapperLib/Dapper/releases + +Archive only (no new entries): + +### 2.1.11 (note: new PRs will not be merged until they add release note wording here) From b446241da3fd6405dbc024a442c177ae531712aa Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 18 Oct 2023 20:38:33 +0100 Subject: [PATCH 247/312] cite AWS --- Readme.md | 49 ++++++++++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/Readme.md b/Readme.md index 218c1ed52..d2cd09d3a 100644 --- a/Readme.md +++ b/Readme.md @@ -21,6 +21,8 @@ MyGet Pre-release feed: https://www.myget.org/gallery/dapper | [Dapper.StrongName](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/v/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/vpre/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName](https://img.shields.io/nuget/dt/Dapper.StrongName.svg)](https://www.nuget.org/packages/Dapper.StrongName/) | [![Dapper.StrongName MyGet](https://img.shields.io/myget/dapper/vpre/Dapper.StrongName.svg)](https://www.myget.org/feed/dapper/package/nuget/Dapper.StrongName) | Package Purposes: +* Dapper + * The core library * Dapper.EntityFramework * Extension handlers for EntityFramework * Dapper.EntityFramework.StrongName @@ -29,24 +31,44 @@ Package Purposes: * Micro-ORM implemented on Dapper, provides CRUD helpers * Dapper.SqlBuilder * Component for building SQL queries dynamically and composably -* Dapper.StrongName - * High-performance micro-ORM supporting MySQL, Sqlite, SqlICE, and Firebird + +Sponsors +-------- + +Dapper was originally developed for and by Stack Overflow, but is F/OSS. Sponsorship is welcome and invited - see the sponsor link at the top of the page. +A huge thanks to everyone (individuals or organisations) who have sponsored Dapper, but a massive thanks in particular to: + +- [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) Features -------- -Dapper is a [NuGet library](https://www.nuget.org/packages/Dapper) that you can add in to your project that will extend your `IDbConnection` interface. +Dapper is a [NuGet library](https://www.nuget.org/packages/Dapper) that you can add in to your project that will enhance your ADO.NET connections via +extension methods on your `DbConnection` instance. This provides a simple and efficient API for invoking SQL, with support for both synchronous and +asynchronous data access, and allows bother buffered and non-buffered queries. -It provides 3 helpers: +It provides multiple helpers, but the key APIs are: -Execute a query and map the results to a strongly typed List ------------------------------------------------------------- +``` csharp +// insert/update/delete etc +var count = connection.Execute(sql [, args]); -```csharp -public static IEnumerable Query(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) +// multi-row query +IEnumerable rows = connection.Query(sql [, args]); + +// single-row query ({Single|First}[OrDefault]) +T row = connection.QuerySingle(sql [, args]); ``` -Example usage: -```csharp +where `args` can be (among other things): + +- a simple POCO (including anonyomous types) for named parameters +- a `Dictionary` +- a `DynamicParameters` instance + +Execute a query and map it to a list of typed objects +------------------------------------------------------- + +``` csharp public class Dog { public int? Age { get; set; } @@ -68,9 +90,6 @@ Assert.Equal(guid, dog.First().Id); Execute a query and map it to a list of dynamic objects ------------------------------------------------------- -```csharp -public static IEnumerable Query (this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, bool buffered = true, int? commandTimeout = null, CommandType? commandType = null) -``` This method will execute SQL and return a dynamic list. Example usage: @@ -87,10 +106,6 @@ Assert.Equal(4, (int)rows[1].B); Execute a Command that returns no results ----------------------------------------- -```csharp -public static int Execute(this IDbConnection cnn, string sql, object param = null, IDbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) -``` - Example usage: ```csharp From cdadfa6db61c9248b6e3169cc813be3df59c095f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 19 Oct 2023 12:25:11 +0100 Subject: [PATCH 248/312] whitespace for auto-SP detection: use unicode spec via regex (#1987) * add fix and test for #1975 * wrong ticket number; is #1986 --- Dapper/CommandDefinition.cs | 7 ++++--- tests/Dapper.Tests/ProcedureTests.cs | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index 1820d86f4..f7132ed52 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -2,6 +2,7 @@ using System.Data; using System.Reflection; using System.Reflection.Emit; +using System.Text.RegularExpressions; using System.Threading; namespace Dapper @@ -103,14 +104,14 @@ public CommandDefinition(string commandText, object? parameters = null, IDbTrans static CommandType InferCommandType(string sql) { - if (sql is null || sql.IndexOfAny(WhitespaceChars) >= 0) return System.Data.CommandType.Text; + if (sql is null || WhitespaceChars.IsMatch(sql)) return System.Data.CommandType.Text; return System.Data.CommandType.StoredProcedure; } } - // if the sql contains any whitespace character (space/tab/cr/lf): interpret as ad-hoc; but "SomeName" should be treated as a stored-proc + // if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode): interpret as ad-hoc; but "SomeName" should be treated as a stored-proc // (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway) - private static readonly char[] WhitespaceChars = new char[] { ' ', '\t', '\r', '\n' }; + private static readonly Regex WhitespaceChars = new(@"\s", RegexOptions.Compiled); private CommandDefinition(object? parameters) : this() { diff --git a/tests/Dapper.Tests/ProcedureTests.cs b/tests/Dapper.Tests/ProcedureTests.cs index 9d76f44b9..9cc71f474 100644 --- a/tests/Dapper.Tests/ProcedureTests.cs +++ b/tests/Dapper.Tests/ProcedureTests.cs @@ -302,5 +302,20 @@ select 1 as Num Assert.Empty(result); } + + [Theory] + [InlineData(" ")] + [InlineData("\u00A0")] // nbsp + [InlineData("\u202F")] // narrow nbsp + [InlineData("\u2000")] // n quad + [InlineData("\t")] + [InlineData("\r")] + [InlineData("\n")] + public async Task Issue1986_AutoProc_Whitespace(string space) + { + var sql = "select!42".Replace("!", space); + var result = await connection.QuerySingleAsync(sql); + Assert.Equal(42, result); + } } } From 2f56056a89d269f2d924678efe7eb26cb129cda3 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 20 Oct 2023 09:24:01 +0100 Subject: [PATCH 249/312] - expand auto-sproc detection to handle more scenarios and explicit exclusions (#1989) - add net7 target - use regex generator when available (net7+) - fix #1984 --- Dapper.StrongName/Dapper.StrongName.csproj | 3 +- Dapper/CommandDefinition.cs | 20 ++++----- Dapper/CompiledRegex.cs | 45 +++++++++++++++++++ Dapper/Dapper.csproj | 2 +- Dapper/Global.cs | 4 ++ Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt | 6 +++ .../PublicAPI/net7.0/PublicAPI.Unshipped.txt | 1 + Dapper/SqlMapper.DapperRow.Descriptor.cs | 18 ++++---- Dapper/SqlMapper.cs | 15 +++---- Directory.Build.props | 2 +- appveyor.yml | 3 ++ tests/Dapper.Tests/Dapper.Tests.csproj | 2 +- tests/Dapper.Tests/ProcedureTests.cs | 22 +++++++++ 13 files changed, 110 insertions(+), 33 deletions(-) create mode 100644 Dapper/CompiledRegex.cs create mode 100644 Dapper/Global.cs create mode 100644 Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt create mode 100644 Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index dec6f7f0c..ef28e32b4 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -5,11 +5,12 @@ Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0;net5.0 + net461;netstandard2.0;net5.0;net7.0 true true enable true + $(DefineConstants);STRONG_NAME diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index f7132ed52..cc117c304 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -2,7 +2,6 @@ using System.Data; using System.Reflection; using System.Reflection.Emit; -using System.Text.RegularExpressions; using System.Threading; namespace Dapper @@ -101,17 +100,18 @@ public CommandDefinition(string commandText, object? parameters = null, IDbTrans CommandTypeDirect = commandType ?? InferCommandType(commandText); Flags = flags; CancellationToken = cancellationToken; - - static CommandType InferCommandType(string sql) - { - if (sql is null || WhitespaceChars.IsMatch(sql)) return System.Data.CommandType.Text; - return System.Data.CommandType.StoredProcedure; - } } - // if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode): interpret as ad-hoc; but "SomeName" should be treated as a stored-proc - // (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway) - private static readonly Regex WhitespaceChars = new(@"\s", RegexOptions.Compiled); + internal static CommandType InferCommandType(string sql) + { + // if the sql contains any whitespace character (space/tab/cr/lf/etc - via unicode), + // has operators, comments, semi-colon, or a known exception: interpret as ad-hoc; + // otherwise, simple names like "SomeName" should be treated as a stored-proc + // (note TableDirect would need to be specified explicitly, but in reality providers don't usually support TableDirect anyway) + + if (sql is null || CompiledRegex.WhitespaceOrReserved.IsMatch(sql)) return System.Data.CommandType.Text; + return System.Data.CommandType.StoredProcedure; + } private CommandDefinition(object? parameters) : this() { diff --git a/Dapper/CompiledRegex.cs b/Dapper/CompiledRegex.cs new file mode 100644 index 000000000..bbf857d56 --- /dev/null +++ b/Dapper/CompiledRegex.cs @@ -0,0 +1,45 @@ +using System.Diagnostics.CodeAnalysis; +using System.Text.RegularExpressions; + +namespace Dapper; + +internal static partial class CompiledRegex +{ +#if DEBUG && NET7_0_OR_GREATER // enables colorization in IDE + [StringSyntax("Regex")] +#endif + private const string + WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$", + LegacyParameterPattern = @"(? LegacyParameterGen(); + internal static Regex LiteralTokens => LiteralTokensGen(); + internal static Regex PseudoPositional => PseudoPositionalGen(); + internal static Regex WhitespaceOrReserved => WhitespaceOrReservedGen(); +#else + internal static Regex LegacyParameter { get; } + = new(LegacyParameterPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); + internal static Regex LiteralTokens { get; } + = new(LiteralTokensPattern, RegexOptions.IgnoreCase | RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); + internal static Regex PseudoPositional { get; } + = new(PseudoPositionalPattern, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled); + internal static Regex WhitespaceOrReserved { get; } + = new(WhitespaceOrReservedPattern, RegexOptions.Compiled | RegexOptions.IgnoreCase); +#endif +} diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index cf98d5abf..8fc9c65f8 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,7 +5,7 @@ orm;sql;micro-orm A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0;net5.0 + net461;netstandard2.0;net5.0;net7.0 enable true diff --git a/Dapper/Global.cs b/Dapper/Global.cs new file mode 100644 index 000000000..f6b5f4ef4 --- /dev/null +++ b/Dapper/Global.cs @@ -0,0 +1,4 @@ +using System.Runtime.CompilerServices; +#if !STRONG_NAME +[assembly: InternalsVisibleTo("Dapper.Tests")] +#endif diff --git a/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..5da46e604 --- /dev/null +++ b/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt @@ -0,0 +1,6 @@ +#nullable enable +Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! \ No newline at end of file diff --git a/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..91b0e1a43 --- /dev/null +++ b/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +#nullable enable \ No newline at end of file diff --git a/Dapper/SqlMapper.DapperRow.Descriptor.cs b/Dapper/SqlMapper.DapperRow.Descriptor.cs index 11e85c02d..0824c6658 100644 --- a/Dapper/SqlMapper.DapperRow.Descriptor.cs +++ b/Dapper/SqlMapper.DapperRow.Descriptor.cs @@ -13,8 +13,8 @@ private sealed class DapperRowTypeDescriptionProvider : TypeDescriptionProvider { public override ICustomTypeDescriptor GetExtendedTypeDescriptor(object instance) => new DapperRowTypeDescriptor(instance); - public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object instance) - => new DapperRowTypeDescriptor(instance); + public override ICustomTypeDescriptor GetTypeDescriptor(Type objectType, object? instance) + => new DapperRowTypeDescriptor(instance!); } //// in theory we could implement this for zero-length results to bind; would require @@ -57,7 +57,7 @@ AttributeCollection ICustomTypeDescriptor.GetAttributes() EventDescriptorCollection ICustomTypeDescriptor.GetEvents() => EventDescriptorCollection.Empty; - EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[] attributes) => EventDescriptorCollection.Empty; + EventDescriptorCollection ICustomTypeDescriptor.GetEvents(Attribute[]? attributes) => EventDescriptorCollection.Empty; internal static PropertyDescriptorCollection GetProperties(DapperRow row) => GetProperties(row?.table, row); internal static PropertyDescriptorCollection GetProperties(DapperTable? table, IDictionary? row = null) @@ -75,9 +75,9 @@ internal static PropertyDescriptorCollection GetProperties(DapperTable? table, I } PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties() => GetProperties(_row); - PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[] attributes) => GetProperties(_row); + PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties(Attribute[]? attributes) => GetProperties(_row); - object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd) => _row; + object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor? pd) => _row; } private sealed class RowBoundPropertyDescriptor : PropertyDescriptor @@ -95,10 +95,10 @@ public RowBoundPropertyDescriptor(Type type, string name, int index) : base(name public override bool ShouldSerializeValue(object component) => ((DapperRow)component).TryGetValue(_index, out _); public override Type ComponentType => typeof(DapperRow); public override Type PropertyType => _type; - public override object GetValue(object component) - => ((DapperRow)component).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value; - public override void SetValue(object component, object? value) - => ((DapperRow)component).SetValue(_index, value is DBNull ? null : value); + public override object GetValue(object? component) + => ((DapperRow)component!).TryGetValue(_index, out var val) ? (val ?? DBNull.Value): DBNull.Value; + public override void SetValue(object? component, object? value) + => ((DapperRow)component!).SetValue(_index, value is DBNull ? null : value); } } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 797b833d2..fb6ac88e6 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1865,7 +1865,7 @@ private static CacheInfo GetCacheInfo(Identity identity, object? exampleParamete private static bool ShouldPassByPosition(string sql) { - return sql?.IndexOf('?') >= 0 && pseudoPositional.IsMatch(sql); + return sql?.IndexOf('?') >= 0 && CompiledRegex.PseudoPositional.IsMatch(sql); } private static void PassByPosition(IDbCommand cmd) @@ -1882,7 +1882,7 @@ private static void PassByPosition(IDbCommand cmd) bool firstMatch = true; int index = 0; // use this to spoof names; in most pseudo-positional cases, the name is ignored, however: // for "snowflake", the name needs to be incremental i.e. "1", "2", "3" - cmd.CommandText = pseudoPositional.Replace(cmd.CommandText, match => + cmd.CommandText = CompiledRegex.PseudoPositional.Replace(cmd.CommandText, match => { string key = match.Groups[1].Value; if (!consumed.Add(key)) @@ -2386,11 +2386,6 @@ private static IEnumerable FilterParameters(IEnumerable /// Replace all literal tokens with their text form. /// @@ -2496,9 +2491,9 @@ internal static void ReplaceLiterals(IParameterLookup parameters, IDbCommand com internal static IList GetLiteralTokens(string sql) { if (string.IsNullOrEmpty(sql)) return LiteralToken.None; - if (!literalTokens.IsMatch(sql)) return LiteralToken.None; + if (!CompiledRegex.LiteralTokens.IsMatch(sql)) return LiteralToken.None; - var matches = literalTokens.Matches(sql); + var matches = CompiledRegex.LiteralTokens.Matches(sql); var found = new HashSet(StringComparer.Ordinal); var list = new List(matches.Count); foreach (Match match in matches) @@ -2538,7 +2533,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true if (filterParams && Settings.SupportLegacyParameterTokens) { - filterParams = !smellsLikeOleDb.IsMatch(identity.Sql); + filterParams = !CompiledRegex.LegacyParameter.IsMatch(identity.Sql); } var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); diff --git a/Directory.Build.props b/Directory.Build.props index d858cf494..e9ebe949c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,7 +21,7 @@ false true true - 9.0 + 11 false true readme.md diff --git a/appveyor.yml b/appveyor.yml index 122b20fab..a39d87789 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,9 @@ skip_commits: files: - '**/*.md' +install: + - choco install dotnet-sdk --version 7.0.402 + environment: Appveyor: true # Postgres diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index f1e808301..8a003f7bd 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,7 +2,7 @@ Dapper.Tests Dapper Core Test Suite - net472;net6.0 + net472;net6.0;net7.0 $(DefineConstants);MSSQLCLIENT $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208 enable diff --git a/tests/Dapper.Tests/ProcedureTests.cs b/tests/Dapper.Tests/ProcedureTests.cs index 9cc71f474..be8dbe746 100644 --- a/tests/Dapper.Tests/ProcedureTests.cs +++ b/tests/Dapper.Tests/ProcedureTests.cs @@ -317,5 +317,27 @@ public async Task Issue1986_AutoProc_Whitespace(string space) var result = await connection.QuerySingleAsync(sql); Assert.Equal(42, result); } + + [Theory] + [InlineData("foo", CommandType.StoredProcedure)] + [InlineData("foo;", CommandType.Text)] + [InlineData("foo bar", CommandType.Text)] + [InlineData("foo bar;", CommandType.Text)] + [InlineData("vacuum", CommandType.Text)] + [InlineData("vacuum;", CommandType.Text)] + [InlineData("FOO", CommandType.StoredProcedure)] + [InlineData("FOO;", CommandType.Text)] + [InlineData("FOO BAR", CommandType.Text)] + [InlineData("FOO BAR;", CommandType.Text)] + [InlineData("VACUUM", CommandType.Text)] + [InlineData("VACUUM;", CommandType.Text)] + + // comments imply text + [InlineData("foo--bar", CommandType.Text)] + [InlineData("foo/*bar*/", CommandType.Text)] + public void InferCommandType(string sql, CommandType commandType) + { + Assert.Equal(commandType, CommandDefinition.InferCommandType(sql)); + } } } From f0e61a740b0c2f5421a64aff0bdb4707ced1c46b Mon Sep 17 00:00:00 2001 From: Korolev Dmitry Date: Sun, 22 Oct 2023 09:54:13 +0200 Subject: [PATCH 250/312] include ExplicitConstructorAttribute's targets to constructor&method (#1982) * add method target for explicitconstructor * add a noting comment (usage is limited with Dapper.AOT) --- Dapper/ExplicitConstructorAttribute.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Dapper/ExplicitConstructorAttribute.cs b/Dapper/ExplicitConstructorAttribute.cs index d801af345..4d5580127 100644 --- a/Dapper/ExplicitConstructorAttribute.cs +++ b/Dapper/ExplicitConstructorAttribute.cs @@ -5,7 +5,10 @@ namespace Dapper /// /// Tell Dapper to use an explicit constructor, passing nulls or 0s for all parameters /// - [AttributeUsage(AttributeTargets.Constructor, AllowMultiple = false)] + /// + /// Usage on methods is limited to the usage with Dapper.AOT (https://github.com/DapperLib/DapperAOT) + /// + [AttributeUsage(AttributeTargets.Constructor | AttributeTargets.Method, AllowMultiple = false)] public sealed class ExplicitConstructorAttribute : Attribute { } From bd649f0e5823f5d2643a9f8ab9f5d40950a1de71 Mon Sep 17 00:00:00 2001 From: Thomas Ingham Date: Thu, 9 Nov 2023 09:16:55 -0500 Subject: [PATCH 251/312] Update Readme.md (#1994) Ironic change of opportunity. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index d2cd09d3a..9fa6fe625 100644 --- a/Readme.md +++ b/Readme.md @@ -44,7 +44,7 @@ Features -------- Dapper is a [NuGet library](https://www.nuget.org/packages/Dapper) that you can add in to your project that will enhance your ADO.NET connections via extension methods on your `DbConnection` instance. This provides a simple and efficient API for invoking SQL, with support for both synchronous and -asynchronous data access, and allows bother buffered and non-buffered queries. +asynchronous data access, and allows both buffered and non-buffered queries. It provides multiple helpers, but the key APIs are: From 6fdafc32e2f39d48f4daaa16c7a5a1de39185b96 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 10 Nov 2023 19:17:10 +0000 Subject: [PATCH 252/312] add commmit and rollback to "text" exclusion list (#1995) --- Dapper/CompiledRegex.cs | 2 +- tests/Dapper.Tests/ProcedureTests.cs | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Dapper/CompiledRegex.cs b/Dapper/CompiledRegex.cs index bbf857d56..5deef70f1 100644 --- a/Dapper/CompiledRegex.cs +++ b/Dapper/CompiledRegex.cs @@ -9,7 +9,7 @@ internal static partial class CompiledRegex [StringSyntax("Regex")] #endif private const string - WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$", + WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$|^commit$|^rollback$", LegacyParameterPattern = @"(? Date: Sat, 11 Nov 2023 07:57:56 +0000 Subject: [PATCH 253/312] mysql on CI has gone rogue; disable? (#1996) --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a39d87789..72683c428 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -31,7 +31,7 @@ environment: SqlServerConnectionString: Server=(local)\SQL2019;Database=tempdb;User ID=sa;Password=Password12! services: - - mysql + # - mysql - postgresql init: @@ -46,7 +46,7 @@ build_script: # Postgres - createdb test # MySQL - - mysql -e "create database test;" --user=root + # - mysql -e "create database test;" --user=root # Our stuff - ps: .\build.ps1 -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages $true From 947b582c79e7ec555b85ef53bce8f84eb45cc2ed Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sat, 11 Nov 2023 08:51:05 +0000 Subject: [PATCH 254/312] fix #1993 - when matching properties, prefer non-normalized property name (#1997) --- Dapper/DefaultTypeMap.cs | 18 +++++++ tests/Dapper.Tests/ConstructorTests.cs | 67 +++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index 38c3cbd79..98029741a 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -206,6 +206,24 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, { // same again, minus underscore delta name = name?.Replace("_", ""); + + // match normalized column name vs actual property name + foreach (var member in members) + { + if (string.Equals(name, selector(member), StringComparison.Ordinal)) + { + return member; + } + } + foreach (var member in members) + { + if (string.Equals(name, selector(member), StringComparison.OrdinalIgnoreCase)) + { + return member; + } + } + + // match normalized column name vs normalized property name foreach (var member in members) { if (string.Equals(name, selector(member)?.Replace("_", ""), StringComparison.Ordinal)) diff --git a/tests/Dapper.Tests/ConstructorTests.cs b/tests/Dapper.Tests/ConstructorTests.cs index 07e7c638a..927c030a4 100644 --- a/tests/Dapper.Tests/ConstructorTests.cs +++ b/tests/Dapper.Tests/ConstructorTests.cs @@ -1,5 +1,4 @@ using System; -using System.Data; using System.Linq; using Xunit; @@ -241,6 +240,72 @@ public void CtorWithoutUnderscores() Assert.Equal("def", obj.LastName); } + [Fact] + public void Issue1993_PreferPropertyOverField() // https://github.com/DapperLib/Dapper/issues/1993 + { + var oldValue = DefaultTypeMap.MatchNamesWithUnderscores; + try + { + DefaultTypeMap.MatchNamesWithUnderscores = true; + + var map = new DefaultTypeMap(typeof(ShowIssue1993)); + var first = map.GetMember("field_first"); + Assert.NotNull(first); + Assert.Null(first.Field); + Assert.Equal(nameof(ShowIssue1993.FieldFirst), first.Property?.Name); + + var last = map.GetMember("field_last"); + Assert.NotNull(last); + Assert.Null(last.Field); + Assert.Equal(nameof(ShowIssue1993.FieldLast), last.Property?.Name); + } + finally + { + DefaultTypeMap.MatchNamesWithUnderscores = oldValue; + } + } + + [Fact] + public void Issue1993_Query() + { + var oldValue = DefaultTypeMap.MatchNamesWithUnderscores; + try + { + DefaultTypeMap.MatchNamesWithUnderscores = true; + + var obj = connection.QueryFirst("select 'abc' as field_first, 'def' as field_last"); + Assert.Equal("abc", obj.FieldFirst); + Assert.Equal("def", obj.FieldLast); + + Assert.Equal("abc", obj.AltFieldFirst); + Assert.Equal("def", obj.AltFieldLast); + } + finally + { + DefaultTypeMap.MatchNamesWithUnderscores = oldValue; + } + } + + public class ShowIssue1993 + { + private string _fieldFirst { get; set; } = null!; // not actually a field + public string FieldFirst + { + get => _fieldFirst; + set => _fieldFirst = AltFieldFirst = value; + } + + public string FieldLast + { + get => _fieldLast; + set => _fieldLast = AltFieldLast = value; + } + private string _fieldLast { get; set; } = null!;// not actually a field + + public string AltFieldFirst { get; set; } = null!; + public string AltFieldLast { get; set; } = null!; + } + class Type_ParamsWithUnderscores { public string FirstName { get; } From cea07ab771a60be051985b248c58d93706a77378 Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Mon, 13 Nov 2023 08:00:27 -0500 Subject: [PATCH 255/312] AppVeyor: Restore MySQL (#1998) Looks like the image was borked with MySQL 8 went in (because it's still trying to start 5.7), see https://github.com/appveyor/ci/issues/3894. --- appveyor.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 72683c428..6197828f6 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -19,7 +19,7 @@ environment: POSTGRES_ENV_POSTGRES_PASSWORD: Password12! POSTGRES_ENV_POSTGRES_DB: test # MySQL - MYSQL_PATH: C:\Program Files\MySql\MySQL Server 5.7 + MYSQL_PATH: C:\Program Files\MySQL\MySQL Server 8.0 MYSQL_PWD: Password12! MYSQL_ENV_MYSQL_USER: root MYSQL_ENV_MYSQL_PASSWORD: Password12! @@ -38,15 +38,18 @@ init: - git config --global core.autocrlf input - SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%PATH% - net start MSSQL$SQL2019 + - ps: Start-Service MySQL80 nuget: disable_publish_on_pr: true -build_script: +before_build: # Postgres - createdb test # MySQL - # - mysql -e "create database test;" --user=root + - '"C:\Program Files\MySQL\MySQL Server 8.0\bin\mysql" -e "create database test;" --user=root' + +build_script: # Our stuff - ps: .\build.ps1 -PullRequestNumber "$env:APPVEYOR_PULL_REQUEST_NUMBER" -CreatePackages $true From 0272d82b7d8146b317aeffb8acc6f7d6db754b7f Mon Sep 17 00:00:00 2001 From: Bill Robertson Date: Tue, 21 Nov 2023 00:50:02 -0600 Subject: [PATCH 256/312] chore: Update Readme.md (#2002) Updates url to point to new release notes publication. The .io one said it wasn't in use anymore and to use the github.com releases information. --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 9fa6fe625..769ea0e33 100644 --- a/Readme.md +++ b/Readme.md @@ -4,7 +4,7 @@ Dapper - a simple object mapper for .Net Release Notes ------------- -Located at [dapperlib.github.io/Dapper](https://dapperlib.github.io/Dapper/) +Located at [https://github.com/DapperLib/Dapper/releases](https://github.com/DapperLib/Dapper/releases/) Packages -------- From c46c69a174fd983885e453414bc358bcfa5594d0 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 23 Nov 2023 08:58:18 +0000 Subject: [PATCH 257/312] 1. give clearer error messages when an ICustomQueryParameter is null (#2003) 2. provide a convenience .ctor on DbString --- Dapper/DbString.cs | 18 +++++++++++++++--- Dapper/PublicAPI.Shipped.txt | 2 ++ Dapper/SqlMapper.cs | 19 +++++++++++++++++++ tests/Dapper.Tests/MiscTests.cs | 23 +++++++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) diff --git a/Dapper/DbString.cs b/Dapper/DbString.cs index 14dc4612b..15b97d6bc 100644 --- a/Dapper/DbString.cs +++ b/Dapper/DbString.cs @@ -28,6 +28,17 @@ public DbString() Length = -1; IsAnsi = IsAnsiDefault; } + + /// + /// Create a new DbString + /// + public DbString(string? value, int length = -1) + { + Value = value; + Length = length; + IsAnsi = IsAnsiDefault; + } + /// /// Ansi vs Unicode /// @@ -44,12 +55,13 @@ public DbString() /// The value of the string /// public string? Value { get; set; } - + /// /// Gets a string representation of this DbString. /// - public override string ToString() => - $"Dapper.DbString (Value: '{Value}', Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})"; + public override string ToString() => Value is null + ? $"Dapper.DbString (Value: null, Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})" + : $"Dapper.DbString (Value: '{Value}', Length: {Length}, IsAnsi: {IsAnsi}, IsFixedLength: {IsFixedLength})"; /// /// Add the parameter to the command... internal use only diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt index 4c5417a6c..c1b08ea99 100644 --- a/Dapper/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI.Shipped.txt @@ -30,6 +30,7 @@ Dapper.CustomPropertyTypeMap.GetMember(string! columnName) -> Dapper.SqlMapper.I Dapper.DbString Dapper.DbString.AddParameter(System.Data.IDbCommand! command, string! name) -> void Dapper.DbString.DbString() -> void +Dapper.DbString.DbString(string? value, int length = -1) -> void Dapper.DbString.IsAnsi.get -> bool Dapper.DbString.IsAnsi.set -> void Dapper.DbString.IsFixedLength.get -> bool @@ -323,6 +324,7 @@ static Dapper.SqlMapper.Settings.UseSingleRowOptimization.set -> void static Dapper.SqlMapper.SetTypeMap(System.Type! type, Dapper.SqlMapper.ITypeMap? map) -> void static Dapper.SqlMapper.SetTypeName(this System.Data.DataTable! table, string! typeName) -> void static Dapper.SqlMapper.ThrowDataException(System.Exception! ex, int index, System.Data.IDataReader! reader, object? value) -> void +static Dapper.SqlMapper.ThrowNullCustomQueryParameter(string! name) -> void static Dapper.SqlMapper.TypeHandlerCache.Parse(object! value) -> T? static Dapper.SqlMapper.TypeHandlerCache.SetValue(System.Data.IDbDataParameter! parameter, object! value) -> void static Dapper.SqlMapper.TypeMapProvider -> System.Func! \ No newline at end of file diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index fb6ac88e6..fb151758e 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2637,6 +2637,16 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true { il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [custom] + if (!prop.PropertyType.IsValueType) + { + // throw if null + var notNull = il.DefineLabel(); + il.Emit(OpCodes.Dup); // stack is [parameters] [custom] [custom] + il.Emit(OpCodes.Brtrue_S, notNull); // stack is [parameters] [custom] + il.Emit(OpCodes.Ldstr, prop.Name); // stack is [parameters] [custom] [name] + il.EmitCall(OpCodes.Call, typeof(SqlMapper).GetMethod(nameof(ThrowNullCustomQueryParameter))!, null); // stack is [parameters] [custom] + il.MarkLabel(notNull); + } il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name] il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters] @@ -3859,6 +3869,14 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro return null; } + /// + /// For internal use only + /// + [Obsolete(ObsoleteInternalUsageOnly, false)] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] + public static void ThrowNullCustomQueryParameter(string name) + => throw new InvalidOperationException($"Member '{name}' is an {nameof(ICustomQueryParameter)} and cannot be null"); + /// /// Throws a data exception, only used internally /// @@ -3867,6 +3885,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro /// The reader the exception occurred in. /// The value that caused the exception. [Obsolete(ObsoleteInternalUsageOnly, false)] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public static void ThrowDataException(Exception ex, int index, IDataReader reader, object? value) { Exception toThrow; diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 24f7bf2bd..aaaf8e978 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -657,6 +657,27 @@ public void TestDbString() 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() { @@ -668,6 +689,8 @@ public void TestDbStringToString() 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()); From 0cd331b044645f7881ad115cb61b98ecceec93c3 Mon Sep 17 00:00:00 2001 From: 0nly1ken0bi <54916539+0nly1ken0bi@users.noreply.github.com> Date: Sun, 3 Dec 2023 11:40:54 +0200 Subject: [PATCH 258/312] Fixed documentation to read better (#2008) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 769ea0e33..680bd2438 100644 --- a/Readme.md +++ b/Readme.md @@ -444,7 +444,7 @@ Limitations and caveats --------------------- Dapper caches information about every query it runs, this allows it to materialize objects quickly and process parameters quickly. The current implementation caches this information in a `ConcurrentDictionary` object. Statements that are only used once are routinely flushed from this cache. Still, if you are generating SQL strings on the fly without using parameters it is possible you may hit memory issues. -Dapper's simplicity means that many feature that ORMs ship with are stripped out. It worries about the 95% scenario, and gives you the tools you need most of the time. It doesn't attempt to solve every problem. +Dapper's simplicity means that many features that ORMs ship with are stripped out. It worries about the 95% scenario, and gives you the tools you need most of the time. It doesn't attempt to solve every problem. Will Dapper work with my DB provider? --------------------- From 3c3509f373150b109a2fabe121b80a9962e8291a Mon Sep 17 00:00:00 2001 From: Ron <1009029+ronwarner@users.noreply.github.com> Date: Thu, 7 Dec 2023 17:20:02 -0600 Subject: [PATCH 259/312] Update SqlMapper.cs (#2010) Fixed typo in comments --- Dapper/SqlMapper.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index fb151758e..b5b6c2ae4 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -631,7 +631,7 @@ private static int ExecuteImpl(this IDbConnection cnn, ref CommandDefinition com } else { - cmd.CommandText = masterSql; // because we do magic replaces on "in" etc + cmd.CommandText = masterSql; // because we do magic replacements on "in" etc cmd.Parameters.Clear(); // current code is Add-tastic } info!.ParamReader!(cmd, obj); From 4160c9f3443c1b0f4c721e65c3f50506caf5e40b Mon Sep 17 00:00:00 2001 From: Andrii Kurdiumov Date: Mon, 18 Dec 2023 18:21:48 +0600 Subject: [PATCH 260/312] Update benchmarks with latest results. (#2016) * Update benchmarks with latest results. * Address PR feedback --- Directory.Packages.props | 4 +- Readme.md | 104 ++++++++++-------- .../Benchmarks.PetaPoco.cs | 23 ++++ .../Benchmarks.RepoDB.cs | 13 +++ .../Dapper.Tests.Performance.csproj | 2 +- .../Dapper.Tests.Performance/app.config | 2 +- 6 files changed, 99 insertions(+), 49 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 06e68e627..2664f9ede 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,8 +22,8 @@ - - + + diff --git a/Readme.md b/Readme.md index 680bd2438..5991d7109 100644 --- a/Readme.md +++ b/Readme.md @@ -156,56 +156,70 @@ A key feature of Dapper is performance. The following metrics show how long it t The benchmarks can be found in [Dapper.Tests.Performance](https://github.com/DapperLib/Dapper/tree/main/benchmarks/Dapper.Tests.Performance) (contributions welcome!) and can be run via: ```bash -dotnet run --project .\benchmarks\Dapper.Tests.Performance\ -c Release -f netcoreapp3.1 -- -f * --join +dotnet run --project .\benchmarks\Dapper.Tests.Performance\ -c Release -f net8.0 -- -f * --join ``` Output from the latest run is: ``` ini -BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19041.208 (2004/?/20H1) -Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores -.NET Core SDK=3.1.201 - [Host] : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT - ShortRun : .NET Core 3.1.3 (CoreCLR 4.700.20.11803, CoreFX 4.700.20.12001), X64 RyuJIT +BenchmarkDotNet v0.13.7, Windows 10 (10.0.19045.3693/22H2/2022Update) +Intel Core i7-3630QM CPU 2.40GHz (Ivy Bridge), 1 CPU, 8 logical and 4 physical cores +.NET SDK 8.0.100 + [Host] : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX + ShortRun : .NET 8.0.0 (8.0.23.53103), X64 RyuJIT AVX ``` -| ORM | Method | Return | Mean | StdDev | Error | Gen 0 | Gen 1 | Gen 2 | Allocated | -|--------------- |------------------------------ |-------- |----------:|----------:|----------:|--------:|-------:|-------:|----------:| -| Belgrade | ExecuteReader | Post | 94.46 μs | 8.115 μs | 12.268 μs | 1.7500 | 0.5000 | - | 8.42 KB | -| Hand Coded | DataTable | dynamic | 105.43 μs | 0.998 μs | 1.508 μs | 3.0000 | - | - | 9.37 KB | -| Hand Coded | SqlCommand | Post | 106.58 μs | 1.191 μs | 1.801 μs | 1.5000 | 0.7500 | 0.1250 | 7.42 KB | -| Dapper | QueryFirstOrDefault<dynamic> | dynamic | 119.52 μs | 1.320 μs | 2.219 μs | 3.6250 | - | - | 11.39 KB | -| Dapper | 'Query<dynamic> (buffered)' | dynamic | 119.93 μs | 1.943 μs | 2.937 μs | 2.3750 | 1.0000 | 0.2500 | 11.73 KB | -| Massive | 'Query (dynamic)' | dynamic | 120.31 μs | 1.340 μs | 2.252 μs | 2.2500 | 1.0000 | 0.1250 | 12.07 KB | -| Dapper | QueryFirstOrDefault<T> | Post | 121.57 μs | 1.564 μs | 2.364 μs | 1.7500 | 0.7500 | - | 11.35 KB | -| Dapper | 'Query<T> (buffered)' | Post | 121.67 μs | 2.913 μs | 4.403 μs | 1.8750 | 0.8750 | - | 11.65 KB | -| PetaPoco | 'Fetch<T> (Fast)' | Post | 124.91 μs | 4.015 μs | 6.747 μs | 2.0000 | 1.0000 | - | 11.5 KB | -| Mighty | Query<T> | Post | 125.23 μs | 2.932 μs | 4.433 μs | 2.2500 | 1.0000 | - | 12.6 KB | -| LINQ to DB | Query<T> | Post | 125.76 μs | 2.038 μs | 3.081 μs | 2.2500 | 0.7500 | 0.2500 | 10.62 KB | -| PetaPoco | Fetch<T> | Post | 127.48 μs | 4.283 μs | 6.475 μs | 2.0000 | 1.0000 | - | 12.18 KB | -| LINQ to DB | 'First (Compiled)' | Post | 128.89 μs | 2.627 μs | 3.971 μs | 2.5000 | 0.7500 | - | 10.92 KB | -| Mighty | Query<dynamic> | dynamic | 129.20 μs | 2.577 μs | 3.896 μs | 2.0000 | 1.0000 | - | 12.43 KB | -| Mighty | SingleFromQuery<T> | Post | 129.41 μs | 2.094 μs | 3.166 μs | 2.2500 | 1.0000 | - | 12.6 KB | -| Mighty | SingleFromQuery<dynamic> | dynamic | 130.59 μs | 2.432 μs | 3.677 μs | 2.0000 | 1.0000 | - | 12.43 KB | -| Dapper | 'Contrib Get<T>' | Post | 134.74 μs | 1.816 μs | 2.746 μs | 2.5000 | 1.0000 | 0.2500 | 12.29 KB | -| ServiceStack | SingleById<T> | Post | 135.01 μs | 1.213 μs | 2.320 μs | 3.0000 | 1.0000 | 0.2500 | 15.27 KB | -| LINQ to DB | First | Post | 151.87 μs | 3.826 μs | 5.784 μs | 3.0000 | 1.0000 | 0.2500 | 13.97 KB | -| EF 6 | SqlQuery | Post | 171.00 μs | 1.460 μs | 2.791 μs | 3.7500 | 1.0000 | - | 23.67 KB | -| DevExpress.XPO | GetObjectByKey<T> | Post | 172.36 μs | 3.758 μs | 5.681 μs | 5.5000 | 1.2500 | - | 29.06 KB | -| Dapper | 'Query<T> (unbuffered)' | Post | 174.40 μs | 3.296 μs | 4.983 μs | 2.0000 | 1.0000 | - | 11.77 KB | -| Dapper | 'Query<dynamic> (unbuffered)' | dynamic | 174.45 μs | 1.988 μs | 3.340 μs | 2.0000 | 1.0000 | - | 11.81 KB | -| DevExpress.XPO | FindObject<T> | Post | 181.76 μs | 5.554 μs | 9.333 μs | 8.0000 | - | - | 27.15 KB | -| DevExpress.XPO | Query<T> | Post | 189.81 μs | 4.187 μs | 8.004 μs | 10.0000 | - | - | 31.61 KB | -| EF Core | 'First (Compiled)' | Post | 199.72 μs | 3.983 μs | 7.616 μs | 4.5000 | - | - | 13.8 KB | -| NHibernate | Get<T> | Post | 248.71 μs | 6.604 μs | 11.098 μs | 5.0000 | 1.0000 | - | 29.79 KB | -| EF Core | First | Post | 253.20 μs | 3.033 μs | 5.097 μs | 5.5000 | - | - | 17.7 KB | -| NHibernate | HQL | Post | 258.70 μs | 11.716 μs | 17.712 μs | 5.0000 | 1.0000 | - | 32.1 KB | -| EF Core | SqlQuery | Post | 268.89 μs | 19.349 μs | 32.516 μs | 6.0000 | - | - | 18.5 KB | -| EF 6 | First | Post | 278.46 μs | 12.094 μs | 18.284 μs | 13.5000 | - | - | 44.18 KB | -| EF Core | 'First (No Tracking)' | Post | 280.88 μs | 8.192 μs | 13.765 μs | 3.0000 | 0.5000 | - | 19.38 KB | -| NHibernate | Criteria | Post | 304.90 μs | 2.232 μs | 4.267 μs | 11.0000 | 1.0000 | - | 60.29 KB | -| EF 6 | 'First (No Tracking)' | Post | 316.55 μs | 7.667 μs | 11.592 μs | 8.5000 | 1.0000 | - | 50.95 KB | -| NHibernate | SQL | Post | 335.41 μs | 3.111 μs | 4.703 μs | 19.0000 | 1.0000 | - | 78.86 KB | -| NHibernate | LINQ | Post | 807.79 μs | 27.207 μs | 45.719 μs | 8.0000 | 2.0000 | - | 53.65 KB | - +| ORM | Method | Return | Mean | StdDev | Error | Gen0 | Gen1 | Gen2 | Allocated | +|-------------------- |------------------------------- |------------- |----------:|----------:|----------:|--------:|-------:|-------:|----------:| +| Dapper cache impact | ExecuteParameters_Cache | Void | 96.75 us | 0.668 us | 1.010 us | 0.6250 | - | - | 2184 B | +| Dapper cache impact | QueryFirstParameters_Cache | Void | 96.86 us | 0.493 us | 0.746 us | 0.8750 | - | - | 2824 B | +| Hand Coded | SqlCommand | Post | 119.70 us | 0.706 us | 1.067 us | 1.3750 | 1.0000 | 0.1250 | 7584 B | +| Hand Coded | DataTable | dynamic | 126.64 us | 1.239 us | 1.873 us | 3.0000 | - | - | 9576 B | +| SqlMarshal | SqlCommand | Post | 132.36 us | 1.008 us | 1.523 us | 2.0000 | 1.0000 | 0.2500 | 11529 B | +| Dapper | QueryFirstOrDefault | Post | 133.73 us | 1.301 us | 2.186 us | 1.7500 | 1.5000 | - | 11608 B | +| Mighty | Query | dynamic | 133.92 us | 1.075 us | 1.806 us | 2.0000 | 1.7500 | - | 12710 B | +| LINQ to DB | Query | Post | 134.24 us | 1.068 us | 1.614 us | 1.7500 | 1.2500 | - | 10904 B | +| RepoDB | ExecuteQuery | Post | 135.83 us | 1.839 us | 3.091 us | 1.7500 | 1.5000 | - | 11649 B | +| Dapper | 'Query (buffered)' | Post | 136.14 us | 1.755 us | 2.653 us | 2.0000 | 1.5000 | - | 11888 B | +| Mighty | Query | Post | 137.96 us | 1.485 us | 2.244 us | 2.2500 | 1.2500 | - | 12201 B | +| Dapper | QueryFirstOrDefault | dynamic | 139.04 us | 1.507 us | 2.279 us | 3.5000 | - | - | 11648 B | +| Mighty | SingleFromQuery | dynamic | 139.74 us | 2.521 us | 3.811 us | 2.0000 | 1.7500 | - | 12710 B | +| Dapper | 'Query (buffered)' | dynamic | 140.13 us | 1.382 us | 2.090 us | 2.0000 | 1.5000 | - | 11968 B | +| ServiceStack | SingleById | Post | 140.76 us | 1.147 us | 2.192 us | 2.5000 | 1.2500 | 0.2500 | 15248 B | +| Dapper | 'Contrib Get' | Post | 141.09 us | 1.394 us | 2.108 us | 2.0000 | 1.5000 | - | 12440 B | +| Mighty | SingleFromQuery | Post | 141.17 us | 1.941 us | 2.935 us | 1.7500 | 1.5000 | - | 12201 B | +| Massive | 'Query (dynamic)' | dynamic | 142.01 us | 4.957 us | 7.494 us | 2.0000 | 1.5000 | - | 12342 B | +| LINQ to DB | 'First (Compiled)' | Post | 144.59 us | 1.295 us | 1.958 us | 1.7500 | 1.5000 | - | 12128 B | +| RepoDB | QueryField | Post | 148.31 us | 1.742 us | 2.633 us | 2.0000 | 1.5000 | 0.5000 | 13938 B | +| Norm | 'Read<> (tuples)' | ValueTuple`8 | 148.58 us | 2.172 us | 3.283 us | 2.0000 | 1.7500 | - | 12745 B | +| Norm | 'Read<()> (named tuples)' | ValueTuple`8 | 150.60 us | 0.658 us | 1.106 us | 2.2500 | 2.0000 | 1.2500 | 14562 B | +| RepoDB | Query | Post | 152.34 us | 2.164 us | 3.271 us | 2.2500 | 1.5000 | 0.2500 | 14106 B | +| RepoDB | QueryDynamic | Post | 154.15 us | 4.108 us | 6.210 us | 2.2500 | 1.7500 | 0.5000 | 13930 B | +| RepoDB | QueryWhere | Post | 155.90 us | 1.953 us | 3.282 us | 2.5000 | 0.5000 | - | 14858 B | +| Dapper cache impact | ExecuteNoParameters_NoCache | Void | 162.35 us | 1.584 us | 2.394 us | - | - | - | 760 B | +| Dapper cache impact | ExecuteNoParameters_Cache | Void | 162.42 us | 2.740 us | 4.142 us | - | - | - | 760 B | +| Dapper cache impact | QueryFirstNoParameters_Cache | Void | 164.35 us | 1.206 us | 1.824 us | 0.2500 | - | - | 1520 B | +| DevExpress.XPO | FindObject | Post | 165.87 us | 1.012 us | 1.934 us | 8.5000 | - | - | 28099 B | +| Dapper cache impact | QueryFirstNoParameters_NoCache | Void | 173.87 us | 1.178 us | 1.781 us | 0.5000 | - | - | 1576 B | +| LINQ to DB | First | Post | 175.21 us | 2.292 us | 3.851 us | 2.0000 | 0.5000 | - | 14041 B | +| EF 6 | SqlQuery | Post | 175.36 us | 2.259 us | 3.415 us | 4.0000 | 0.7500 | - | 24209 B | +| Norm | 'Read<> (class)' | Post | 186.37 us | 1.305 us | 2.496 us | 3.0000 | 0.5000 | - | 17579 B | +| DevExpress.XPO | GetObjectByKey | Post | 186.78 us | 3.407 us | 5.151 us | 4.5000 | 1.0000 | - | 30114 B | +| Dapper | 'Query (unbuffered)' | dynamic | 194.62 us | 1.335 us | 2.019 us | 1.7500 | 1.5000 | - | 12048 B | +| Dapper | 'Query (unbuffered)' | Post | 195.01 us | 0.888 us | 1.343 us | 2.0000 | 1.5000 | - | 12008 B | +| DevExpress.XPO | Query | Post | 199.46 us | 5.500 us | 9.243 us | 10.0000 | - | - | 32083 B | +| Belgrade | FirstOrDefault | Task`1 | 228.70 us | 2.181 us | 3.665 us | 4.5000 | 0.5000 | - | 20555 B | +| EF Core | 'First (Compiled)' | Post | 265.45 us | 17.745 us | 26.828 us | 2.0000 | - | - | 7521 B | +| NHibernate | Get | Post | 276.02 us | 8.029 us | 12.139 us | 6.5000 | 1.0000 | - | 29885 B | +| NHibernate | HQL | Post | 277.74 us | 13.032 us | 19.703 us | 8.0000 | 1.0000 | - | 31886 B | +| NHibernate | Criteria | Post | 300.22 us | 14.908 us | 28.504 us | 13.0000 | 1.0000 | - | 57562 B | +| EF 6 | First | Post | 310.55 us | 27.254 us | 45.799 us | 13.0000 | - | - | 43309 B | +| EF Core | First | Post | 317.12 us | 1.354 us | 2.046 us | 3.5000 | - | - | 11306 B | +| EF Core | SqlQuery | Post | 322.34 us | 23.990 us | 40.314 us | 5.0000 | - | - | 18195 B | +| NHibernate | SQL | Post | 325.54 us | 3.937 us | 7.527 us | 22.0000 | 1.0000 | - | 80007 B | +| EF 6 | 'First (No Tracking)' | Post | 331.14 us | 27.760 us | 46.649 us | 12.0000 | 1.0000 | - | 50237 B | +| EF Core | 'First (No Tracking)' | Post | 337.82 us | 27.814 us | 46.740 us | 3.0000 | 1.0000 | - | 17986 B | +| NHibernate | LINQ | Post | 604.74 us | 5.549 us | 10.610 us | 10.0000 | - | - | 46061 B | +| Dapper cache impact | ExecuteParameters_NoCache | Void | 623.42 us | 3.978 us | 6.684 us | 3.0000 | 2.0000 | - | 10001 B | +| Dapper cache impact | QueryFirstParameters_NoCache | Void | 630.77 us | 3.027 us | 4.576 us | 3.0000 | 2.0000 | - | 10640 B | Feel free to submit patches that include other ORMs - when running benchmarks, be sure to compile in Release and not attach a debugger (Ctrl+F5). diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs index 1a3c99735..2af4f52b3 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs @@ -1,10 +1,32 @@ using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Jobs; using PetaPoco; using System.ComponentModel; using System.Linq; namespace Dapper.Tests.Performance { +#if !NET5_0_OR_GREATER +/* +System.Reflection.TargetInvocationException: Exception has been thrown by the target of an invocation. + ---> System.InvalidProgramException: Common Language Runtime detected an invalid program. + at System.Reflection.Emit.DynamicMethod.CreateDelegate(Type delegateType, Object target) + at PetaPoco.Database.PocoData.GetFactory[T](String key, Boolean ForceDateTimesToUtc, IDataReader r) in /_/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs:line 1127 + at PetaPoco.Database.Fetch[T](String sql, Object[] args) in /_/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs:line 458 + at Dapper.Tests.Performance.PetaPocoBenchmarks.FetchFast() in /_/benchmarks/Dapper.Tests.Performance/Benchmarks.PetaPoco.cs:line 38 + at BenchmarkDotNet.Autogenerated.Runnable_42.WorkloadActionUnroll(Int64 invokeCount) in /_/benchmarks/Dapper.Tests.Performance/bin/Release/net8.0/5e0d07b1-6b4c-4578-a0eb-d46563cab999/5e0d07b1-6b4c-4578-a0eb-d46563cab999.notcs:line 49834 + at BenchmarkDotNet.Engines.Engine.RunIteration(IterationData data) + at BenchmarkDotNet.Engines.EngineFactory.Jit(Engine engine, Int32 jitIndex, Int32 invokeCount, Int32 unrollFactor) + at BenchmarkDotNet.Engines.EngineFactory.CreateReadyToRun(EngineParameters engineParameters) + at BenchmarkDotNet.Autogenerated.Runnable_42.Run(IHost host, String benchmarkName) in /_/benchmarks/Dapper.Tests.Performance/bin/Release/net8.0/5e0d07b1-6b4c-4578-a0eb-d46563cab999/5e0d07b1-6b4c-4578-a0eb-d46563cab999.notcs:line 49238 + at System.RuntimeMethodHandle.InvokeMethod(Object target, Void** arguments, Signature sig, Boolean isConstructor) + at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) + --- End of inner exception stack trace --- + at System.Reflection.MethodBaseInvoker.InvokeDirectByRefWithFewArgs(Object obj, Span`1 copyOfArgs, BindingFlags invokeAttr) + at System.Reflection.MethodBaseInvoker.InvokeWithFewArgs(Object obj, BindingFlags invokeAttr, Binder binder, Object[] parameters, CultureInfo culture) + at System.Reflection.MethodBase.Invoke(Object obj, Object[] parameters) + at BenchmarkDotNet.Autogenerated.UniqueProgramName.AfterAssemblyLoadingAttached(String[] args) in /_/benchmarks/Dapper.Tests.Performance/bin/Release/net8.0/5e0d07b1-6b4c-4578-a0eb-d46563cab999/5e0d07b1-6b4c-4578-a0eb-d46563cab999.notcs:line 57 +*/ [Description("PetaPoco")] public class PetaPocoBenchmarks : BenchmarkBase { @@ -38,4 +60,5 @@ public Post FetchFast() return _dbFast.Fetch("SELECT * from Posts where Id=@0", i).First(); } } +#endif } diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs index 6c16d60ce..10694ea79 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs @@ -2,6 +2,9 @@ using System.Linq; using BenchmarkDotNet.Attributes; using RepoDb; +using RepoDb.DbHelpers; +using RepoDb.DbSettings; +using RepoDb.StatementBuilders; namespace Dapper.Tests.Performance { @@ -13,6 +16,16 @@ public void Setup() { BaseSetup(); GlobalConfiguration.Setup().UseSqlServer(); + + // We need this since benchmarks using System.Data.SqlClient + var dbSetting = new SqlServerDbSetting(); + DbSettingMapper + .Add(dbSetting, true); + DbHelperMapper + .Add(new SqlServerDbHelper(), true); + StatementBuilderMapper + .Add(new SqlServerStatementBuilder(dbSetting), true); + ClassMapper.Add("Posts"); } diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 10ad41cff..52396c03e 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -3,7 +3,7 @@ Dapper.Tests.Performance Dapper Core Performance Suite Exe - net462;net5.0 + net462;net8.0 false $(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060 diff --git a/benchmarks/Dapper.Tests.Performance/app.config b/benchmarks/Dapper.Tests.Performance/app.config index 5ea174a5c..9e85395f8 100644 --- a/benchmarks/Dapper.Tests.Performance/app.config +++ b/benchmarks/Dapper.Tests.Performance/app.config @@ -1,6 +1,6 @@  - + \ No newline at end of file From d7c16035d2d6314bddcbdbbf6db4f35218f3ac9d Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 2 Jan 2024 15:32:04 +0000 Subject: [PATCH 261/312] fix codegen error in value-type multimap #2005 (#2022) in horizontal multi-map, don't "returnValueLocal = null" for value-types (invalid IL) fix #2005 --- Dapper/SqlMapper.cs | 11 ++++--- Directory.Build.props | 2 +- Directory.Packages.props | 30 +++++++++---------- appveyor.yml | 2 +- .../DapperCacheImpact.cs | 2 +- .../Dapper.Tests.Performance/LegacyTests.cs | 1 + global.json | 5 ++++ tests/Dapper.Tests/Dapper.Tests.csproj | 4 +-- tests/Dapper.Tests/MiscTests.cs | 11 +++++++ tests/Dapper.Tests/Providers/FirebirdTests.cs | 2 +- .../Dapper.Tests/Providers/PostgresqlTests.cs | 4 +-- 11 files changed, 47 insertions(+), 27 deletions(-) create mode 100644 global.json diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index b5b6c2ae4..e23502b77 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1944,7 +1944,7 @@ private static Func GetDeserializer(Type type, DbDataReade } return GetTypeDeserializer(type, reader, startBound, length, returnNullIfFirstMissing); } - return GetStructDeserializer(type, underlyingType ?? type, startBound, useGetFieldValue); + return GetSimpleValueDeserializer(type, underlyingType ?? type, startBound, useGetFieldValue); } private static Func GetHandlerDeserializer(ITypeHandler handler, Type type, int startBound) @@ -3049,7 +3049,7 @@ private static DbDataReader ExecuteReaderImpl(IDbConnection cnn, ref CommandDefi return paramReader; } - private static Func GetStructDeserializer(Type type, Type effectiveType, int index, bool useGetFieldValue) + private static Func GetSimpleValueDeserializer(Type type, Type effectiveType, int index, bool useGetFieldValue) { // no point using special per-type handling here; it boils down to the same, plus not all are supported anyway (see: SqlDataReader.GetChar - not supported!) #pragma warning disable 618 @@ -3578,8 +3578,11 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, if (first && returnNullIfFirstMissing) { il.Emit(OpCodes.Pop); - il.Emit(OpCodes.Ldnull); // stack is now [null] - il.Emit(OpCodes.Stloc, returnValueLocal); + if (!type.IsValueType) // for struct, the retval is already initialized as default + { + il.Emit(OpCodes.Ldnull); // stack is now [null] + il.Emit(OpCodes.Stloc, returnValueLocal); + } il.Emit(OpCodes.Br, allDone); } diff --git a/Directory.Build.props b/Directory.Build.props index e9ebe949c..6a4b94e1c 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,7 +21,7 @@ false true true - 11 + 12 false true readme.md diff --git a/Directory.Packages.props b/Directory.Packages.props index 2664f9ede..4ddd3896f 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,11 +1,10 @@ - - + @@ -13,34 +12,35 @@ - + - - - + + + + - + - + - - - - + + + + - + - - + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index 6197828f6..e766f8a28 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ skip_commits: - '**/*.md' install: - - choco install dotnet-sdk --version 7.0.402 + - choco install dotnet-sdk --version 8.0.100 environment: Appveyor: true diff --git a/benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs b/benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs index 9e17249bf..5386a2833 100644 --- a/benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs +++ b/benchmarks/Dapper.Tests.Performance/DapperCacheImpact.cs @@ -10,7 +10,7 @@ public class DapperCacheImpact : BenchmarkBase [GlobalSetup] public void Setup() => BaseSetup(); - private object args = new { Id = 42, Name = "abc" }; + private readonly object args = new { Id = 42, Name = "abc" }; public class Foo { diff --git a/benchmarks/Dapper.Tests.Performance/LegacyTests.cs b/benchmarks/Dapper.Tests.Performance/LegacyTests.cs index ebd899dfb..dd9588b43 100644 --- a/benchmarks/Dapper.Tests.Performance/LegacyTests.cs +++ b/benchmarks/Dapper.Tests.Performance/LegacyTests.cs @@ -128,6 +128,7 @@ private static void Try(Action action, string blame) } } + [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1806:Do not ignore method results", Justification = "Intentional - just make sure we have something")] public async Task RunAsync(int iterations) { using (var connection = GetOpenConnection()) diff --git a/global.json b/global.json new file mode 100644 index 000000000..f3365c418 --- /dev/null +++ b/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "8.0.100" + } +} \ No newline at end of file diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 8a003f7bd..5863bc8ed 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,9 +2,9 @@ Dapper.Tests Dapper Core Test Suite - net472;net6.0;net7.0 + net472;net6.0;net8.0 $(DefineConstants);MSSQLCLIENT - $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208 + $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208;CA1861 enable diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index aaaf8e978..cc762bf89 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -1310,5 +1310,16 @@ public HazGetOnlyAndCtor(int idProperty, string nameProperty) NameProperty = nameProperty; } } + + 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); + } } } diff --git a/tests/Dapper.Tests/Providers/FirebirdTests.cs b/tests/Dapper.Tests/Providers/FirebirdTests.cs index f63df6219..d234a8676 100644 --- a/tests/Dapper.Tests/Providers/FirebirdTests.cs +++ b/tests/Dapper.Tests/Providers/FirebirdTests.cs @@ -33,7 +33,7 @@ public void Issue178_Firebird() connection.Execute("insert into Issue178(id) values(42)"); // raw ADO.net using (var sqlCmd = new FbCommand(sql, connection)) - using (IDataReader reader1 = sqlCmd.ExecuteReader()) + using (var reader1 = sqlCmd.ExecuteReader()) { Assert.True(reader1.Read()); Assert.Equal(1, reader1.GetInt32(0)); diff --git a/tests/Dapper.Tests/Providers/PostgresqlTests.cs b/tests/Dapper.Tests/Providers/PostgresqlTests.cs index 32114370e..261490a53 100644 --- a/tests/Dapper.Tests/Providers/PostgresqlTests.cs +++ b/tests/Dapper.Tests/Providers/PostgresqlTests.cs @@ -49,7 +49,7 @@ public void TestPostgresqlArrayParameters() { using var conn = GetOpenNpgsqlConnection(); - IDbTransaction transaction = conn.BeginTransaction(); + var transaction = conn.BeginTransaction(); conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", Cats); @@ -66,7 +66,7 @@ public void TestPostgresqlListParameters() { using var conn = GetOpenNpgsqlConnection(); - IDbTransaction transaction = conn.BeginTransaction(); + var transaction = conn.BeginTransaction(); conn.Execute("create table tcat ( id serial not null, breed character varying(20) not null, name character varying (20) not null);"); conn.Execute("insert into tcat(breed, name) values(:Breed, :Name) ", new List(Cats)); From 8f369914ac4a03967fb68b3af6b0a85582473e3c Mon Sep 17 00:00:00 2001 From: Ohorodniichuk Dmytro Date: Sun, 7 Jan 2024 23:42:09 +0200 Subject: [PATCH 262/312] Issue #1164 (#1795) Fix unsigned types mapping arithmetic overflow Add tests to reproduce issue #1164 Co-authored-by: Dmytro Ohorodniichuk --- Dapper/SqlMapper.cs | 8 +++---- tests/Dapper.Tests/MiscTests.cs | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 41 insertions(+), 4 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index e23502b77..f0c909fce 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -3801,20 +3801,20 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro switch (Type.GetTypeCode(via ?? to)) { case TypeCode.Byte: - opCode = OpCodes.Conv_Ovf_I1_Un; break; + opCode = OpCodes.Conv_Ovf_U1_Un; break; case TypeCode.SByte: opCode = OpCodes.Conv_Ovf_I1; break; case TypeCode.UInt16: - opCode = OpCodes.Conv_Ovf_I2_Un; break; + opCode = OpCodes.Conv_Ovf_U2_Un; break; case TypeCode.Int16: opCode = OpCodes.Conv_Ovf_I2; break; case TypeCode.UInt32: - opCode = OpCodes.Conv_Ovf_I4_Un; break; + opCode = OpCodes.Conv_Ovf_U4_Un; break; case TypeCode.Boolean: // boolean is basically an int, at least at this level case TypeCode.Int32: opCode = OpCodes.Conv_Ovf_I4; break; case TypeCode.UInt64: - opCode = OpCodes.Conv_Ovf_I8_Un; break; + opCode = OpCodes.Conv_Ovf_U8_Un; break; case TypeCode.Int64: opCode = OpCodes.Conv_Ovf_I8; break; case TypeCode.Single: diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index cc762bf89..9028919ea 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -1309,6 +1309,43 @@ 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; } internal record struct One(int OID); From 3904675a7893cd690695a8b424e8d365a0c91a7b Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 13 Feb 2024 16:27:00 +0000 Subject: [PATCH 263/312] CI: use preinstalled sdk (#2042) * CI: use preinstalled sdk * package updates, in particular SQL Server CVE (tests only) --- Directory.Packages.props | 24 ++++++++++++------------ appveyor.yml | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4ddd3896f..4c55df6f4 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -12,35 +12,35 @@ - - + + - + - - - - + + + + - + - + - + - + - + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index e766f8a28..08c5eb159 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,8 +6,8 @@ skip_commits: files: - '**/*.md' -install: - - choco install dotnet-sdk --version 8.0.100 +# install: +# - choco install dotnet-sdk --version 8.0.100 environment: Appveyor: true From 4f8e92fba53e789bf55d3054b30b3a5555a40363 Mon Sep 17 00:00:00 2001 From: "Kasper B. Graversen" Date: Thu, 15 Feb 2024 08:30:52 +0100 Subject: [PATCH 264/312] Exposing the transaction used in class Database. (#2038) This enables to do queries not supported by rainbow within the same transaction Closes #1944 --- Dapper.Rainbow/Database.Async.cs | 8 ++++---- Dapper.Rainbow/Database.cs | 32 +++++++++++++++++++------------- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/Dapper.Rainbow/Database.Async.cs b/Dapper.Rainbow/Database.Async.cs index cdbf7e41f..e1a78c47b 100644 --- a/Dapper.Rainbow/Database.Async.cs +++ b/Dapper.Rainbow/Database.Async.cs @@ -88,7 +88,7 @@ public Task> AllAsync() => /// The parameters to use. /// The number of rows affected. public Task ExecuteAsync(string sql, dynamic param = null) => - _connection.ExecuteAsync(sql, param as object, _transaction, _commandTimeout); + _connection.ExecuteAsync(sql, param as object, Transaction, _commandTimeout); /// /// Asynchronously queries the current database. @@ -98,7 +98,7 @@ public Task ExecuteAsync(string sql, dynamic param = null) => /// The parameters to use. /// An enumerable of for the rows fetched. public Task> QueryAsync(string sql, dynamic param = null) => - _connection.QueryAsync(sql, param as object, _transaction, _commandTimeout); + _connection.QueryAsync(sql, param as object, Transaction, _commandTimeout); /// /// Asynchronously queries the current database for a single record. @@ -108,7 +108,7 @@ public Task> QueryAsync(string sql, dynamic param = null) => /// The parameters to use. /// An enumerable of for the rows fetched. public Task QueryFirstOrDefaultAsync(string sql, dynamic param = null) => - _connection.QueryFirstOrDefaultAsync(sql, param as object, _transaction, _commandTimeout); + _connection.QueryFirstOrDefaultAsync(sql, param as object, Transaction, _commandTimeout); /// /// Perform an asynchronous multi-mapping query with 2 input types. @@ -195,7 +195,7 @@ public Task> QueryAsyncThe parameters to use. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public Task> QueryAsync(string sql, dynamic param = null) => - _connection.QueryAsync(sql, param as object, _transaction); + _connection.QueryAsync(sql, param as object, Transaction); /// /// Execute a command that returns multiple result sets, and access each in turn. diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index 670f22631..58d7298b1 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -175,7 +175,11 @@ public Table(Database database, string likelyTableName) private DbConnection _connection; private int _commandTimeout; - private DbTransaction _transaction; + + /// + /// Get access to the underlying transaction + /// + public DbTransaction Transaction { get; } /// /// Get underlying database connection. @@ -215,9 +219,11 @@ internal virtual Action CreateTableConstructorForTable() /// Begins a transaction in this database. /// /// The isolation level to use. - public void BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommitted) + /// The transaction created + public DbTransaction BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommitted) { - _transaction = _connection.BeginTransaction(isolation); + Transaction = _connection.BeginTransaction(isolation); + return Transaction; } /// @@ -225,8 +231,8 @@ public void BeginTransaction(IsolationLevel isolation = IsolationLevel.ReadCommi /// public void CommitTransaction() { - _transaction.Commit(); - _transaction = null; + Transaction.Commit(); + Transaction = null; } /// @@ -234,8 +240,8 @@ public void CommitTransaction() /// public void RollbackTransaction() { - _transaction.Rollback(); - _transaction = null; + Transaction.Rollback(); + Transaction = null; } /// @@ -336,7 +342,7 @@ private bool TableExists(string name) if (!string.IsNullOrEmpty(schemaName)) builder.Append("TABLE_SCHEMA = @schemaName AND "); builder.Append("TABLE_NAME = @name"); - return _connection.Query(builder.ToString(), new { schemaName, name }, _transaction).Count() == 1; + return _connection.Query(builder.ToString(), new { schemaName, name }, Transaction).Count() == 1; } /// @@ -346,7 +352,7 @@ private bool TableExists(string name) /// The parameters to use. /// The number of rows affected. public int Execute(string sql, dynamic param = null) => - _connection.Execute(sql, param as object, _transaction, _commandTimeout); + _connection.Execute(sql, param as object, Transaction, _commandTimeout); /// /// Queries the current database. @@ -357,7 +363,7 @@ public int Execute(string sql, dynamic param = null) => /// Whether to buffer the results. /// An enumerable of for the rows fetched. public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) => - _connection.Query(sql, param as object, _transaction, buffered, _commandTimeout); + _connection.Query(sql, param as object, Transaction, buffered, _commandTimeout); /// /// Queries the current database for a single record. @@ -367,7 +373,7 @@ public IEnumerable Query(string sql, dynamic param = null, bool buffered = /// The parameters to use. /// An enumerable of for the rows fetched. public T QueryFirstOrDefault(string sql, dynamic param = null) => - _connection.QueryFirstOrDefault(sql, param as object, _transaction, _commandTimeout); + _connection.QueryFirstOrDefault(sql, param as object, Transaction, _commandTimeout); /// /// Perform a multi-mapping query with 2 input types. @@ -455,7 +461,7 @@ public IEnumerable QueryWhether the results should be buffered in memory. /// Note: each row can be accessed via "dynamic", or by casting to an IDictionary<string,object> public IEnumerable Query(string sql, dynamic param = null, bool buffered = true) => - _connection.Query(sql, param as object, _transaction, buffered); + _connection.Query(sql, param as object, Transaction, buffered); /// /// Execute a command that returns multiple result sets, and access each in turn. @@ -477,7 +483,7 @@ public virtual void Dispose() if (connection.State != ConnectionState.Closed) { _connection = null; - _transaction = null; + Transaction = null; connection?.Close(); } GC.SuppressFinalize(this); From 7d811c466540a9a35d0317982f010f367d4e889a Mon Sep 17 00:00:00 2001 From: "Kasper B. Graversen" Date: Sat, 17 Feb 2024 01:16:42 +0100 Subject: [PATCH 265/312] dapper.rainbow guide (#2043) --- Dapper.Rainbow/readme.md | 118 +++++++++++++++++++++++++++++++++++++++ Readme.md | 2 +- 2 files changed, 119 insertions(+), 1 deletion(-) create mode 100644 Dapper.Rainbow/readme.md diff --git a/Dapper.Rainbow/readme.md b/Dapper.Rainbow/readme.md new file mode 100644 index 000000000..c1f34b67a --- /dev/null +++ b/Dapper.Rainbow/readme.md @@ -0,0 +1,118 @@ +# Using Dapper.Rainbow in C# for CRUD Operations + +This guide outlines how to use `Dapper.Rainbow` in C# for CRUD operations. + +## 1. Setting Up + +Add Dapper and Dapper.Rainbow to your project via NuGet: + +```powershell +Install-Package Dapper -Version x.x.x +Install-Package Dapper.Rainbow -Version x.x.x +``` + +*Replace `x.x.x` with the latest version numbers.* + +## 2. Database Setup and Requirements + +For `Dapper.Rainbow` to function correctly, ensure each table has a primary key column named `Id`. + +Example `Users` table schema: + +```sql +CREATE TABLE Users ( + Id INT IDENTITY(1,1) PRIMARY KEY, + Name VARCHAR(100), + Email VARCHAR(100) +); +``` + +## 3. Establishing Database Connection + +Open a connection to your database: + +```csharp +using System.Data.SqlClient; + +var connectionString = "your_connection_string_here"; +using var connection = new SqlConnection(connectionString); +connection.Open(); // Open the connection +``` + +## 4. Defining Your Database Context + +Define a class for your database context: + +```csharp +using Dapper; +using System.Data; + +public class MyDatabase : Database +{ + public Table Users { get; set; } +} + +public class User +{ + public int Id { get; set; } + public string Name { get; set; } + public string Email { get; set; } +} +``` + +## 5. Performing CRUD Operations + +### Insert + +```csharp +var db = new MyDatabase { Connection = connection }; +var newUser = new User { Name = "John Doe", Email = "john.doe@example.com" }; +var insertedUser = db.Users.Insert(newUser); +``` + +### Select + +Fetch users by ID or all users: + +```csharp +var user = db.Users.Get(id); // Single user by ID +var users = connection.Query("SELECT * FROM Users"); // All users +``` + +### Update + +```csharp +var userToUpdate = db.Users.Get(id); +userToUpdate.Email = "new.email@example.com"; +db.Users.Update(userToUpdate); +``` + +### Delete + +```csharp +db.Users.Delete(id); +``` + +## 6. Working with Foreign Keys + +Example schema for a `Posts` table with a foreign key to `Users`: + +```sql +CREATE TABLE Posts ( + Id INT IDENTITY(1,1) PRIMARY KEY, + UserId INT, + Content VARCHAR(255), + FOREIGN KEY (UserId) REFERENCES Users(Id) +); +``` + +Inserting a parent (`User`) and a child (`Post`) row: + +```csharp +var newUser = new User { Name = "Jane Doe", Email = "jane.doe@example.com" }; +var userId = db.Users.Insert(newUser); + +var newPost = new Post { UserId = userId, Content = "Hello, World!" }; +db.Connection.Insert(newPost); // Using Dapper for the child table +``` + diff --git a/Readme.md b/Readme.md index 5991d7109..5f9202dc3 100644 --- a/Readme.md +++ b/Readme.md @@ -28,7 +28,7 @@ Package Purposes: * Dapper.EntityFramework.StrongName * Extension handlers for EntityFramework * Dapper.Rainbow - * Micro-ORM implemented on Dapper, provides CRUD helpers + * Micro-ORM implemented on Dapper, provides CRUD helpers ([readme](Dapper.Rainbow\readme.md)) * Dapper.SqlBuilder * Component for building SQL queries dynamically and composably From 108806565d8acd49599c0a49c63ec722b403aa80 Mon Sep 17 00:00:00 2001 From: Daryl Johnston Date: Sat, 17 Feb 2024 08:03:01 +0000 Subject: [PATCH 266/312] Issue #2044 (#2045) --- Dapper.Rainbow/Database.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dapper.Rainbow/Database.cs b/Dapper.Rainbow/Database.cs index 58d7298b1..74ab9ba2f 100644 --- a/Dapper.Rainbow/Database.cs +++ b/Dapper.Rainbow/Database.cs @@ -179,7 +179,7 @@ public Table(Database database, string likelyTableName) /// /// Get access to the underlying transaction /// - public DbTransaction Transaction { get; } + public DbTransaction Transaction { get; private set; } /// /// Get underlying database connection. From 8d2cb0cf35aa612770c932c8ca29fb4129c03915 Mon Sep 17 00:00:00 2001 From: Ion Dormenco Date: Sun, 18 Feb 2024 17:14:08 +0200 Subject: [PATCH 267/312] Update Readme.md (#2047) Fix path to Dapper.Rainbow readme --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 5f9202dc3..8c8be6bd7 100644 --- a/Readme.md +++ b/Readme.md @@ -28,7 +28,7 @@ Package Purposes: * Dapper.EntityFramework.StrongName * Extension handlers for EntityFramework * Dapper.Rainbow - * Micro-ORM implemented on Dapper, provides CRUD helpers ([readme](Dapper.Rainbow\readme.md)) + * Micro-ORM implemented on Dapper, provides CRUD helpers ([readme](Dapper.Rainbow/readme.md)) * Dapper.SqlBuilder * Component for building SQL queries dynamically and composably From 360367ca5475425944fb390433cb0f1817ad2dcb Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 6 Mar 2024 14:59:19 +0000 Subject: [PATCH 268/312] add REVERT to CommandText checklist (#2048) --- Dapper/CompiledRegex.cs | 2 +- tests/Dapper.Tests/ProcedureTests.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Dapper/CompiledRegex.cs b/Dapper/CompiledRegex.cs index 5deef70f1..faffb7c9a 100644 --- a/Dapper/CompiledRegex.cs +++ b/Dapper/CompiledRegex.cs @@ -9,7 +9,7 @@ internal static partial class CompiledRegex [StringSyntax("Regex")] #endif private const string - WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$|^commit$|^rollback$", + WhitespaceOrReservedPattern = @"[\s;/\-+*]|^vacuum$|^commit$|^rollback$|^revert$", LegacyParameterPattern = @"(? Date: Wed, 6 Mar 2024 20:58:21 +0000 Subject: [PATCH 269/312] string and byte[] : add UseGetFieldValue (#2050) * string and byte[] : add UseGetFieldValue to prevent problems with npgsql using global deserializer * switch to GetFieldValue changes some messaging in invalid cast scenarios; if anything, new version is clearer, so: fine * test fixes --- Dapper.sln | 1 + Dapper/SqlMapper.cs | 14 ++++++++++---- global.json | 3 ++- tests/Dapper.Tests/MiscTests.cs | 2 +- tests/Dapper.Tests/TypeHandlerTests.cs | 10 +++++----- 5 files changed, 19 insertions(+), 11 deletions(-) diff --git a/Dapper.sln b/Dapper.sln index 7a610ba6a..a0e6bd769 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -15,6 +15,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution global.json = global.json docs\index.md = docs\index.md License.txt = License.txt + .github\workflows\main.yml = .github\workflows\main.yml nuget.config = nuget.config Readme.md = Readme.md version.json = version.json diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index f0c909fce..57d84edc1 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -192,7 +192,9 @@ public TypeMapEntry(DbType dbType, TypeMapEntryFlags flags) public bool Equals(TypeMapEntry other) => other.DbType == DbType && other.Flags == Flags; public static readonly TypeMapEntry DoNotSet = new((DbType)(-2), TypeMapEntryFlags.None), - DecimalFieldValue = new(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); + DecimalFieldValue = new(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue), + StringFieldValue = new(DbType.String, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue), + BinaryFieldValue = new(DbType.Binary, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); public static implicit operator TypeMapEntry(DbType dbType) => new(dbType, TypeMapEntryFlags.SetType); @@ -214,13 +216,13 @@ static SqlMapper() [typeof(double)] = DbType.Double, [typeof(decimal)] = DbType.Decimal, [typeof(bool)] = DbType.Boolean, - [typeof(string)] = DbType.String, + [typeof(string)] = TypeMapEntry.StringFieldValue, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(DateTime)] = TypeMapEntry.DoNotSet, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(TimeSpan)] = TypeMapEntry.DoNotSet, - [typeof(byte[])] = DbType.Binary, + [typeof(byte[])] = TypeMapEntry.BinaryFieldValue, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, [typeof(short?)] = DbType.Int16, @@ -3905,7 +3907,11 @@ public static void ThrowDataException(Exception ex, int index, IDataReader reade } try { - if (value is null || value is DBNull) + if (value is null && ex is InvalidCastException) + { + formattedValue = "n/a - " + ex.Message; // provide some context + } + else if (value is null || value is DBNull) { formattedValue = ""; } diff --git a/global.json b/global.json index f3365c418..7da276347 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "8.0.100" + "version": "8.0.100", + "rollForward": "latestMajor" } } \ No newline at end of file diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 9028919ea..7f5848565 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -265,7 +265,7 @@ static async Task TestExceptionsAsync(DbConnection connection, string sql, st await TestExceptionsAsync( connection, "Select null as Foo", - "Error parsing column 0 (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, diff --git a/tests/Dapper.Tests/TypeHandlerTests.cs b/tests/Dapper.Tests/TypeHandlerTests.cs index 2e754ca46..80c3292ff 100644 --- a/tests/Dapper.Tests/TypeHandlerTests.cs +++ b/tests/Dapper.Tests/TypeHandlerTests.cs @@ -28,12 +28,12 @@ public void TestChangingDefaultStringTypeMappingToAnsiString() SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString + SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, true); // Change Default String Handling to AnsiString var result02 = connection.Query(sql, param).FirstOrDefault(); Assert.Equal("varchar", result02); SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String + SqlMapper.AddTypeMap(typeof(string), DbType.String, true); // Restore Default to Unicode String } [Fact] @@ -47,12 +47,12 @@ public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault() SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString); // Change Default String Handling to AnsiString + SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, true); // Change Default String Handling to AnsiString var result02 = connection.QueryFirstOrDefault(sql, param); Assert.Equal("varchar", result02); SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.String); // Restore Default to Unicode String + SqlMapper.AddTypeMap(typeof(string), DbType.String, true); // Restore Default to Unicode String } [Fact] @@ -643,7 +643,7 @@ public void Issue149_TypeMismatch_SequentialAccess() { Guid guid = Guid.Parse("cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e"); var ex = Assert.ThrowsAny(() => connection.Query("select @guid as Id", new { guid }).First()); - Assert.Equal("Error parsing column 0 (Id=cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e - Object)", ex.Message); + Assert.Equal("Error parsing column 0 (Id=n/a - Unable to cast object of type 'System.Guid' to type 'System.String'.)", ex.Message); } public class Issue149_Person { public string? Id { get; set; } } From 402f20ce37e719a23b4f4a507391b65f12cd8657 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 7 Mar 2024 12:19:30 +0000 Subject: [PATCH 270/312] implement DateOnly/TimeOnly (#2051) fix #1715 adds net6 TFM --- Dapper/Dapper.csproj | 2 +- Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt | 6 ++ .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 + Dapper/SqlMapper.cs | 55 +++++++++------ tests/Dapper.Tests/DateTimeOnlyTests.cs | 67 +++++++++++++++++++ tests/Dapper.Tests/MiscTests.cs | 2 +- 6 files changed, 109 insertions(+), 24 deletions(-) create mode 100644 Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt create mode 100644 Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt create mode 100644 tests/Dapper.Tests/DateTimeOnlyTests.cs diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 8fc9c65f8..0ede9f790 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,7 +5,7 @@ orm;sql;micro-orm A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0;net5.0;net7.0 + net461;netstandard2.0;net5.0;net6.0;net7.0 enable true diff --git a/Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..5da46e604 --- /dev/null +++ b/Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt @@ -0,0 +1,6 @@ +#nullable enable +Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! \ No newline at end of file diff --git a/Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt new file mode 100644 index 000000000..91b0e1a43 --- /dev/null +++ b/Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +#nullable enable \ No newline at end of file diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 57d84edc1..5575a6f5f 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -192,6 +192,7 @@ public TypeMapEntry(DbType dbType, TypeMapEntryFlags flags) public bool Equals(TypeMapEntry other) => other.DbType == DbType && other.Flags == Flags; public static readonly TypeMapEntry DoNotSet = new((DbType)(-2), TypeMapEntryFlags.None), + DoNotSetFieldValue = new((DbType)(-2), TypeMapEntryFlags.UseGetFieldValue), DecimalFieldValue = new(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue), StringFieldValue = new(DbType.String, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue), BinaryFieldValue = new(DbType.Binary, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); @@ -202,7 +203,11 @@ public static implicit operator TypeMapEntry(DbType dbType) static SqlMapper() { - typeMap = new Dictionary(41) + typeMap = new Dictionary(41 +#if NET6_0_OR_GREATER + + 4 // {Date|Time}Only[?] +#endif + ) { [typeof(byte)] = DbType.Byte, [typeof(sbyte)] = DbType.SByte, @@ -245,6 +250,12 @@ static SqlMapper() [typeof(SqlDecimal?)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney?)] = TypeMapEntry.DecimalFieldValue, +#if NET6_0_OR_GREATER + [typeof(DateOnly)] = TypeMapEntry.DoNotSetFieldValue, + [typeof(TimeOnly)] = TypeMapEntry.DoNotSetFieldValue, + [typeof(DateOnly?)] = TypeMapEntry.DoNotSetFieldValue, + [typeof(TimeOnly?)] = TypeMapEntry.DoNotSetFieldValue, +#endif }; ResetTypeHandlers(false); } @@ -257,7 +268,7 @@ static SqlMapper() [MemberNotNull(nameof(typeHandlers))] private static void ResetTypeHandlers(bool clone) { - typeHandlers = new Dictionary(); + typeHandlers = []; AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); @@ -370,10 +381,10 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clo var newCopy = clone ? new Dictionary(snapshot) : snapshot; #pragma warning disable 618 - typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, new object?[] { handler }); + typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); if (secondary is not null) { - typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, new object?[] { handler }); + typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); } #pragma warning restore 618 if (handler is null) @@ -1240,7 +1251,7 @@ internal enum Row SingleOrDefault = 3 } - private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = Array.Empty(); + private static readonly int[] ErrTwoRows = new int[2], ErrZeroRows = []; private static void ThrowMultipleRows(Row row) { _ = row switch @@ -2538,7 +2549,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true filterParams = !CompiledRegex.LegacyParameter.IsMatch(identity.Sql); } - var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, new[] { typeof(IDbCommand), typeof(object) }, type, true); + var dm = new DynamicMethod("ParamInfo" + Guid.NewGuid().ToString(), null, [typeof(IDbCommand), typeof(object)], type, true); var il = dm.GetILGenerator(); @@ -2909,7 +2920,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true { if (locals is null) { - locals = new Dictionary(); + locals = []; local = null; } else @@ -2946,14 +2957,14 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true { typeof(bool), typeof(sbyte), typeof(byte), typeof(ushort), typeof(short), typeof(uint), typeof(int), typeof(ulong), typeof(long), typeof(float), typeof(double), typeof(decimal) - }.ToDictionary(x => Type.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), new[] { typeof(IFormatProvider) })!); + }.ToDictionary(x => Type.GetTypeCode(x), x => x.GetPublicInstanceMethod(nameof(object.ToString), [typeof(IFormatProvider)])!); private static MethodInfo? GetToString(TypeCode typeCode) { return toStrings.TryGetValue(typeCode, out MethodInfo? method) ? method : null; } - private static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), new Type[] { typeof(string), typeof(string) })!, + private static readonly MethodInfo StringReplace = typeof(string).GetPublicInstanceMethod(nameof(string.Replace), [typeof(string), typeof(string)])!, InvariantCulture = typeof(CultureInfo).GetProperty(nameof(CultureInfo.InvariantCulture), BindingFlags.Public | BindingFlags.Static)!.GetGetMethod()!; private static int ExecuteCommand(IDbConnection cnn, ref CommandDefinition command, Action? paramReader) @@ -3117,7 +3128,7 @@ static Func ReadViaGetFieldValueFactory(Type type, int ind return factory(index); } // cache of ReadViaGetFieldValueFactory for per-value T - static readonly Hashtable s_ReadViaGetFieldValueCache = new(); + static readonly Hashtable s_ReadViaGetFieldValueCache = []; static Func UnderlyingReadViaGetFieldValueFactory(int index) => reader => reader.IsDBNull(index) ? null! : reader.GetFieldValue(index)!; @@ -3147,14 +3158,14 @@ private static T Parse(object? value) } private static readonly MethodInfo - enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), new Type[] { typeof(Type), typeof(string), typeof(bool) })!, + enumParse = typeof(Enum).GetMethod(nameof(Enum.Parse), [typeof(Type), typeof(string), typeof(bool)])!, getItem = typeof(DbDataReader).GetProperties(BindingFlags.Instance | BindingFlags.Public) .Where(p => p.GetIndexParameters().Length > 0 && p.GetIndexParameters()[0].ParameterType == typeof(int)) .Select(p => p.GetGetMethod()).First()!, getFieldValueT = typeof(DbDataReader).GetMethod(nameof(DbDataReader.GetFieldValue), - BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null)!, + BindingFlags.Instance | BindingFlags.Public, null, [typeof(int)], null)!, isDbNull = typeof(DbDataReader).GetMethod(nameof(DbDataReader.IsDBNull), - BindingFlags.Instance | BindingFlags.Public, null, new Type[] { typeof(int) }, null)!; + BindingFlags.Instance | BindingFlags.Public, null, [typeof(int)], null)!; /// /// Gets type-map for the given type @@ -3191,7 +3202,7 @@ public static ITypeMap GetTypeMap(Type type) } // use Hashtable to get free lockless reading - private static readonly Hashtable _typeMaps = new(); + private static readonly Hashtable _typeMaps = []; /// /// Set custom mapping for type deserializers @@ -3263,7 +3274,7 @@ public static Func GetTypeDeserializer( private static LocalBuilder GetTempLocal(ILGenerator il, ref Dictionary? locals, Type type, bool initAndLoad) { if (type is null) throw new ArgumentNullException(nameof(type)); - locals ??= new Dictionary(); + locals ??= []; if (!locals.TryGetValue(type, out LocalBuilder? found)) { found = il.DeclareLocal(type); @@ -3294,7 +3305,7 @@ private static Func GetTypeDeserializerImpl( } var returnType = type.IsValueType ? typeof(object) : type; - var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, new[] { typeof(DbDataReader) }, type, true); + var dm = new DynamicMethod("Deserialize" + Guid.NewGuid().ToString(), returnType, [typeof(DbDataReader)], type, true); var il = dm.GetILGenerator(); if (IsValueTuple(type)) @@ -3403,7 +3414,7 @@ private static void GenerateValueTupleDeserializer(Type valueTupleType, DbDataRe if (nullableUnderlyingType is not null) { - var nullableTupleConstructor = valueTupleType.GetConstructor(new[] { nullableUnderlyingType }); + var nullableTupleConstructor = valueTupleType.GetConstructor([nullableUnderlyingType]); il.Emit(OpCodes.Newobj, nullableTupleConstructor!); } @@ -3668,7 +3679,7 @@ private static void LoadReaderValueViaGetFieldValue(ILGenerator il, int index, T if (underlyingType != memberType) { // Nullable; wrap it - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { underlyingType })!); // stack is now [...][T?] + il.Emit(OpCodes.Newobj, memberType.GetConstructor([underlyingType])!); // stack is now [...][T?] } } @@ -3731,13 +3742,13 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind if (nullUnderlyingType is not null) { - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new[] { nullUnderlyingType })!); // stack is now [...][typed-value] + il.Emit(OpCodes.Newobj, memberType.GetConstructor([nullUnderlyingType])!); // stack is now [...][typed-value] } } else if (memberType.FullName == LinqBinary) { il.Emit(OpCodes.Unbox_Any, typeof(byte[])); // stack is now [...][byte-array] - il.Emit(OpCodes.Newobj, memberType.GetConstructor(new Type[] { typeof(byte[]) })!);// stack is now [...][binary] + il.Emit(OpCodes.Newobj, memberType.GetConstructor([typeof(byte[])])!);// stack is now [...][binary] } else { @@ -3762,7 +3773,7 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind FlexibleConvertBoxedFromHeadOfStack(il, colType, nullUnderlyingType ?? unboxType, null); if (nullUnderlyingType is not null) { - il.Emit(OpCodes.Newobj, unboxType.GetConstructor(new[] { nullUnderlyingType })!); // stack is now [...][typed-value] + il.Emit(OpCodes.Newobj, unboxType.GetConstructor([nullUnderlyingType])!); // stack is now [...][typed-value] } } } @@ -3846,7 +3857,7 @@ private static void FlexibleConvertBoxedFromHeadOfStack(ILGenerator il, Type fro il.Emit(OpCodes.Ldtoken, via ?? to); // stack is now [target][target][value][member-type-token] il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null); // stack is now [target][target][value][member-type] il.EmitCall(OpCodes.Call, InvariantCulture, null); // stack is now [target][target][value][member-type][culture] - il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), new Type[] { typeof(object), typeof(Type), typeof(IFormatProvider) })!, null); // stack is now [target][target][boxed-member-type-value] + il.EmitCall(OpCodes.Call, typeof(Convert).GetMethod(nameof(Convert.ChangeType), [typeof(object), typeof(Type), typeof(IFormatProvider)])!, null); // stack is now [target][target][boxed-member-type-value] il.Emit(OpCodes.Unbox_Any, to); // stack is now [target][target][typed-value] } } diff --git a/tests/Dapper.Tests/DateTimeOnlyTests.cs b/tests/Dapper.Tests/DateTimeOnlyTests.cs new file mode 100644 index 000000000..015af2d51 --- /dev/null +++ b/tests/Dapper.Tests/DateTimeOnlyTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Threading.Tasks; +using Xunit; + +#if NET6_0_OR_GREATER +namespace Dapper.Tests; + +/* we do **NOT** expect this to work against System.Data +[Collection("DateTimeOnlyTests")] +public sealed class SystemSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } +*/ +#if MSSQLCLIENT +[Collection("DateTimeOnlyTests")] +public sealed class MicrosoftSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } +#endif +public abstract class DateTimeOnlyTests : TestBase where TProvider : DatabaseProvider +{ + public class HazDateTimeOnly + { + public DateOnly Date { get; set; } + public TimeOnly Time { get; set; } + } + + [Fact] + public void TypedInOut() + { + var now = DateTime.Now; + var args = new HazDateTimeOnly + { + Date = DateOnly.FromDateTime(now), + Time = TimeOnly.FromDateTime(now), + }; + var row = connection.QuerySingle("select @date as [Date], @time as [Time]", args); + Assert.Equal(args.Date, row.Date); + Assert.Equal(args.Time, row.Time); + } + + [Fact] + public async Task TypedInOutAsync() + { + var now = DateTime.Now; + var args = new HazDateTimeOnly + { + Date = DateOnly.FromDateTime(now), + Time = TimeOnly.FromDateTime(now), + }; + var row = await connection.QuerySingleAsync("select @date as [Date], @time as [Time]", args); + Assert.Equal(args.Date, row.Date); + Assert.Equal(args.Time, row.Time); + } + + [Fact] + public void UntypedInOut() + { + var now = DateTime.Now; + var args = new DynamicParameters(); + var date = DateOnly.FromDateTime(now); + var time = TimeOnly.FromDateTime(now); + args.Add("date", date); + args.Add("time", time); + var row = connection.QuerySingle("select @date as [Date], @time as [Time]", args); + // untyped, observation is that these come back as DateTime and TimeSpan + Assert.Equal(date, DateOnly.FromDateTime((DateTime)row.Date)); + Assert.Equal(time, TimeOnly.FromTimeSpan((TimeSpan)row.Time)); + } +} +#endif diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 7f5848565..1bf4bbfb0 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -1345,7 +1345,7 @@ public void Issue1164_OverflowExceptionForUInt64() private class Issue1164Object { - public T Value; + public T Value = default!; } internal record struct One(int OID); From 803e3510ea50fc7012c2aa050fd484b1a7d2e62b Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 09:13:24 +0100 Subject: [PATCH 271/312] Dapper Plus (#2069) add Dapper Plus as sponsor --- Build.csproj | 2 +- Dapper.StrongName/Dapper.StrongName.csproj | 2 +- Dapper.sln | 11 +++++++---- Dapper/Dapper.csproj | 2 +- Readme.md | 3 +++ docs/dapper-sponsor.png | Bin 0 -> 11112 bytes docs/dapperplus.png | Bin 0 -> 5543 bytes docs/docs.csproj | 6 ++++++ docs/readme.md | 14 +++++++++++++- 9 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 docs/dapper-sponsor.png create mode 100644 docs/dapperplus.png create mode 100644 docs/docs.csproj diff --git a/Build.csproj b/Build.csproj index f2f2d0d6b..c2ed16c13 100644 --- a/Build.csproj +++ b/Build.csproj @@ -1,7 +1,7 @@ - + \ No newline at end of file diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index ef28e32b4..770b7ce7a 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -3,7 +3,7 @@ Dapper.StrongName orm;sql;micro-orm Dapper (Strong Named) - A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. + A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0;net5.0;net7.0 true diff --git a/Dapper.sln b/Dapper.sln index a0e6bd769..7df2cba0d 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -49,10 +49,8 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" - ProjectSection(SolutionItems) = preProject - docs\index.md = docs\index.md - docs\readme.md = docs\readme.md - EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -96,6 +94,10 @@ Global {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -110,6 +112,7 @@ Global {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} {B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F} = {9D960D4D-80A2-4DAC-B386-8F4235EC73E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 0ede9f790..28951f871 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -3,7 +3,7 @@ Dapper Dapper orm;sql;micro-orm - A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc.. + A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver net461;netstandard2.0;net5.0;net6.0;net7.0 enable diff --git a/Readme.md b/Readme.md index 8c8be6bd7..e5fbd8aec 100644 --- a/Readme.md +++ b/Readme.md @@ -38,8 +38,11 @@ Sponsors Dapper was originally developed for and by Stack Overflow, but is F/OSS. Sponsorship is welcome and invited - see the sponsor link at the top of the page. A huge thanks to everyone (individuals or organisations) who have sponsored Dapper, but a massive thanks in particular to: +- [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper (from 2024 onwards) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) +Dapper Plus logo + Features -------- Dapper is a [NuGet library](https://www.nuget.org/packages/Dapper) that you can add in to your project that will enhance your ADO.NET connections via diff --git a/docs/dapper-sponsor.png b/docs/dapper-sponsor.png new file mode 100644 index 0000000000000000000000000000000000000000..f3c64f7f8f38630d4219755c1aafc7fd3061b4bf GIT binary patch literal 11112 zcmd^lWmsHWvt~CE+}$;S;O>&(1P$(V(>RTH zQ6JvxcgIH_PFQX##-0EGe(&!WFo_eN=0PY0Q#69fyE;4DxWE8D9un3P3eT0q-MmHl z1#O)q`1!dUB?Lr;g@wdL#KnXK1;qtK#f8O1xL^_jLVN-OBK#uz>2_BD0NS~Oo)OGQ zLtO&m>dXVScD1tM@o{#0nBZXwABl%cXB!xp(Z?C;;wj-H&Gx5;#KZM(F)y18Hsikz zDGzH~2^|Hcf2lrvl4i4q!Q3QxdA+^8dAtRATs`b~`NhS>dHDo*1q8SsG`Kx|U0`4z zZWm9M2c*A4DcE>IJRIC$4z4bYzoEfau5g$%8{1!6|KXA~IpxBR3)x z_kh^^djb3lP~O7^470J85#SRL;^q_J77&r*{m0N>Ih6X>fT9mf)mrAkKoBe-Xv1y8 z4;JDU5)|g=789`H=N1NoA)-Q}HnyU+;(uZNhsl3KQnGpg5*82^5D^yV7vg^?dNIDg zZ2lblZzg)KaESfyuw(@O()=&kf2sd3()@S2{xj6S)B67?(Qf~?sD5Yp&(-=nzYhZs z`4#+Iejj8K&*3n8R}UEtu!n;UH`Eo*sOMqhYy}1Ta?ACUSc5&RrGA_Kr`kV4{l72F zhwkyWs{G4eAF$hhtH%dri9c({)kDwK6)GbScJTsB@%~f%zjN|Gd(l619=g)M6*TW( zT}>uj2*L{hpaQhj^%PG}Pw(#ThA;u|2!1!?+uK_JfZ*!t>iqot^78WT_V)Pb=+UD` z9Y7!o3d$D%AU8KRJ3D)4X9pFPs168RSXij3siC8z>+kO;B_&NvOze5ECm?7A0%L@$vB=KYny}c0RODO-)@qJiN8FH9I>y9UYyA#!5x~Bqb$9Nl7UIACsJ% zd;kN$#KhDO1V#~Ht*oqYb91w@vd+xR^kD*2RkhgI1Tyh3c6WEx)YPhhKn6w*PA*|S ze(?@0l(DffDk`er;9ycxrXpN)Cnu){AW%_JorQ%bIy(A23BHbwAt5164K`{?Nr|to zFCQOYYisN9@bC~Wx{ZxZdwWL@I=YmUl!=MSvuDcX<>ee491;?8fq{YLIB1iTQ>Lb7 z%_u0JKYxy)A}s|1=jP^4j*o|ih9V;)y8r+Y5g80j^1i-4dwct%pFf{Hdv@)u=t3Ak|i!2|ByROK%Sh;BMjorsYy3Cw;vlD2n1q6 zjDIsP?|5aU!px`$1!c9r|K{>Cm51XiJ9`5*>TYYZw6wz6;o(|DM2!t(XK`U8E)E8R z9gL0~PEDR{Y;g047HX&>Kp+nY?53a?OiONebE~GJnlsYbuBuq+?q2Zn+O4abQc=mk#dr?{F1EH#l$W*o zc;!%1?sj*7|2j4$Cq+a=J=0L%_u|C@#3GA_r`FlIOjfpokx^gYBp@*C;`A&S9led6 zeWk2yqqum$&o2oNb4F7Y85Z1UXgFYP{aH>f84qLN(WA8V>@t0wnx{|a<`#U=(TQn( zBmw|H08B?y_x}I$Bj|Ly7yw{uR8@Gc=acy(3(JSpCLLL}-59S~^ZM#qN~dIfPa^2Fs&V-Wzfk!PVXMK$N6lwBH`40{ zlx1p$AHJYv?;*DqS&@F<8VrwF&1jm30>8GjbfVy&A^00_7JI51J1Op((V$x!GPdSR zmbsyi{Aase??+zk9j;)ffJU~1DmZ67sM7$iDlp}7k7Eur;yfxaW?RCzcyOQNuuk{% zy+6j47YV!y9j=N_xh{soJ%1TtknV$HZ^je$xP$%rm^MrZ5O8!uO$vw5b-&)kP8zA^ zppA2Z@>77Rq4owWpPa7}`2bRdw_tJl$Dg_{HJ>_#u3Zr+CXXR*+oi;3{6Z=?eLm}{ zbam5tl{e{@GRfC;?q} z9D1Q}#x8pIpRw6SG$QSraho_EA-6`jFP%L>gAN-*)m=itqEWf?#ZB<*F7=~(A4=Xd zWN+`u{`cj_&w0TdNq2wLc#H|_0GW*&$Uvdiwl2d0SFT>_>P;rqs{oSQ%gZ2bE_MEs zEz@<y0Q29I=;*+RyGR(NP~Ce*^k?gJScJme@(pdCJj3fKNyh$Qeud;(`V=odm; z7cD`aEy6F;B^ouHLqa%F{S;FF{`PO4&(p2aZ(Pi?{JjiP z>oS!>AY4v`G0vYvGvYe=f8eA~dBvdiGu{gUSU<>8f^jrmKuW8XWVAH*TxNt{#ynFv z56F08tv=TPOX={LtdhmOa4w7Rx}I> zmXAhxE9TkTr@`mLM^sOo@5K~Gmr3*>(!(x z(`i`b9uuOZAhQs62ujx)HQ*Q;>oEONi#{s3*#YKdLuEZUs2YadU5&NgTn_MMvY=dm zZd6J7BJu&uMqbQ-r@}a(;)Skc1XQ5+*szT4O&G$^P{AvlD=v{n=4-v7wQtZ9*61yU z%hrpPIPo*3qcJtm6VYBbBdx*|R6ZS~W^{x|kWDj$L^7y6Re>m4j5Abfqtv7XqC90f zz#H9`Q?VYLkVqvF&DHRp6vet3!ZM4q{bK_OL>&dya)m+s>7P9HGRDe&<%64K@s2G4 z{oM>k(nO{D8#GL4(nAsYj&XhqNDqB`jC|{WDmafh*?+`ma!;Jl$c+akcbnAt!%-p8-IIo!mm8CVCTM!W%HL^yNzddWS11(5fBs%bT?5~)- zz|67t^UEk*+z@R>DJBc$cy$3N79(TmM2e0^fU-?+vj$4S+E;CyHd$>q#)X^>&!nw+ z5o{)#b0>1y`IF`>quC(e~2}3xevGFJBgHAIdubn38*(n&jQ~J6w>Og7V`6{0T!CSf7ob@V^;R zqFH-vCinMg04Ns zq`eoi?)Q%UL)cRwjoSEwDJROiF)vg4F!?cFJ>NNT1A@YUX<0l^G4zG$87Q)2M(A0h zxYEJ`6-OTvhZz~ZH%Hr)(??~YZ9>s+gg`!IMqYC#bIx(rv;k+l>}?f)Zd88>=S6jl z!E1W=BBz!c0>uCqE%$MmD*ZS{oHSNmJBKjxJX{5S(H zI!FWYVUrph_W{rd3GOR)<7-DJn+p_R!yr-#`uv`0H^J z{Mh{hT)Pqj&dnxTfANkYq;al;n>>}yv*HokB~a8v4^sn$epZyXp}5)?nwPt?2JiG% zfUF`LRj+UCeGtEn6xQIQ`8!yVp>dq;6ZJLQvlj|^ps0=Mrt`t1ceX-wG~G{LA|9)| z?Jo-;qPn?cetDsM%~WHRCTAKgP6=%%TxoT7j(hdAb>N0$k11{?%cO%=(Ev>i`g1G9 ziHb(ZHr@^s4o&K@wqqIE#JpntPVtU_R?ETeq6NR<#gNkXt3-hjWO0XB_!nz?d1=SV z*I4-nQX;}GBim}FD^aD z7R8Eb(6#bPANRsAXYpC~poq8`&Mzh8T`r2+2486k4Cp=ZNl_oN6$QfCuvIx9gny}r zK!pNuQ{}PPw?}KOK}fylX?u_J{vV7|a zoC{>S?6fpv<`uFSW@?qHB>c|H)%y5?+&vA}H);i3c+j7D$`1NnKS}mRLo3c6`in#B zi>vgPcnJCHQEJuPjv>_DmuS1=`N~VeVo%mG>1 z<_n&+*#WOs>B>jk{3s0j7jjAoXf~Qc^P%c^y*QnDZqR(AO6w{G&f3p9{+S<$9(G;z zL}|&-xWZhmq{RL;v1vrmFh^m_3njr0I-QZmXV8_;M|00gSkC1|&*wK(Q-DZuT?g53Mr(TvPq- zX3HT-32a*Qm-$x5?QF7!!%=WW+ja5b136;`Cn`dffm1=$@EGxO=)8Es>w2^|clepk zDT%tQnh9!TV*;!ye5NnHat;(UCeC)%y!^FSS&oT^i=$d?poZ5z+HP+UO-U)avdLi# z+ehajXee7G-?4~fAiw~0BH3*W(6}mu?wFBDks#0S)1%+rjSqsJ9RO@AHv^3#?6w_9_)g}oHX&8s**N|WYp5ybVQAENa;ho;N^#ewncf|_R)Sq2Hk$z zRzOuptJ%3lWK5R%vpcKO4?xgC*ERl>zaN|6f)unf+w>!5_4Z2)vw23(!ykLMPeLg<4*i0 z@*I?3V3b*|d@=PS>cJEhs~j{~$!b(m(Oj&a3Pl$^zv5$DeyGHiX%Y-GpBf}OrY|83 z_UoU0>BKxZUdCUzj!nR8LK=N<)9>^tZTZ^5VK#e^6M6BCNjt+rb0zE3girdW=(S2x zL=t2Qed`9skLT(-?UR)>V02J<^wqONHmP%QqjX;%T+3R`Nb{N$$WhwRT zARGe$oVmQ27&ey;A>tjT$C{+e!@NFk<*i38oOz>tSUT#mQ){h$GQPNL6)k)-_!CYK zor;Z~r8Ix3mXz`$nUW`W}cnv!0GWU4E7Bd_$FPJPRI zC-Bs~6iip!T@#*3C)*!6BX>W4(p%3J5^Zjdp|4Rh14*@Pq{U?NK`gR& zPxQHzvmFJ~hbq2BU&2czr*rjG?79q`=Ip-~7YwS6n5@$i=RPqC7V_OF9^;6nH>#yQ zo{J&GnnUfSNYy=-Y5$h4xw{)LaWy&%Mkv$XicmR7mVi(*dsCr?_&!%i{!-Jx(MsskK2q(PLtQcS{Y9l`w>y@~r!usa^ziwLTMW}v2#bi)jEbe;t}{+k zZoauTuSwm}PrX*^X)X#;2?hK!w5wbGlXlevJ(4oP{;PC##K}~aje=n`gkka63*?pyP zxnJ3rqb z&8-3-H2hr=%`q?bQSu-{s)F>>jtJk`+ExaSJqEhvb8o z8)AiJhCT{WAM&hp2YWw_ghi0j4pqpU%-_G|bm!KmH_E9@r~Km1;Cf}1O%Jw2-+jT+ z8;sC56`@A(PBrQ|9sl}vA(raF#$dEK^M?nFWNXpwAjx$(_%?H(Ugsj+UVNq@4o|7b z;qZ>P<6_eLl+E0*r6m#j7Y%gk3HtVo*1=SRQ^f&W4|(PH&c^RYMj7f{jim8e9Qq~6jP<4!m;1FSZiX9DHiMZBy^n}9;907BscW>Dbv#97y%Ku`U9<(X zjJF{4UF5pR&E*=w`e;t)>J0%>u}8Bch1v_Ie`^&kNL0xXIQer>=F|fYr$EPdssa({ z-p{Jg0#h5Eto1mNG_H;W4~qvMwref@oYW0oU<)&sG+vu#sIau{WxVM< zj+MFEKEsn8nVW*AO@8UEUjZfXDP-+&v$cCv5P@iB^2pdU^8=;QGJn{0qzP>G(YUsy ze7x;3_+_Ee%37$9C)+~$O1MsA5o>KGcGQ37lLeykAotlPxf_6KyA4Z+$iaF1CPb)p z$5RBK3A9iM#)x_{kSiG2U?=QE|{|&Kz8)<19~2rvruSBV@=#5rqxClM0bZWSTln zN5_sR+WtygAqMl3{_}1VGUy0jFW=z=L0d}U6gJ~7g`}L^!4TYI4?j?B(~h0!2hCWj zS@?(sH;w*E>Sz-+ed<}3&(Xrl$K_yA!XgBdd^<%lpe$f3q`4GgzOBQ7C?Tu@IsM+E z??0KVCB5M6vh{qT9FZt2ra=>9b}=_r*&H6v{hB4%fdZ9iYx1p*Hz$9B!xB>V=H1(n z_Kl`0k~&tA=`Bo3;dl%Au%d4YfR7^sG?2*jM+#n6Z^O$JvYbX|#Sy|iggu1`)LC3_ zzd53R2=rFzSB}&48l!NzRalgY!42Fi7 zr}!GmMcZmc04Ei;EsZoY@RY51bzUD03t29hW$k)QP2z`CjMorNFkM^TW)W1@XGWS< z3;2zXlEz3k`u4rw3x1jxZ5>?uTv*y!;9+^^bOwdI4ZGE{6yI0cb|s0xx2pLzYrFrm zvhIy*r|E2b)cNA=n?yu;vP6(V8ds5f2V!i4`*YVGuWcwo=*>un1oZ?)aZN%C{TaYC--gAb{l{dFOw%;2_>Vhu;Q zrR{2pG0R9@4me1RV(1x5>+7>AMaj$P?+nawph&BH9-_krO~rW}uXtg1OQ6ByaWJ`& z7b59?36p)0(4Tm36q{puxH)A5hLo>8qInl^E;b{;F0>C#+B(`nW7uUABa~ zFJFyWx~m-am;nP`r!PE?@5Kqa?LF1sL4&Fd#G~xTvcHEmTR$-xn$tnB);~67j5o&= zCP6M(eIF{n9oojrLQQ^775TMK(;WNP>FM@;8>F_p2ZbNX%bvRQC{j~kLdVQfK{A~+ zfWR?7OVmK#{m?oCzeop5A6=^C40W*UJ!H{0&l1Nz_<9U3Gpfrv`A4xJ_l;$3HN&btNEJzh z5c2|tiaxp)aAg7U_8ln9`F$!uyTUZiyqis{r4v=JsU1ZzQWJ~T>dyE`f2}v%eU~sD zAKf1qha604mY~~jEbda%he{1_Fqs^TTzqx%;AN6|9sVeDX;Hoov^|fYZAYIuF zmNm;fLBPbpy51aj0B_x|o1EHT&K0~~8;aeP5o_O%9giGSR-0n0HPzFK0@G_*y& zMi;*c-&d#^B%XJcFmaJ+7bS!?xEE3>bDfxlzx<fe8LlLaBiB1v_SmB!c0skg0M! zA8mIZo&9Sg7<4~3MS&3qFipU0!amp3%28bM;f!s0c6gmDtRibe5PEqzn5<+pDtO=yq3dJD_tf9bEGM*QW6@A*fqwdx zHSztqMP!BgN=B+aKOXt}2536s&wd{=(<=32HzhX=WzkomYZddDI#QQ7GAAqd&_qjh z>?IpDp*Sk(UTT=b!%;*{BwmlyU`q|x~PZG{f)3myHx~rv<;}>8_R9iL(V#J<9Q>|I` z``w^%~LlsCyBd`^YuaU!oIyJhHPKOHK#44y_@rW=RIP=MhXqbN^{<|*5#SpG{o0h zAJo}WXU;^snks@v=rW~(s5nWxdCFqIb2k>XeKUSsB(WouY5d;41hZZc&T6!h|wGH`xX1%)cYge03Fd&9xEJ&<9f^D`3zZ5UIcW))e@vML&Ddiw7 z^%sG!nbbtMXh$p6xQtsEvD3pA@BNE_UjI&^=J0ma?4zc$9K((Fn!b=2LCxYzA)tTI z*WiQuh_FcEiM#!X41b?@S!jcnAe8wJH{pgp zsCqaU5HVqE7(on2r8_c0+Wlh_Qco9hXg^Fv#ixpd-Cj{yuF&2Z8nwveb#G`BmK6%b zoHefabPHndj6-@;#ovp}Tz8(ajBIhg(y+FyHL|JRS&UfG2%et1Tbo@8#GkzV^@{Md#uRn*k6m)yRvhE_lXJVvvwWlikE#UnOAj8HqrHoBoGvIVI zN_lxsYTaZmLM75cx8`2b&HZiVP~OL&*yj5&I|BAsACN_hVd63xh{l|wSf{F&PDZc$ zLUIbv?7X$MR+!A~?{zG+u45$e?=~_=Pm?M@wimQ)qX*f`Jrh;PVImzS6Oe3`xOKeQ za)g9cP5sSc-Qmy@>3)W*f#?r^i_Yz`@VAxsE7(t_wOk9}7gF+j?kg>FPH+TUV5jJY zXYk-THON23VQ-GYWm@h!TlvnN-K{FRh38dR+A7Y<)obP+(fwbq)?GY0`Afs34~s1w zWcrjD*UXadW+&e@sc4>^F@JYg<9!H5j&{tNjUBv(cnoo1w!<8 zplUv#?F3%|u1YqHYk3{T+sPO$hd9e7C@%mV27g&O5zhb9ZhBQsoTsWsSkNOsJpKS34ACgVh6Is=~JRw}DEE4FUY@IJ&=4>8-*SAd~6;?Ne}&MjHFEvc5{ ch2Nu&PR#QJq3@Reeuh<5QB$E(4ixr30G)gu*Z=?k literal 0 HcmV?d00001 diff --git a/docs/dapperplus.png b/docs/dapperplus.png new file mode 100644 index 0000000000000000000000000000000000000000..2911442c49204c62c881042922f30e5628f97cb9 GIT binary patch literal 5543 zcmb7I`9Boi*T1tE#-3#?qcQfBkZrP$CHqcMeHht4DHMeUGl)_uqA2^`LWFM$F;ZDW z%9d@&l5Are+n9NLpTFRF-oKo4&pr2jUgvf1>vhjLNe*`AJe=a30016K3sWZmAcrah zaKH{lV7Z&ip@4-NTe@&?aEwmbQxEqH9Bf?9n1|RHYigRCTb|X{Idn1pFD!O2YiTU%c(Eju|l_9iA;)YZ{w`47_4-A+z1P-vfsh_{b_J2&^QgoFYuEhS~GZ%2+q z0dV1hyPUjg3;;(2Bw2fVRWdRx7VFQucZP;$w*WZQ7n+;5Mn+Z!2F7pQ>gMM^v}|v0 zGZ>6+PR>LW>Tr1o0Qi~#Xa(Ra01W_i08j@&EdXx;Ff+3`4BxxAF)}hVG&D0i)7jbe;>GKCLPCp8P18k1;o;FA zJ~VVlN`?XupJA+h80u7*lbthA33rECJ!K-rcj*gg#!r39*O1wR0>;~~ORddWf7BRf zE)8At2(!|Q&5EhXc*Xp(9>MwqfZ(vDsgX-W_Od(jqF@I~uAoLN8C&!s`C-@T)di(s zj%pLEl++n)Dt_2l0RCy3#e%J#s~i{~a3AQ{da&~s6it!~dOyrh)Srz;o0$pyKM+!| zVB!3Sv|m;^PeNGfq$Pb3NOHUvp=Zakko9`&mUwy1o-m0^c-r$>plEGobJL#ul#6Cq znzr^G-cIwrm!I~?o)}qoVN0hGoljyHj=>+Pt+kuz9si0t=!1|-q#1Z|2YQF+{IEg9-;Ws=+0~ic){L*~Aqh_4aDg5B$720{RBl|KkP5Dv zxNf}qIj_1*GV4Ug_kH=;GtC&MI$dj9tA%a54256#v2bTbPi5g- zAG)coXC?Yz-(Ug605%(bvXmq%}i-rzB+`<9)awKaGdBE{2xN~oW=b~B(Wc>hVt<<;4q zZ;`s26lQJJXuY0cvo@Qc?47gJAxDV`6!u_IVu&cM;<&06rET8<+aGw6)wOZ+_zml5 zhhwtdCW{aIIlrnb;7BNyPvKdEYZ8-T|!HDeP5gI;_UKx&T8ZCQ zf@iMMucjSvi+Yw6*RZE2I#AcVeVEEaLs5z<1Ha$P*pK94>cZ&iT2B5Y2w%fu=4pnL6 zySUyRJzv6`w`Y65uv%<$uh$L3SkqX}M!l${HiC7dpwdPm;=h-|m4&M#ykGhk`j5~( zrEt6=A$d%=%|qhh@3lv^8+J6~_R4h`dt%mCL|DDiQVUorFFvd_{(WAx=+XQM+CfzsY*L=hcuC7);?+7#5_jU8##`JX`b>;@eb9q7FH)t8`>JE_agOo5QuV>+@cUVH!S@C^$ zu7+60ks{2w8I{e`6|on*aE&b+sb${@Np8CmFb!FF`+Qmp3NUM-h0CxX9mi73{N27x z&y9F)`ui1g<1;?txd7{#k|yXE=Rh3ea>|u5l#N!!TUwDlxg40$osxk(HVR#kg7ClN zFGFu$R9fM&lRS=he@seYJH`^KAiD+Tqegw}HkvN?hLmk?piTP9=CNliAy6fnJX>YI z5e^^FqCR5McCovX*Jbx8tTwQpQ6NIy2&R*nuC?;V=*qR!Q|v=$oZgGwGtg&GE#=dd zBkAts#%#|Cy@HrU&Y<74quVq7itr^C@*&((abO%p++9?GW??@opWax~$kYuD$m5==sWgK5Uo~;k^znEEOY9aWz}KS-ePifCMEjYA?^kon#|%UV57d8x zhQl8vBU>V3=gyk=EhtqCZ=F7_I2m%r!|=VlFMQj1nElc^sw$K^H8~fEhf1`d2bZhD z9-p7Jj#;p_dF&M>OJcc$g_;^rf7n9Is=QrU$H9Zna19(?=D z?U|1wH>OFqhhe*1KruI|QxZJ}I61hnIKI>&G|QS* z<+yzpa)Iq<|Fy|FL!|C*AiXAVVCz3yH=3cNBMwY;#Sf7j0a1}K5qZ)$EbqG?)Vx2* zHTS+WT8iOJJ=oIZ%!N)hfDfZnRqLVlxeci3qY+u;;a&-*k}|p-*f-_zfRx|d=p*eA`*(nfz!p35 z#ncz-`|eJL0S~s#axjzGW;npMci-9?ihN}ZgWG-pxhn6O zOS6x*ecZl20mWep4JjV}-%ERsQ#0@Zpa;(#yBw3btyqu0B{?CHFGu$0!>{`@zqO4| zPcx>y-ovjWXnddXQ=cI< zCf^75#2JM_e44{if1JB}t)6?@h6&t4epT45X6n6!6Wb}Af+TmSdwda}8^b*dJADCE zuG%oG1(Yh0Jw|@Ns^I1Clznq$aqz{ZRmr8*RpND^M?)nwf5pV7qC#QVfoHx_MU-k! z5e@ZFj|SvEMBHPul+Fh*>`Ol+c5siulD6vkODJ+oP|RQj{w}-}1jb=FTx>gP%IHJ8 z>H-pOyqUM|wi@Py1l>+KTMBOGb;ms4!u!Pe8u1AoS$=qC(ieKv+)Xp(Jahy)jMyhZ zBTPBeAr@6i!w7Q|mS*eZNyurcA}@(d65T%)=N$*I+@x;kdH1I6V62h7Vl~zej2j3QA+rNJJ!rD$fVg^W<_h=b2hz6`y zSNOi+5S%@)<}@P*b2q(QCgmq_r@PHwnQcq}p6QAF28Jq+?nMb=pMW`0iZbAfDd;ip=OyTZZTImxjQbm#t?T{hd_?zFL zv^!g$dgnh0)1G-^+Q31!rkAiEG&?878}zGkQ6%XK3iFMe95!Sx^367}Q(TTf0hSq$ zKcroVx*iqOqhOSYe7XX6ZV?FZAZTy4%2GNG8{r#8fYB4Px0~$-{R+ST$(_u9kZs_D zDkRIb7+4{O5!l!BF-$XFi!9HDpka4>Wlhbp&2U1NCkw|7QG%ruzGaJ8Woo#y&)BM# zL!8D>_Wxb;3d}RT|p&mO5h@0H`3%?E3 z3~&kxkW5d<5CQRrF+CWybSF>t-%6&ENp-GxUQ!wNJzLcir;$7+H5nBv5&qmTx%`uA zcF5Xyu~krGMAKAs$IMuEF?=H)oj-Jba;;Z1F%!sA0-)ugz$qS1-H^0=)Nx$33i2!G z^>Ov{Gmd1H7K2W+K0TytnaE2l(=#?fTM;?OW zr?_NjVNr;Ilg>f@sV+Q{ULzWq?@PYWAl99Cf$|!-#u;&^6ow{duc@UX-<=Re^d&bG z6o;g8{;qW7;;>aUq$t0*ntcAvt{nd}Drf^=mqRSbs!W+gHdFb`2aP<4y}-bU58~Q_ zE+O2W-6j-_1%+fvY@(!6D)25ahrI&tr&9VxC2=)8R?ms~=~X4-1UHThPr9WB?2mJ4 z_Wz!|$xNV3jb#JZ$0llOU-u_j1 z^7+o^`G=H0dGMU`YK#gU(PBzDLu*H;;m@<`EXMS2W1`I4#Nm7-0`ocJ;3k=dYR6(ySNj%lY70evIP$@F=(9kq zrP={LxVic%kt;}TGeqQuTQl54mw zhu7r!`7b}XetWIjLC}TZ&6Ds++@jDGe=+u&tNqeATgl$fy`@V@V?L>U@Uz8iDNgNr z(hph$@a(V24ame-B=&R}-X|88RS(T{B40K@ zTw#-dZPt$@*e@}`0DM5=0uvob1t-Z|bC(0e@}qMSwj5UI9aw>Kf5)2?`FwaM^If&4 zD5p=w&ETGox>QU4H~4^4|777WvNi)r8a8aTDWzGzF;%M*C%d)JtX<6XTvj~J+WeD$ zl8pW}V_Y|XiFH!Q?V>U9csqV?Bt^I{PAhlgDmVOJ{t^XMIk&D$`a(YLH + + net8.0 + + diff --git a/docs/readme.md b/docs/readme.md index 19da484b7..70df3947e 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -14,4 +14,16 @@ var customers = connection.Query( But all the execute/single-row/scalar/async/etc functionality you would expect: is there as extension methods on your `DbConnection`. -See [GitHub](https://github.com/DapperLib/Dapper) for more information and examples. \ No newline at end of file +See [GitHub](https://github.com/DapperLib/Dapper) for more information and examples. + +Sponsors +-------- + +Dapper was originally developed for and by Stack Overflow, but is F/OSS. Sponsorship is welcome and invited - see the sponsor link at the top of the page. +A huge thanks to everyone (individuals or organisations) who have sponsored Dapper, but a massive thanks in particular to: + +- [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper (from 2024 onwards) +- [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) + + +Dapper Plus logo From d40f9f0ca9ed9ec4171bdd088dfa81b07e84ba8c Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 09:17:59 +0100 Subject: [PATCH 272/312] image size --- Readme.md | 2 +- docs/readme.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index e5fbd8aec..c59d7c1bc 100644 --- a/Readme.md +++ b/Readme.md @@ -41,7 +41,7 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper (from 2024 onwards) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) -Dapper Plus logo +Dapper Plus logo Features -------- diff --git a/docs/readme.md b/docs/readme.md index 70df3947e..fe752eafc 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -26,4 +26,4 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) -Dapper Plus logo +Dapper Plus logo From a11879c6dc285262d8e2bb48b8ac89c8b6580b6f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 09:41:32 +0100 Subject: [PATCH 273/312] more context --- Readme.md | 2 +- docs/dapperplus.md | 13 +++++++++++++ docs/readme.md | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) create mode 100644 docs/dapperplus.md diff --git a/Readme.md b/Readme.md index c59d7c1bc..5c361eeec 100644 --- a/Readme.md +++ b/Readme.md @@ -38,7 +38,7 @@ Sponsors Dapper was originally developed for and by Stack Overflow, but is F/OSS. Sponsorship is welcome and invited - see the sponsor link at the top of the page. A huge thanks to everyone (individuals or organisations) who have sponsored Dapper, but a massive thanks in particular to: -- [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper (from 2024 onwards) +- [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) Dapper Plus logo diff --git a/docs/dapperplus.md b/docs/dapperplus.md new file mode 100644 index 000000000..4081912c7 --- /dev/null +++ b/docs/dapperplus.md @@ -0,0 +1,13 @@ +# Dapper and Dapper Plus + +Dapper is the micro-ORM developed initially by Stack Overflow and now maintained independently, that offers simple, high performance +access to the ADO.NET API. + +Dapper Plus is a separate tool by ZZZ Projects, which builds on the path set by Dapper, offering features like bulk operations, +and a range of [documentation for Dapper](https://www.learndapper.com/). + +From 2024, Dapper Plus is now a major sponsor of Dapper, helping to secure ongoing quality development and support of the Dapper platform. +This sponsorship does not impact the ownership, license, or any other particulars of how Dapper operates. The core Dapper libraries continue +to be freely available and fully open source. + +Dapper Plus logo \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md index fe752eafc..53521b68c 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -22,7 +22,7 @@ Sponsors Dapper was originally developed for and by Stack Overflow, but is F/OSS. Sponsorship is welcome and invited - see the sponsor link at the top of the page. A huge thanks to everyone (individuals or organisations) who have sponsored Dapper, but a massive thanks in particular to: -- [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper (from 2024 onwards) +- [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) From ad16263ac4a23eec23956e69a207d931a165aaf8 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 09:43:31 +0100 Subject: [PATCH 274/312] tyop --- Readme.md | 2 +- docs/dapperplus.md | 2 +- docs/readme.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Readme.md b/Readme.md index 5c361eeec..62f4e302a 100644 --- a/Readme.md +++ b/Readme.md @@ -41,7 +41,7 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) -Dapper Plus logo +Dapper Plus logo Features -------- diff --git a/docs/dapperplus.md b/docs/dapperplus.md index 4081912c7..4a6a0f269 100644 --- a/docs/dapperplus.md +++ b/docs/dapperplus.md @@ -10,4 +10,4 @@ From 2024, Dapper Plus is now a major sponsor of Dapper, helping to secure ongoi This sponsorship does not impact the ownership, license, or any other particulars of how Dapper operates. The core Dapper libraries continue to be freely available and fully open source. -Dapper Plus logo \ No newline at end of file +Dapper Plus logo \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md index 53521b68c..7ca3a9b9d 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -26,4 +26,4 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) -Dapper Plus logo +Dapper Plus logo From fa9c96aec00596c6306e2e2665efbfe59fefbf24 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 11:13:05 +0100 Subject: [PATCH 275/312] revert #2050 - see #2049 for more details (#2070) * revert #2050 - see #2049 for more details --- Dapper/SqlMapper.cs | 15 ++++++----- tests/Dapper.Tests/TypeHandlerTests.cs | 35 +++++++++++++++++--------- 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 5575a6f5f..c63740759 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -193,9 +193,7 @@ public TypeMapEntry(DbType dbType, TypeMapEntryFlags flags) public static readonly TypeMapEntry DoNotSet = new((DbType)(-2), TypeMapEntryFlags.None), DoNotSetFieldValue = new((DbType)(-2), TypeMapEntryFlags.UseGetFieldValue), - DecimalFieldValue = new(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue), - StringFieldValue = new(DbType.String, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue), - BinaryFieldValue = new(DbType.Binary, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); + DecimalFieldValue = new(DbType.Decimal, TypeMapEntryFlags.SetType | TypeMapEntryFlags.UseGetFieldValue); public static implicit operator TypeMapEntry(DbType dbType) => new(dbType, TypeMapEntryFlags.SetType); @@ -221,13 +219,13 @@ static SqlMapper() [typeof(double)] = DbType.Double, [typeof(decimal)] = DbType.Decimal, [typeof(bool)] = DbType.Boolean, - [typeof(string)] = TypeMapEntry.StringFieldValue, + [typeof(string)] = DbType.String, [typeof(char)] = DbType.StringFixedLength, [typeof(Guid)] = DbType.Guid, [typeof(DateTime)] = TypeMapEntry.DoNotSet, [typeof(DateTimeOffset)] = DbType.DateTimeOffset, [typeof(TimeSpan)] = TypeMapEntry.DoNotSet, - [typeof(byte[])] = TypeMapEntry.BinaryFieldValue, + [typeof(byte[])] = DbType.Binary, [typeof(byte?)] = DbType.Byte, [typeof(sbyte?)] = DbType.SByte, [typeof(short?)] = DbType.Int16, @@ -3928,7 +3926,12 @@ public static void ThrowDataException(Exception ex, int index, IDataReader reade } else { - formattedValue = Convert.ToString(value) + " - " + Type.GetTypeCode(value.GetType()); + formattedValue = Convert.ToString(value) + " - " + Identify(value.GetType()); + } + static string Identify(Type type) + { + var tc = Type.GetTypeCode(type); + return tc == TypeCode.Object ? type.Name : tc.ToString(); } } catch (Exception valEx) diff --git a/tests/Dapper.Tests/TypeHandlerTests.cs b/tests/Dapper.Tests/TypeHandlerTests.cs index 80c3292ff..385ffb90b 100644 --- a/tests/Dapper.Tests/TypeHandlerTests.cs +++ b/tests/Dapper.Tests/TypeHandlerTests.cs @@ -28,12 +28,18 @@ public void TestChangingDefaultStringTypeMappingToAnsiString() SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, true); // Change Default String Handling to AnsiString - var result02 = connection.Query(sql, param).FirstOrDefault(); - Assert.Equal("varchar", result02); + SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, false); // Change Default String Handling to AnsiString + try + { + var result02 = connection.Query(sql, param).FirstOrDefault(); + Assert.Equal("varchar", result02); - SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.String, true); // Restore Default to Unicode String + SqlMapper.PurgeQueryCache(); + } + finally + { + SqlMapper.AddTypeMap(typeof(string), DbType.String, false); // Restore Default to Unicode String + } } [Fact] @@ -46,13 +52,18 @@ public void TestChangingDefaultStringTypeMappingToAnsiStringFirstOrDefault() Assert.Equal("nvarchar", result01); SqlMapper.PurgeQueryCache(); + SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, false); // Change Default String Handling to AnsiString + try + { + var result02 = connection.QueryFirstOrDefault(sql, param); + Assert.Equal("varchar", result02); - SqlMapper.AddTypeMap(typeof(string), DbType.AnsiString, true); // Change Default String Handling to AnsiString - var result02 = connection.QueryFirstOrDefault(sql, param); - Assert.Equal("varchar", result02); - - SqlMapper.PurgeQueryCache(); - SqlMapper.AddTypeMap(typeof(string), DbType.String, true); // Restore Default to Unicode String + SqlMapper.PurgeQueryCache(); + } + finally + { + SqlMapper.AddTypeMap(typeof(string), DbType.String, false); // Restore Default to Unicode String + } } [Fact] @@ -643,7 +654,7 @@ public void Issue149_TypeMismatch_SequentialAccess() { Guid guid = Guid.Parse("cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e"); var ex = Assert.ThrowsAny(() => connection.Query("select @guid as Id", new { guid }).First()); - Assert.Equal("Error parsing column 0 (Id=n/a - Unable to cast object of type 'System.Guid' to type 'System.String'.)", ex.Message); + Assert.Equal("Error parsing column 0 (Id=cf0ef7ac-b6fe-4e24-aeda-a2b45bb5654e - Guid)", ex.Message); } public class Issue149_Person { public string? Id { get; set; } } From 92c81bc2c2b768b7bda9caa6acc3af226c648144 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 11:57:42 +0100 Subject: [PATCH 276/312] fix image in readme --- docs/readme.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/readme.md b/docs/readme.md index 7ca3a9b9d..e7b6875dd 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -25,5 +25,4 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) - -Dapper Plus logo +[![Dapper Plus logo](https://raw.githubusercontent.com/DapperLib/Dapper/main/docs/dapper-sponsor.png)](https://www.learndapper.com/) From b51cc77596f092de88e844ce8f2c75d6fd371936 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 12 Apr 2024 12:12:02 +0100 Subject: [PATCH 277/312] Update Readme.md (actually just to bump build) --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index 62f4e302a..de4b6ccd3 100644 --- a/Readme.md +++ b/Readme.md @@ -41,7 +41,7 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) -Dapper Plus logo +Dapper Plus logo Features -------- From 2fa046cd398d6680494c4c0733c0993123eabbff Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sat, 13 Apr 2024 09:07:53 +0100 Subject: [PATCH 278/312] Create cla.yml --- .github/workflows/cla.yml | 43 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) create mode 100644 .github/workflows/cla.yml diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml new file mode 100644 index 000000000..dcd86e1cb --- /dev/null +++ b/.github/workflows/cla.yml @@ -0,0 +1,43 @@ +name: "CLA Assistant" +on: + issue_comment: + types: [created] + pull_request_target: + types: [opened,closed,synchronize] + +# explicitly configure permissions, in case your GITHUB_TOKEN workflow permissions are set to read-only in repository settings +permissions: + actions: write + contents: write + pull-requests: write + statuses: write + +jobs: + CLAAssistant: + runs-on: ubuntu-latest + steps: + - name: "CLA Assistant" + if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target' + uses: contributor-assistant/github-action@v2.3.0 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # the below token should have repo scope and must be manually added by you in the repository's secret + # This token is required only if you have configured to store the signatures in a remote repository/organization + PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} + with: + path-to-signatures: 'signatures/version1/cla.json' + path-to-document: 'https://github.com/cla-assistant/github-action/blob/master/SAPCLA.md' # e.g. a CLA or a DCO document + # branch should not be protected + branch: 'main' + allowlist: user1,bot* + + # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken + #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) + #remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository) + #create-file-commit-message: 'For example: Creating file for storing CLA Signatures' + #signed-commit-message: 'For example: $contributorName has signed the CLA in $owner/$repo#$pullRequestNo' + #custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign' + #custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA' + #custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.' + #lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true) + #use-dco-flag: true - If you are using DCO instead of CLA From 907a4d9b35eda1cbe9b7a79f12a6befb87fc46c6 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 26 Apr 2024 08:49:47 +0100 Subject: [PATCH 279/312] disable DateOnly / TimeOnly support - see #2071 / #2072 --- Dapper/SqlMapper.cs | 4 ++-- tests/Dapper.Tests/DateTimeOnlyTests.cs | 26 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index c63740759..47e40ea41 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -202,7 +202,7 @@ public static implicit operator TypeMapEntry(DbType dbType) static SqlMapper() { typeMap = new Dictionary(41 -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER && DATEONLY + 4 // {Date|Time}Only[?] #endif ) @@ -248,7 +248,7 @@ static SqlMapper() [typeof(SqlDecimal?)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney?)] = TypeMapEntry.DecimalFieldValue, -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER && DATEONLY [typeof(DateOnly)] = TypeMapEntry.DoNotSetFieldValue, [typeof(TimeOnly)] = TypeMapEntry.DoNotSetFieldValue, [typeof(DateOnly?)] = TypeMapEntry.DoNotSetFieldValue, diff --git a/tests/Dapper.Tests/DateTimeOnlyTests.cs b/tests/Dapper.Tests/DateTimeOnlyTests.cs index 015af2d51..68e8bc170 100644 --- a/tests/Dapper.Tests/DateTimeOnlyTests.cs +++ b/tests/Dapper.Tests/DateTimeOnlyTests.cs @@ -9,7 +9,7 @@ namespace Dapper.Tests; [Collection("DateTimeOnlyTests")] public sealed class SystemSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } */ -#if MSSQLCLIENT +#if MSSQLCLIENT && DATEONLY [Collection("DateTimeOnlyTests")] public sealed class MicrosoftSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } #endif @@ -17,8 +17,11 @@ public abstract class DateTimeOnlyTests : TestBase where T { public class HazDateTimeOnly { + public string Name { get; set; } public DateOnly Date { get; set; } public TimeOnly Time { get; set; } + public DateOnly? NDate { get; set; } + public TimeOnly? NTime { get; set; } } [Fact] @@ -27,12 +30,18 @@ public void TypedInOut() var now = DateTime.Now; var args = new HazDateTimeOnly { + Name = nameof(TypedInOut), Date = DateOnly.FromDateTime(now), Time = TimeOnly.FromDateTime(now), + NDate = DateOnly.FromDateTime(now), + NTime = TimeOnly.FromDateTime(now), }; - var row = connection.QuerySingle("select @date as [Date], @time as [Time]", args); + var row = connection.QuerySingle("select @name as [Name], @date as [Date], @time as [Time], @ndate as [NDate], @ntime as [NTime]", args); + Assert.Equal(args.Name, row.Name); Assert.Equal(args.Date, row.Date); Assert.Equal(args.Time, row.Time); + Assert.Equal(args.NDate, row.NDate); + Assert.Equal(args.NTime, row.NTime); } [Fact] @@ -41,12 +50,18 @@ public async Task TypedInOutAsync() var now = DateTime.Now; var args = new HazDateTimeOnly { + Name = nameof(TypedInOutAsync), Date = DateOnly.FromDateTime(now), Time = TimeOnly.FromDateTime(now), + NDate = DateOnly.FromDateTime(now), + NTime = TimeOnly.FromDateTime(now), }; - var row = await connection.QuerySingleAsync("select @date as [Date], @time as [Time]", args); + var row = await connection.QuerySingleAsync("select @name as [Name], @date as [Date], @time as [Time], @ndate as [NDate], @ntime as [NTime]", args); + Assert.Equal(args.Name, row.Name); Assert.Equal(args.Date, row.Date); Assert.Equal(args.Time, row.Time); + Assert.Equal(args.NDate, row.NDate); + Assert.Equal(args.NTime, row.NTime); } [Fact] @@ -54,11 +69,14 @@ public void UntypedInOut() { var now = DateTime.Now; var args = new DynamicParameters(); + var name = nameof(UntypedInOut); var date = DateOnly.FromDateTime(now); var time = TimeOnly.FromDateTime(now); + args.Add("name", name); args.Add("date", date); args.Add("time", time); - var row = connection.QuerySingle("select @date as [Date], @time as [Time]", args); + var row = connection.QuerySingle("select @name as [Name], @date as [Date], @time as [Time]", args); + Assert.Equal(name, (string)row.Name); // untyped, observation is that these come back as DateTime and TimeSpan Assert.Equal(date, DateOnly.FromDateTime((DateTime)row.Date)); Assert.Equal(time, TimeOnly.FromTimeSpan((TimeSpan)row.Time)); From c5f12069333a35ec93bcc7867c86174eb09195e9 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 26 Apr 2024 07:50:49 +0000 Subject: [PATCH 280/312] Creating file for storing CLA Signatures --- signatures/version1/cla.json | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 signatures/version1/cla.json diff --git a/signatures/version1/cla.json b/signatures/version1/cla.json new file mode 100644 index 000000000..18d5487f3 --- /dev/null +++ b/signatures/version1/cla.json @@ -0,0 +1,3 @@ +{ + "signedContributors": [] +} \ No newline at end of file From 9fdb30a43e3cf311253ae3a3629cb67b5c8db698 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 26 Apr 2024 09:02:37 +0100 Subject: [PATCH 281/312] disable DateOnly / TimeOnly support - see #2071 / #2072 (#2080) --- Dapper/SqlMapper.cs | 4 ++-- tests/Dapper.Tests/DateTimeOnlyTests.cs | 26 +++++++++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index c63740759..47e40ea41 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -202,7 +202,7 @@ public static implicit operator TypeMapEntry(DbType dbType) static SqlMapper() { typeMap = new Dictionary(41 -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER && DATEONLY + 4 // {Date|Time}Only[?] #endif ) @@ -248,7 +248,7 @@ static SqlMapper() [typeof(SqlDecimal?)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney)] = TypeMapEntry.DecimalFieldValue, [typeof(SqlMoney?)] = TypeMapEntry.DecimalFieldValue, -#if NET6_0_OR_GREATER +#if NET6_0_OR_GREATER && DATEONLY [typeof(DateOnly)] = TypeMapEntry.DoNotSetFieldValue, [typeof(TimeOnly)] = TypeMapEntry.DoNotSetFieldValue, [typeof(DateOnly?)] = TypeMapEntry.DoNotSetFieldValue, diff --git a/tests/Dapper.Tests/DateTimeOnlyTests.cs b/tests/Dapper.Tests/DateTimeOnlyTests.cs index 015af2d51..68e8bc170 100644 --- a/tests/Dapper.Tests/DateTimeOnlyTests.cs +++ b/tests/Dapper.Tests/DateTimeOnlyTests.cs @@ -9,7 +9,7 @@ namespace Dapper.Tests; [Collection("DateTimeOnlyTests")] public sealed class SystemSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } */ -#if MSSQLCLIENT +#if MSSQLCLIENT && DATEONLY [Collection("DateTimeOnlyTests")] public sealed class MicrosoftSqlClientDateTimeOnlyTests : DateTimeOnlyTests { } #endif @@ -17,8 +17,11 @@ public abstract class DateTimeOnlyTests : TestBase where T { public class HazDateTimeOnly { + public string Name { get; set; } public DateOnly Date { get; set; } public TimeOnly Time { get; set; } + public DateOnly? NDate { get; set; } + public TimeOnly? NTime { get; set; } } [Fact] @@ -27,12 +30,18 @@ public void TypedInOut() var now = DateTime.Now; var args = new HazDateTimeOnly { + Name = nameof(TypedInOut), Date = DateOnly.FromDateTime(now), Time = TimeOnly.FromDateTime(now), + NDate = DateOnly.FromDateTime(now), + NTime = TimeOnly.FromDateTime(now), }; - var row = connection.QuerySingle("select @date as [Date], @time as [Time]", args); + var row = connection.QuerySingle("select @name as [Name], @date as [Date], @time as [Time], @ndate as [NDate], @ntime as [NTime]", args); + Assert.Equal(args.Name, row.Name); Assert.Equal(args.Date, row.Date); Assert.Equal(args.Time, row.Time); + Assert.Equal(args.NDate, row.NDate); + Assert.Equal(args.NTime, row.NTime); } [Fact] @@ -41,12 +50,18 @@ public async Task TypedInOutAsync() var now = DateTime.Now; var args = new HazDateTimeOnly { + Name = nameof(TypedInOutAsync), Date = DateOnly.FromDateTime(now), Time = TimeOnly.FromDateTime(now), + NDate = DateOnly.FromDateTime(now), + NTime = TimeOnly.FromDateTime(now), }; - var row = await connection.QuerySingleAsync("select @date as [Date], @time as [Time]", args); + var row = await connection.QuerySingleAsync("select @name as [Name], @date as [Date], @time as [Time], @ndate as [NDate], @ntime as [NTime]", args); + Assert.Equal(args.Name, row.Name); Assert.Equal(args.Date, row.Date); Assert.Equal(args.Time, row.Time); + Assert.Equal(args.NDate, row.NDate); + Assert.Equal(args.NTime, row.NTime); } [Fact] @@ -54,11 +69,14 @@ public void UntypedInOut() { var now = DateTime.Now; var args = new DynamicParameters(); + var name = nameof(UntypedInOut); var date = DateOnly.FromDateTime(now); var time = TimeOnly.FromDateTime(now); + args.Add("name", name); args.Add("date", date); args.Add("time", time); - var row = connection.QuerySingle("select @date as [Date], @time as [Time]", args); + var row = connection.QuerySingle("select @name as [Name], @date as [Date], @time as [Time]", args); + Assert.Equal(name, (string)row.Name); // untyped, observation is that these come back as DateTime and TimeSpan Assert.Equal(date, DateOnly.FromDateTime((DateTime)row.Date)); Assert.Equal(time, TimeOnly.FromTimeSpan((TimeSpan)row.Time)); From 9212a8da6e550303f480a32132b5cccfad852b4c Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sun, 28 Apr 2024 10:04:28 +0100 Subject: [PATCH 282/312] Non-CLA --- Dapper.sln | 1 + NonCLA.md | 29 +++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 NonCLA.md diff --git a/Dapper.sln b/Dapper.sln index 7df2cba0d..04fcc008d 100644 --- a/Dapper.sln +++ b/Dapper.sln @@ -16,6 +16,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution docs\index.md = docs\index.md License.txt = License.txt .github\workflows\main.yml = .github\workflows\main.yml + NonCLA.md = NonCLA.md nuget.config = nuget.config Readme.md = Readme.md version.json = version.json diff --git a/NonCLA.md b/NonCLA.md new file mode 100644 index 000000000..72a6c11ec --- /dev/null +++ b/NonCLA.md @@ -0,0 +1,29 @@ +# Dapper - the "Non CLA" CLA + +IANAL. YANAL (I hope). Let's keep this simple; if you want to contribute to Dapper, great! Let's just check a few things for the record. + +By accepting this agreement, you're saying: + +## You're allowed to contribute this code + +The code needs to be yours (without being owned by some employer, etc), or contributed with the owner's knowledge and permission, or +in accordance with a licence that clearly allows the code to be reused in line with this project's licence (http://www.apache.org/licenses/LICENSE-2.0), +and in that last case: a cross-reference back to the origin would be nice. + +(for the pedants: "licence" and "license" to be used interchangeably here; language is fun) + +## No backsies + +Contributing code to Dapper is permanent; you can't later demand that we remove your code because... well, anything. Even if one +of the maintainers wears socks that look *really ugly*. + +## For gratis + +Contributing code to Dapper gets you the bugfix or feature or whatever that you want; you have our thanks and appreciation, but unless we've +agreed something separately: that's it. No turning up unannounced at the tri-annual BBQ, or demanding... again, anything. + +--- + +That's it. Basically "don't make our life harder". + +Thanks! \ No newline at end of file From 8d7d262871fa195cd7a5d00b59a6ab610dd19ee2 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sun, 28 Apr 2024 10:07:12 +0100 Subject: [PATCH 283/312] Update cla.yml --- .github/workflows/cla.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index dcd86e1cb..5e3b83b36 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -26,10 +26,10 @@ jobs: PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }} with: path-to-signatures: 'signatures/version1/cla.json' - path-to-document: 'https://github.com/cla-assistant/github-action/blob/master/SAPCLA.md' # e.g. a CLA or a DCO document + path-to-document: 'https://raw.githubusercontent.com/DapperLib/Dapper/main/NonCLA.md' # e.g. a CLA or a DCO document # branch should not be protected branch: 'main' - allowlist: user1,bot* + # allowlist: user1,bot* # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) From 13b2ef2cc2fb8830db3e3194c096647a596c7c9b Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sun, 28 Apr 2024 10:25:21 +0100 Subject: [PATCH 284/312] Update NonCLA.md --- NonCLA.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/NonCLA.md b/NonCLA.md index 72a6c11ec..f5c9414ef 100644 --- a/NonCLA.md +++ b/NonCLA.md @@ -12,6 +12,12 @@ and in that last case: a cross-reference back to the origin would be nice. (for the pedants: "licence" and "license" to be used interchangeably here; language is fun) +## It isn't "your code" any more + +Contributed code belongs to the Dapper project, not you the contributor. That means +Dapper can use it, not use it, remove it, or +edit it in any way - *even changing the spacing and variable names*. I know: shocking. + ## No backsies Contributing code to Dapper is permanent; you can't later demand that we remove your code because... well, anything. Even if one @@ -26,4 +32,4 @@ agreed something separately: that's it. No turning up unannounced at the tri-ann That's it. Basically "don't make our life harder". -Thanks! \ No newline at end of file +Thanks! From bfa91d3e3c17835e1646d99a98ac9e4b7f374e4d Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 30 Apr 2024 20:23:51 +0100 Subject: [PATCH 285/312] change dapper-plus citation (#2083) --- docs/dapper-sponsor.png | Bin 11112 -> 9750 bytes docs/dapperplus.md | 2 +- docs/readme.md | 2 +- 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/dapper-sponsor.png b/docs/dapper-sponsor.png index f3c64f7f8f38630d4219755c1aafc7fd3061b4bf..b9023052bce45898e2646896cafce17a7ed977d5 100644 GIT binary patch literal 9750 zcmXw91ymH@_ur*EC8QgqTe`au$pxf)>F!WM8l)QprE6Kb1PSR_kfl^Q7NiydfggVV z=l?lx=FEKFop;{cnfuOt_ud3O9aVfBDjWa+fUmBm^a=n#fj?cFW1&8s9aVlI002~g zp4MySr&|Dk5D3IUK|w)9CB}FHCJ81MIW{&nE*=#=K0YBKArTQZF)_J0uMFUHi;GK0 zC`d_3NlPoq{e!BaqN1|0mWqmsy1K5WrlyXLfu7#0|6purXl7z!VrFIr0-0M_SX)|J z+Su6G+uJ)jIyyNyxw^W7!CoF79-f|_K0ZFazPVG-!)5fKs5 z(b2I$U|d{WLPElOLYzcG?8L;xBp@(}lpvV^J2^Qy<@vMJ)YJ?Vlni?6OaiPAA3kIO zfm!%iS?tW&_?S64Ik~)?dHKcpKwy4;egPh4AwFgiFK>~!NHG9VTwGj2Nm+`5QdU-0 z4g{7f$W&HVRsn%k+UlQBP(ITuEO>gwvvO&S1z#>U2`rlw{f zu=)A(797-;7cW}uZChGeTESol5D0-lpg<;wr?(Xj4 z;OO=A?CtIA0|NW7QTiAd`uqC_0DwUXiZ2QZU%q@90sw~4(TC#VhKGm00svpJ03%OL zLu16)cw}T`G&*85J$(!a91|2A8yg$Pz!=8_jN93b2M2%4%bvgjOyHqUOiWBpPEL^# z%+OQLe5{z6nVIF_n46nh5EEFat6f}#!4#xmYHG_m>dVl!<>loSlUFPMu)1n%x$5t~ zy1Kg7-M#MNvYwy6zP`R$Te~?pxVgFcBRu%$*x1jXKYzu={rdH5dv<0gJ$>i%=bfFM z-SYBX*y8T)?tWwa!P@GECc!(GsL2PXyc6Ja5#L1U0C(~0WCnu+{ z#nZ!s^WVSE&(1DZS1&dED~*MANV|6Cz&ZfV|2u$LTm;llB&M&rwld~E8X*Ch zBn9Bp)_;_;iLbn$uak#2pt@mF?TNzkeWH}?eQdp*J$;=$+ySK6jLc6w&Oct!!!yX+ z+0n@tP)9f^@x&7R$9mga{}W~Kbq3o5M6L<3pL($W^%!_K_y$;e+XE~-AODHt{x`U@ zkFA^alc+bKeE^dBL}UG<3sLlf*qK&G%=d?uU}rm+koNlSi&ViG%k%Ip4<-nL;`Xzj zUufGc9Rlp?eixH;jM?Y*ED8*h2IchIx0{BgK$$4ji+XQ8dvo2SXP0&y1$f>H(O2|> z*Ixzk3tl%jvJ`{6XGiO6V-lkgu8?-=u(-nGpqcPZZZ;4h#P7z~pH8@>yw@34Vz6V^ z|Cb*7dvIy}L}Lr?#~TuapXQFW|6zH6z1>k1lvnl}ufkNElWhIiO(kzozm5i!>m9 z5r%LRF3h{4=S|M%SSVC2$R?TIcSKNE*@ z(lZ$BH9XYFLz@$MW*gcel94V@VJmsRDVguIr#LmOX$zAkhw#hDy4u#@*8Hhgd(LF^ z^iAz9SuqG^$jkapMFRiBwLLp^Sy@cDu082a_H9)mO#=<8782>W@|v3L<*D4h>H|HB ztJ%{)l76s0`9t zcjBLq$WeF1b-~Ex&9mmRtc7l%VeNkUb~6>XJ!Ui~W<_@;!K_w#op9`8b%zGt!5mx6 z!-X!6Nv|rFuEX^ltJ@YsuFPK>h~<76n%ed(Kb&dD>MJY_zrRI6Gi>|Xh5!&_W1q3{ zkGZ{`8YTc}U4+Ng*&LfU6cO*9Pr+8v|X7u zXs48UC@2&XYqc!Ydkqk&eA>;d_HtppXmK#=G_B$6)=qYPQ`!qo7M#?vxO|9s26BO| ziG#pi$S2|?ts6aq>f0)*7%DsSQVUrdEaV>4(VRvw1y7{c8@{(8rYZFf;bH|ZUhLLawRH?{ z3~b8OSFSw>6Gm+$PhF1IvnrGS!C>)uDI{iEc3x8N7NB#r|vq-$`B6K;8dSLFUVDlVz0f2GsNS1#+Yf9ruKO;-gbCNNGM(MR zguo90wm19B)D>b&)zDxuVTLWgiMY2amyf|n?hyC{ zZv4};IhRo3FEBQ7!dYa{if%6pEmUwc zN$+d)3sDjUPBIXCgEogtF5b^=mnq<9A4}()gvzL&FWh&$+2LXMW!q+s?kep)z7H zcR`DqU_6kJIKI$&K;ns%Xf7dd$t+yL?5&=*bwqZdlA60x~x?(0B zTZLEHknBW_4R2eN7JAqy&`WJr+u{kISNy#snta}1av-=uii#DMmug3@?RkR4v z=od`#92;k>y&So^IDn2n-sET`xEj1O4h|TY&@xfs{uuYg1U6KNGic<`EcYDbE*HoC zc_tua@Hq?;pE|n%QQ1JrsA?rsm#tM%>GoPNX$Be8U@zU@UH9+i*EaSY<_pw#Tu^y; z`9Cj(W&CNCS&yWd+-sO%iA2pfU?j2xfk07ovL&&^3gU&gq8AOlUv}X5D)AsLJO56- zDA^g77XE_qq16vBM4F>|67X^20{0L+9sUxDvDBlYI6Lo=oN+T1YO~oL(1cvRl{rqo zWgRvt@d&m6Fmr5YGf~P*GV)wqTX1(WczrErUNT8BM2eDY{Nb$xV^)V~z`$UTC9UTP zkvFqpM$WXJFF0$c5HJ0bZftbkKcjL^czEFhd(-MWBt}-@o5Qu2i(^KKg?YN4QeSt7 z=w2BbTLyh4Gd2%je?7g4xsr=_8 zp~>9*Tbfb%uBf~}SiKPnjQ8ni7)e9!XQ2Z zv6r}$O9yr@t6ghp9C%mWeL>@gon^~^C=F{9IS-Rj0PUc%h?+kJw4N6k@>EPcOH!?i z`)m^?uHRVPdrO#KisxEA&9N(H5kl41N|Ddt^2(CMTdt$t!NLi5A&fB;#V|cH&`})q zElB?u&V~bfKggo8)9~)nNeps^Qn{7YP*MH+xbewTf63<9e(PJ$%@g*&sNTXF%-QA9pBd@-?BghaHu|zh6sbANaWxXC}wchGj;>vEnEU2ZEda{mLy&XC2E?${S z0Thr7*ww|YWWZB__?+3(qaJ+rhB9C&b_&l4d-a~-Ygu}R)~~f$I28yWUv0a|$sRMX*ZBbykz_b~hzj{ezI0!J@Q}w^$VN-fLJyS{H!kBXyo1z^83Qb)FOQpP7 zd8$=Qq(M71w9+b&*GT!PpnG0UxN@n% ztZZ(_=!G!pd1&yq2x0B;_v0Q+d?es)RJ6fL7>!@emNUxBmG9Sb$V+IOrx!Z2YWQKS zJJcmGBIfdz1wO`b?fOjG(Akc&Dz~r3A!A$&7u#4(N+uza!qyRF@LNbub}!RFKaPb% zwCL(E(&tQ_X#PvZlo?r^x}{nti8|VcSQ*&UvL+m&lL#S1XxF}uPG9V-eXPu&YOcY6 zS2BNa*ZzDm*E=(oBzWnXgvhOVu;@bj6`?*4cS*UX*UN{R``J@Eie9W`=ghTpoCz0y z4y&YlwZ~&`QaRYyXs$K^(h#w9V{OUmtWlu(zNg*BZ`{clVrY1Xmv7Nf|EIV07U0TL zuMet#`qI+*(?>YT);6P-oGd4e*JO~aX$MuoW@Ceq{ucT;`wbr?t| zuf9RY@0P^Kr^v;i0Jr{q@{MwhUt>^Ow5cG0@>Rjq_g$>i6i z{iXG;C#TufEj=qejt9?RjZ3cXB=kyc^g40(MZePFVUvXJs5>>hv1a#aZ_xOlAw9XY z&-k$5%CY?HsKKHudCkcnGFer)EE5eepl|-`aIN)a-BL(shp%s6$~qgwP*%=n$S>I! zYS?=P6zcy(dh}$d$Ttr9s0H>oAsA|NC(8Zm&^}q8s&9K#7PJO?>Ez;s5SW?V{Kw7f zeC66*fkPFs&Xf7h(QO)3)Zh;X`o-Dny()L~XjD!6>#izmfC|AcD(w)*K=-9sIxe&9rUJ%oesE+V;QlHtRRKGF10OuCBx$&P&VL-G za$ENNp#_?;y>WpT1?$v1Q0~6bbnkNBFz%n|g!ut?n46?4dflrJ@0bbtwqxDu1$2Ev z%IOO-xtuzFS>-Oso<#T_{Q7#$9e&wi1-}}%Ss=jE+;Fp}d3#*op%Zpf7~I(<@jes- z$_{C-j)?q-zr3DRb7olCBa6?+=4lO$5x-wQnxNQ>cnrH<3)qz0LxcEqC(zurFuH;% zlS(?ScMCi$y>G9@`@4A4;5lRNxNsIqefE$I@^(8{G&r=`X;ePbxnGYUWMPoPm?vVt zEbfHeQ#SZj2Q^hJ_Cfl8z0lFo7=(ca7z~ zE`R-Qr8SrcGL_lvsuH)g*v{7w{O4yJeYxAFj}kxl4#)8$Ev10erwturnV#*4c%$~p zVrWC6miyi51rOXzT-GcOrd?S7+=;$Jf}f%3VmLUqDR{SlVp;0!&laJN=jP+Sx-o9O zwebAOV)e|2sbq)(gYDzrMm6Lc-M^NOrpu>%Z(HCDb!yUej|<58pA~yS8*_QE27H;*MYuqBXN)09W0XYOUTPLo@rAB5hKdJsqjQRi4k?ve#eLCpBN4wtpSvq?QeQdl>(`>%+OT*)S=b z>YtD5*ml(P${8K*=FmP6IVRP;b(wxOBX#lj;saQQBdA^-OZ9j{kkcGIPLt9~FMG?* z6pifJD%P3X|LvK&<)oAt{QRthGkVOqQhTQLOF}uur;<^A!kqZ&+Lf)Kiu&hCAE(#L z=U8JH>itn*%V=ok+3t?IVJ#;&6vYw6wgo~bBAeeW)TLv++E{IAv`^Qsup6s137r9frEOU@Qtr3I|h|z`=Gwq304K8@_YPYrja#@{Sasnn)r=y=ftfH=% zoeWPNP<^)#l^&v7ZL=QI^Xsz%&bM^AlB2W4)Df}x>8n-U!@rEecV21BCm z+g%4Wygx=Gt0yV4VUpeLE-%*N``y%{qO!7t(etAM3hc}Bj`Z65nuzYcIafE>*1nu~ zXLhN5zUt72%73I2?ko)oa~AHo))wKwzb}^qRiLwE^g3Ph(Ty$9L{X1ez2$1MHyEDSz5dD_9>`xBh3O6{s*3?-E*Y-$)V1Tn}tIg@xzacA?q zA50n5Xr6f-f7?_xkh)C*yofp2r*{+gzoF?({p;z35{Z4sSW6Qr%Q+C<8{MJ>Ks7U^ zeh}m?06F}~&YMq$(E3o?LqL1J`1@_o3lSIq zfZ5k{`>I^v$OTM#i>^Nn-GLpCzSru6>@|aEnAvBF9nEeyl;x^%3Tl7$Io@)O@cX*R z9)Es`9r7kArfFN=YHU=VY06q360#9UDd?(ZA}+s5Bp^Y4$M3?}PqR*_W$%Vg8BI!x zmo}{vw{L1YxiW8GwUK_AJ&iUr`;kG-E(bp{ZQCvs!?R(kWeQ`=fnDzSgA!ItW?t;< zwisNQl`+(b34s(qSNNnOs>o~VB6>+X@BfNd^oI2KO)5RSTQL^8gsG;0L@f#7)^7!B zea(tmpBMk#zjF1Bi!UK5EMDx3w5b)r2)k+H>Uvl~ek=`G`9255NOLK3~rLQc33{NKO8ZLPT(MLyDb#Mm7t z3y|KKQ)z}Pog_&Zbxp1qntB|6%y?$0zMn*leD6Ov9Xp@^4QC9p0=#B~-!)Yf1RkSj z{tf0iyCfI#_kWr7A$Rcl-Fov*BLoZmZg@E)0KCbo3=-PQk??8jesXLe3jpV+BGof( zLY>nv@TL)qayuC0R|W#7cH%K&6{4#J7;sw`ci3HHY--%U`6DCkoy1{?#! zpZD#<9+s@(E;2WWI7uQ00k|0tgMst>jDc`@-W=PhLyDZ~#fx$7Gudb({6U0q zpt8U~lEMt{Ks6;wX-K}m%Mi0;A4n@xT2Pif`F!qZHVnT+uy>0UDm%G8g=(MT^u4Mr zBB)5m%7^46`_~;VhwR9t=?EyRz}sUM+4Eo$A<#gvFVZw`CsWOw}zdTo2Hzt z+_Ly81h=q)t}r-WcOkK`+R~GEO68g1gQB5=!whg!U!vT3Gn~%eXvA-)yDP-m@~KSF z%y?@g)PI^!#BXq-I3wIBK(Wb~P%srt{vdIEl~Nz=)%ZBh=no!UA&q6t@ z*u2X3Het)V^5Cf$d_h5wOK6bWVmH)`RdE~sC6MM+;TT?afcxp|tk>li ztFkci0YU1N?;S#1xyJx`NgKqj*meY9c$b1Lza>!j^z(Vpy-a$3#LZc&v?O$5Zpj6h zV2vyfXuG{69iO^}l36qo6S{fc4%LH#I$nl)sc0cPOOTISWpVTrhg}lMcPACa(4&QOwbJ3@;BAn5Q*ul%3cY zN*(iUp2KD!ZywW%>Yl=#(R;1Fv-pZ8G;;bFZ+`~b>f^_Yro>sXgy`$U`C&Ege{Vo6 zDhmgngPz5GbL;IhB*@uKy; zLHzFaEFGKK9=i@lV}WR8>_E4N!mm0E2_3bwLSoYW(UMrmF!2KSAJV;4$-=(|`0p2H zMpKWo!pnZn{!@F`wJqP65p~l@b+YuRhH8^ofF0&2QuFRz5Yk{X{EzUp-?>*A-OG1( zeRq~_=cY&bgJM!Sl9e6Q@e8WnH-e8;LC#|(H+{RLZz7s(yGfohjf=4szb@lo`S(rl zcd9hmk8*46a%Q}4Guxp|Yau#%4zwvkqsaajwFDeZVUHD~p?=;A7{5M|wwKFymF&E) z!iL}d?dyCbxFL0OU09|+hVxR$gIdw;IO~TFt6GWWPdK4jmU+CGn_lOrN_X|ljpM1#a3rT@vdYM(`Y5`V3ccr`ZnBqRg;Kpq4u-9*K z86OSJSG`pn_12LJtF6Ovg7tB(TQCx z1Kr}nKl?)^DWM&%LtGiDnM_+%Zjb8$bMXzhO#R_yH#eh-As@=dYHLr1LrbV3Pyb|! zTl<`=X6y)2c}i8|^~sM7wj$4dhWgR_VNzF1$*RmSuZjP@QylW-JMZQA@t2D|3IVzu7By*abLu>}x#RZhs%T2{m5k?uA|LevU#O z!z@|v>*&4^Uwey9)7{z3fLz>UJ0FHTruWxNEhhgdWHF$GW5BXq!wgr07SWj+)G+j| zx=6c?c29@qMh@>9Lo2H;!klWSn$m^)1n=H2h*?meR|j%s*A< z(6i80B6xpD`V;|gc3!B$m-MfXxQMxzExgs007|lzgmDzx(M;U zHyB7!Eo60Qp}u^`oO?a-GTAxI$?BOy9BslCW1`VHofK0xjr}g$6)KcgA$_sI*IRv{3OkVl8LMW#Il3;A_>(To zbAFg#nO`47<}?%jyD3LqmL476C6V?QUVKgQw2(f8bmchBSRl&UHJW`}{1SGS9a6Pb zWrK9d6P#WO>dA+kv|7S0w>}2Q)M#=??B3;v-~N*J;*J)%lZxa~4jG)-sA{d3Sm_!* zcfIes-I~7*V;M${wLL>9p9U9QE}()fY;M}Z>38pxtQtWL>$UM1@Wc%d)Rm`Xzh;{3 zAhcM5sR5%+`*rIK2=bKUod4&qTqZ$zEyK0#N^X&g6yH~SaGH0XCO>fZ6l#B}91!1` z38k;!Y#%~L)=0u+CA*YEkcW*ubqA`InD8#K8xbDw*RT6MR=W(%6sbCtLr#1wMa%;< z*g+KE`e_a5?!RP_uUuZfs*f64tB`Kwc}gWcRTGq-9|h8m_J3zaTPZMFuU&(1P$(V(>RTH zQ6JvxcgIH_PFQX##-0EGe(&!WFo_eN=0PY0Q#69fyE;4DxWE8D9un3P3eT0q-MmHl z1#O)q`1!dUB?Lr;g@wdL#KnXK1;qtK#f8O1xL^_jLVN-OBK#uz>2_BD0NS~Oo)OGQ zLtO&m>dXVScD1tM@o{#0nBZXwABl%cXB!xp(Z?C;;wj-H&Gx5;#KZM(F)y18Hsikz zDGzH~2^|Hcf2lrvl4i4q!Q3QxdA+^8dAtRATs`b~`NhS>dHDo*1q8SsG`Kx|U0`4z zZWm9M2c*A4DcE>IJRIC$4z4bYzoEfau5g$%8{1!6|KXA~IpxBR3)x z_kh^^djb3lP~O7^470J85#SRL;^q_J77&r*{m0N>Ih6X>fT9mf)mrAkKoBe-Xv1y8 z4;JDU5)|g=789`H=N1NoA)-Q}HnyU+;(uZNhsl3KQnGpg5*82^5D^yV7vg^?dNIDg zZ2lblZzg)KaESfyuw(@O()=&kf2sd3()@S2{xj6S)B67?(Qf~?sD5Yp&(-=nzYhZs z`4#+Iejj8K&*3n8R}UEtu!n;UH`Eo*sOMqhYy}1Ta?ACUSc5&RrGA_Kr`kV4{l72F zhwkyWs{G4eAF$hhtH%dri9c({)kDwK6)GbScJTsB@%~f%zjN|Gd(l619=g)M6*TW( zT}>uj2*L{hpaQhj^%PG}Pw(#ThA;u|2!1!?+uK_JfZ*!t>iqot^78WT_V)Pb=+UD` z9Y7!o3d$D%AU8KRJ3D)4X9pFPs168RSXij3siC8z>+kO;B_&NvOze5ECm?7A0%L@$vB=KYny}c0RODO-)@qJiN8FH9I>y9UYyA#!5x~Bqb$9Nl7UIACsJ% zd;kN$#KhDO1V#~Ht*oqYb91w@vd+xR^kD*2RkhgI1Tyh3c6WEx)YPhhKn6w*PA*|S ze(?@0l(DffDk`er;9ycxrXpN)Cnu){AW%_JorQ%bIy(A23BHbwAt5164K`{?Nr|to zFCQOYYisN9@bC~Wx{ZxZdwWL@I=YmUl!=MSvuDcX<>ee491;?8fq{YLIB1iTQ>Lb7 z%_u0JKYxy)A}s|1=jP^4j*o|ih9V;)y8r+Y5g80j^1i-4dwct%pFf{Hdv@)u=t3Ak|i!2|ByROK%Sh;BMjorsYy3Cw;vlD2n1q6 zjDIsP?|5aU!px`$1!c9r|K{>Cm51XiJ9`5*>TYYZw6wz6;o(|DM2!t(XK`U8E)E8R z9gL0~PEDR{Y;g047HX&>Kp+nY?53a?OiONebE~GJnlsYbuBuq+?q2Zn+O4abQc=mk#dr?{F1EH#l$W*o zc;!%1?sj*7|2j4$Cq+a=J=0L%_u|C@#3GA_r`FlIOjfpokx^gYBp@*C;`A&S9led6 zeWk2yqqum$&o2oNb4F7Y85Z1UXgFYP{aH>f84qLN(WA8V>@t0wnx{|a<`#U=(TQn( zBmw|H08B?y_x}I$Bj|Ly7yw{uR8@Gc=acy(3(JSpCLLL}-59S~^ZM#qN~dIfPa^2Fs&V-Wzfk!PVXMK$N6lwBH`40{ zlx1p$AHJYv?;*DqS&@F<8VrwF&1jm30>8GjbfVy&A^00_7JI51J1Op((V$x!GPdSR zmbsyi{Aase??+zk9j;)ffJU~1DmZ67sM7$iDlp}7k7Eur;yfxaW?RCzcyOQNuuk{% zy+6j47YV!y9j=N_xh{soJ%1TtknV$HZ^je$xP$%rm^MrZ5O8!uO$vw5b-&)kP8zA^ zppA2Z@>77Rq4owWpPa7}`2bRdw_tJl$Dg_{HJ>_#u3Zr+CXXR*+oi;3{6Z=?eLm}{ zbam5tl{e{@GRfC;?q} z9D1Q}#x8pIpRw6SG$QSraho_EA-6`jFP%L>gAN-*)m=itqEWf?#ZB<*F7=~(A4=Xd zWN+`u{`cj_&w0TdNq2wLc#H|_0GW*&$Uvdiwl2d0SFT>_>P;rqs{oSQ%gZ2bE_MEs zEz@<y0Q29I=;*+RyGR(NP~Ce*^k?gJScJme@(pdCJj3fKNyh$Qeud;(`V=odm; z7cD`aEy6F;B^ouHLqa%F{S;FF{`PO4&(p2aZ(Pi?{JjiP z>oS!>AY4v`G0vYvGvYe=f8eA~dBvdiGu{gUSU<>8f^jrmKuW8XWVAH*TxNt{#ynFv z56F08tv=TPOX={LtdhmOa4w7Rx}I> zmXAhxE9TkTr@`mLM^sOo@5K~Gmr3*>(!(x z(`i`b9uuOZAhQs62ujx)HQ*Q;>oEONi#{s3*#YKdLuEZUs2YadU5&NgTn_MMvY=dm zZd6J7BJu&uMqbQ-r@}a(;)Skc1XQ5+*szT4O&G$^P{AvlD=v{n=4-v7wQtZ9*61yU z%hrpPIPo*3qcJtm6VYBbBdx*|R6ZS~W^{x|kWDj$L^7y6Re>m4j5Abfqtv7XqC90f zz#H9`Q?VYLkVqvF&DHRp6vet3!ZM4q{bK_OL>&dya)m+s>7P9HGRDe&<%64K@s2G4 z{oM>k(nO{D8#GL4(nAsYj&XhqNDqB`jC|{WDmafh*?+`ma!;Jl$c+akcbnAt!%-p8-IIo!mm8CVCTM!W%HL^yNzddWS11(5fBs%bT?5~)- zz|67t^UEk*+z@R>DJBc$cy$3N79(TmM2e0^fU-?+vj$4S+E;CyHd$>q#)X^>&!nw+ z5o{)#b0>1y`IF`>quC(e~2}3xevGFJBgHAIdubn38*(n&jQ~J6w>Og7V`6{0T!CSf7ob@V^;R zqFH-vCinMg04Ns zq`eoi?)Q%UL)cRwjoSEwDJROiF)vg4F!?cFJ>NNT1A@YUX<0l^G4zG$87Q)2M(A0h zxYEJ`6-OTvhZz~ZH%Hr)(??~YZ9>s+gg`!IMqYC#bIx(rv;k+l>}?f)Zd88>=S6jl z!E1W=BBz!c0>uCqE%$MmD*ZS{oHSNmJBKjxJX{5S(H zI!FWYVUrph_W{rd3GOR)<7-DJn+p_R!yr-#`uv`0H^J z{Mh{hT)Pqj&dnxTfANkYq;al;n>>}yv*HokB~a8v4^sn$epZyXp}5)?nwPt?2JiG% zfUF`LRj+UCeGtEn6xQIQ`8!yVp>dq;6ZJLQvlj|^ps0=Mrt`t1ceX-wG~G{LA|9)| z?Jo-;qPn?cetDsM%~WHRCTAKgP6=%%TxoT7j(hdAb>N0$k11{?%cO%=(Ev>i`g1G9 ziHb(ZHr@^s4o&K@wqqIE#JpntPVtU_R?ETeq6NR<#gNkXt3-hjWO0XB_!nz?d1=SV z*I4-nQX;}GBim}FD^aD z7R8Eb(6#bPANRsAXYpC~poq8`&Mzh8T`r2+2486k4Cp=ZNl_oN6$QfCuvIx9gny}r zK!pNuQ{}PPw?}KOK}fylX?u_J{vV7|a zoC{>S?6fpv<`uFSW@?qHB>c|H)%y5?+&vA}H);i3c+j7D$`1NnKS}mRLo3c6`in#B zi>vgPcnJCHQEJuPjv>_DmuS1=`N~VeVo%mG>1 z<_n&+*#WOs>B>jk{3s0j7jjAoXf~Qc^P%c^y*QnDZqR(AO6w{G&f3p9{+S<$9(G;z zL}|&-xWZhmq{RL;v1vrmFh^m_3njr0I-QZmXV8_;M|00gSkC1|&*wK(Q-DZuT?g53Mr(TvPq- zX3HT-32a*Qm-$x5?QF7!!%=WW+ja5b136;`Cn`dffm1=$@EGxO=)8Es>w2^|clepk zDT%tQnh9!TV*;!ye5NnHat;(UCeC)%y!^FSS&oT^i=$d?poZ5z+HP+UO-U)avdLi# z+ehajXee7G-?4~fAiw~0BH3*W(6}mu?wFBDks#0S)1%+rjSqsJ9RO@AHv^3#?6w_9_)g}oHX&8s**N|WYp5ybVQAENa;ho;N^#ewncf|_R)Sq2Hk$z zRzOuptJ%3lWK5R%vpcKO4?xgC*ERl>zaN|6f)unf+w>!5_4Z2)vw23(!ykLMPeLg<4*i0 z@*I?3V3b*|d@=PS>cJEhs~j{~$!b(m(Oj&a3Pl$^zv5$DeyGHiX%Y-GpBf}OrY|83 z_UoU0>BKxZUdCUzj!nR8LK=N<)9>^tZTZ^5VK#e^6M6BCNjt+rb0zE3girdW=(S2x zL=t2Qed`9skLT(-?UR)>V02J<^wqONHmP%QqjX;%T+3R`Nb{N$$WhwRT zARGe$oVmQ27&ey;A>tjT$C{+e!@NFk<*i38oOz>tSUT#mQ){h$GQPNL6)k)-_!CYK zor;Z~r8Ix3mXz`$nUW`W}cnv!0GWU4E7Bd_$FPJPRI zC-Bs~6iip!T@#*3C)*!6BX>W4(p%3J5^Zjdp|4Rh14*@Pq{U?NK`gR& zPxQHzvmFJ~hbq2BU&2czr*rjG?79q`=Ip-~7YwS6n5@$i=RPqC7V_OF9^;6nH>#yQ zo{J&GnnUfSNYy=-Y5$h4xw{)LaWy&%Mkv$XicmR7mVi(*dsCr?_&!%i{!-Jx(MsskK2q(PLtQcS{Y9l`w>y@~r!usa^ziwLTMW}v2#bi)jEbe;t}{+k zZoauTuSwm}PrX*^X)X#;2?hK!w5wbGlXlevJ(4oP{;PC##K}~aje=n`gkka63*?pyP zxnJ3rqb z&8-3-H2hr=%`q?bQSu-{s)F>>jtJk`+ExaSJqEhvb8o z8)AiJhCT{WAM&hp2YWw_ghi0j4pqpU%-_G|bm!KmH_E9@r~Km1;Cf}1O%Jw2-+jT+ z8;sC56`@A(PBrQ|9sl}vA(raF#$dEK^M?nFWNXpwAjx$(_%?H(Ugsj+UVNq@4o|7b z;qZ>P<6_eLl+E0*r6m#j7Y%gk3HtVo*1=SRQ^f&W4|(PH&c^RYMj7f{jim8e9Qq~6jP<4!m;1FSZiX9DHiMZBy^n}9;907BscW>Dbv#97y%Ku`U9<(X zjJF{4UF5pR&E*=w`e;t)>J0%>u}8Bch1v_Ie`^&kNL0xXIQer>=F|fYr$EPdssa({ z-p{Jg0#h5Eto1mNG_H;W4~qvMwref@oYW0oU<)&sG+vu#sIau{WxVM< zj+MFEKEsn8nVW*AO@8UEUjZfXDP-+&v$cCv5P@iB^2pdU^8=;QGJn{0qzP>G(YUsy ze7x;3_+_Ee%37$9C)+~$O1MsA5o>KGcGQ37lLeykAotlPxf_6KyA4Z+$iaF1CPb)p z$5RBK3A9iM#)x_{kSiG2U?=QE|{|&Kz8)<19~2rvruSBV@=#5rqxClM0bZWSTln zN5_sR+WtygAqMl3{_}1VGUy0jFW=z=L0d}U6gJ~7g`}L^!4TYI4?j?B(~h0!2hCWj zS@?(sH;w*E>Sz-+ed<}3&(Xrl$K_yA!XgBdd^<%lpe$f3q`4GgzOBQ7C?Tu@IsM+E z??0KVCB5M6vh{qT9FZt2ra=>9b}=_r*&H6v{hB4%fdZ9iYx1p*Hz$9B!xB>V=H1(n z_Kl`0k~&tA=`Bo3;dl%Au%d4YfR7^sG?2*jM+#n6Z^O$JvYbX|#Sy|iggu1`)LC3_ zzd53R2=rFzSB}&48l!NzRalgY!42Fi7 zr}!GmMcZmc04Ei;EsZoY@RY51bzUD03t29hW$k)QP2z`CjMorNFkM^TW)W1@XGWS< z3;2zXlEz3k`u4rw3x1jxZ5>?uTv*y!;9+^^bOwdI4ZGE{6yI0cb|s0xx2pLzYrFrm zvhIy*r|E2b)cNA=n?yu;vP6(V8ds5f2V!i4`*YVGuWcwo=*>un1oZ?)aZN%C{TaYC--gAb{l{dFOw%;2_>Vhu;Q zrR{2pG0R9@4me1RV(1x5>+7>AMaj$P?+nawph&BH9-_krO~rW}uXtg1OQ6ByaWJ`& z7b59?36p)0(4Tm36q{puxH)A5hLo>8qInl^E;b{;F0>C#+B(`nW7uUABa~ zFJFyWx~m-am;nP`r!PE?@5Kqa?LF1sL4&Fd#G~xTvcHEmTR$-xn$tnB);~67j5o&= zCP6M(eIF{n9oojrLQQ^775TMK(;WNP>FM@;8>F_p2ZbNX%bvRQC{j~kLdVQfK{A~+ zfWR?7OVmK#{m?oCzeop5A6=^C40W*UJ!H{0&l1Nz_<9U3Gpfrv`A4xJ_l;$3HN&btNEJzh z5c2|tiaxp)aAg7U_8ln9`F$!uyTUZiyqis{r4v=JsU1ZzQWJ~T>dyE`f2}v%eU~sD zAKf1qha604mY~~jEbda%he{1_Fqs^TTzqx%;AN6|9sVeDX;Hoov^|fYZAYIuF zmNm;fLBPbpy51aj0B_x|o1EHT&K0~~8;aeP5o_O%9giGSR-0n0HPzFK0@G_*y& zMi;*c-&d#^B%XJcFmaJ+7bS!?xEE3>bDfxlzx<fe8LlLaBiB1v_SmB!c0skg0M! zA8mIZo&9Sg7<4~3MS&3qFipU0!amp3%28bM;f!s0c6gmDtRibe5PEqzn5<+pDtO=yq3dJD_tf9bEGM*QW6@A*fqwdx zHSztqMP!BgN=B+aKOXt}2536s&wd{=(<=32HzhX=WzkomYZddDI#QQ7GAAqd&_qjh z>?IpDp*Sk(UTT=b!%;*{BwmlyU`q|x~PZG{f)3myHx~rv<;}>8_R9iL(V#J<9Q>|I` z``w^%~LlsCyBd`^YuaU!oIyJhHPKOHK#44y_@rW=RIP=MhXqbN^{<|*5#SpG{o0h zAJo}WXU;^snks@v=rW~(s5nWxdCFqIb2k>XeKUSsB(WouY5d;41hZZc&T6!h|wGH`xX1%)cYge03Fd&9xEJ&<9f^D`3zZ5UIcW))e@vML&Ddiw7 z^%sG!nbbtMXh$p6xQtsEvD3pA@BNE_UjI&^=J0ma?4zc$9K((Fn!b=2LCxYzA)tTI z*WiQuh_FcEiM#!X41b?@S!jcnAe8wJH{pgp zsCqaU5HVqE7(on2r8_c0+Wlh_Qco9hXg^Fv#ixpd-Cj{yuF&2Z8nwveb#G`BmK6%b zoHefabPHndj6-@;#ovp}Tz8(ajBIhg(y+FyHL|JRS&UfG2%et1Tbo@8#GkzV^@{Md#uRn*k6m)yRvhE_lXJVvvwWlikE#UnOAj8HqrHoBoGvIVI zN_lxsYTaZmLM75cx8`2b&HZiVP~OL&*yj5&I|BAsACN_hVd63xh{l|wSf{F&PDZc$ zLUIbv?7X$MR+!A~?{zG+u45$e?=~_=Pm?M@wimQ)qX*f`Jrh;PVImzS6Oe3`xOKeQ za)g9cP5sSc-Qmy@>3)W*f#?r^i_Yz`@VAxsE7(t_wOk9}7gF+j?kg>FPH+TUV5jJY zXYk-THON23VQ-GYWm@h!TlvnN-K{FRh38dR+A7Y<)obP+(fwbq)?GY0`Afs34~s1w zWcrjD*UXadW+&e@sc4>^F@JYg<9!H5j&{tNjUBv(cnoo1w!<8 zplUv#?F3%|u1YqHYk3{T+sPO$hd9e7C@%mV27g&O5zhb9ZhBQsoTsWsSkNOsJpKS34ACgVh6Is=~JRw}DEE4FUY@IJ&=4>8-*SAd~6;?Ne}&MjHFEvc5{ ch2Nu&PR#QJq3@Reeuh<5QB$E(4ixr30G)gu*Z=?k diff --git a/docs/dapperplus.md b/docs/dapperplus.md index 4a6a0f269..b15817721 100644 --- a/docs/dapperplus.md +++ b/docs/dapperplus.md @@ -10,4 +10,4 @@ From 2024, Dapper Plus is now a major sponsor of Dapper, helping to secure ongoi This sponsorship does not impact the ownership, license, or any other particulars of how Dapper operates. The core Dapper libraries continue to be freely available and fully open source. -Dapper Plus logo \ No newline at end of file +Dapper Plus logo \ No newline at end of file diff --git a/docs/readme.md b/docs/readme.md index e7b6875dd..e201fdcd8 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -25,4 +25,4 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) -[![Dapper Plus logo](https://raw.githubusercontent.com/DapperLib/Dapper/main/docs/dapper-sponsor.png)](https://www.learndapper.com/) +[![Dapper Plus logo](https://raw.githubusercontent.com/DapperLib/Dapper/main/docs/dapper-sponsor.png)](https://dapper-plus.net/) From 52160dc44699ec7eb5ad57d0dddc6ded4662fcb9 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 30 Apr 2024 20:28:19 +0100 Subject: [PATCH 286/312] missed one --- Readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Readme.md b/Readme.md index de4b6ccd3..c22aee4ba 100644 --- a/Readme.md +++ b/Readme.md @@ -41,7 +41,7 @@ A huge thanks to everyone (individuals or organisations) who have sponsored Dapp - [Dapper Plus](https://dapper-plus.net/) is a major sponsor and is proud to contribute to the development of Dapper ([read more](https://dapperlib.github.io/Dapper/dapperplus)) - [AWS](https://github.com/aws) who sponsored Dapper from Oct 2023 via the [.NET on AWS Open Source Software Fund](https://github.com/aws/dotnet-foss) -Dapper Plus logo +Dapper Plus logo Features -------- From 9ed3525598494dddc1fbeb4e95e018239fffed13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A9dric=20Luthi?= Date: Wed, 3 Jul 2024 14:32:18 +0200 Subject: [PATCH 287/312] Do not close the inner reader when disposing wrapped data readers (#2100) When disposing wrapped readers, only call Dispose() on the inner reader and let it catch exceptions if appropriate. Note: this actually happened with `Microsoft.Data.SqlClient`. ``` Microsoft.Data.SqlClient.SqlException (0x80131904): Execution Timeout Expired. The timeout period elapsed prior to completion of the operation or the server is not responding. ---> System.ComponentModel.Win32Exception (258): The wait operation timed out. at void Microsoft.Data.SqlClient.SqlConnection.OnError(SqlException exception, bool breakConnection, Action wrapCloseInAction) at void Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, bool callerHasConnectionLock, bool asyncClose) at bool Microsoft.Data.SqlClient.TdsParser.TryRun(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj, out bool dataReady) at bool Microsoft.Data.SqlClient.SqlDataReader.TryCloseInternal(bool closeReader) at void Microsoft.Data.SqlClient.SqlDataReader.Close() at void Dapper.DbWrappedReader.Dispose(bool disposing) in /_/Dapper/WrappedReader.cs:line 149 at ValueTask System.Data.Common.DbDataReader.DisposeAsync() ``` --- Dapper/WrappedReader.cs | 2 - tests/Dapper.Tests/WrappedReaderTests.cs | 158 +++++++++++++++++++++++ 2 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 tests/Dapper.Tests/WrappedReaderTests.cs diff --git a/Dapper/WrappedReader.cs b/Dapper/WrappedReader.cs index 42fd6da93..04321f976 100644 --- a/Dapper/WrappedReader.cs +++ b/Dapper/WrappedReader.cs @@ -129,7 +129,6 @@ protected override void Dispose(bool disposing) { if (disposing) { - _reader.Close(); _reader.Dispose(); _reader = DisposedReader.Instance; // all future ops are no-ops _cmd?.Dispose(); @@ -245,7 +244,6 @@ protected override void Dispose(bool disposing) { if (disposing) { - _reader.Close(); _reader.Dispose(); _reader = DisposedReader.Instance; // all future ops are no-ops } diff --git a/tests/Dapper.Tests/WrappedReaderTests.cs b/tests/Dapper.Tests/WrappedReaderTests.cs new file mode 100644 index 000000000..0bdf48b08 --- /dev/null +++ b/tests/Dapper.Tests/WrappedReaderTests.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections; +using System.Data; +using System.Data.Common; +using Xunit.Abstractions; + +namespace Dapper.Tests; + +public class WrappedReaderTests(ITestOutputHelper testOutputHelper) +{ + [Fact] + public void DbWrappedReader_Dispose_DoesNotThrow() + { + var reader = new DbWrappedReader(new DummyDbCommand(), new ThrowOnCloseDbDataReader(testOutputHelper)); + reader.Dispose(); + } + +#if !NETFRAMEWORK + [Fact] + public async System.Threading.Tasks.Task DbWrappedReader_DisposeAsync_DoesNotThrow() + { + var reader = new DbWrappedReader(new DummyDbCommand(), new ThrowOnCloseDbDataReader(testOutputHelper)); + await reader.DisposeAsync(); + } +#endif + + [Fact] + public void WrappedBasicReader_Dispose_DoesNotThrow() + { + var reader = new WrappedBasicReader(new ThrowOnCloseIDataReader()); + reader.Dispose(); + } + +#if !NETFRAMEWORK + [Fact] + public async System.Threading.Tasks.Task WrappedBasicReader_DisposeAsync_DoesNotThrow() + { + var reader = new WrappedBasicReader(new ThrowOnCloseIDataReader()); + await reader.DisposeAsync(); + } +#endif + + private class DummyDbCommand : DbCommand + { + public override void Cancel() => throw new NotSupportedException(); + public override int ExecuteNonQuery() => throw new NotSupportedException(); + public override object ExecuteScalar() => throw new NotSupportedException(); + public override void Prepare() => throw new NotSupportedException(); + public override string CommandText { get; set; } + public override int CommandTimeout { get; set; } + public override CommandType CommandType { get; set; } + public override UpdateRowSource UpdatedRowSource { get; set; } + protected override DbConnection? DbConnection { get; set; } + protected override DbParameterCollection DbParameterCollection => throw new NotSupportedException(); + protected override DbTransaction? DbTransaction { get; set; } + public override bool DesignTimeVisible { get; set; } + protected override DbParameter CreateDbParameter() => throw new NotSupportedException(); + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => throw new NotSupportedException(); + } + + private class DummyDbException(string message) : DbException(message); + + private class ThrowOnCloseDbDataReader(ITestOutputHelper testOutputHelper) : DbDataReader + { + // This is basically what SqlClient does, see https://github.com/dotnet/SqlClient/blob/v5.2.1/src/Microsoft.Data.SqlClient/netcore/src/Microsoft/Data/SqlClient/SqlDataReader.cs#L835-L849 + protected override void Dispose(bool disposing) + { + try + { + if (disposing) + { + Close(); + } + base.Dispose(disposing); + } + catch (DbException e) + { + testOutputHelper.WriteLine($"Ignored exception when disposing {e}"); + } + } + + public override void Close() => throw new DummyDbException("Exception during Close()"); + + public override bool GetBoolean(int ordinal) => throw new NotSupportedException(); + public override byte GetByte(int ordinal) => throw new NotSupportedException(); + public override long GetBytes(int ordinal, long dataOffset, byte[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); + public override char GetChar(int ordinal) => throw new NotSupportedException(); + public override long GetChars(int ordinal, long dataOffset, char[]? buffer, int bufferOffset, int length) => throw new NotSupportedException(); + public override string GetDataTypeName(int ordinal) => throw new NotSupportedException(); + public override DateTime GetDateTime(int ordinal) => throw new NotSupportedException(); + public override decimal GetDecimal(int ordinal) => throw new NotSupportedException(); + public override double GetDouble(int ordinal) => throw new NotSupportedException(); + public override Type GetFieldType(int ordinal) => throw new NotSupportedException(); + public override float GetFloat(int ordinal) => throw new NotSupportedException(); + public override Guid GetGuid(int ordinal) => throw new NotSupportedException(); + public override short GetInt16(int ordinal) => throw new NotSupportedException(); + public override int GetInt32(int ordinal) => throw new NotSupportedException(); + public override long GetInt64(int ordinal) => throw new NotSupportedException(); + public override string GetName(int ordinal) => throw new NotSupportedException(); + public override int GetOrdinal(string name) => throw new NotSupportedException(); + public override string GetString(int ordinal) => throw new NotSupportedException(); + public override object GetValue(int ordinal) => throw new NotSupportedException(); + public override int GetValues(object[] values) => throw new NotSupportedException(); + public override bool IsDBNull(int ordinal) => throw new NotSupportedException(); + public override int FieldCount => throw new NotSupportedException(); + public override object this[int ordinal] => throw new NotSupportedException(); + public override object this[string name] => throw new NotSupportedException(); + public override int RecordsAffected => throw new NotSupportedException(); + public override bool HasRows => throw new NotSupportedException(); + public override bool IsClosed => throw new NotSupportedException(); + public override bool NextResult() => throw new NotSupportedException(); + public override bool Read() => throw new NotSupportedException(); + public override int Depth => throw new NotSupportedException(); + public override IEnumerator GetEnumerator() => throw new NotSupportedException(); + } + + private class ThrowOnCloseIDataReader : IDataReader + { + public void Dispose() + { + // Assume that IDataReader Dispose implementation does not throw + } + + public void Close() => throw new DummyDbException("Exception during Close()"); + + public bool GetBoolean(int i) => throw new NotSupportedException(); + public byte GetByte(int i) => throw new NotSupportedException(); + public long GetBytes(int i, long fieldOffset, byte[]? buffer, int bufferoffset, int length) => throw new NotSupportedException(); + public char GetChar(int i) => throw new NotSupportedException(); + public long GetChars(int i, long fieldoffset, char[]? buffer, int bufferoffset, int length) => throw new NotSupportedException(); + public IDataReader GetData(int i) => throw new NotSupportedException(); + public string GetDataTypeName(int i) => throw new NotSupportedException(); + public DateTime GetDateTime(int i) => throw new NotSupportedException(); + public decimal GetDecimal(int i) => throw new NotSupportedException(); + public double GetDouble(int i) => throw new NotSupportedException(); + public Type GetFieldType(int i) => throw new NotSupportedException(); + public float GetFloat(int i) => throw new NotSupportedException(); + public Guid GetGuid(int i) => throw new NotSupportedException(); + public short GetInt16(int i) => throw new NotSupportedException(); + public int GetInt32(int i) => throw new NotSupportedException(); + public long GetInt64(int i) => throw new NotSupportedException(); + public string GetName(int i) => throw new NotSupportedException(); + public int GetOrdinal(string name) => throw new NotSupportedException(); + public string GetString(int i) => throw new NotSupportedException(); + public object GetValue(int i) => throw new NotSupportedException(); + public int GetValues(object[] values) => throw new NotSupportedException(); + public bool IsDBNull(int i) => throw new NotSupportedException(); + public int FieldCount => throw new NotSupportedException(); + public object this[int i] => throw new NotSupportedException(); + public object this[string name] => throw new NotSupportedException(); + public DataTable? GetSchemaTable() => throw new NotSupportedException(); + public bool NextResult() => throw new NotSupportedException(); + public bool Read() => throw new NotSupportedException(); + public int Depth => throw new NotSupportedException(); + public bool IsClosed => throw new NotSupportedException(); + public int RecordsAffected => throw new NotSupportedException(); + } +} From 70e57f820dc5d845ecacb92b5628a4f5f8fc63ae Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Mon, 7 Oct 2024 12:50:42 +0100 Subject: [PATCH 288/312] appveyor pgsql update (#2119) --- appveyor.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 08c5eb159..c277902bd 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,7 +12,7 @@ skip_commits: environment: Appveyor: true # Postgres - POSTGRES_PATH: C:\Program Files\PostgreSQL\9.6 + POSTGRES_PATH: C:\Program Files\PostgreSQL\13 PGUSER: postgres PGPASSWORD: Password12! POSTGRES_ENV_POSTGRES_USER: postgres @@ -32,7 +32,7 @@ environment: services: # - mysql - - postgresql + - postgresql13 init: - git config --global core.autocrlf input From 38df7bb89eca60df3d07890e364eacdbdc443086 Mon Sep 17 00:00:00 2001 From: goerch Date: Mon, 7 Oct 2024 14:00:15 +0200 Subject: [PATCH 289/312] Fix #2113 (#2118) --- Dapper/CommandDefinition.cs | 14 ++++--------- Dapper/SqlMapper.Async.cs | 2 +- Dapper/SqlMapper.cs | 2 +- tests/Dapper.Tests/AsyncTests.cs | 36 ++++++++++++++++++++++++++++++++ 4 files changed, 42 insertions(+), 12 deletions(-) diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index cc117c304..19963ba67 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -11,16 +11,9 @@ namespace Dapper /// public readonly struct CommandDefinition { - internal static CommandDefinition ForCallback(object? parameters) + internal static CommandDefinition ForCallback(object? parameters, CommandFlags flags) { - if (parameters is DynamicParameters) - { - return new CommandDefinition(parameters); - } - else - { - return default; - } + return new CommandDefinition(parameters is DynamicParameters ? parameters : null, flags); } internal void OnCompleted() @@ -113,9 +106,10 @@ internal static CommandType InferCommandType(string sql) return System.Data.CommandType.StoredProcedure; } - private CommandDefinition(object? parameters) : this() + private CommandDefinition(object? parameters, CommandFlags flags) : this() { Parameters = parameters; + Flags = flags; CommandText = ""; } diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 6fb8ca4c2..a0f84afc2 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -939,7 +939,7 @@ private static async Task> MultiMapAsync(null, CommandDefinition.ForCallback(command.Parameters), map, splitOn, reader, identity, true); + var results = MultiMapImpl(null, CommandDefinition.ForCallback(command.Parameters, command.Flags), map, splitOn, reader, identity, true); return command.Buffered ? results.ToList() : results; } finally diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 47e40ea41..e95d35297 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1658,7 +1658,7 @@ private static IEnumerable MultiMapImpl(this IDbConnection? cn var deserializers = GenerateDeserializers(identity, splitOn, reader); deserializer = cinfo.Deserializer = new DeserializerState(hash, deserializers[0]); otherDeserializers = cinfo.OtherDeserializers = deserializers.Skip(1).ToArray(); - SetQueryCache(identity, cinfo); + if (command.AddToCache) SetQueryCache(identity, cinfo); } Func mapIt = GenerateMapper(types.Length, deserializer.Func, otherDeserializers, map); diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index 7a9abf2d1..a2f214b42 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -995,5 +995,41 @@ public void AssertNoCacheWorksForQueryMultiple() Assert.Equal(123, c); Assert.Equal(456, d); } + + [Fact] + public async Task AssertNoCacheWorksForMultiMap() + { + const int a = 123, b = 456; + var cmdDef = new CommandDefinition("select @a as a, @b as b;", new + { + a, + b + }, commandType: CommandType.Text, flags: CommandFlags.NoCache | CommandFlags.Buffered); + + SqlMapper.PurgeQueryCache(); + var before = SqlMapper.GetCachedSQLCount(); + Assert.Equal(0, before); + + await MarsConnection.QueryAsync(cmdDef, splitOn: "b", map: (a, b) => (a, b)); + Assert.Equal(0, SqlMapper.GetCachedSQLCount()); + } + + [Fact] + public async Task AssertNoCacheWorksForQueryAsync() + { + const int a = 123, b = 456; + var cmdDef = new CommandDefinition("select @a as a, @b as b;", new + { + a, + b + }, commandType: CommandType.Text, flags: CommandFlags.NoCache | CommandFlags.Buffered); + + SqlMapper.PurgeQueryCache(); + var before = SqlMapper.GetCachedSQLCount(); + Assert.Equal(0, before); + + await MarsConnection.QueryAsync<(int, int)>(cmdDef); + Assert.Equal(0, SqlMapper.GetCachedSQLCount()); + } } } From dddd102eefd94770492d5bc19e2679745f11889f Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Tue, 8 Oct 2024 13:23:00 +0100 Subject: [PATCH 290/312] update package refs and fixup (#2120) --- Directory.Packages.props | 47 ++++++++++--------- .../Dapper.Tests.Performance.csproj | 12 ++++- .../Linq2DB/Linq2DBContext.cs | 3 +- tests/Dapper.Tests/WrappedReaderTests.cs | 7 ++- 4 files changed, 43 insertions(+), 26 deletions(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index 4c55df6f4..23d2ece4c 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -6,41 +6,46 @@ - + + - + - - - - - - - - - - + + + + + + + + + + - - + + - + - - - + + + - + + + + + - - + + \ No newline at end of file diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 52396c03e..dd0269a0d 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -14,7 +14,7 @@ - + @@ -28,6 +28,16 @@ + + + + + + + + + + diff --git a/benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs b/benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs index 0922cd72c..43d491786 100644 --- a/benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs +++ b/benchmarks/Dapper.Tests.Performance/Linq2DB/Linq2DBContext.cs @@ -1,10 +1,9 @@ using LinqToDB; -using Microsoft.EntityFrameworkCore; namespace Dapper.Tests.Performance.Linq2Db { public class Linq2DBContext : LinqToDB.Data.DataConnection { - public ITable Posts => GetTable(); + public ITable Posts => this.GetTable(); } } diff --git a/tests/Dapper.Tests/WrappedReaderTests.cs b/tests/Dapper.Tests/WrappedReaderTests.cs index 0bdf48b08..7f884f420 100644 --- a/tests/Dapper.Tests/WrappedReaderTests.cs +++ b/tests/Dapper.Tests/WrappedReaderTests.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections; using System.Data; using System.Data.Common; @@ -46,7 +46,10 @@ private class DummyDbCommand : DbCommand public override int ExecuteNonQuery() => throw new NotSupportedException(); public override object ExecuteScalar() => throw new NotSupportedException(); public override void Prepare() => throw new NotSupportedException(); - public override string CommandText { get; set; } + +#pragma warning disable CS8765 // nullability of value + public override string CommandText { get; set; } = ""; +#pragma warning restore CS8765 // nullability of value public override int CommandTimeout { get; set; } public override CommandType CommandType { get; set; } public override UpdateRowSource UpdatedRowSource { get; set; } From e0479ba16d72498f83a44b9f749ee20ed9121fe1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Robert=20Sil=C3=A9n?= Date: Tue, 8 Oct 2024 15:23:18 +0300 Subject: [PATCH 291/312] add mention of MariaDB to Readme.md (#2116) --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index c22aee4ba..d7bc4afaa 100644 --- a/Readme.md +++ b/Readme.md @@ -449,7 +449,7 @@ using (var reader = connection.ExecuteReader("select * from Shapes")) } ``` -User Defined Variables in MySQL +User Defined Variables in MySQL/MariaDB --------------------- In order to use Non-parameter SQL variables with MySql Connector, you have to add the following option to your connection string: @@ -465,7 +465,7 @@ Dapper's simplicity means that many features that ORMs ship with are stripped ou Will Dapper work with my DB provider? --------------------- -Dapper has no DB specific implementation details, it works across all .NET ADO providers including [SQLite](https://www.sqlite.org/), SQL CE, Firebird, Oracle, MySQL, PostgreSQL and SQL Server. +Dapper has no DB specific implementation details, it works across all .NET ADO providers including [SQLite](https://www.sqlite.org/), SQL CE, Firebird, Oracle, MariaDB, MySQL, PostgreSQL and SQL Server. Do you have a comprehensive list of examples? --------------------- From b4f80b6b6b599e8298a9a3fd6d85d00d105df0a1 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 9 Oct 2024 11:15:28 +0100 Subject: [PATCH 292/312] Improve performance of "queryunbuffered", and correctness of "first" APIs (#2121) * see #2115 - correctness: do no use SingleRow by default; affects trailing errors - performance: QueryUnbufferedAsync can mirror cmd?.Cancel() in finally (this is consistent with all other scenarios) * remove settings tweak --- Dapper/SqlMapper.Async.cs | 5 + Dapper/SqlMapper.Settings.cs | 5 +- Dapper/SqlMapper.cs | 6 +- Directory.Packages.props | 98 ++++++++--------- tests/Dapper.Tests/Dapper.Tests.csproj | 2 + tests/Dapper.Tests/SingleRowTests.cs | 146 +++++++++++++++++++++++++ 6 files changed, 208 insertions(+), 54 deletions(-) create mode 100644 tests/Dapper.Tests/SingleRowTests.cs diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index a0f84afc2..9408d5735 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -1333,6 +1333,11 @@ static async IAsyncEnumerable Impl(IDbConnection cnn, Type effectiveType, Com { if (reader is not null) { + if (!reader.IsClosed) + { + try { cmd?.Cancel(); } + catch { /* don't spoil any existing exception */ } + } await reader.DisposeAsync(); } if (wasClosed) cnn.Close(); diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index 343906b74..cbbc3c687 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -11,9 +11,10 @@ public static partial class SqlMapper /// public static class Settings { - // disable single result by default; prevents errors AFTER the select being detected properly - private const CommandBehavior DefaultAllowedCommandBehaviors = ~CommandBehavior.SingleResult; + // disable single row/result by default; prevents errors AFTER the select being detected properly + private const CommandBehavior DefaultAllowedCommandBehaviors = ~(CommandBehavior.SingleResult | CommandBehavior.SingleRow); internal static CommandBehavior AllowedCommandBehaviors { get; private set; } = DefaultAllowedCommandBehaviors; + private static void SetAllowedCommandBehaviors(CommandBehavior behavior, bool enabled) { if (enabled) AllowedCommandBehaviors |= behavior; diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index e95d35297..fa5ce52df 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -1148,7 +1148,7 @@ private static GridReader QueryMultipleImpl(this IDbConnection cnn, ref CommandD if (!reader.IsClosed) { try { cmd?.Cancel(); } - catch { /* don't spoil the existing exception */ } + catch { /* don't spoil any existing exception */ } } reader.Dispose(); } @@ -1229,7 +1229,7 @@ private static IEnumerable QueryImpl(this IDbConnection cnn, CommandDefini if (!reader.IsClosed) { try { cmd?.Cancel(); } - catch { /* don't spoil the existing exception */ } + catch { /* don't spoil any existing exception */ } } reader.Dispose(); } @@ -1321,7 +1321,7 @@ private static T QueryRowImpl(IDbConnection cnn, Row row, ref CommandDefiniti if (!reader.IsClosed) { try { cmd?.Cancel(); } - catch { /* don't spoil the existing exception */ } + catch { /* don't spoil any existing exception */ } } reader.Dispose(); } diff --git a/Directory.Packages.props b/Directory.Packages.props index 23d2ece4c..4cc865132 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,51 +1,51 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 5863bc8ed..f370ebbcc 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -6,6 +6,7 @@ $(DefineConstants);MSSQLCLIENT $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208;CA1861 enable + true @@ -16,6 +17,7 @@ + diff --git a/tests/Dapper.Tests/SingleRowTests.cs b/tests/Dapper.Tests/SingleRowTests.cs new file mode 100644 index 000000000..a26d757f8 --- /dev/null +++ b/tests/Dapper.Tests/SingleRowTests.cs @@ -0,0 +1,146 @@ +using System; +using System.Collections.Generic; +using System.Data.Common; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Threading; +using System.Threading.Tasks; +using FastMember; +using Xunit; +using Xunit.Abstractions; +using static Dapper.SqlMapper; + +namespace Dapper.Tests; + +[Collection("SingleRowTests")] +public sealed class SystemSqlClientSingleRowTests(ITestOutputHelper log) : SingleRowTests(log) +{ + protected override async Task InjectDataAsync(DbConnection conn, DbDataReader source) + { + using var bcp = new System.Data.SqlClient.SqlBulkCopy((System.Data.SqlClient.SqlConnection)conn); + bcp.DestinationTableName = "#mydata"; + bcp.EnableStreaming = true; + await bcp.WriteToServerAsync(source); + } +} +#if MSSQLCLIENT +[Collection("SingleRowTests")] +public sealed class MicrosoftSqlClientSingleRowTests(ITestOutputHelper log) : SingleRowTests(log) +{ + protected override async Task InjectDataAsync(DbConnection conn, DbDataReader source) + { + using var bcp = new Microsoft.Data.SqlClient.SqlBulkCopy((Microsoft.Data.SqlClient.SqlConnection)conn); + bcp.DestinationTableName = "#mydata"; + bcp.EnableStreaming = true; + await bcp.WriteToServerAsync(source); + } +} +#endif +public abstract class SingleRowTests(ITestOutputHelper log) : TestBase where TProvider : DatabaseProvider +{ + protected abstract Task InjectDataAsync(DbConnection connection, DbDataReader source); + + [Fact] + public async Task QueryFirst_PerformanceAndCorrectness() + { + using var conn = GetOpenConnection(); + conn.Execute("create table #mydata(id int not null, name nvarchar(250) not null)"); + + var rand = new Random(); + var data = from id in Enumerable.Range(1, 500_000) + select new MyRow { Id = rand.Next(), Name = CreateName(rand) }; + + Stopwatch watch; + using (var reader = ObjectReader.Create(data)) + { + await InjectDataAsync(conn, reader); + watch = Stopwatch.StartNew(); + var count = await conn.QuerySingleAsync("""select count(1) from #mydata"""); + watch.Stop(); + log.WriteLine($"bulk-insert complete; {count} rows in {watch.ElapsedMilliseconds}ms"); + } + + // just errors + var ex = Assert.ThrowsAny(() => conn.Execute("raiserror('bad things', 16, 1)")); + log.WriteLine(ex.Message); + ex = await Assert.ThrowsAnyAsync(async () => await conn.ExecuteAsync("raiserror('bad things', 16, 1)")); + log.WriteLine(ex.Message); + + // just data + watch = Stopwatch.StartNew(); + var row = conn.QueryFirst("select top 1 * from #mydata"); + watch.Stop(); + log.WriteLine($"sync top 1 read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); + + watch = Stopwatch.StartNew(); + row = await conn.QueryFirstAsync("select top 1 * from #mydata"); + watch.Stop(); + log.WriteLine($"async top 1 read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); + + watch = Stopwatch.StartNew(); + row = conn.QueryFirst("select * from #mydata"); + watch.Stop(); + log.WriteLine($"sync read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); + + watch = Stopwatch.StartNew(); + row = await conn.QueryFirstAsync("select * from #mydata"); + watch.Stop(); + log.WriteLine($"async read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); + + // data with trailing errors + + watch = Stopwatch.StartNew(); + ex = Assert.ThrowsAny(() => conn.QueryFirst("select * from #mydata; raiserror('bad things', 16, 1)")); + watch.Stop(); + log.WriteLine($"sync read with error complete in {watch.ElapsedMilliseconds}ms; {ex.Message}"); + + watch = Stopwatch.StartNew(); + ex = await Assert.ThrowsAnyAsync(async () => await conn.QueryFirstAsync("select * from #mydata; raiserror('bad things', 16, 1)")); + watch.Stop(); + log.WriteLine($"async read with error complete in {watch.ElapsedMilliseconds}ms; {ex.Message}"); + + // unbuffered read with trailing errors - do not expect to see this unless we consume all! + + watch = Stopwatch.StartNew(); + row = conn.Query("select * from #mydata", buffered: false).First(); + watch.Stop(); + log.WriteLine($"sync unbuffered LINQ read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); + +#if NET5_0_OR_GREATER + watch = Stopwatch.StartNew(); + row = await conn.QueryUnbufferedAsync("select * from #mydata").FirstAsync(); + watch.Stop(); + log.WriteLine($"async unbuffered LINQ read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); +#endif + + static unsafe string CreateName(Random rand) + { + const string Alphabet = "abcdefghijklmnopqrstuvwxyz 0123456789,;-"; + var len = rand.Next(5, 251); + char* ptr = stackalloc char[len]; + for (int i = 0; i < len; i++) + { + ptr[i] = Alphabet[rand.Next(Alphabet.Length)]; + } + return new string(ptr, 0, len); + } + + } + + public class MyRow + { + public int Id { get; set; } + public string Name { get; set; } = ""; + } +} + +internal static class AsyncLinqHelper +{ + public static async ValueTask FirstAsync(this IAsyncEnumerable source, CancellationToken cancellationToken = default) + { + await using var iter = source.GetAsyncEnumerator(cancellationToken); + if (!await iter.MoveNextAsync()) Array.Empty().First(); // for consistent error + return iter.Current; + } +} From a3804ceb1d6851eccd4521246ae0d40d80f586f9 Mon Sep 17 00:00:00 2001 From: Andrea Latanza <160479234+alatanza@users.noreply.github.com> Date: Sun, 13 Oct 2024 10:31:25 +0200 Subject: [PATCH 293/312] fix: properly handle value types when setting properties on dynamic objects returned by Dapper queries (#2122) --- Dapper/SqlMapper.DapperRowMetaObject.cs | 2 +- tests/Dapper.Tests/MiscTests.cs | 42 +++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/Dapper/SqlMapper.DapperRowMetaObject.cs b/Dapper/SqlMapper.DapperRowMetaObject.cs index 75e7a15a6..7b5350dfb 100644 --- a/Dapper/SqlMapper.DapperRowMetaObject.cs +++ b/Dapper/SqlMapper.DapperRowMetaObject.cs @@ -81,7 +81,7 @@ public override System.Dynamic.DynamicMetaObject BindSetMember(System.Dynamic.Se var parameters = new System.Linq.Expressions.Expression[] { System.Linq.Expressions.Expression.Constant(binder.Name), - value.Expression, + System.Linq.Expressions.Expression.Convert(value.Expression, typeof(object)), }; var callMethod = CallMethod(setValueMethod, parameters); diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 1bf4bbfb0..9323be671 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -1309,9 +1309,9 @@ public HazGetOnlyAndCtor(int idProperty, string nameProperty) IdProperty = idProperty; NameProperty = nameProperty; } - } - - [Fact] + } + + [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 @@ -1358,5 +1358,41 @@ public async Task QuerySplitStruct() // https://github.com/DapperLib/Dapper/issu 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); + } } } From 00a3808f7db14a2779e0ab9b4096b5405512a6f3 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 17 Oct 2024 13:27:42 +0100 Subject: [PATCH 294/312] Update issue templates --- .github/ISSUE_TEMPLATE/bug_report.md | 32 ++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 000000000..19273c5e6 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,32 @@ +--- +name: Bug report +about: Create a report to help us improve +title: '' +labels: bug, needs-triage +assignees: '' + +--- + +**Check your library version, and try updating** +To help, we're going to need to know your library version. If it isn't the latest: *go do that* - it might +fix the problem, and even if it doesn't: you're going to need to update if we find a problem and fix it, +so you might as well get ready for that now. + +**Describe the bug** +A clear and concise description of what the bug is. + +**To Reproduce** +Steps to reproduce the behavior: +1. Go to '...' +2. Click on '....' +3. Scroll down to '....' +4. See error + +**Expected and actual behavior** +A clear and concise description of what you expected to happen, and what actually happens. + +**Additional context** +Add any other context about the problem here: +- what DB backend (and version) are you using, if relevant? +- what ADO.NET provider (and version) are you using, if relevant? +- what OS and .NET runtime (and version) are you using, if relevant? From 9f4f783a41844a3cb939f345125fcfc7746ffe90 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 31 Oct 2024 07:23:21 +0000 Subject: [PATCH 295/312] Mark AddTypeHandlerImpl as obsolete and prevent lost updates via AddTypeHandler (#2129) * - mark AddTypeHandlerImpl as obsolete - syncronize type-handler mutates, to prevent lost writes * CI: revert pgsql uddate * suppress Impl's use of clone: false * avoid additional local snapshot, now that we're synchronized * explicitly request postgresql96 * more pgsql poking * pg path fix --- Dapper/SqlMapper.cs | 70 ++++++++++++++++++++++++++++----------------- appveyor.yml | 6 ++-- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index fa5ce52df..d23e949a5 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -266,11 +266,14 @@ static SqlMapper() [MemberNotNull(nameof(typeHandlers))] private static void ResetTypeHandlers(bool clone) { - typeHandlers = []; - AddTypeHandlerImpl(typeof(DataTable), new DataTableHandler(), clone); - AddTypeHandlerImpl(typeof(XmlDocument), new XmlDocumentHandler(), clone); - AddTypeHandlerImpl(typeof(XDocument), new XDocumentHandler(), clone); - AddTypeHandlerImpl(typeof(XElement), new XElementHandler(), clone); + lock (typeHandlersSyncLock) + { + typeHandlers = []; + AddTypeHandlerCore(typeof(DataTable), new DataTableHandler(), clone); + AddTypeHandlerCore(typeof(XmlDocument), new XmlDocumentHandler(), clone); + AddTypeHandlerCore(typeof(XDocument), new XDocumentHandler(), clone); + AddTypeHandlerCore(typeof(XElement), new XElementHandler(), clone); + } } /// @@ -339,7 +342,7 @@ public static void RemoveTypeMap(Type type) /// /// The type to handle. /// The handler to process the . - public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerImpl(type, handler, true); + public static void AddTypeHandler(Type type, ITypeHandler handler) => AddTypeHandlerCore(type, handler, true); /// /// Determine if the specified type will be processed by a custom handler. /// @@ -353,7 +356,16 @@ public static void RemoveTypeMap(Type type) /// The type to handle. /// The handler to process the . /// Whether to clone the current type handler map. + [Obsolete("Please use " + nameof(AddTypeHandler), error: true)] + [Browsable(false), EditorBrowsable(EditorBrowsableState.Never)] public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clone) + { + // this method was accidentally made public; we'll mark it as illegal, but + // preserve existing usage in compiled code; sorry about this! + AddTypeHandlerCore(type, handler, true); // do not allow suppress clone + } + + private static void AddTypeHandlerCore(Type type, ITypeHandler? handler, bool clone) { if (type is null) throw new ArgumentNullException(nameof(type)); @@ -373,29 +385,34 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clo } } - var snapshot = typeHandlers; - if (snapshot.TryGetValue(type, out var oldValue) && handler == oldValue) return; // nothing to do + // synchronize between callers mutating type-handlers; note that regular query + // code may still be accessing the field, so we still use snapshot/mutate/swap; + // the synchronize is just to prevent lost writes + lock (typeHandlersSyncLock) + { + if (typeHandlers.TryGetValue(type, out var oldValue) && handler == oldValue) return; // nothing to do - var newCopy = clone ? new Dictionary(snapshot) : snapshot; + var newCopy = clone ? new Dictionary(typeHandlers) : typeHandlers; #pragma warning disable 618 - typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); - if (secondary is not null) - { - typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); - } + typeof(TypeHandlerCache<>).MakeGenericType(type).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); + if (secondary is not null) + { + typeof(TypeHandlerCache<>).MakeGenericType(secondary).GetMethod(nameof(TypeHandlerCache.SetHandler), BindingFlags.Static | BindingFlags.NonPublic)!.Invoke(null, [handler]); + } #pragma warning restore 618 - if (handler is null) - { - newCopy.Remove(type); - if (secondary is not null) newCopy.Remove(secondary); - } - else - { - newCopy[type] = handler; - if (secondary is not null) newCopy[secondary] = handler; + if (handler is null) + { + newCopy.Remove(type); + if (secondary is not null) newCopy.Remove(secondary); + } + else + { + newCopy[type] = handler; + if (secondary is not null) newCopy[secondary] = handler; + } + typeHandlers = newCopy; } - typeHandlers = newCopy; } /// @@ -403,9 +420,10 @@ public static void AddTypeHandlerImpl(Type type, ITypeHandler? handler, bool clo /// /// The type to handle. /// The handler for the type . - public static void AddTypeHandler(TypeHandler handler) => AddTypeHandlerImpl(typeof(T), handler, true); + public static void AddTypeHandler(TypeHandler handler) => AddTypeHandlerCore(typeof(T), handler, true); private static Dictionary typeHandlers; + private static readonly object typeHandlersSyncLock = new(); internal const string LinqBinary = "System.Data.Linq.Binary"; @@ -479,7 +497,7 @@ public static void SetDbType(IDataParameter parameter, object value) { handler = (ITypeHandler)Activator.CreateInstance( typeof(SqlDataRecordHandler<>).MakeGenericType(argTypes))!; - AddTypeHandlerImpl(type, handler, true); + AddTypeHandlerCore(type, handler, true); return DbType.Object; } catch diff --git a/appveyor.yml b/appveyor.yml index c277902bd..a8a7fb3b9 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,10 +9,12 @@ skip_commits: # install: # - choco install dotnet-sdk --version 8.0.100 +stack: postgresql 15 + environment: Appveyor: true # Postgres - POSTGRES_PATH: C:\Program Files\PostgreSQL\13 + POSTGRES_PATH: C:\Program Files\PostgreSQL\15 PGUSER: postgres PGPASSWORD: Password12! POSTGRES_ENV_POSTGRES_USER: postgres @@ -32,7 +34,7 @@ environment: services: # - mysql - - postgresql13 + - postgresql15 init: - git config --global core.autocrlf input From 6434c694b2a212b32a96f5bfb63db6de84fefd5b Mon Sep 17 00:00:00 2001 From: Nick Craver Date: Thu, 31 Oct 2024 20:38:20 -0400 Subject: [PATCH 296/312] Build: Update Postgres script (#2130) --- appveyor.yml | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index a8a7fb3b9..ca2e79161 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,12 +9,10 @@ skip_commits: # install: # - choco install dotnet-sdk --version 8.0.100 -stack: postgresql 15 - environment: Appveyor: true # Postgres - POSTGRES_PATH: C:\Program Files\PostgreSQL\15 + POSTGRES_PATH: C:\Program Files\PostgreSQL\16 PGUSER: postgres PGPASSWORD: Password12! POSTGRES_ENV_POSTGRES_USER: postgres @@ -32,14 +30,11 @@ environment: PostgesConnectionString: Server=localhost;Port=5432;User Id=postgres;Password=Password12!;Database=test SqlServerConnectionString: Server=(local)\SQL2019;Database=tempdb;User ID=sa;Password=Password12! -services: - # - mysql - - postgresql15 - init: - git config --global core.autocrlf input - SET PATH=%POSTGRES_PATH%\bin;%MYSQL_PATH%\bin;%PATH% - net start MSSQL$SQL2019 + - net start postgresql-x64-16 - ps: Start-Service MySQL80 nuget: From cfc2fdd4da1e58944c505562ca0a61693ebd6aac Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sat, 25 Jan 2025 18:04:36 +0000 Subject: [PATCH 297/312] install dotnet --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index a31c48f20..1c00a9d85 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -40,6 +40,8 @@ jobs: steps: - name: Checkout code uses: actions/checkout@v1 + - name: Setup dotnet + uses: actions/setup-dotnet@v4 - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: Dapper Tests From 4e576022718cf22a6c42191702d15e83c1cd18e9 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sat, 25 Jan 2025 18:07:46 +0000 Subject: [PATCH 298/312] Update main.yml --- .github/workflows/main.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1c00a9d85..497e5a0bf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -42,6 +42,8 @@ jobs: uses: actions/checkout@v1 - name: Setup dotnet uses: actions/setup-dotnet@v4 + with: + dotnet-version: '9.0.x' - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: Dapper Tests From bd4f75b512de3e00f2c2631d5309961a1ecfea23 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Thu, 6 Feb 2025 12:11:30 +0000 Subject: [PATCH 299/312] normalize async API surface over all TFMs (#2144) * - normalize async API surface over all TFMs (fix #2143) - target net8.0 instead of net5.0 - lib updates * net9 SDK * perf tests: clean up SqlClient warning * CI fix: test concurrency * rev snowflake --- .../Dapper.ProviderTools.csproj | 2 +- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 2 +- Dapper.StrongName/Dapper.StrongName.csproj | 7 +++- Dapper/Dapper.csproj | 7 +++- Dapper/PublicAPI.Shipped.txt | 5 +++ Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt | 6 --- Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt | 6 --- .../PublicAPI/net6.0/PublicAPI.Unshipped.txt | 1 - Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt | 6 --- .../PublicAPI/net7.0/PublicAPI.Unshipped.txt | 1 - Dapper/PublicAPI/net8.0/PublicAPI.Shipped.txt | 1 + .../PublicAPI.Unshipped.txt | 0 Dapper/SqlMapper.Async.cs | 6 ++- Dapper/SqlMapper.GridReader.Async.cs | 19 ++++++---- Directory.Build.props | 3 +- Directory.Packages.props | 37 ++++++++++--------- appveyor.yml | 4 +- .../Benchmarks.HandCoded.cs | 2 +- .../Benchmarks.RepoDB.cs | 8 ++-- .../Dapper.Tests.Performance/Benchmarks.cs | 2 +- .../Dapper.Tests.Performance.csproj | 10 ++++- .../Dapper.Tests.Performance/LegacyTests.cs | 2 +- .../Dapper.Tests.Performance/Program.cs | 2 +- .../SqlDataReaderHelper.cs | 2 +- global.json | 2 +- tests/Dapper.Tests/AsyncTests.cs | 2 - tests/Dapper.Tests/Dapper.Tests.csproj | 16 +++++--- tests/Dapper.Tests/DateTimeOnlyTests.cs | 2 +- tests/Dapper.Tests/Helpers/IsExternalInit.cs | 7 ++++ tests/Dapper.Tests/ParameterTests.cs | 15 ++++++++ tests/Dapper.Tests/ProviderTests.cs | 2 + .../Dapper.Tests/Providers/SnowflakeTests.cs | 2 +- tests/Dapper.Tests/SingleRowTests.cs | 4 +- tests/Dapper.Tests/TestBase.cs | 2 + 34 files changed, 116 insertions(+), 79 deletions(-) delete mode 100644 Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt delete mode 100644 Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt delete mode 100644 Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt delete mode 100644 Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt delete mode 100644 Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt create mode 100644 Dapper/PublicAPI/net8.0/PublicAPI.Shipped.txt rename Dapper/PublicAPI/{net5.0 => net8.0}/PublicAPI.Unshipped.txt (100%) create mode 100644 tests/Dapper.Tests/Helpers/IsExternalInit.cs diff --git a/Dapper.ProviderTools/Dapper.ProviderTools.csproj b/Dapper.ProviderTools/Dapper.ProviderTools.csproj index 61e8b4c58..176887317 100644 --- a/Dapper.ProviderTools/Dapper.ProviderTools.csproj +++ b/Dapper.ProviderTools/Dapper.ProviderTools.csproj @@ -5,7 +5,7 @@ Dapper Provider Tools Provider-agnostic ADO.NET helper utilities Marc Gravell - net461;netstandard2.0;net5.0 + net461;netstandard2.0;net8.0 true enable diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index b597bd9eb..c101afcaa 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -5,7 +5,7 @@ Dapper SqlBuilder component The Dapper SqlBuilder component, for building SQL queries dynamically. Sam Saffron, Johan Danforth - net461;netstandard2.0;net5.0 + net461;netstandard2.0;net8.0 false false enable diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 770b7ce7a..f202f8f6c 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -5,7 +5,7 @@ Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0;net5.0;net7.0 + net461;netstandard2.0;net8.0 true true enable @@ -17,7 +17,12 @@ + + + + + diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 28951f871..af5febfb8 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,7 +5,7 @@ orm;sql;micro-orm A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0;net5.0;net6.0;net7.0 + net461;netstandard2.0;net8.0 enable true @@ -24,7 +24,12 @@ + + + + + diff --git a/Dapper/PublicAPI.Shipped.txt b/Dapper/PublicAPI.Shipped.txt index c1b08ea99..456aa8bb1 100644 --- a/Dapper/PublicAPI.Shipped.txt +++ b/Dapper/PublicAPI.Shipped.txt @@ -69,6 +69,7 @@ Dapper.SqlMapper.GridReader.CancellationToken.get -> System.Threading.Cancellati Dapper.SqlMapper.GridReader.Command.get -> System.Data.IDbCommand! Dapper.SqlMapper.GridReader.Command.set -> void Dapper.SqlMapper.GridReader.Dispose() -> void +Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask Dapper.SqlMapper.GridReader.GridReader(System.Data.IDbCommand! command, System.Data.Common.DbDataReader! reader, Dapper.SqlMapper.Identity? identity, System.Action? onCompleted = null, object? state = null, bool addToCache = false, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> void Dapper.SqlMapper.GridReader.IsConsumed.get -> bool Dapper.SqlMapper.GridReader.OnAfterGrid(int index) -> void @@ -112,6 +113,8 @@ Dapper.SqlMapper.GridReader.ReadSingleOrDefault() -> T? Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync(System.Type! type) -> System.Threading.Tasks.Task! Dapper.SqlMapper.GridReader.ReadSingleOrDefaultAsync() -> System.Threading.Tasks.Task! +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! +Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! Dapper.SqlMapper.GridReader.ResultIndex.get -> int Dapper.SqlMapper.ICustomQueryParameter Dapper.SqlMapper.ICustomQueryParameter.AddParameter(System.Data.IDbCommand! command, string! name) -> void @@ -295,6 +298,8 @@ static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, System.Type! type, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, Dapper.CommandDefinition command) -> System.Threading.Tasks.Task! static Dapper.SqlMapper.QuerySingleOrDefaultAsync(this System.Data.IDbConnection! cnn, string! sql, object? param = null, System.Data.IDbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Threading.Tasks.Task! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! +static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! static Dapper.SqlMapper.ReadChar(object! value) -> char static Dapper.SqlMapper.ReadNullableChar(object! value) -> char? static Dapper.SqlMapper.RemoveTypeMap(System.Type! type) -> void diff --git a/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt deleted file mode 100644 index 5da46e604..000000000 --- a/Dapper/PublicAPI/net5.0/PublicAPI.Shipped.txt +++ /dev/null @@ -1,6 +0,0 @@ -#nullable enable -Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask -Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! -Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! \ No newline at end of file diff --git a/Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt deleted file mode 100644 index 5da46e604..000000000 --- a/Dapper/PublicAPI/net6.0/PublicAPI.Shipped.txt +++ /dev/null @@ -1,6 +0,0 @@ -#nullable enable -Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask -Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! -Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! \ No newline at end of file diff --git a/Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt deleted file mode 100644 index 91b0e1a43..000000000 --- a/Dapper/PublicAPI/net6.0/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable \ No newline at end of file diff --git a/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt deleted file mode 100644 index 5da46e604..000000000 --- a/Dapper/PublicAPI/net7.0/PublicAPI.Shipped.txt +++ /dev/null @@ -1,6 +0,0 @@ -#nullable enable -Dapper.SqlMapper.GridReader.DisposeAsync() -> System.Threading.Tasks.ValueTask -Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! -Dapper.SqlMapper.GridReader.ReadUnbufferedAsync() -> System.Collections.Generic.IAsyncEnumerable! -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! -static Dapper.SqlMapper.QueryUnbufferedAsync(this System.Data.Common.DbConnection! cnn, string! sql, object? param = null, System.Data.Common.DbTransaction? transaction = null, int? commandTimeout = null, System.Data.CommandType? commandType = null) -> System.Collections.Generic.IAsyncEnumerable! \ No newline at end of file diff --git a/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt deleted file mode 100644 index 91b0e1a43..000000000 --- a/Dapper/PublicAPI/net7.0/PublicAPI.Unshipped.txt +++ /dev/null @@ -1 +0,0 @@ -#nullable enable \ No newline at end of file diff --git a/Dapper/PublicAPI/net8.0/PublicAPI.Shipped.txt b/Dapper/PublicAPI/net8.0/PublicAPI.Shipped.txt new file mode 100644 index 000000000..ab058de62 --- /dev/null +++ b/Dapper/PublicAPI/net8.0/PublicAPI.Shipped.txt @@ -0,0 +1 @@ +#nullable enable diff --git a/Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt b/Dapper/PublicAPI/net8.0/PublicAPI.Unshipped.txt similarity index 100% rename from Dapper/PublicAPI/net5.0/PublicAPI.Unshipped.txt rename to Dapper/PublicAPI/net8.0/PublicAPI.Unshipped.txt diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 9408d5735..fb7ce448c 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -1249,7 +1249,6 @@ private static async Task ExecuteWrappedReaderImplAsync(IDbConnect return Parse(result); } -#if NET5_0_OR_GREATER /// /// Execute a query asynchronously using . /// @@ -1338,12 +1337,15 @@ static async IAsyncEnumerable Impl(IDbConnection cnn, Type effectiveType, Com try { cmd?.Cancel(); } catch { /* don't spoil any existing exception */ } } +#if NET5_0_OR_GREATER await reader.DisposeAsync(); +#else + reader.Dispose(); +#endif } if (wasClosed) cnn.Close(); } } } -#endif } } diff --git a/Dapper/SqlMapper.GridReader.Async.cs b/Dapper/SqlMapper.GridReader.Async.cs index 5b5b9b73d..a32d53124 100644 --- a/Dapper/SqlMapper.GridReader.Async.cs +++ b/Dapper/SqlMapper.GridReader.Async.cs @@ -10,9 +10,7 @@ namespace Dapper public static partial class SqlMapper { public partial class GridReader -#if NET5_0_OR_GREATER : IAsyncDisposable -#endif { /// /// Read the next grid of results, returned as a dynamic object @@ -161,11 +159,8 @@ protected async Task OnAfterGridAsync(int index) #endif reader = null!; onCompleted?.Invoke(state); -#if NET5_0_OR_GREATER + await DisposeAsync(); -#else - Dispose(); -#endif } } @@ -247,7 +242,6 @@ private async Task> ReadBufferedAsync(int index, Func /// Read the next grid of results. /// @@ -283,19 +277,29 @@ private async IAsyncEnumerable ReadUnbufferedAsync(int index, Func /// Dispose the grid, closing and disposing both the underlying reader and command. /// +#pragma warning disable CS1998 // Async method lacks 'await' operators and will run synchronously - for netfx version public async ValueTask DisposeAsync() +#pragma warning restore CS1998 // Async method lacks 'await' operators and will run synchronously { if (reader is not null) { if (!reader.IsClosed) Command?.Cancel(); +#if NET5_0_OR_GREATER await reader.DisposeAsync(); +#else + reader.Dispose(); +#endif reader = null!; } if (Command is not null) { if (Command is DbCommand typed) { +#if NET5_0_OR_GREATER await typed.DisposeAsync(); +#else + typed.Dispose(); +#endif } else { @@ -305,7 +309,6 @@ public async ValueTask DisposeAsync() } GC.SuppressFinalize(this); } -#endif } } } diff --git a/Directory.Build.props b/Directory.Build.props index 6a4b94e1c..cb8af4d2d 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -21,10 +21,11 @@ false true true - 12 + 13 false true readme.md + true diff --git a/Directory.Packages.props b/Directory.Packages.props index 4cc865132..d72d03090 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -7,45 +7,46 @@ - + + - + - - - + + + - - - - - - + + + + + + - + - - + + - + - + - - + + \ No newline at end of file diff --git a/appveyor.yml b/appveyor.yml index ca2e79161..8c1e48d84 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,8 +6,8 @@ skip_commits: files: - '**/*.md' -# install: -# - choco install dotnet-sdk --version 8.0.100 +install: + - choco install dotnet-sdk --version 9.0.101 environment: Appveyor: true diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.HandCoded.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.HandCoded.cs index 05013e465..3fcfc6d3d 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.HandCoded.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.HandCoded.cs @@ -2,7 +2,7 @@ using System; using System.ComponentModel; using System.Data; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; namespace Dapper.Tests.Performance { diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs index 10694ea79..dd762afcf 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.RepoDB.cs @@ -20,11 +20,11 @@ public void Setup() // We need this since benchmarks using System.Data.SqlClient var dbSetting = new SqlServerDbSetting(); DbSettingMapper - .Add(dbSetting, true); + .Add(dbSetting, true); DbHelperMapper - .Add(new SqlServerDbHelper(), true); + .Add(new SqlServerDbHelper(), true); StatementBuilderMapper - .Add(new SqlServerStatementBuilder(dbSetting), true); + .Add(new SqlServerStatementBuilder(dbSetting), true); ClassMapper.Add("Posts"); } @@ -54,7 +54,7 @@ public Post QueryDynamic() public Post QueryField() { Step(); - return _connection.Query(new QueryField[] { new(nameof(Post.Id), i) }).First(); + return _connection.Query([new(nameof(Post.Id), i)]).First(); } [Benchmark(Description = "ExecuteQuery")] diff --git a/benchmarks/Dapper.Tests.Performance/Benchmarks.cs b/benchmarks/Dapper.Tests.Performance/Benchmarks.cs index aa5effb53..8a92b5e23 100644 --- a/benchmarks/Dapper.Tests.Performance/Benchmarks.cs +++ b/benchmarks/Dapper.Tests.Performance/Benchmarks.cs @@ -1,7 +1,7 @@ using BenchmarkDotNet.Attributes; using System; using System.Configuration; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; namespace Dapper.Tests.Performance { diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index dd0269a0d..fa18bd9c5 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -21,16 +21,22 @@ - - + + + + + + + + diff --git a/benchmarks/Dapper.Tests.Performance/LegacyTests.cs b/benchmarks/Dapper.Tests.Performance/LegacyTests.cs index dd9588b43..9bf4cdb46 100644 --- a/benchmarks/Dapper.Tests.Performance/LegacyTests.cs +++ b/benchmarks/Dapper.Tests.Performance/LegacyTests.cs @@ -1,7 +1,7 @@ using System; using System.Collections.Generic; using System.Data; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Diagnostics; using System.Linq; diff --git a/benchmarks/Dapper.Tests.Performance/Program.cs b/benchmarks/Dapper.Tests.Performance/Program.cs index 1e102cf87..4ffe7de9a 100644 --- a/benchmarks/Dapper.Tests.Performance/Program.cs +++ b/benchmarks/Dapper.Tests.Performance/Program.cs @@ -1,6 +1,6 @@ using BenchmarkDotNet.Running; using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Linq; using static System.Console; diff --git a/benchmarks/Dapper.Tests.Performance/SqlDataReaderHelper.cs b/benchmarks/Dapper.Tests.Performance/SqlDataReaderHelper.cs index d6d286e96..f7c215cda 100644 --- a/benchmarks/Dapper.Tests.Performance/SqlDataReaderHelper.cs +++ b/benchmarks/Dapper.Tests.Performance/SqlDataReaderHelper.cs @@ -1,5 +1,5 @@ using System; -using System.Data.SqlClient; +using Microsoft.Data.SqlClient; using System.Runtime.CompilerServices; namespace Dapper.Tests.Performance diff --git a/global.json b/global.json index 7da276347..2cbaab19b 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.100", + "version": "9.0.101", "rollForward": "latestMajor" } } \ No newline at end of file diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index a2f214b42..ec83dc2d9 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -46,7 +46,6 @@ public async Task TestBasicStringUsageAsync() Assert.Equal(new[] { "abc", "def" }, arr); } -#if NET5_0_OR_GREATER [Fact] public async Task TestBasicStringUsageUnbufferedDynamicAsync() { @@ -158,7 +157,6 @@ public async Task TestBasicStringUsageViaGridReaderUnbufferedAsync_Cancellation( var arr = results.ToArray(); Assert.Equal(new[] { "abc", "def" }, arr); // don't expect the ghi because of cancellation } -#endif [Fact] public async Task TestBasicStringUsageQueryFirstAsync() diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index f370ebbcc..4c242bf81 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,17 +2,17 @@ Dapper.Tests Dapper Core Test Suite - net472;net6.0;net8.0 + net481;net8.0;net9.0 $(DefineConstants);MSSQLCLIENT $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208;CA1861 enable true - + $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB - + @@ -22,16 +22,20 @@ - - + + - + + + + + diff --git a/tests/Dapper.Tests/DateTimeOnlyTests.cs b/tests/Dapper.Tests/DateTimeOnlyTests.cs index 68e8bc170..cabad699d 100644 --- a/tests/Dapper.Tests/DateTimeOnlyTests.cs +++ b/tests/Dapper.Tests/DateTimeOnlyTests.cs @@ -17,7 +17,7 @@ public abstract class DateTimeOnlyTests : TestBase where T { public class HazDateTimeOnly { - public string Name { get; set; } + public string Name { get; set; } = ""; public DateOnly Date { get; set; } public TimeOnly Time { get; set; } public DateOnly? NDate { get; set; } diff --git a/tests/Dapper.Tests/Helpers/IsExternalInit.cs b/tests/Dapper.Tests/Helpers/IsExternalInit.cs new file mode 100644 index 000000000..7a67b19de --- /dev/null +++ b/tests/Dapper.Tests/Helpers/IsExternalInit.cs @@ -0,0 +1,7 @@ +namespace System.Runtime.CompilerServices; + +#if !NET5_0_OR_GREATER +internal static class IsExternalInit +{ +} +#endif diff --git a/tests/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs index b6408359e..650624c20 100644 --- a/tests/Dapper.Tests/ParameterTests.cs +++ b/tests/Dapper.Tests/ParameterTests.cs @@ -17,6 +17,8 @@ using Microsoft.SqlServer.Types; #endif +[assembly: CollectionBehavior(DisableTestParallelization = true)] + namespace Dapper.Tests { [Collection(NonParallelDefinition.Name)] // because it creates SQL types that compete between the two providers @@ -61,16 +63,22 @@ public void AddParameter(IDbCommand command, string name) private static IEnumerable CreateSqlDataRecordList(IDbCommand command, IEnumerable numbers) { +#pragma warning disable CS0618 // Type or member is obsolete if (command is System.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_SD(numbers); +#pragma warning restore CS0618 // Type or member is obsolete if (command is Microsoft.Data.SqlClient.SqlCommand) return CreateSqlDataRecordList_MD(numbers); throw new ArgumentException(nameof(command)); } private static IEnumerable CreateSqlDataRecordList(IDbConnection connection, IEnumerable numbers) { +#pragma warning disable CS0618 // Type or member is obsolete if (connection is System.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_SD(numbers); +#pragma warning restore CS0618 // Type or member is obsolete if (connection is Microsoft.Data.SqlClient.SqlConnection) return CreateSqlDataRecordList_MD(numbers); throw new ArgumentException(nameof(connection)); } + +#pragma warning disable CS0618 // Type or member is obsolete private static List CreateSqlDataRecordList_SD(IEnumerable numbers) { var number_list = new List(); @@ -88,6 +96,7 @@ private static IEnumerable CreateSqlDataRecordList(IDbConnection co return number_list; } +#pragma warning restore CS0618 // Type or member is obsolete private static List CreateSqlDataRecordList_MD(IEnumerable numbers) { @@ -147,6 +156,7 @@ public void AddParameter(IDbCommand command, string name) private static IDbDataParameter AddStructured(IDbCommand command, object value) { +#pragma warning disable CS0618 // Type or member is obsolete if (command is System.Data.SqlClient.SqlCommand sdcmd) { var p = sdcmd.Parameters.Add("integers", SqlDbType.Structured); @@ -155,6 +165,7 @@ private static IDbDataParameter AddStructured(IDbCommand command, object value) p.Value = value; return p; } +#pragma warning restore CS0618 // Type or member is obsolete else if (command is Microsoft.Data.SqlClient.SqlCommand mdcmd) { var p = mdcmd.Parameters.Add("integers", SqlDbType.Structured); @@ -497,11 +508,13 @@ public void TestSqlDataRecordListParametersWithTypeHandlers() // Variable type has to be IEnumerable for TypeHandler to kick in. object args; +#pragma warning disable CS0618 // Type or member is obsolete if (connection is System.Data.SqlClient.SqlConnection) { IEnumerable records = CreateSqlDataRecordList_SD(new int[] { 1, 2, 3 }); args = new { integers = records }; } +#pragma warning restore CS0618 // Type or member is obsolete else if (connection is Microsoft.Data.SqlClient.SqlConnection) { IEnumerable records = CreateSqlDataRecordList_MD(new int[] { 1, 2, 3 }); @@ -1655,10 +1668,12 @@ Id int not null primary key identity(1,1), { recvValue = msReader.GetSqlDecimal(1); } +#pragma warning disable CS0618 // Type or member is obsolete else if (reader is System.Data.SqlClient.SqlDataReader sdReader) { recvValue = sdReader.GetSqlDecimal(1); } +#pragma warning restore CS0618 // Type or member is obsolete else { throw new InvalidOperationException($"unexpected reader type: {reader.GetType().FullName}"); diff --git a/tests/Dapper.Tests/ProviderTests.cs b/tests/Dapper.Tests/ProviderTests.cs index e3a553c5f..c0f2d58f2 100644 --- a/tests/Dapper.Tests/ProviderTests.cs +++ b/tests/Dapper.Tests/ProviderTests.cs @@ -10,8 +10,10 @@ public class ProviderTests [Fact] public void BulkCopy_SystemDataSqlClient() { +#pragma warning disable CS0618 // Type or member is obsolete using var conn = new System.Data.SqlClient.SqlConnection(); Test(conn); +#pragma warning restore CS0618 // Type or member is obsolete } [Fact] diff --git a/tests/Dapper.Tests/Providers/SnowflakeTests.cs b/tests/Dapper.Tests/Providers/SnowflakeTests.cs index 64b2d11f7..610592461 100644 --- a/tests/Dapper.Tests/Providers/SnowflakeTests.cs +++ b/tests/Dapper.Tests/Providers/SnowflakeTests.cs @@ -1,4 +1,4 @@ -#if !NET462 // platform not supported exception +#if !NETFRAMEWORK // platform not supported exception using System; using System.Collections.Generic; using System.IO; diff --git a/tests/Dapper.Tests/SingleRowTests.cs b/tests/Dapper.Tests/SingleRowTests.cs index a26d757f8..ab554514f 100644 --- a/tests/Dapper.Tests/SingleRowTests.cs +++ b/tests/Dapper.Tests/SingleRowTests.cs @@ -18,7 +18,9 @@ public sealed class SystemSqlClientSingleRowTests(ITestOutputHelper log) : Singl { protected override async Task InjectDataAsync(DbConnection conn, DbDataReader source) { +#pragma warning disable CS0618 // Type or member is obsolete using var bcp = new System.Data.SqlClient.SqlBulkCopy((System.Data.SqlClient.SqlConnection)conn); +#pragma warning restore CS0618 // Type or member is obsolete bcp.DestinationTableName = "#mydata"; bcp.EnableStreaming = true; await bcp.WriteToServerAsync(source); @@ -107,12 +109,10 @@ public async Task QueryFirst_PerformanceAndCorrectness() watch.Stop(); log.WriteLine($"sync unbuffered LINQ read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); -#if NET5_0_OR_GREATER watch = Stopwatch.StartNew(); row = await conn.QueryUnbufferedAsync("select * from #mydata").FirstAsync(); watch.Stop(); log.WriteLine($"async unbuffered LINQ read first complete; row {row.Id} in {watch.ElapsedMilliseconds}ms"); -#endif static unsafe string CreateName(Random rand) { diff --git a/tests/Dapper.Tests/TestBase.cs b/tests/Dapper.Tests/TestBase.cs index 7c7b2920f..e5e2c6560 100644 --- a/tests/Dapper.Tests/TestBase.cs +++ b/tests/Dapper.Tests/TestBase.cs @@ -74,7 +74,9 @@ public DbConnection GetOpenConnection(bool mars) } public sealed class SystemSqlClientProvider : SqlServerDatabaseProvider { +#pragma warning disable CS0618 // Type or member is obsolete public override DbProviderFactory Factory => System.Data.SqlClient.SqlClientFactory.Instance; +#pragma warning restore CS0618 // Type or member is obsolete } #if MSSQLCLIENT public sealed class MicrosoftSqlClientProvider : SqlServerDatabaseProvider From e97a91935243299d45bece0c401103abadf12a1b Mon Sep 17 00:00:00 2001 From: Jesper Noordsij <45041769+jnoordsij@users.noreply.github.com> Date: Tue, 8 Apr 2025 12:01:52 +0200 Subject: [PATCH 300/312] Fix and clarify OrWhere caveats in SqlBuilder docs (#2149) * Fix and clarify OrWhere caveats in SqlBuilder docs * Add example with OrWhere call first --- Dapper.SqlBuilder/Readme.md | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/Dapper.SqlBuilder/Readme.md b/Dapper.SqlBuilder/Readme.md index ea69d9b80..5a0968f9f 100644 --- a/Dapper.SqlBuilder/Readme.md +++ b/Dapper.SqlBuilder/Readme.md @@ -85,10 +85,16 @@ var count = conn.ExecuteScalar(countTemplate.RawSql, countTemplate.Paramete Limitations and caveats -------- -OrWhere use `and` not `or` to concat sql problem +### Combining the Where and OrWhere methods -[Issue 647](https://github.com/DapperLib/Dapper/issues/647) +The OrWhere method currently groups all `and` and `or` clauses by type, +then join the groups with `and` or `or` depending on the first call. +This may result in possibly unexpected outcomes. +See also [issue 647](https://github.com/DapperLib/Dapper/issues/647). +#### Example Where first + +When providing the following clauses ```csharp sql.Where("a = @a1"); sql.OrWhere("b = @b1"); @@ -97,11 +103,26 @@ sql.OrWhere("b = @b2"); ``` SqlBuilder will generate sql -```sql= -a = @a1 AND b = @b1 AND a = @a2 AND b = @b2 +```sql +a = @a1 AND a = @a2 AND ( b = @b1 OR b = @b2 ) ``` -not +and not say ```sql a = @a1 OR b = @b1 AND a = @a2 OR b = @b2 ``` + +#### Example OrWhere first + +When providing the following clauses +```csharp +sql.OrWhere("b = @b1"); +sql.Where("a = @a1"); +sql.OrWhere("b = @b2"); +sql.Where("a = @a2"); +``` + +SqlBuilder will generate sql +```sql +a = @a1 OR a = @a2 OR ( b = @b1 OR b = @b2 ) +``` From d994b6d93265f1318a064f5f3ebbcb1f3aaf53cf Mon Sep 17 00:00:00 2001 From: ri-rgb <70099947+ri-rgb@users.noreply.github.com> Date: Tue, 8 Apr 2025 11:07:45 +0100 Subject: [PATCH 301/312] OutputExpression wasn't working for methods that use QueryRowAsync (#2156) Added command.OnCompleted() to QueryRowAsync Co-authored-by: Rich --- Dapper/SqlMapper.Async.cs | 1 + tests/Dapper.Tests/AsyncTests.cs | 28 ++++++++++++++++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index fb7ce448c..eade08cb2 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -503,6 +503,7 @@ private static async Task QueryRowAsync(this IDbConnection cnn, Row row, T ThrowZeroRows(row); } while (await reader.NextResultAsync(cancel).ConfigureAwait(false)) { /* ignore result sets after the first */ } + command.OnCompleted(); return result; } finally diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index ec83dc2d9..9c3ec4721 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -667,6 +667,34 @@ public async Task TestSupportForDynamicParametersOutputExpressions_Query_Default Assert.Equal(42, result); } + [Fact] + public async Task TestSupportForDynamicParametersOutputExpressions_QueryFirst() + { + var bob = new Person { Name = "bob", PersonId = 1, Address = new Address { PersonId = 2 } }; + + var p = new DynamicParameters(bob); + p.Output(bob, b => b.PersonId); + p.Output(bob, b => b.Occupation); + p.Output(bob, b => b.NumberOfLegs); + p.Output(bob, b => b.Address!.Name); + p.Output(bob, b => b.Address!.PersonId); + + var result = (await connection.QueryFirstAsync(@" +SET @Occupation = 'grillmaster' +SET @PersonId = @PersonId + 1 +SET @NumberOfLegs = @NumberOfLegs - 1 +SET @AddressName = 'bobs burgers' +SET @AddressPersonId = @PersonId +select 42", p).ConfigureAwait(false)); + + Assert.Equal("grillmaster", bob.Occupation); + Assert.Equal(2, bob.PersonId); + Assert.Equal(1, bob.NumberOfLegs); + Assert.Equal("bobs burgers", bob.Address.Name); + Assert.Equal(2, bob.Address.PersonId); + Assert.Equal(42, result); + } + [Fact] public async Task TestSupportForDynamicParametersOutputExpressions_Query_BufferedAsync() { From 5e6a6eb82f623f52265f93d7243e4ccb7bff5b81 Mon Sep 17 00:00:00 2001 From: kabaome Date: Mon, 28 Apr 2025 03:34:35 +0900 Subject: [PATCH 302/312] CI: use preinstalled sdk (#2160) --- appveyor.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/appveyor.yml b/appveyor.yml index 8c1e48d84..ae9d477bf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,9 +6,6 @@ skip_commits: files: - '**/*.md' -install: - - choco install dotnet-sdk --version 9.0.101 - environment: Appveyor: true # Postgres From 5c7143f2e3585d4708294a3b0530a134e18ace86 Mon Sep 17 00:00:00 2001 From: kabaome Date: Thu, 1 May 2025 04:31:40 +0900 Subject: [PATCH 303/312] CI: serialize DB-dependent tests (#2163) * CI: serialize DB-dependent tests * github workflows test --- .github/workflows/main.yml | 2 +- build.ps1 | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 497e5a0bf..7a910196d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -47,7 +47,7 @@ jobs: - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: Dapper Tests - run: dotnet test tests/Dapper.Tests/Dapper.Tests.csproj -c Release --logger GitHubActions /p:CI=true + run: dotnet test tests/Dapper.Tests/Dapper.Tests.csproj -c Release --logger GitHubActions -p:CI=true -p:TestTfmsInParallel=false env: MySqlConnectionString: Server=localhost;Port=${{ job.services.mysql.ports[3306] }};Uid=root;Pwd=root;Database=test;Allow User Variables=true OLEDBConnectionString: Provider=SQLOLEDB;Server=tcp:localhost,${{ job.services.sqlserver.ports[1433] }};Database=tempdb;User Id=sa;Password=Password.; diff --git a/build.ps1 b/build.ps1 index f406c745f..59193ff75 100644 --- a/build.ps1 +++ b/build.ps1 @@ -22,8 +22,8 @@ dotnet build ".\Build.csproj" -c Release /p:CI=true Write-Host "Done building." -ForegroundColor "Green" if ($RunTests) { - Write-Host "Running tests: Build.csproj traversal (all frameworks)" -ForegroundColor "Magenta" - dotnet test ".\Build.csproj" -c Release --no-build + Write-Host "Running tests: Build.csproj" -ForegroundColor "Magenta" + dotnet test ".\Build.csproj" -c Release --no-build -p:TestTfmsInParallel=false if ($LastExitCode -ne 0) { Write-Host "Error with tests, aborting build." -Foreground "Red" Exit 1 From 00b1023f292c436eb9830b3383fde68e177a13bf Mon Sep 17 00:00:00 2001 From: mgravell Date: Sun, 1 Jun 2025 06:50:51 +0100 Subject: [PATCH 304/312] rev snowflake for clean build --- Directory.Packages.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Directory.Packages.props b/Directory.Packages.props index d72d03090..8787a7767 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -35,7 +35,7 @@ - + From 288730e69b05c32cac898d9b55ebea219ea8a2d1 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Fri, 6 Mar 2026 07:10:01 +0000 Subject: [PATCH 305/312] TFM packaging for .NET 10 (#2195) --- .github/workflows/main.yml | 2 +- Dapper.StrongName/Dapper.StrongName.csproj | 2 +- Dapper/Dapper.csproj | 2 +- global.json | 2 +- tests/Dapper.Tests/Dapper.Tests.csproj | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7a910196d..3145873c1 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -43,7 +43,7 @@ jobs: - name: Setup dotnet uses: actions/setup-dotnet@v4 with: - dotnet-version: '9.0.x' + dotnet-version: '10.0.x' - name: .NET Build run: dotnet build Build.csproj -c Release /p:CI=true - name: Dapper Tests diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index f202f8f6c..2aae43d73 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -5,7 +5,7 @@ Dapper (Strong Named) A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0;net8.0 + net461;netstandard2.0;net8.0;net10.0 true true enable diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index af5febfb8..98d8f11eb 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -5,7 +5,7 @@ orm;sql;micro-orm A high performance Micro-ORM supporting SQL Server, MySQL, Sqlite, SqlCE, Firebird etc. Major Sponsor: Dapper Plus from ZZZ Projects. Sam Saffron;Marc Gravell;Nick Craver - net461;netstandard2.0;net8.0 + net461;netstandard2.0;net8.0;net10.0 enable true diff --git a/global.json b/global.json index 2cbaab19b..f7b5e40c2 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.101", + "version": "10.0.102", "rollForward": "latestMajor" } } \ No newline at end of file diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 4c242bf81..e02bb4ba3 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -2,7 +2,7 @@ Dapper.Tests Dapper Core Test Suite - net481;net8.0;net9.0 + net481;net8.0;net10.0 $(DefineConstants);MSSQLCLIENT $(NoWarn);IDE0017;IDE0034;IDE0037;IDE0039;IDE0042;IDE0044;IDE0051;IDE0052;IDE0059;IDE0060;IDE0063;IDE1006;xUnit1004;CA1806;CA1816;CA1822;CA1825;CA2208;CA1861 enable From 155a83319a0b36b66f3e9317241f686b4e6bd1e6 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 29 Apr 2026 06:23:17 +0100 Subject: [PATCH 306/312] fix CI (#2196) * fix CI * try again --- appveyor.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/appveyor.yml b/appveyor.yml index ae9d477bf..5ec88faaf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -6,6 +6,11 @@ skip_commits: files: - '**/*.md' +install: +- ps: | + Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -UseBasicParsing -OutFile "$env:temp\dotnet-install.ps1" + & $env:temp\dotnet-install.ps1 -Architecture x64 -Version '10.0.102' -InstallDir "$env:ProgramFiles\dotnet" + environment: Appveyor: true # Postgres From b9bdd7c40be9adec3f9f556ffe00acfda77a0604 Mon Sep 17 00:00:00 2001 From: Andi Date: Wed, 29 Apr 2026 08:29:21 +0200 Subject: [PATCH 307/312] Fix segfault when ICustomQueryParameter is a value type (#2189) (#2198) The IL emitted by CreateParamInfoGenerator used Callvirt to invoke AddParameter on an unboxed struct sitting on the evaluation stack. Callvirt expects a reference, so the CLR misinterpreted the raw struct bytes as an object pointer, causing a fatal CLR error (0x80131506). --- Dapper/SqlMapper.cs | 8 +++++-- tests/Dapper.Tests/ParameterTests.cs | 34 ++++++++++++++++++++++++++-- 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index d23e949a5..e87a017bf 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -2666,7 +2666,11 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true { il.Emit(OpCodes.Ldloc, typedParameterLocal); // stack is now [parameters] [typed-param] il.Emit(callOpCode, prop.GetGetMethod()!); // stack is [parameters] [custom] - if (!prop.PropertyType.IsValueType) + if (prop.PropertyType.IsValueType) + { + il.Emit(OpCodes.Box, prop.PropertyType); // stack is [parameters] [boxed-custom] + } + else { // throw if null var notNull = il.DefineLabel(); @@ -2678,7 +2682,7 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true } il.Emit(OpCodes.Ldarg_0); // stack is now [parameters] [custom] [command] il.Emit(OpCodes.Ldstr, prop.Name); // stack is now [parameters] [custom] [command] [name] - il.EmitCall(OpCodes.Callvirt, prop.PropertyType.GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters] + il.EmitCall(OpCodes.Callvirt, typeof(ICustomQueryParameter).GetMethod(nameof(ICustomQueryParameter.AddParameter))!, null); // stack is now [parameters] continue; } #pragma warning disable 618 diff --git a/tests/Dapper.Tests/ParameterTests.cs b/tests/Dapper.Tests/ParameterTests.cs index 650624c20..5eb455c65 100644 --- a/tests/Dapper.Tests/ParameterTests.cs +++ b/tests/Dapper.Tests/ParameterTests.cs @@ -61,6 +61,21 @@ public void AddParameter(IDbCommand command, string name) } } + public readonly struct DbCustomParamStruct : SqlMapper.ICustomQueryParameter + { + private readonly IDbDataParameter _sqlParameter; + + public DbCustomParamStruct(IDbDataParameter sqlParameter) + { + _sqlParameter = sqlParameter; + } + + public void AddParameter(IDbCommand command, string name) + { + command.Parameters.Add(_sqlParameter); + } + } + private static IEnumerable CreateSqlDataRecordList(IDbCommand command, IEnumerable numbers) { #pragma warning disable CS0618 // Type or member is obsolete @@ -885,8 +900,23 @@ public void TestCustomParameterReuse() Assert.Equal(123, result2.Foo); Assert.Equal("abc", result2.Bar); } - - + + [Fact] + public void TestCustomParameterValueType() + { + // Value type (struct) ICustomQueryParameter previously caused a segfault + // because the IL emitted Callvirt on an unboxed struct (see #2189) + var args = new { + foo = new DbCustomParamStruct(Provider.CreateRawParameter("foo", 123)), + bar = "abc" + }; + var result = connection.Query("select Foo=@foo, Bar=@bar", args).Single(); + int foo = result.Foo; + string bar = result.Bar; + Assert.Equal(123, foo); + Assert.Equal("abc", bar); + } + [Fact] public void TestDynamicParamNullSupport() { From 464cb337ba9a2989a190a58b608d0d5afb3b6494 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Wed, 29 Apr 2026 08:38:12 +0100 Subject: [PATCH 308/312] Update dependencies (#2202) * first snapshot, warnings * intermediate * more * more fixings * cleanup * nuke NETCOREAPP3_1 ref --- Dapper.StrongName/Dapper.StrongName.csproj | 2 + Dapper/Dapper.csproj | 2 + Directory.Build.props | 2 +- Directory.Packages.props | 53 ++++++++++--------- .../Dapper.Tests.Performance.csproj | 18 ++++--- tests/Dapper.Tests/Dapper.Tests.csproj | 3 +- tests/Dapper.Tests/MiscTests.cs | 2 +- 7 files changed, 46 insertions(+), 36 deletions(-) diff --git a/Dapper.StrongName/Dapper.StrongName.csproj b/Dapper.StrongName/Dapper.StrongName.csproj index 2aae43d73..705385fd6 100644 --- a/Dapper.StrongName/Dapper.StrongName.csproj +++ b/Dapper.StrongName/Dapper.StrongName.csproj @@ -19,10 +19,12 @@ + + diff --git a/Dapper/Dapper.csproj b/Dapper/Dapper.csproj index 98d8f11eb..d9518dc45 100644 --- a/Dapper/Dapper.csproj +++ b/Dapper/Dapper.csproj @@ -26,10 +26,12 @@ + + diff --git a/Directory.Build.props b/Directory.Build.props index cb8af4d2d..227e4eed5 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -14,7 +14,7 @@ git https://github.com/DapperLib/Dapper false - $(NOWARN);IDE0056;IDE0057;IDE0079 + $(NOWARN);IDE0056;IDE0057;IDE0079;DX1001 true embedded en-US diff --git a/Directory.Packages.props b/Directory.Packages.props index 8787a7767..19c2ac3a3 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,50 +3,53 @@ + - + - - + + - + - - + + - - - - + + + + - - - - - + + + + + + - - + + - + - - + + - - + + - + + - + - + \ No newline at end of file diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index fa18bd9c5..f9692833a 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -3,7 +3,7 @@ Dapper.Tests.Performance Dapper Core Performance Suite Exe - net462;net8.0 + net472;net10.0 false $(NoWarn);IDE0063;IDE0034;IDE0059;IDE0060 @@ -27,14 +27,16 @@ + + - + - - + + @@ -50,20 +52,20 @@ - + $(DefineConstants);NET4X - + - + - + diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index e02bb4ba3..17e3f1cc6 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -24,6 +24,7 @@ + @@ -32,7 +33,7 @@ - + diff --git a/tests/Dapper.Tests/MiscTests.cs b/tests/Dapper.Tests/MiscTests.cs index 9323be671..077e93647 100644 --- a/tests/Dapper.Tests/MiscTests.cs +++ b/tests/Dapper.Tests/MiscTests.cs @@ -9,7 +9,7 @@ using Microsoft.CSharp.RuntimeBinder; using Xunit; -#if NETCOREAPP3_1 || NET462 || NET472 +#if NET472 namespace System.Runtime.CompilerServices { [EditorBrowsable(EditorBrowsableState.Never)] From 512f024667b79b4a17a38f33da59b072a43579f1 Mon Sep 17 00:00:00 2001 From: Henrik Date: Wed, 29 Apr 2026 09:39:02 +0200 Subject: [PATCH 309/312] Make private & internal classes sealed and less enumeration of lists & LINQ (#2197) --- Dapper.SqlBuilder/SqlBuilder.cs | 12 ++++------ Dapper/DefaultTypeMap.cs | 24 +++++++++---------- Dapper/DynamicParameters.cs | 14 ++--------- Dapper/FeatureSupport.cs | 2 +- Dapper/SqlMapper.CacheInfo.cs | 2 +- Dapper/SqlMapper.DontMap.cs | 2 +- Dapper/SqlMapper.Link.cs | 2 +- Dapper/SqlMapper.TypeDeserializerCache.cs | 2 +- Dapper/SqlMapper.cs | 8 +++---- .../Massive/Massive.cs | 1 - .../PetaPoco/PetaPoco.cs | 9 ++++--- 11 files changed, 32 insertions(+), 46 deletions(-) diff --git a/Dapper.SqlBuilder/SqlBuilder.cs b/Dapper.SqlBuilder/SqlBuilder.cs index 5726fdc6e..cb45d0582 100644 --- a/Dapper.SqlBuilder/SqlBuilder.cs +++ b/Dapper.SqlBuilder/SqlBuilder.cs @@ -10,7 +10,7 @@ public class SqlBuilder private readonly Dictionary _data = new Dictionary(); private int _seq; - private class Clause + private sealed class Clause { public Clause(string sql, object? parameters, bool isInclusive) { @@ -23,7 +23,7 @@ public Clause(string sql, object? parameters, bool isInclusive) public bool IsInclusive { get; } } - private class Clauses : List + private sealed class Clauses : List { private readonly string _joiner, _prefix, _postfix; @@ -36,11 +36,9 @@ public Clauses(string joiner, string prefix = "", string postfix = "") public string ResolveClauses(DynamicParameters p) { - foreach (var item in this) - { - p.AddDynamicParams(item.Parameters); - } - return this.Any(a => a.IsInclusive) + ForEach(item => p.AddDynamicParams(item.Parameters)); + + return Exists(a => a.IsInclusive) ? _prefix + string.Join(_joiner, this.Where(a => !a.IsInclusive) diff --git a/Dapper/DefaultTypeMap.cs b/Dapper/DefaultTypeMap.cs index 98029741a..c2f691883 100644 --- a/Dapper/DefaultTypeMap.cs +++ b/Dapper/DefaultTypeMap.cs @@ -10,7 +10,7 @@ namespace Dapper /// public sealed class DefaultTypeMap : SqlMapper.ITypeMap { - private readonly List _fields; + private readonly FieldInfo[] _fields; private readonly Type _type; /// @@ -42,7 +42,7 @@ internal static MethodInfo GetPropertySetterOrThrow(PropertyInfo propertyInfo, T BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance, Type.DefaultBinder, propertyInfo.PropertyType, - propertyInfo.GetIndexParameters().Select(p => p.ParameterType).ToArray(), + Array.ConvertAll(propertyInfo.GetIndexParameters(), p => p.ParameterType), null)!.GetSetMethod(true); } @@ -54,9 +54,9 @@ internal static List GetSettableProps(Type t) .ToList(); } - internal static List GetSettableFields(Type t) + private static FieldInfo[] GetSettableFields(Type t) { - return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance).ToList(); + return t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); } /// @@ -156,20 +156,20 @@ public SqlMapper.IMemberMap GetConstructorParameter(ConstructorInfo constructor, // preference order is: // exact match over underscore match, exact case over wrong case, backing fields over regular fields, match-inc-underscores over match-exc-underscores - var field = _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); + var field = Array.Find(_fields, p => string.Equals(p.Name, columnName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, columnName, StringComparison.OrdinalIgnoreCase)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); if (field is null && MatchNamesWithUnderscores) { var effectiveColumnName = columnName.Replace("_", ""); backingFieldName = "<" + effectiveColumnName + ">k__BackingField"; - field = _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) - ?? _fields.Find(p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase)) - ?? _fields.Find(p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); + field = Array.Find(_fields, p => string.Equals(p.Name, effectiveColumnName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.Ordinal)) + ?? Array.Find(_fields, p => string.Equals(p.Name, effectiveColumnName, StringComparison.OrdinalIgnoreCase)) + ?? Array.Find(_fields, p => string.Equals(p.Name, backingFieldName, StringComparison.OrdinalIgnoreCase)); } if (field is not null) diff --git a/Dapper/DynamicParameters.cs b/Dapper/DynamicParameters.cs index f6708b5ce..1feb4d020 100644 --- a/Dapper/DynamicParameters.cs +++ b/Dapper/DynamicParameters.cs @@ -62,10 +62,7 @@ public void AddDynamicParams(object? param) if (subDynamic.templates is not null) { templates ??= new List(); - foreach (var t in subDynamic.templates) - { - templates.Add(t); - } + templates.AddRange(subDynamic.templates); } } else @@ -212,14 +209,7 @@ protected void AddParameters(IDbCommand command, SqlMapper.Identity identity) } // Now that the parameters are added to the command, let's place our output callbacks - var tmp = outputCallbacks; - if (tmp is not null) - { - foreach (var generator in tmp) - { - generator(); - } - } + outputCallbacks?.ForEach(generator => generator()); } foreach (var param in parameters.Values) diff --git a/Dapper/FeatureSupport.cs b/Dapper/FeatureSupport.cs index 4d7d77e2e..f00c38ed3 100644 --- a/Dapper/FeatureSupport.cs +++ b/Dapper/FeatureSupport.cs @@ -6,7 +6,7 @@ namespace Dapper /// /// Handles variances in features per DBMS /// - internal class FeatureSupport + internal sealed class FeatureSupport { private static readonly FeatureSupport Default = new FeatureSupport(false), diff --git a/Dapper/SqlMapper.CacheInfo.cs b/Dapper/SqlMapper.CacheInfo.cs index d235beb6f..69edc4eea 100644 --- a/Dapper/SqlMapper.CacheInfo.cs +++ b/Dapper/SqlMapper.CacheInfo.cs @@ -7,7 +7,7 @@ namespace Dapper { public static partial class SqlMapper { - private class CacheInfo + private sealed class CacheInfo { public DeserializerState Deserializer { get; set; } public Func[]? OtherDeserializers { get; set; } diff --git a/Dapper/SqlMapper.DontMap.cs b/Dapper/SqlMapper.DontMap.cs index a97c5811e..765c7b937 100644 --- a/Dapper/SqlMapper.DontMap.cs +++ b/Dapper/SqlMapper.DontMap.cs @@ -5,6 +5,6 @@ public static partial class SqlMapper /// /// Dummy type for excluding from multi-map /// - private class DontMap { /* hiding constructor */ } + private sealed class DontMap { /* hiding constructor */ } } } diff --git a/Dapper/SqlMapper.Link.cs b/Dapper/SqlMapper.Link.cs index 92b434881..710714412 100644 --- a/Dapper/SqlMapper.Link.cs +++ b/Dapper/SqlMapper.Link.cs @@ -12,7 +12,7 @@ public static partial class SqlMapper /// /// The type to cache. /// The value type of the cache. - internal class Link where TKey : class + internal sealed class Link where TKey : class { public static void Clear(ref Link? head) => Interlocked.Exchange(ref head, null); public static bool TryGet(Link? link, TKey key, [NotNullWhen(true)] out TValue? value) diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index 2c2f646db..3a34cd292 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -8,7 +8,7 @@ namespace Dapper { public static partial class SqlMapper { - private class TypeDeserializerCache + private sealed class TypeDeserializerCache { private TypeDeserializerCache(Type type) { diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index e87a017bf..6c8fe844e 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -29,7 +29,7 @@ namespace Dapper /// public static partial class SqlMapper { - private class PropertyInfoByNameComparer : IComparer + private sealed class PropertyInfoByNameComparer : IComparer { public int Compare(PropertyInfo? x, PropertyInfo? y) => string.CompareOrdinal(x?.Name, y?.Name); } @@ -3534,9 +3534,9 @@ private static void GenerateDeserializerFromMap(Type type, DbDataReader reader, il.Emit(OpCodes.Ldloc, returnValueLocal); // [target] } - var members = (specializedConstructor is not null - ? names.Select(n => typeMap.GetConstructorParameter(specializedConstructor, n)) - : names.Select(n => typeMap.GetMember(n))).ToList(); + var members = Array.ConvertAll(names, specializedConstructor is not null + ? n => typeMap.GetConstructorParameter(specializedConstructor, n) + : n => typeMap.GetMember(n)); // stack is now [target] bool first = true; diff --git a/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs b/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs index f452a95cb..f4c1c8192 100644 --- a/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs +++ b/benchmarks/Dapper.Tests.Performance/Massive/Massive.cs @@ -322,7 +322,6 @@ public virtual DbCommand CreateUpdateCommand(object o, object key) var settings = (IDictionary)expando; var sbKeys = new StringBuilder(); const string stub = "UPDATE {0} SET {1} WHERE {2} = @{3}"; - var args = new List(); var result = CreateCommand(stub, null); int counter = 0; foreach (var item in settings) diff --git a/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs b/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs index add3df772..2ffa3887c 100644 --- a/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs +++ b/benchmarks/Dapper.Tests.Performance/PetaPoco/PetaPoco.cs @@ -1020,10 +1020,9 @@ public string LastCommand { get { - var sb = new StringBuilder(); if (_lastSql == null) return ""; - sb.Append(_lastSql); + var sb = new StringBuilder(_lastSql); if (_lastArgs != null) { sb.Append("\r\n\r\n"); @@ -1038,14 +1037,14 @@ public string LastCommand public static IMapper Mapper { get; set; } - internal class PocoColumn + internal sealed class PocoColumn { public string ColumnName; public PropertyInfo PropertyInfo; public bool ResultColumn; } - internal class PocoData + internal sealed class PocoData { public static PocoData ForType(Type t) { @@ -1272,7 +1271,7 @@ public Func GetFactory(string key, bool ForceDateTimesToUtc, // ShareableConnection represents either a shared connection used by a transaction, // or a one-off connection if not in a transaction. // Non-shared connections are disposed - private class ShareableConnection : IDisposable + private sealed class ShareableConnection : IDisposable { public ShareableConnection(Database db) { From 2a8faab235c21892fb4c9a4529510d030516d691 Mon Sep 17 00:00:00 2001 From: Andi Date: Sat, 16 May 2026 22:08:03 +0200 Subject: [PATCH 310/312] Fix/prefer typehandlers for enums (#2200) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add Settings.PreferTypeHandlersForEnums opt-in flag When enabled, Dapper checks for a registered TypeHandler for enum types before falling back to the default integer boxing behavior. This allows custom enum serialization (e.g. storing enums as strings via TypeHandlers) to work on both reads and writes. Patched locations: - LookupDbType: return DbType.Object with handler when flag is on - CreateParamInfoGenerator: skip integer boxing when handler exists - GetSimpleValueDeserializer: route to TypeHandler before Enum.ToObject - Parse: route to TypeHandler before Enum.ToObject Default is false — zero behavior change for existing users. * Patch IL-emitted type deserializer and add tests - Fix 6th enum-before-handler location in GetTypeDeserializer IL emit, preserving Nullable wrapping for nullable enum properties - Fix em-dash in comment (non-ASCII) - Add two tests: round-trip write+read via StringEnumHandler, and nullable enum with null value * Update SDK version from 10.0.102 to 8.0.100 * Update global.json we have fixed the CI glitch separately --------- Co-authored-by: Marc Gravell --- Dapper/PublicAPI.Unshipped.txt | 4 +- Dapper/SqlMapper.Settings.cs | 9 +- Dapper/SqlMapper.cs | 59 ++++++--- global.json | 2 +- .../PreferTypeHandlersForEnumsTests.cs | 112 ++++++++++++++++++ 5 files changed, 169 insertions(+), 17 deletions(-) create mode 100644 tests/Dapper.Tests/PreferTypeHandlersForEnumsTests.cs diff --git a/Dapper/PublicAPI.Unshipped.txt b/Dapper/PublicAPI.Unshipped.txt index 91b0e1a43..cf9343ccf 100644 --- a/Dapper/PublicAPI.Unshipped.txt +++ b/Dapper/PublicAPI.Unshipped.txt @@ -1 +1,3 @@ -#nullable enable \ No newline at end of file +#nullable enable +static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.get -> bool +static Dapper.SqlMapper.Settings.PreferTypeHandlersForEnums.set -> void \ No newline at end of file diff --git a/Dapper/SqlMapper.Settings.cs b/Dapper/SqlMapper.Settings.cs index cbbc3c687..c19835c11 100644 --- a/Dapper/SqlMapper.Settings.cs +++ b/Dapper/SqlMapper.Settings.cs @@ -65,7 +65,7 @@ static Settings() public static void SetDefaults() { CommandTimeout = null; - ApplyNullValues = PadListExpansions = UseIncrementalPseudoPositionalParameterNames = false; + ApplyNullValues = PadListExpansions = UseIncrementalPseudoPositionalParameterNames = PreferTypeHandlersForEnums = false; AllowedCommandBehaviors = DefaultAllowedCommandBehaviors; FetchSize = InListStringSplitCount = -1; } @@ -129,6 +129,13 @@ public static long FetchSize /// public static bool SupportLegacyParameterTokens { get; set; } = true; + /// + /// When true, Dapper checks for a registered TypeHandler for enum types before + /// falling back to the default behavior of sending enums as their underlying integer type. + /// This enables custom enum serialization (e.g. storing enums as strings), while preserving existing behavior. + /// + public static bool PreferTypeHandlersForEnums { get; set; } + private static long s_FetchSize = -1; } } diff --git a/Dapper/SqlMapper.cs b/Dapper/SqlMapper.cs index 6c8fe844e..2fa0e72b7 100644 --- a/Dapper/SqlMapper.cs +++ b/Dapper/SqlMapper.cs @@ -465,6 +465,10 @@ public static void SetDbType(IDataParameter parameter, object value) if (nullUnderlyingType is not null) type = nullUnderlyingType; if (type.IsEnum && !typeMap.ContainsKey(type)) { + if (Settings.PreferTypeHandlersForEnums && typeHandlers.TryGetValue(type, out handler)) + { + return DbType.Object; + } type = Enum.GetUnderlyingType(type); } if (typeMap.TryGetValue(type, out var mapEntry)) @@ -2755,7 +2759,12 @@ private static bool IsValueTuple(Type? type) => (type?.IsValueType == true if ((nullType ?? propType).IsEnum) { - if (nullType is not null) + if (handler is not null) + { + // TypeHandler registered - box as the enum type, handler does conversion + checkForNull = nullType is not null; + } + else if (nullType is not null) { // Nullable; we want to box as the underlying type; that's just *hard*; for // simplicity, box as Nullable and call SanitizeParameterValue @@ -3101,7 +3110,16 @@ private static Func GetSimpleValueDeserializer(Type type, #pragma warning restore 618 if (effectiveType.IsEnum) - { // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum + { + if (Settings.PreferTypeHandlersForEnums && typeHandlers.TryGetValue(type, out var enumHandler)) + { + return r => + { + var val = r.GetValue(index); + return val is DBNull ? null! : enumHandler.Parse(type, val)!; + }; + } + // assume the value is returned as the correct type (int/byte/etc), but box back to the typed enum return r => { var val = r.GetValue(index); @@ -3164,6 +3182,10 @@ private static T Parse(object? value) type = Nullable.GetUnderlyingType(type) ?? type; if (type.IsEnum) { + if (Settings.PreferTypeHandlersForEnums && typeHandlers.TryGetValue(type, out ITypeHandler? enumHandler)) + { + return (T)enumHandler.Parse(type, value)!; + } if (value is float || value is double || value is decimal) { value = Convert.ChangeType(value, Enum.GetUnderlyingType(type), CultureInfo.InvariantCulture); @@ -3742,22 +3764,31 @@ private static void LoadReaderValueOrBranchToDBNullLabel(ILGenerator il, int ind if (unboxType.IsEnum) { - Type numericType = Enum.GetUnderlyingType(unboxType); - if (colType == typeof(string)) + if (Settings.PreferTypeHandlersForEnums && typeHandlers.ContainsKey(unboxType)) { - stringEnumLocal ??= il.DeclareLocal(typeof(string)); - il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] - il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] - il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] - il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null);// stack is now [...][enum-type] - il.Emit(OpCodes.Ldloc, stringEnumLocal); // stack is now [...][enum-type][string] - il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] - il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] - il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] +#pragma warning disable 618 + il.EmitCall(OpCodes.Call, typeof(TypeHandlerCache<>).MakeGenericType(unboxType).GetMethod(nameof(TypeHandlerCache.Parse))!, null); // stack is now [...][typed-value] +#pragma warning restore 618 } else { - FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); + Type numericType = Enum.GetUnderlyingType(unboxType); + if (colType == typeof(string)) + { + stringEnumLocal ??= il.DeclareLocal(typeof(string)); + il.Emit(OpCodes.Castclass, typeof(string)); // stack is now [...][string] + il.Emit(OpCodes.Stloc, stringEnumLocal); // stack is now [...] + il.Emit(OpCodes.Ldtoken, unboxType); // stack is now [...][enum-type-token] + il.EmitCall(OpCodes.Call, typeof(Type).GetMethod(nameof(Type.GetTypeFromHandle))!, null);// stack is now [...][enum-type] + il.Emit(OpCodes.Ldloc, stringEnumLocal); // stack is now [...][enum-type][string] + il.Emit(OpCodes.Ldc_I4_1); // stack is now [...][enum-type][string][true] + il.EmitCall(OpCodes.Call, enumParse, null); // stack is now [...][enum-as-object] + il.Emit(OpCodes.Unbox_Any, unboxType); // stack is now [...][typed-value] + } + else + { + FlexibleConvertBoxedFromHeadOfStack(il, colType, unboxType, numericType); + } } if (nullUnderlyingType is not null) diff --git a/global.json b/global.json index f7b5e40c2..1e3e94a0b 100644 --- a/global.json +++ b/global.json @@ -3,4 +3,4 @@ "version": "10.0.102", "rollForward": "latestMajor" } -} \ No newline at end of file +} diff --git a/tests/Dapper.Tests/PreferTypeHandlersForEnumsTests.cs b/tests/Dapper.Tests/PreferTypeHandlersForEnumsTests.cs new file mode 100644 index 000000000..958f0d546 --- /dev/null +++ b/tests/Dapper.Tests/PreferTypeHandlersForEnumsTests.cs @@ -0,0 +1,112 @@ +using System; +using System.Data; +using System.Linq; +using Xunit; + +namespace Dapper.Tests +{ + [Collection(NonParallelDefinition.Name)] + public sealed class SystemSqlClientPreferTypeHandlersForEnumsTests : PreferTypeHandlersForEnumsTests { } +#if MSSQLCLIENT + [Collection(NonParallelDefinition.Name)] + public sealed class MicrosoftSqlClientPreferTypeHandlersForEnumsTests : PreferTypeHandlersForEnumsTests { } +#endif + + public abstract class PreferTypeHandlersForEnumsTests : TestBase where TProvider : DatabaseProvider + { + private enum Color + { + Red = 1, + Green = 2, + Blue = 3 + } + + private class ColorResult + { + public Color Value { get; set; } + } + + private class NullableColorResult + { + public Color? Value { get; set; } + } + + /// + /// A TypeHandler that stores enum values as their name strings + /// and parses them back from strings. + /// + private class StringEnumHandler : SqlMapper.TypeHandler where TEnum : struct, Enum + { + public static readonly StringEnumHandler Instance = new(); + public int ParseCallCount; + public int SetValueCallCount; + + public override TEnum Parse(object? value) + { + ParseCallCount++; + return (TEnum)Enum.Parse(typeof(TEnum), (string)value!); + } + + public override void SetValue(IDbDataParameter parameter, TEnum value) + { + SetValueCallCount++; + parameter.DbType = DbType.AnsiString; + parameter.Value = value.ToString(); + } + } + + [Fact] + public void EnumTypeHandler_WriteAndRead_UsesHandlerWhenEnabled() + { + var handler = new StringEnumHandler(); + var oldSetting = SqlMapper.Settings.PreferTypeHandlersForEnums; + try + { + SqlMapper.ResetTypeHandlers(); + SqlMapper.AddTypeHandler(typeof(Color), handler); + SqlMapper.Settings.PreferTypeHandlersForEnums = true; + SqlMapper.PurgeQueryCache(); + + // Round-trip: write as string, read back via handler + var result = connection.Query( + "SELECT @Value AS Value", new { Value = Color.Green }).Single(); + + Assert.Equal(Color.Green, result.Value); + Assert.True(handler.SetValueCallCount > 0, "SetValue should have been called"); + Assert.True(handler.ParseCallCount > 0, "Parse should have been called"); + } + finally + { + SqlMapper.Settings.PreferTypeHandlersForEnums = oldSetting; + SqlMapper.ResetTypeHandlers(); + SqlMapper.PurgeQueryCache(); + } + } + + [Fact] + public void EnumTypeHandler_NullableWithNull_ReturnsNull() + { + var handler = new StringEnumHandler(); + var oldSetting = SqlMapper.Settings.PreferTypeHandlersForEnums; + try + { + SqlMapper.ResetTypeHandlers(); + SqlMapper.AddTypeHandler(typeof(Color), handler); + SqlMapper.Settings.PreferTypeHandlersForEnums = true; + SqlMapper.PurgeQueryCache(); + + Color? input = null; + var result = connection.Query( + "SELECT @Value AS Value", new { Value = input }).Single(); + + Assert.Null(result.Value); + } + finally + { + SqlMapper.Settings.PreferTypeHandlersForEnums = oldSetting; + SqlMapper.ResetTypeHandlers(); + SqlMapper.PurgeQueryCache(); + } + } + } +} From 953f3718052940e72d0f20e22d836b1595f5dcd8 Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sat, 16 May 2026 21:08:13 +0100 Subject: [PATCH 311/312] keep deps up to date (#2204) * keep deps up to date * d'oh * slnx --- Dapper.sln => Dapper.sln.old | 242 +++++++++--------- Dapper.slnx | 38 +++ Directory.Packages.props | 18 +- .../Dapper.Tests.Performance.csproj | 2 +- tests/Dapper.Tests/Dapper.Tests.csproj | 8 +- 5 files changed, 175 insertions(+), 133 deletions(-) rename Dapper.sln => Dapper.sln.old (98%) create mode 100644 Dapper.slnx diff --git a/Dapper.sln b/Dapper.sln.old similarity index 98% rename from Dapper.sln rename to Dapper.sln.old index 04fcc008d..e324cc009 100644 --- a/Dapper.sln +++ b/Dapper.sln.old @@ -1,121 +1,121 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.7.33906.173 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - appveyor.yml = appveyor.yml - Build.csproj = Build.csproj - build.ps1 = build.ps1 - Dapper.png = Dapper.png - Directory.Build.props = Directory.Build.props - Directory.Packages.props = Directory.Packages.props - global.json = global.json - docs\index.md = docs\index.md - License.txt = License.txt - .github\workflows\main.yml = .github\workflows\main.yml - NonCLA.md = NonCLA.md - nuget.config = nuget.config - Readme.md = Readme.md - version.json = version.json - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "Dapper\Dapper.csproj", "{FAC24C3F-68F9-4247-A4B9-21D487E99275}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.StrongName", "Dapper.StrongName\Dapper.StrongName.csproj", "{549C51A1-222B-4E12-96F1-3AEFF45A7709}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "tests\Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework", "Dapper.EntityFramework\Dapper.EntityFramework.csproj", "{BE401F7B-8611-4A1E-AEAA-5CB700128C16}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{196928F0-7052-4585-90E8-817BD720F5E3}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{8A74F0B6-188F-45D2-8A4B-51E4F211805A}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" - ProjectSection(SolutionItems) = preProject - tests\Directory.Build.props = tests\Directory.Build.props - tests\Directory.Build.targets = tests\Directory.Build.targets - tests\docker-compose.yml = tests\docker-compose.yml - EndProjectSection -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.StrongName", "Dapper.EntityFramework.StrongName\Dapper.EntityFramework.StrongName.csproj", "{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "benchmarks\Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.Build.0 = Release|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.Build.0 = Debug|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.ActiveCfg = Release|Any CPU - {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.Build.0 = Release|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.Build.0 = Debug|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.ActiveCfg = Release|Any CPU - {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.Build.0 = Release|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.Build.0 = Debug|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.ActiveCfg = Release|Any CPU - {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.Build.0 = Release|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.Build.0 = Debug|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.ActiveCfg = Release|Any CPU - {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.Build.0 = Release|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.Build.0 = Release|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.Build.0 = Release|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {FAC24C3F-68F9-4247-A4B9-21D487E99275} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {549C51A1-222B-4E12-96F1-3AEFF45A7709} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {052C0817-DB26-4925-8929-8C5E42D148D5} = {568BD46C-1C65-4D44-870C-12CD72563262} - {BE401F7B-8611-4A1E-AEAA-5CB700128C16} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {196928F0-7052-4585-90E8-817BD720F5E3} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} - {B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} - {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F} = {9D960D4D-80A2-4DAC-B386-8F4235EC73E6} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.7.33906.173 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{A34907DF-958A-4E4C-8491-84CF303FD13E}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + appveyor.yml = appveyor.yml + Build.csproj = Build.csproj + build.ps1 = build.ps1 + Dapper.png = Dapper.png + Directory.Build.props = Directory.Build.props + Directory.Packages.props = Directory.Packages.props + global.json = global.json + docs\index.md = docs\index.md + License.txt = License.txt + .github\workflows\main.yml = .github\workflows\main.yml + NonCLA.md = NonCLA.md + nuget.config = nuget.config + Readme.md = Readme.md + version.json = version.json + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper", "Dapper\Dapper.csproj", "{FAC24C3F-68F9-4247-A4B9-21D487E99275}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.StrongName", "Dapper.StrongName\Dapper.StrongName.csproj", "{549C51A1-222B-4E12-96F1-3AEFF45A7709}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests", "tests\Dapper.Tests\Dapper.Tests.csproj", "{052C0817-DB26-4925-8929-8C5E42D148D5}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework", "Dapper.EntityFramework\Dapper.EntityFramework.csproj", "{BE401F7B-8611-4A1E-AEAA-5CB700128C16}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.SqlBuilder", "Dapper.SqlBuilder\Dapper.SqlBuilder.csproj", "{196928F0-7052-4585-90E8-817BD720F5E3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Rainbow", "Dapper.Rainbow\Dapper.Rainbow.csproj", "{8A74F0B6-188F-45D2-8A4B-51E4F211805A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{4E956F6B-6BD8-46F5-BC85-49292FF8F9AB}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{568BD46C-1C65-4D44-870C-12CD72563262}" + ProjectSection(SolutionItems) = preProject + tests\Directory.Build.props = tests\Directory.Build.props + tests\Directory.Build.targets = tests\Directory.Build.targets + tests\docker-compose.yml = tests\docker-compose.yml + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.EntityFramework.StrongName", "Dapper.EntityFramework.StrongName\Dapper.EntityFramework.StrongName.csproj", "{39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.Tests.Performance", "benchmarks\Dapper.Tests.Performance\Dapper.Tests.Performance.csproj", "{F017075A-2969-4A8E-8971-26F154EB420F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Dapper.ProviderTools", "Dapper.ProviderTools\Dapper.ProviderTools.csproj", "{B06DB435-0C74-4BD3-BC97-52AF7CF9916B}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "docs", "docs", "{9D960D4D-80A2-4DAC-B386-8F4235EC73E6}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs", "docs\docs.csproj", "{C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FAC24C3F-68F9-4247-A4B9-21D487E99275}.Release|Any CPU.Build.0 = Release|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Debug|Any CPU.Build.0 = Debug|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.ActiveCfg = Release|Any CPU + {549C51A1-222B-4E12-96F1-3AEFF45A7709}.Release|Any CPU.Build.0 = Release|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {052C0817-DB26-4925-8929-8C5E42D148D5}.Release|Any CPU.Build.0 = Release|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE401F7B-8611-4A1E-AEAA-5CB700128C16}.Release|Any CPU.Build.0 = Release|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {196928F0-7052-4585-90E8-817BD720F5E3}.Release|Any CPU.Build.0 = Release|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8A74F0B6-188F-45D2-8A4B-51E4F211805A}.Release|Any CPU.Build.0 = Release|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB}.Release|Any CPU.Build.0 = Release|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F017075A-2969-4A8E-8971-26F154EB420F}.Release|Any CPU.Build.0 = Release|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B}.Release|Any CPU.Build.0 = Release|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {FAC24C3F-68F9-4247-A4B9-21D487E99275} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {549C51A1-222B-4E12-96F1-3AEFF45A7709} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {052C0817-DB26-4925-8929-8C5E42D148D5} = {568BD46C-1C65-4D44-870C-12CD72563262} + {BE401F7B-8611-4A1E-AEAA-5CB700128C16} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {196928F0-7052-4585-90E8-817BD720F5E3} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {8A74F0B6-188F-45D2-8A4B-51E4F211805A} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {39D3EEB6-9C05-4F4A-8C01-7B209742A7EB} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {F017075A-2969-4A8E-8971-26F154EB420F} = {568BD46C-1C65-4D44-870C-12CD72563262} + {B06DB435-0C74-4BD3-BC97-52AF7CF9916B} = {4E956F6B-6BD8-46F5-BC85-49292FF8F9AB} + {C2F722AC-B2D4-4E97-AF8E-C036B3D9211F} = {9D960D4D-80A2-4DAC-B386-8F4235EC73E6} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {928A4226-96F3-409A-8A83-9E7444488710} + EndGlobalSection +EndGlobal diff --git a/Dapper.slnx b/Dapper.slnx new file mode 100644 index 000000000..674239170 --- /dev/null +++ b/Dapper.slnx @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Directory.Packages.props b/Directory.Packages.props index 19c2ac3a3..aa293885e 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -3,10 +3,10 @@ - + - + @@ -18,16 +18,16 @@ - - + + - + - - + + @@ -45,8 +45,8 @@ - - + + diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index f9692833a..2a229c33e 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -14,7 +14,7 @@ - + diff --git a/tests/Dapper.Tests/Dapper.Tests.csproj b/tests/Dapper.Tests/Dapper.Tests.csproj index 17e3f1cc6..93b498618 100644 --- a/tests/Dapper.Tests/Dapper.Tests.csproj +++ b/tests/Dapper.Tests/Dapper.Tests.csproj @@ -12,11 +12,15 @@ $(DefineConstants);ENTITY_FRAMEWORK;LINQ2SQL;OLEDB - + + + + + + - From 72a54c475f75e18cb93cba0809d00a5e6e49efd9 Mon Sep 17 00:00:00 2001 From: David Federman Date: Sat, 16 May 2026 13:25:15 -0700 Subject: [PATCH 312/312] Add ReferenceTrimmer and remove unused references (#2191) * Add ReferenceTrimmer and remove unused references * Update Directory.Packages.props latest --------- Co-authored-by: Marc Gravell --- Dapper.SqlBuilder/Dapper.SqlBuilder.csproj | 3 ++- Directory.Packages.props | 5 ++++- .../Dapper.Tests.Performance.csproj | 10 ++++------ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj index c101afcaa..a455a5941 100644 --- a/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj +++ b/Dapper.SqlBuilder/Dapper.SqlBuilder.csproj @@ -19,7 +19,8 @@ - + + diff --git a/Directory.Packages.props b/Directory.Packages.props index aa293885e..252f417b9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -52,4 +52,7 @@ - \ No newline at end of file + + + + diff --git a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj index 2a229c33e..51c28957a 100644 --- a/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj +++ b/benchmarks/Dapper.Tests.Performance/Dapper.Tests.Performance.csproj @@ -15,7 +15,6 @@ - @@ -40,11 +39,11 @@ - - - + + + - + @@ -58,7 +57,6 @@ -