Asynchronously process entities as they are returned from the database engine

async-await asynchronous c# entity-framework entity-framework-6

Question

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:

  1. Large queries don't need to be stored in memory as the entire collection, so savings in memory footprint and ability to process much larger resultsets not constrained by memory
  2. The duration of the whole process will likely be shorter due to processing each item during latency of data retrieval

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.

1
7
3/14/2015 8:47:06 PM

Accepted Answer

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.

3
3/14/2015 9:20:28 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow