Skip to content
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

Async query serialization #2152

Closed
tomasfabian opened this issue May 7, 2020 · 8 comments
Closed

Async query serialization #2152

tomasfabian opened this issue May 7, 2020 · 8 comments
Assignees

Comments

@tomasfabian
Copy link

tomasfabian commented May 7, 2020

Is there a way / is it necessary to execute the serialization of entities to JSON from the database query asynchronously? I think that it would be beneficial to return the thread to the thread pool during execution of the query.
Is this already done by WebApi or OData within a dedicated middleware? If no, can it be overriden?

This should be fast because the query wasn't executed at this point, so I don't use Task in the return value:

    [EnableQuery]
    public OkObjectResult Get(ODataQueryOptions<TEntity> queryOptions)
    {
      ValidateQuery(queryOptions);

      IQueryable<TEntity> entities = repository.GetAll();

      return Ok(entities);
    }

Thank you, Tomas

@henrikdahl8240
Copy link

@tomasfabian If I understand your question correct, You should just return an object, which implements IAsyncEnumerable.

You may read it here: https://docs.microsoft.com/en-us/aspnet/core/web-api/action-return-types?view=aspnetcore-3.1#return-ienumerablet-or-iasyncenumerablet

This addresses, what you ask for:

In ASP.NET Core 3.0 and later, returning IAsyncEnumerable from an action:

No longer results in synchronous iteration.
Becomes as efficient as returning IEnumerable.
...

@habbes habbes self-assigned this May 12, 2020
@habbes
Copy link
Contributor

habbes commented May 12, 2020

Hi @tomasfabian, please let us know if the solution proposed by @henrikdahl8240 works for you.

@tomasfabian
Copy link
Author

Hi @henrikdahl8240, yes you understand my question correctly. I'm using AspNetCore 3 OData 7.4 with Entity Framework 6.4. I'm afraid, that I'm probably not able to return IAsyncEnumerable, because OData queries are composable:
http://localhost:20501/Orders?$filter=IdDispatcher eq 1&$expand=OrderGroup,OrderItems
so the return type has to be IQueryable<TEntity>. In the end the user can use projection (Select) and the final resulting type will be different type as TEntity.

However I tried to take control of the query composition in this way:

    //[EnableQuery]
    public async IAsyncEnumerable<object> Get(ODataQueryOptions<TEntity> queryOptions, [EnumeratorCancellation]CancellationToken cancellationToken)
    {
      ValidateQuery(queryOptions);

      IQueryable<TEntity> queryable = repository.GetAll();

      var dbAsyncEnumerable = ((IDbAsyncEnumerable)queryOptions.ApplyTo(queryable));

      using (var enumerator = dbAsyncEnumerable.GetAsyncEnumerator())
      {
        while (await enumerator.MoveNextAsync(cancellationToken))
        {
          yield return enumerator.Current;
        }
      }
    }

But the client side dataServiceContext throws System.InvalidOperationException: The response payload is a not a valid response payload.

var test = await ((DataServiceQuery<Order>) dataServiceContext.Orders.Take(1)).ExecuteAsync();

I think that it is caused by missing metadata.

Thank you very much, Tomas

@henrikdahl8240
Copy link

You didn't write, that you use Entity Framework 6.4. I assumed you use Entity Framework Core >= 3.0 due to the nature of your question.

For EF Core the problem is solved using e.g. EntityFrameworkQueryableExtensions.AsAsyncEnumerable(IQueryable) Method as documented here: https://docs.microsoft.com/en-us/dotnet/api/microsoft.entityframeworkcore.entityframeworkqueryableextensions.asasyncenumerable?view=efcore-3.1.

I think the issue is, that Entity Framework 6.4 is not a good citizen when it comes to .NET 3.0 which introduced IAsyncEnumerable. EF 6.4 just supports .NET Core "to survive" so to speak. EF 6.4 does not support IAsyncEnumerable for instance because .NET Framework does not support IAsyncEnumerable.

Basically I think, that your situation boils down to either it will never come to work with EF 6.4 or you can change for EF Core, which is a good citizen when it comes to .NET 3.0 and therefore supports IAsyncEnumerable first class using the method I referred to.

Actually I don't think, that what you refer to as "composable" is what is called composable when it comes to OData, i.e. to just use some basic OData features like filter and select. OData supports composability when it comes to functions. You explicitly ´mark a function in the builder to be composable if you have such a wish.

@tomasfabian
Copy link
Author

tomasfabian commented May 13, 2020

I didn't mention EF 6.4, because I mentioned IQueryable, but in this case IQueryProvider is more important as I understand it. I found out that EF Core extends this interface with IAsyncQueryProvider.

In my opinion EF 6.4 could support IAsyncEnumerable for NETCOREAPP3.1 by using conditional compilation. I understand that NETFRAMEWORK is out of the question, because it does not support NETSTANDARD2.1 where IAsyncEnumerable was introduced.
Edit: actually there is a NETSTANDARD2.0 compatibility package https://www.nuget.org/packages/Microsoft.Bcl.AsyncInterfaces/

@habbes it seems that other ORM's like NHibernate have the same issue:
nhibernate/nhibernate-core#2267
maybe LLBL Gen Pro, too.
Should I post an issue to dotnet/ef6 repository and close this one? Has this issue be addressed by all the ORM's individually? Maybe it could be solved in one place, I think that you have a lot of smart people in your team to find a viable solution.

@henrikdahl8240 long story short regarding to the .Net Core "good citizen". I have created a lot of services in .Net Framework with Entity Framework during the last decade. Nowadays I'm migrating them to .Net Core and Microsoft made a GREAT decision and ported EF6 to .Net Core. It would be very time consuming for us to test hundreds of WPF and other client apps relying on these web services (successfully migrated to .NET Core). WCF is another story... I'm ready to switch to EF Core when it will be ready, too. But it lacks features like many-to-many without CLR class for join table. It has 1384 opened issues at the time of writing. It would be too risky to move to EF Core at this moment, and it will be a lot of work for developers, testers etc. We and other teams in the world can achieve this transition only step-by-step.

I'm aware about that custom functions in OData can be configured as IsComposable = true. AFAIK expression trees can be combined and as a result IQueryable is composable with query composition. I verified that this is also true:

dataServiceContext.Orders/*(DataServiceQuery<Order>)*/.IsComposable == true

I apologize if I confused you or if I misunderstood the meaning of English word composable (Capable of being composed (as from multiple lesser elements)). I meant what you wrote: using additional basic OData features like $select.

@henrikdahl8240 thank you very much for your insight and help!

@henrikdahl8240
Copy link

@tomasfabian Thank you for your constructive comments.

Your situation sounds quite like mine, i.e. interest/motivation in migrating from .NET Framework to .NET Core.

So do you actually have som real success with that, because I actually do not and trust me, I have spent really many hours doing it?

For instance I get the problem after switching to 7.4.0 from 7.3.0, that after thousands of invocations of the OData web service in parallel, suddenly the client gets responses like the connection was closed unexpectedly. You have not face that, I mean after many invocations in parallel, which I suppose is within your scope as you do something major, as it sounds like?

I am also faced with the issue, that the routing does not select the right method in case of multiple overloads because the first of the given name is trivially being selected. You may see #2075, particularly:

Thanks for reporting. This is known issue that ODataActionSelector in Core version simply select the first matched candidates if there are multiple matched.
The matching process is simple using the method name.

If #2148 would also be relevant to you, perhaps you could drop a "me too" comment there.

As far as I understand, concerning "composability" in scope of OData, it basically means h(g(f(x))) where f and g are functions marked as composable.

I am sorry if I just waste your time with my questions, but it could just be interesting to hear, if you actually have real success with .NET Core or you are basically stuck with the same shortcomings.

@tomasfabian
Copy link
Author

@henrikdahl8240 just shortly as this is not related to this issue (please contact me via email if you would like to share knowledge). I'm currently using multitargeting - production still uses AspNet.OData (NET472) and in development I'm migrating to AspNetCore.OData (NETCOREAPP3.1). Up to this time I was able to migrate everything except of some operations related to OData batch support. Some bugs were introduced there. I reported these issues under my old account @tfabian.
I haven't done stress tests up to this time, but you should maybe report a new issue to OData team if the connection is closed unexpectedly only in v 7.4.0

@KenitoInc
Copy link
Contributor

@tomasfabian It seems your question was answered. I will close this issue

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants