In EF 6, I would like to asynchronously process entities as they are returned from the database engine.
I know I can call ToListAsync()
and ForEachAsync()
and perhaps they do what I'm looking for, but I'm not convinced. I think what I am looking for is the combination of the two.
From my understanding, ToListAsync()
will complete the task when the entire query is read from the database engine and converted to entities. Which means you have to wait for the entire query to return before beginning to process.
I can't determine if ForEachAsync()
does what I'm looking for but what I am assuming, due to not finding information elsewhere, is that ForEachAsync()
simply works on an already-retrieved collection and the processing of each item is async.
What would be ideal is that ForEachAsync()
(or another yet unknown method) would call the task as the data was being retrieved from the database engine.
So... does ForEachAsync()
actually do this, and if not, is there a way for it to be done?
The reasons for wanting this are twofold:
Update: Basically, if a DbContext
raised an event like OnEntityLoaded
for each entity when you called LoadAsync()
, I could accomplish everything that I want. Because I could then just enqueue the entity to a separate task processor and the entities could be processed efficiently and take advantage of any I/O latency. I can always tune the separate task processor, so I don't really need EF to support asynchronous processing of the entities, just asynchronously load and fire an event/call a delegate on each entity load.
Update 2: And if ForEachAsync()
was called as the entities were being loaded, then that would also accomplish what I'm after.
ForEachAsync
, unlike ToListAsync
, doesn't get all the items in advance and just lets you iterate over it. The iteration is async
itself.
QueryableExtensions.ForEachAsync
delegates over to IDbAsyncEnumerable.ForEachAsync
which is this:
internal static async Task ForEachAsync(
this IDbAsyncEnumerable source, Action<object> action, CancellationToken cancellationToken)
{
DebugCheck.NotNull(source);
DebugCheck.NotNull(action);
cancellationToken.ThrowIfCancellationRequested();
using (var enumerator = source.GetAsyncEnumerator())
{
if (await enumerator.MoveNextAsync(cancellationToken).WithCurrentCulture())
{
Task<bool> moveNextTask;
do
{
cancellationToken.ThrowIfCancellationRequested();
var current = enumerator.Current;
moveNextTask = enumerator.MoveNextAsync(cancellationToken);
action(current);
}
while (await moveNextTask.WithCurrentCulture());
}
}
}
You can see that it's very similar to how an iteration over IEnumerable
is made but with async-await
in mind. Instead of IEnumerable
, GetEnumerator
, IEnumerator
and MoveNext
we have IDbAsyncEnumerable
, GetAsyncEnumerator
, IDbAsyncEnumerator
and MoveNextAsync
.
MoveNextAsync
allows to actually asynchronously retrieve items when needed.