-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
6 changed files
with
208 additions
and
54 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,51 +1,51 @@ | ||
<Project> | ||
<ItemGroup> | ||
<!-- note: 6.2.0 has regressions; don't force the update --> | ||
<PackageVersion Include="EntityFramework" Version="6.1.3" /> | ||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" /> | ||
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" /> | ||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" /> | ||
<PackageVersion Include="Microsoft.SqlServer.Types" Version="14.0.1016.290" /> | ||
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.143" /> | ||
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" /> | ||
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||
<!-- tests --> | ||
<PackageVersion Include="Azure.Identity" Version="1.12.1" /> | ||
<PackageVersion Include="Belgrade.Sql.Client" Version="1.1.4" /> | ||
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" /> | ||
<PackageVersion Include="Dashing" Version="2.10.1" /> | ||
<PackageVersion Include="Dapper.Contrib" Version="2.0.78" /> | ||
<PackageVersion Include="DuckDB.NET.Data.Full" Version="1.1.1" /> | ||
<PackageVersion Include="DevExpress.Xpo" Version="24.1.6" /> | ||
<PackageVersion Include="FirebirdSql.Data.FirebirdClient" Version="10.3.1" /> | ||
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" /> | ||
<PackageVersion Include="Iesi.Collections" Version="4.1.1" /> | ||
<PackageVersion Include="linq2db.SqlServer" Version="5.4.1" /> | ||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" /> | ||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.8" /> | ||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" /> | ||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> | ||
<PackageVersion Include="Mighty" Version="3.2.0" /> | ||
<PackageVersion Include="MySqlConnector" Version="2.3.7" /> | ||
<PackageVersion Include="NHibernate" Version="5.5.2" /> | ||
<PackageVersion Include="Norm.net" Version="5.4.0" /> | ||
<PackageVersion Include="Npgsql" Version="8.0.4" /> | ||
<PackageVersion Include="PetaPoco" Version="5.1.306" /> | ||
<PackageVersion Include="RepoDb.SqlServer" Version="1.13.1" /> | ||
<PackageVersion Include="ServiceStack.OrmLite.SqlServer" Version="8.4.0" /> | ||
<PackageVersion Include="Snowflake.Data" Version="4.1.0" /> | ||
<PackageVersion Include="SqlMarshal" Version="0.5.0" /> | ||
<PackageVersion Include="SubSonic" Version="3.0.0.4" /> | ||
<PackageVersion Include="Susanoo.SqlServer" Version="1.2.4.2" /> | ||
<PackageVersion Include="System.Data.SqlClient" Version="4.8.6" /> | ||
<PackageVersion Include="System.Data.SQLite" Version="1.0.119" /> | ||
<PackageVersion Include="System.Net.Http" Version="4.3.4" /> | ||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" /> | ||
<PackageVersion Include="System.Reflection.Metadata" Version="8.0.0" /> | ||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" /> | ||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" /> | ||
<PackageVersion Include="xunit" Version="2.9.2" /> | ||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" /> | ||
</ItemGroup> | ||
<ItemGroup> | ||
<!-- note: 6.2.0 has regressions; don't force the update --> | ||
<PackageVersion Include="EntityFramework" Version="6.1.3" /> | ||
<PackageVersion Include="FastMember" Version="1.5.0" /> | ||
<PackageVersion Include="Microsoft.CSharp" Version="4.7.0" /> | ||
<PackageVersion Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.3" /> | ||
<PackageVersion Include="Microsoft.SourceLink.GitHub" Version="8.0.0" /> | ||
<PackageVersion Include="Microsoft.SqlServer.Types" Version="14.0.1016.290" /> | ||
<PackageVersion Include="Nerdbank.GitVersioning" Version="3.6.143" /> | ||
<PackageVersion Include="Microsoft.CodeAnalysis.PublicApiAnalyzers" Version="3.3.4" /> | ||
<PackageVersion Include="System.Reflection.Emit.Lightweight" Version="4.7.0" /> | ||
<!-- tests --> | ||
<PackageVersion Include="Azure.Identity" Version="1.12.1" /> | ||
<PackageVersion Include="Belgrade.Sql.Client" Version="1.1.4" /> | ||
<PackageVersion Include="BenchmarkDotNet" Version="0.14.0" /> | ||
<PackageVersion Include="Dashing" Version="2.10.1" /> | ||
<PackageVersion Include="Dapper.Contrib" Version="2.0.78" /> | ||
<PackageVersion Include="DuckDB.NET.Data.Full" Version="1.1.1" /> | ||
<PackageVersion Include="DevExpress.Xpo" Version="24.1.6" /> | ||
<PackageVersion Include="FirebirdSql.Data.FirebirdClient" Version="10.3.1" /> | ||
<PackageVersion Include="GitHubActionsTestLogger" Version="2.4.1" /> | ||
<PackageVersion Include="Iesi.Collections" Version="4.1.1" /> | ||
<PackageVersion Include="linq2db.SqlServer" Version="5.4.1" /> | ||
<PackageVersion Include="Microsoft.Data.SqlClient" Version="5.2.2" /> | ||
<PackageVersion Include="Microsoft.Data.Sqlite" Version="8.0.8" /> | ||
<PackageVersion Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.8" /> | ||
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.11.1" /> | ||
<PackageVersion Include="Mighty" Version="3.2.0" /> | ||
<PackageVersion Include="MySqlConnector" Version="2.3.7" /> | ||
<PackageVersion Include="NHibernate" Version="5.5.2" /> | ||
<PackageVersion Include="Norm.net" Version="5.4.0" /> | ||
<PackageVersion Include="Npgsql" Version="8.0.4" /> | ||
<PackageVersion Include="PetaPoco" Version="5.1.306" /> | ||
<PackageVersion Include="RepoDb.SqlServer" Version="1.13.1" /> | ||
<PackageVersion Include="ServiceStack.OrmLite.SqlServer" Version="8.4.0" /> | ||
<PackageVersion Include="Snowflake.Data" Version="4.1.0" /> | ||
<PackageVersion Include="SqlMarshal" Version="0.5.0" /> | ||
<PackageVersion Include="SubSonic" Version="3.0.0.4" /> | ||
<PackageVersion Include="Susanoo.SqlServer" Version="1.2.4.2" /> | ||
<PackageVersion Include="System.Data.SqlClient" Version="4.8.6" /> | ||
<PackageVersion Include="System.Data.SQLite" Version="1.0.119" /> | ||
<PackageVersion Include="System.Net.Http" Version="4.3.4" /> | ||
<PackageVersion Include="System.Private.Uri" Version="4.3.2" /> | ||
<PackageVersion Include="System.Reflection.Metadata" Version="8.0.0" /> | ||
<PackageVersion Include="System.Text.RegularExpressions" Version="4.3.1" /> | ||
<PackageVersion Include="System.ValueTuple" Version="4.5.0" /> | ||
<PackageVersion Include="xunit" Version="2.9.2" /> | ||
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.2" /> | ||
</ItemGroup> | ||
</Project> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<SystemSqlClientProvider>(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<MicrosoftSqlClientProvider>(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<TProvider>(ITestOutputHelper log) : TestBase<TProvider> 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<int>("""select count(1) from #mydata"""); | ||
watch.Stop(); | ||
log.WriteLine($"bulk-insert complete; {count} rows in {watch.ElapsedMilliseconds}ms"); | ||
} | ||
|
||
// just errors | ||
var ex = Assert.ThrowsAny<DbException>(() => conn.Execute("raiserror('bad things', 16, 1)")); | ||
log.WriteLine(ex.Message); | ||
ex = await Assert.ThrowsAnyAsync<DbException>(async () => await conn.ExecuteAsync("raiserror('bad things', 16, 1)")); | ||
log.WriteLine(ex.Message); | ||
|
||
// just data | ||
watch = Stopwatch.StartNew(); | ||
var row = conn.QueryFirst<MyRow>("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<MyRow>("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<MyRow>("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<MyRow>("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<DbException>(() => conn.QueryFirst<MyRow>("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<DbException>(async () => await conn.QueryFirstAsync<MyRow>("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<MyRow>("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<MyRow>("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<T> FirstAsync<T>(this IAsyncEnumerable<T> source, CancellationToken cancellationToken = default) | ||
{ | ||
await using var iter = source.GetAsyncEnumerator(cancellationToken); | ||
if (!await iter.MoveNextAsync()) Array.Empty<T>().First(); // for consistent error | ||
return iter.Current; | ||
} | ||
} |