diff --git a/Dapper/CommandDefinition.cs b/Dapper/CommandDefinition.cs index 8c193cebc..5860cd461 100644 --- a/Dapper/CommandDefinition.cs +++ b/Dapper/CommandDefinition.cs @@ -9,7 +9,7 @@ namespace Dapper /// /// Represents the key aspects of a sql operation /// - public struct CommandDefinition + public readonly struct CommandDefinition { internal static CommandDefinition ForCallback(object parameters) { diff --git a/Dapper/SqlMapper.Async.cs b/Dapper/SqlMapper.Async.cs index 59e37262d..78d3e04c5 100644 --- a/Dapper/SqlMapper.Async.cs +++ b/Dapper/SqlMapper.Async.cs @@ -530,7 +530,7 @@ public static Task ExecuteAsync(this IDbConnection cnn, CommandDefinition c } } - private struct AsyncExecState + private readonly struct AsyncExecState { public readonly DbCommand Command; public readonly Task Task; @@ -1220,6 +1220,24 @@ private static async Task ExecuteScalarImplAsync(IDbConnection cnn, Comman } #if NET5_0_OR_GREATER + /// + /// Execute a query asynchronously using . + /// + /// The connection to query on. + /// The SQL to execute for the query. + /// The parameters to pass, if any. + /// The transaction to use, if any. + /// The command timeout (in seconds). + /// The type of command to execute. + /// + /// A sequence of data of dynamic data + /// + public static IAsyncEnumerable QueryUnbufferedAsync(this DbConnection cnn, string sql, object param = null, DbTransaction transaction = null, int? commandTimeout = null, CommandType? commandType = null) + { + // note: in many cases of adding a new async method I might add a CancellationToken - however, cancellation is expressed via WithCancellation on iterators + return QueryUnbufferedAsync(cnn, typeof(object), new CommandDefinition(sql, param, transaction, commandTimeout, commandType, CommandFlags.None, default)); + } + /// /// Execute a query asynchronously using . /// diff --git a/Dapper/SqlMapper.DeserializerState.cs b/Dapper/SqlMapper.DeserializerState.cs index bf1c2fce1..4b594e0f5 100644 --- a/Dapper/SqlMapper.DeserializerState.cs +++ b/Dapper/SqlMapper.DeserializerState.cs @@ -6,7 +6,7 @@ namespace Dapper { public static partial class SqlMapper { - private struct DeserializerState + private readonly struct DeserializerState { public readonly int Hash; public readonly Func Func; diff --git a/Dapper/SqlMapper.LiteralToken.cs b/Dapper/SqlMapper.LiteralToken.cs index 5ad399ab8..b5fdff0ec 100644 --- a/Dapper/SqlMapper.LiteralToken.cs +++ b/Dapper/SqlMapper.LiteralToken.cs @@ -8,7 +8,7 @@ public static partial class SqlMapper /// /// Represents a placeholder for a value that should be replaced as a literal value in the resulting sql /// - internal struct LiteralToken + internal readonly struct LiteralToken { /// /// The text in the original command that should be replaced diff --git a/Dapper/SqlMapper.TypeDeserializerCache.cs b/Dapper/SqlMapper.TypeDeserializerCache.cs index 65fd3bf40..f24e58a6d 100644 --- a/Dapper/SqlMapper.TypeDeserializerCache.cs +++ b/Dapper/SqlMapper.TypeDeserializerCache.cs @@ -1,9 +1,8 @@ using System; -using System.Data; using System.Collections; using System.Collections.Generic; -using System.Text; using System.Data.Common; +using System.Text; namespace Dapper { @@ -16,7 +15,7 @@ private TypeDeserializerCache(Type type) this.type = type; } - private static readonly Hashtable byType = new Hashtable(); + private static readonly Hashtable byType = new(); private readonly Type type; internal static void Purge(Type type) { @@ -51,9 +50,9 @@ internal static Func GetReader(Type type, DbDataReader rea return found.GetReader(reader, startBound, length, returnNullIfFirstMissing); } - private readonly Dictionary> readers = new Dictionary>(); + private readonly Dictionary> readers = new(); - private struct DeserializerKey : IEquatable + private readonly struct DeserializerKey : IEquatable { private readonly int startBound, length; private readonly bool returnNullIfFirstMissing; diff --git a/docs/index.md b/docs/index.md index 2dcceaa0e..9170ff21f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -22,7 +22,9 @@ Note: to get the latest pre-release build, add ` -Pre` to the end of the command ### unreleased -- reinstate fallback support for `IDataReader`, and implement missing `DbDataReader` async APIs +- add missing non-generic `AsyncEnumerable QueryUnbufferedAsync(...)` API (#1925 via mgravell, fixes #1922) +- formally mark all `struct` types as `readonly` (#1925 via mgravell) +- reinstate fallback support for `IDataReader`, and implement missing `DbDataReader` async APIs (#1913 via mgravell) (note: new PRs will not be merged until they add release note wording here) diff --git a/tests/Dapper.Tests/AsyncTests.cs b/tests/Dapper.Tests/AsyncTests.cs index b8d1fff88..8309a22ca 100644 --- a/tests/Dapper.Tests/AsyncTests.cs +++ b/tests/Dapper.Tests/AsyncTests.cs @@ -47,6 +47,20 @@ public async Task TestBasicStringUsageAsync() } #if NET5_0_OR_GREATER + [Fact] + public async Task TestBasicStringUsageUnbufferedDynamicAsync() + { + var results = new List(); + await foreach (var row in connection.QueryUnbufferedAsync("select 'abc' as [Value] union all select @txt", new { txt = "def" }) + .ConfigureAwait(false)) + { + string value = row.Value; + results.Add(value); + } + var arr = results.ToArray(); + Assert.Equal(new[] { "abc", "def" }, arr); + } + [Fact] public async Task TestBasicStringUsageUnbufferedAsync() { @@ -192,7 +206,7 @@ public async Task TestBasicStringUsageAsyncNonBuffered() [Fact] public void TestLongOperationWithCancellation() { - CancellationTokenSource cancel = new CancellationTokenSource(TimeSpan.FromSeconds(5)); + CancellationTokenSource cancel = new(TimeSpan.FromSeconds(5)); var task = connection.QueryAsync(new CommandDefinition("waitfor delay '00:00:10';select 1", cancellationToken: cancel.Token)); try {