From 2fa046cd398d6680494c4c0733c0993123eabbff Mon Sep 17 00:00:00 2001 From: Marc Gravell Date: Sat, 13 Apr 2024 09:07:53 +0100 Subject: [PATCH 01/22] 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 02/22] 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 03/22] 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 04/22] 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 05/22] 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 06/22] 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 07/22] 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 08/22] 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 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 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 09/22] 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 10/22] 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 11/22] 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 12/22] 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 13/22] 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 14/22] 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 15/22] 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 16/22] 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 17/22] 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 18/22] 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 19/22] 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 20/22] 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 21/22] 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 22/22] 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