Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronously process entities as they are returned from the database engine

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.

like image 792
MikeJansen Avatar asked Mar 13 '15 17:03

MikeJansen


People also ask

When to use async EF core?

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.

Should DB calls be async?

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.

Is LINQ async?

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.

What does SaveChangesAsync return?

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.


Video Answer


1 Answers

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.

like image 80
i3arnon Avatar answered Oct 19 '22 07:10

i3arnon