5andr0
5andr0

Reputation: 2036

Multiple active db operations during await foreach DbSet.AsAsyncEnumerable() loop allowed on same context?

Am I allowed to execute SaveChanges during an active loop over IAsyncEnumerable from DbSet?
The Api remarks "Multiple active operations on the same context instance are not supported. Use await to ensure that any asynchronous operations have completed before calling another method on this context. See Avoiding DbContext threading issues for more information and examples."

But I'm not sure if an open IAsyncEnumerable loop counts as an active operation.

I'd like to mark my entries so that they are only queried once.
Do I have to use another dbcontext to save the status marker?

while(!cancellationToken.IsCancellationRequested)
{
    await using var scope = serviceScopeFactory.CreateAsyncScope();
    var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

    await foreach(var o in db.Orders
                    .Where(o => o.Status != 1) // and some other conditions
                    .AsAsyncEnumerable()
                    .WithCancellation(cancellationToken)
                    .ConfigureAwait(false))
    {
        o.Status = 1;
        await db.SaveChangesAsync(cancellationToken);
        // run fire and forget async work once
    }
}

Upvotes: 1

Views: 31

Answers (1)

Theodor Zoulias
Theodor Zoulias

Reputation: 43474

I don't have much experience with the Entity framework, but from what I see there is no concurrency in your code. The C# compiler transforms the await foreach statement to something like this:

while(!cancellationToken.IsCancellationRequested)
{
    await using var scope = serviceScopeFactory.CreateAsyncScope();
    var db = scope.ServiceProvider.GetRequiredService<ApplicationDbContext>();

    var enumerator = db.Orders
        .Where(o => o.Status != 1) // and some other conditions
        .AsAsyncEnumerable()
        .GetAsyncEnumerator(cancellationToken);

    try
    {
        while (await enumerator.MoveNextAsync().ConfigureAwait(false))
        {
            var o = enumerator.Current;
            o.Status = 1;
            await db.SaveChangesAsync(cancellationToken);
            // run fire and forget async work once
        }
    }
    finally
    {
        await enumerator.DisposeAsync().ConfigureAwait(false);
    }
}

The asynchronous operations enumerator.MoveNextAsync() and db.SaveChangesAsync() are performed sequentially.

There are of course questions about the code represented by the // run fire and forget async work once comment, but since we don't see the code I'll just have to assume that you know what you are doing there.

Upvotes: 0

Related Questions