-
Notifications
You must be signed in to change notification settings - Fork 476
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Returning an IQueryable from EF core 6 generates synchronous database calls #2598
Comments
@jr01 we welcome a contribution on this. Feel free to do a PR and we'll be happy to review and merge. Thanks. |
@jr01 Are you planning on submitting a PR for this? I also just noticed this and would be interested in this feature being added. |
@wbuck @ElizabethOkerio The workaround is good enough for our current needs. Also we have been happy with a workaround for count async #2325 for about a year now ... |
+1 on this issue. |
@jr01 we added support for |
@ElizabethOkerio Does this mean that in the latest update, returning If so, that's absolutely brilliant. kudos. |
@ElizabethOkerio with the steps above and the latest release (8.2.3) the code still throws in |
Wouldn't it be here instead? I do see differences there. |
This should be the right repo to look at: https://github.com/OData/AspNetCoreOData/blob/main/src/Microsoft.AspNetCore.OData/Formatter/Serialization/ODataResourceSetSerializer.cs Having such a controller action method will make async calls.
|
@ElizabethOkerio Is 8.2.3 the release with this in it? |
yes. |
@mbrankintrintech @jr01 we'd like feedback on this on whether it works as expected and if there are any improvements that we can make. |
The big feedback for me would be that (if that this works) let's expand it to the other functions, namely the count command so that the count query is also non-blocking. 😄 |
Thanks. Yes, this is in our backlog. |
@ElizabethOkerio I think I was mistaken before and I now do see the Yes, the following is now making a non-blocking async call to the database, so that's great!: [EnableQuery]
public IAsyncEnumerable<Person> Get()
{
var query = this.dbContext.Persons.AsAsyncEnumerable();
return query;
} However, when returning an ActionResult with an encapsulated IAsyncEnumerable it still runs synchronously: [EnableQuery]
public ActionResult<IAsyncEnumerable<Person>> Get()
{
var query = this.dbContext.Persons.AsAsyncEnumerable();
return this.Ok(query);
} Maybe this would work when this part of the check wasn't done?: writeContext.Type != null &&
writeContext.Type.IsGenericType &&
writeContext.Type.GetGenericTypeDefinition() == typeof(IAsyncEnumerable<>) Another thing that doesn't seem to work is an [EnableQuery]
public async IAsyncEnumerable<Person> Get([EnumeratorCancellation] CancellationToken cancellationToken)
{
await Task.Delay(10, cancellationToken);
var query = this.dbContext.Persons.AsAsyncEnumerable();
await foreach (var item in query)
{
yield return item;
}
} |
@jr01 Thanks. I'll look into this. |
@ElizabethOkerio I hope I'm not late, but according to the AsAsyncEnumerable implementation in EF Core: public static IAsyncEnumerable<TSource> AsAsyncEnumerable<TSource>(
this IQueryable<TSource> source)
{
Check.NotNull(source, nameof(source));
if (source is IAsyncEnumerable<TSource> asyncEnumerable)
{
return asyncEnumerable;
}
throw new InvalidOperationException(CoreStrings.IQueryableNotAsync(typeof(TSource)));
} The object representing EF Core's IQueryable already implements IAsyncEnumerable, so it might be beneficial to add a simple check for the IAsyncEnumerable interface to make it compatible with all existing EF Core queries. With this check, we can eliminate the need for the AsAsyncEnumerable call and enable asynchronous request serialization where possible. I believe this is a critical enhancement because query execution times can extend to tens of seconds or even minutes in practical scenarios. Prolonged synchronous queries can lead to thread starvation. Thus, implementing this straightforward check can significantly boost OData performance in real-world use cases. |
@DimaMegaMan I could be wrong here but I don't think the source [EnableQuery]
public IQueryable<Person> Get()
{
return this.dbContext.Persons;
} should lead to async database calls being made? Currently, If you return an |
I think I have an explanation of what might be happening with this not working by default in the aspnet core version of this here OData/AspNetCoreOData#1194 |
I'm using the [EnableQuery] attribute inside a normal [ApiController].
I'm not defining EdmModels or ClrTypes anywhere for the IQueryable code, so why would returning IAsyncEnumerable suddenly complain about it? |
@aldrashan We'll look into this. |
I've been investigating the available options for improving the performance of OData for large data sets. Essentially, I hit a brick wall due to this issue. From what I'm seeing, the root cause is the use of Conversely, the recently merged support for A possible solution might be to replace |
On .Net 5 / MVC 5 + EF Core 5 when an controller action with
[EnableQuery]
returned an EF CoreIQuerable
which implementedIAsyncEnumerable
the database call would be executed asynchronously.With .Net 6 / MVC 6 + EF Core 6 the database call is executed synchronously.
Assemblies affected
Microsoft.AspNetCore.OData 8.0.4
Reproduce steps
In an ASP.Net 6 + oData 8.0.4 + EF core 6 project:
Expected result
Asynchronous DB calls are made.
Actual result
A synchronous DB call is made - not good for scaling/performance.
With the
BlockNonAsyncQueriesInterceptor
the exception occurs at the foreach loop here: https://github.com/OData/WebApi/blob/master/src/Microsoft.AspNet.OData.Shared/Formatter/Serialization/ODataResourceSetSerializer.cs#L230Additional detail
I'm not sure if this is related at all, but I found MVC 5 buffered
IAsyncEnumerable
and this got removed in MVC 6 - https://docs.microsoft.com/en-us/dotnet/core/compatibility/aspnet-core/6.0/iasyncenumerable-not-buffered-by-mvcAnyway I found a workaround by introducing an
EnableQueryAsync
attribute and execute theIAsyncEnumerable
and buffer the results in memory:Maybe a solution is if
ODataResourceSetSerializer
and other collection serializers check ifIEnumerable
is anIAsyncEnumerable
and performs anawait foreach
?The text was updated successfully, but these errors were encountered: