diff --git a/src/Expressions.Database/Queries/QueryLog.cs b/src/Expressions.Database/Queries/QueryLog.cs index 1aac59a..9dd602d 100644 --- a/src/Expressions.Database/Queries/QueryLog.cs +++ b/src/Expressions.Database/Queries/QueryLog.cs @@ -29,9 +29,17 @@ internal static class QueryLog 4, "Error trying to query the single element"); + private static readonly Action s_asyncEnumerableErrorCallback = LoggerMessage.Define( + LogLevel.Error, + 5, + "Error trying to enumerate found elements"); + public static void AnyError(ILogger logger, Exception exception) => s_anyErrorCallback(logger, exception); public static void CountError(ILogger logger, Exception exception) => s_countErrorCallback(logger, exception); public static void FirstError(ILogger logger, Exception exception) => s_firstErrorCallback(logger, exception); public static void ListError(ILogger logger, Exception exception) => s_listErrorCallback(logger, exception); public static void SingleError(ILogger logger, Exception exception) => s_singleErrorCallback(logger, exception); + + public static void AsyncEnumerableError(ILogger logger, Exception exception) => + s_asyncEnumerableErrorCallback(logger, exception); } diff --git a/src/Expressions.EntityFrameworkCore/Queries/EFQuery.cs b/src/Expressions.EntityFrameworkCore/Queries/EFQuery.cs index 6d7c02f..abba187 100644 --- a/src/Expressions.EntityFrameworkCore/Queries/EFQuery.cs +++ b/src/Expressions.EntityFrameworkCore/Queries/EFQuery.cs @@ -1,4 +1,5 @@ -using Microsoft.EntityFrameworkCore; +using System.Runtime.CompilerServices; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Raiqub.Expressions.Queries; @@ -131,4 +132,27 @@ and not InvalidOperationException throw; } } + + public async IAsyncEnumerable ToAsyncEnumerable( + [EnumeratorCancellation] CancellationToken cancellationToken = default) + { + IAsyncEnumerable enumerable; + try + { + enumerable = _dataSource.AsAsyncEnumerable(); + } + catch (Exception exception) when (exception is not ArgumentNullException + and not InvalidOperationException) + { + QueryLog.AsyncEnumerableError(_logger, exception); + throw; + } + + await foreach (TResult result in enumerable + .WithCancellation(cancellationToken) + .ConfigureAwait(false)) + { + yield return result; + } + } } diff --git a/src/Expressions.Marten/Queries/MartenQuery.cs b/src/Expressions.Marten/Queries/MartenQuery.cs index a040a63..59743cd 100644 --- a/src/Expressions.Marten/Queries/MartenQuery.cs +++ b/src/Expressions.Marten/Queries/MartenQuery.cs @@ -131,4 +131,9 @@ and not InvalidOperationException throw; } } + + public IAsyncEnumerable ToAsyncEnumerable(CancellationToken cancellationToken = default) + { + return _dataSource.ToAsyncEnumerable(cancellationToken); + } } diff --git a/src/Expressions.Reading/Queries/IQuery.cs b/src/Expressions.Reading/Queries/IQuery.cs index e6facac..6aeb552 100644 --- a/src/Expressions.Reading/Queries/IQuery.cs +++ b/src/Expressions.Reading/Queries/IQuery.cs @@ -54,4 +54,12 @@ public interface IQuery /// Query contains more than one element. /// If the is canceled. Task SingleOrDefaultAsync(CancellationToken cancellationToken = default); + + /// + /// Execute this query to an . This is valuable for reading + /// and processing large result sets without having to keep the entire result set in memory + /// + /// A to observe while waiting for the task to complete. + /// The query results. + IAsyncEnumerable ToAsyncEnumerable(CancellationToken cancellationToken = default); }