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.
In general, one would use async await when you want to keep the currently running thread from blocking. This frees the thread for other tasks (like updating the UI), while we await the asynchronous result.
Asynchronous calls are most useful when facing relatively infrequent large, expensive operations that could tie up response threads which could otherwise be servicing requests while the originator waits. For quick, common operations, async can slow things down.
Note that there are no async versions of some LINQ operators such as Where or OrderBy, because these only build up the LINQ expression tree and don't cause the query to be executed in the database. Only operators which cause query execution have async counterparts.
SaveChangesAsync() returns the number of lines changed. It is possible (at least theoretically) that two user will simultenously work to delete the same id in the database.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With