| #if NET5_0_OR_GREATER |
| /// <summary> |
| /// Execute a query asynchronously using <see cref="IAsyncEnumerable{dynamic}"/>. |
| /// </summary> |
| /// <param name="cnn">The connection to query on.</param> |
| /// <param name="sql">The SQL to execute for the query.</param> |
| /// <param name="param">The parameters to pass, if any.</param> |
| /// <param name="transaction">The transaction to use, if any.</param> |
| /// <param name="commandTimeout">The command timeout (in seconds).</param> |
| /// <param name="commandType">The type of command to execute.</param> |
| /// <returns> |
| /// A sequence of data of dynamic data |
| /// </returns> |
| publicstaticIAsyncEnumerable<dynamic>QueryUnbufferedAsync(thisDbConnectioncnn,stringsql,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 |
| returnQueryUnbufferedAsync<dynamic>(cnn,typeof(object),newCommandDefinition(sql,param,transaction,commandTimeout,commandType,CommandFlags.None,default)); |
| } |
| |
| /// <summary> |
| /// Execute a query asynchronously using <see cref="IAsyncEnumerable{T}"/>. |
| /// </summary> |
| /// <typeparam name="T">The type of results to return.</typeparam> |
| /// <param name="cnn">The connection to query on.</param> |
| /// <param name="sql">The SQL to execute for the query.</param> |
| /// <param name="param">The parameters to pass, if any.</param> |
| /// <param name="transaction">The transaction to use, if any.</param> |
| /// <param name="commandTimeout">The command timeout (in seconds).</param> |
| /// <param name="commandType">The type of command to execute.</param> |
| /// <returns> |
| /// A sequence of data of <typeparamref name="T"/>; if a basic type (int, string, etc) is queried then the data from the first column is assumed, otherwise an instance is |
| /// created per row, and a direct column-name===member-name mapping is assumed (case insensitive). |
| /// </returns> |
| publicstaticIAsyncEnumerable<T>QueryUnbufferedAsync<T>(thisDbConnectioncnn,stringsql,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 |
| returnQueryUnbufferedAsync<T>(cnn,typeof(T),newCommandDefinition(sql,param,transaction,commandTimeout,commandType,CommandFlags.None,default)); |
| } |
| |
| privatestaticIAsyncEnumerable<T>QueryUnbufferedAsync<T>(thisIDbConnectioncnn,TypeeffectiveType,CommandDefinitioncommand) |
| { |
| returnImpl(cnn,effectiveType,command,command.CancellationToken);// proxy to allow CT expression |
| |
| staticasyncIAsyncEnumerable<T>Impl(IDbConnectioncnn,TypeeffectiveType,CommandDefinitioncommand, |
| [EnumeratorCancellation]CancellationTokencancel) |
| { |
| object?param=command.Parameters; |
| varidentity=newIdentity(command.CommandText,command.CommandTypeDirect,cnn,effectiveType,param?.GetType()); |
| varinfo=GetCacheInfo(identity,param,command.AddToCache); |
| boolwasClosed=cnn.State==ConnectionState.Closed; |
| usingvarcmd=command.TrySetupAsyncCommand(cnn,info.ParamReader); |
| DbDataReader?reader=null; |
| try |
| { |
| if(wasClosed)awaitcnn.TryOpenAsync(cancel).ConfigureAwait(false); |
| reader=awaitExecuteReaderWithFlagsFallbackAsync(cmd,wasClosed,CommandBehavior.SequentialAccess|CommandBehavior.SingleResult,cancel).ConfigureAwait(false); |
| |
| vartuple=info.Deserializer; |
| inthash=GetColumnHash(reader); |
| if(tuple.Funcisnull||tuple.Hash!=hash) |
| { |
| if(reader.FieldCount==0) |
| { |
| yieldbreak; |
| } |
| tuple=info.Deserializer=newDeserializerState(hash,GetDeserializer(effectiveType,reader,0,-1,false)); |
| if(command.AddToCache)SetQueryCache(identity,info); |
| } |
| |
| varfunc=tuple.Func; |
| |
| varconvertToType=Nullable.GetUnderlyingType(effectiveType)??effectiveType; |
| while(awaitreader.ReadAsync(cancel).ConfigureAwait(false)) |
| { |
| objectval=func(reader); |
| yieldreturnGetValue<T>(reader,effectiveType,val); |
| } |
| while(awaitreader.NextResultAsync(cancel).ConfigureAwait(false)){/* ignore subsequent result sets */} |
| command.OnCompleted(); |
| } |
| finally |
| { |
| if(readeris not null) |
| { |
| if(!reader.IsClosed) |
| { |
| try{cmd?.Cancel();} |
| catch{/* don't spoil any existing exception */} |
| } |
| awaitreader.DisposeAsync(); |
| } |
| if(wasClosed)cnn.Close(); |
| } |
| } |
| } |
| #endif |
Currently,
IAsyncEnumerablesupport is conditioned on .NET5 or greater. For example:Dapper/Dapper/SqlMapper.Async.cs
Lines 1252 to 1347 in 6434c69
However, there shouldn't be any reason not to support
IAsyncEnumerableon every target currently supported by Dapper, since there exists a well-maintained BCL package that adds the interface on older frameworks:To allow for
IAsyncEnumerableto work everywhere, it would just be a matter of including a dependency on the package above for thenetstandard2.0andnet462targets. This approach is used heavily by other libraries for this exact same purpose.We currently have a solution that targets NET472 and we wanted to be able to tap into
IAsyncEnumerablein a couple of places where we are currently being forced to useTask<IEnumerable<T>>methods such asQueryAsync.Could there be an update to the library that adds support for the
IAsyncEnumerable-returning methods for all frameworks based on the above suggestion?