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
{