Remote.Linq icon indicating copy to clipboard operation
Remote.Linq copied to clipboard

IQueryable doesn't implement IAsyncEnumerable

Open mleader1-cbs opened this issue 1 year ago • 9 comments

The source 'IQueryable' doesn't implement 'IAsyncEnumerable<xxx>'. Only sources that implement 'IAsyncEnumerable' can be used for Entity Framework asynchronous operations

Hi, I'm experiencing issues when implementing serialized Linq expression using Remote.Linq, can anyone shed some light here pls:

Client implementation:

public class xxxDataClient {
   protected Func<Expression, CancellationToken, ValueTask<DynamicObject>> QueryDataProvider { get; private set; }

        public xxxDataClient (Uri baseUri = null)
        {
            BaseUri = GetClientBaseUri(baseUri);
            _httpClient = new HttpClient() { BaseAddress = BaseUri };
            QueryDataProvider = async (expression, cancellation) =>
            {
                try
                {
                    var query = new TheDataClientQuery
                    {
                        Expression = expression
                    };


                    var response = await _httpClient.PostAsync($"{_endpointRelativePath}", query, _formatter).ConfigureAwait(false);

                    if (response.StatusCode == HttpStatusCode.InternalServerError)
                    {
                        byte[] errorMessageData = await response.Content.ReadAsByteArrayAsync(cancellation).ConfigureAwait(false);
                        string errorMessage = Encoding.UTF8.GetString(errorMessageData);
                        throw new RemoteLinqException(errorMessage);
                    }

                    response.EnsureSuccessStatusCode();
                    try
                    {
                        var result = await response.Content.ReadAsAsync<DynamicObject>(new[] { _formatter }, cancellation).ConfigureAwait(false);
                        return result;
                    }
                    catch (Exception ex)
                    {
                        if (response.StatusCode == HttpStatusCode.OK)
                        {
                            return default;
                        }
                        throw;
                    }
                }
                catch (SocketException ex)
                {
                    throw;
                }
            };
        }


        public IAsyncRemoteQueryable<xxTheDbModel> BpCostCentreConfigurations => RemoteQueryable.Factory.CreateAsyncQueryable<xxTheDbModel>(this.QueryDataProvider, new CbsTypeMappingExpressionTranslatorContext());


}

```



**Server side code**  : Server side using WebApi, the code is very simple - using EntityFramework Core:

``````csharp
    public class LinqQueryController : WebApiControllerBase
    {
        private TheDbContext _theDbContext;

        public LinqQueryController(
            TheDbContext  theDbContext) : base()
        {
            _theDbContext= theDbContext;
        }

        [HttpPost("")]
        [AllowAnonymous]
        public ValueTask<DynamicObject?>? Query([FromBody] CbsDataClientQuery query)
        {
            var result = query?.Expression?.ExecuteWithEntityFrameworkCoreAsync(this._vqsDbContext);
            return result;
        }

        [HttpDelete("")]
        public Task<bool> Delete([FromBody] CbsDataClientQuery query)
        {
            return default;
        }
    }


``


The error throws at the client side when doing the request, so I can assume it hasn't even reached that far to the httpClient request.

The error is:

``````csharp
System.InvalidOperationException: The source 'IQueryable' doesn't implement 'IAsyncEnumerable<xxTheDbModel>'. Only sources that implement 'IAsyncEnumerable' can be used for Entity Framework asynchronous operations.
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.AsAsyncEnumerable[TSource](IQueryable`1 source)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at 
....
```


Based on the README.md the package **Remote.Linq.Async.Queryable** should do the job, however it doesn't make any changes (or does it need any setup?)

Many thanks

mleader1-cbs avatar Dec 20 '23 11:12 mleader1-cbs

Package Remote.Linq.Async.Queryable is required to integrate with Ix.NET / System.Linq.Async.Queryable which may be or may not be required in your case.

The error message provided indicates, the query is tried to be executed as async stream (IAsyncEnumerable<>). To perform streaming end-to-end, the client side queryable may be created using RemoteQueryable.Factory.CreateAsyncStreamQueryable<T>() method. However, to actually enable result streaming, both client (xxxDataClient) as well as server (LinqQueryController) would need to be adopted.

A simpler way to start with, would be to execute the query asynchronously, yet not as async stream.

In either case, I suggest having a look at the various samples, demonstrating the options available:

  • 02_RemoteQueryable_Async async execution style: foreach (var item in await query.ToListAsync())
  • 03_RemoteQueryable_AsyncStream async streaming style: await foreach (var item in query.AsAsyncEnumerable())
  • 04_RemoteQueryable_AsyncQueryable Creates System.Linq.IAsyncQueryable<T> that can be used to execute as async batch as well as async stream. style: await foreach (var item in query) as well as: foreach (var item in await query.ToListAsync())

Notes:

  1. Remote.Linq provides an AsAsyncEnumerable() extension method to allow consume any IQueryable<> as async stream.
  2. Only queryables of type System.Linq.IAsyncQueryable<T> and Remote.Linq.IAsyncRemoteStreamQueryable<T> are actually executed as async stream end-to-end.
  3. Any other IQueryable<> consumed as "AsAsyncEnumerable()" is executed as async batch query and then turned into an async stream locally.

6bee avatar Dec 20 '23 15:12 6bee

Hi @6bee thanks for the quick update - Yes I did look into the examples (they are indeed very comprehensive however hard to make it to compile so I only looked into the code files) tried to implement almost exactly the same however simply fails when using Async implementation.

I'll have to look into it further after Christmas, but thanks for the details, will update you once I pick this up back in the new year.

Cheers

mleader1-cbs avatar Dec 21 '23 08:12 mleader1-cbs

Thanks for your valuable feedback. I've modified the sample projects so they should all build now from a fresh cloned repo. What's more, I've added some further information to my first answer above. Hope this makes things clearer.

6bee avatar Dec 21 '23 11:12 6bee

"Only sources that implement 'IAsyncEnumerable' can be used for Entity Framework asynchronous operations."

Having a second look, the error message actually indicates an issue for executing the query with Entity Framework.

Which versions of Remote Linq and EF or EF Core are you using?

Note: There are samples for EF and EF Core respectively: 40_RemoteQueryable_ToEntityFramework 41_RemoteQueryable_ToEntityFrameworkCore

6bee avatar Dec 21 '23 11:12 6bee

hi @6bee thanks for the quick update - I'm using 7.1.0

	<PackageReference Include="Remote.Linq.EntityFrameworkCore" Version="7.1.0" />

(and yes I did have looked into these samples too... :D )

mleader1-cbs avatar Dec 21 '23 12:12 mleader1-cbs

I can create a clean sample code in the new year for a further test (i.e. simple DbModel shared cross EF Core and Client library, simple console app using the client library submitting request to server implementation)

As far as I understand the code I have implemented didn't have any major difference to your sample, but just in case I will test further by creating a demo project which can be shared with you if it fails again.

Thanks!

mleader1-cbs avatar Dec 21 '23 12:12 mleader1-cbs

Hi @6bee Happy new year. hope everyone had a great break.

Just a quick update on this ticket - After implementing a clean sample code - and realizing it works... I spotted that the original issue was caused by referencing both EntityFrameworkCore namespaces together with Remote.Linq, I believe it must be the extension methods for Linq Expression causing it.

After removing the reference of EntityFrameworkCore in the code:

using Microsoft.EntityFrameworkCore;

The "InvalidOperations" issue simply went away!


However new issue occurred on the server side, complaining the Db model "is not marked as serializable" from Aqua.Dynamic.DynamicObjectMapperException.

Type 'xxxDbModel' in Assembly 'xxx, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null' is not marked as serializable.

I believe this is something new that I may have not looked at previously, however if you don't mind shed some lights I'll be very thankful. :)

Many thanks. (This ticket can be considered resolved)

mleader1-cbs avatar Jan 10 '24 09:01 mleader1-cbs

Thanks for your feedback. Yes, this explains the error indeed: using EF Core extension method ToListAsync() [Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync<TSource>(this IQueryable<TSource> source)] cannot work on client side as this would want to translate the expression to a database query.

As for the DynamicObjectMapperException, I assume the error message originates from an inner exception? Would you have an exact stacktrace?

6bee avatar Jan 10 '24 21:01 6bee

Hey, facing the same issue here - i think it's fixable to have Remote.Linq work with the IAsyncQueryProvider / IAsyncEnumerable out of the box and having support for the EFCore Methods. I will write a short repro and fix on the weekend :-) I think it would also make this go away in the AsyncRemoteQueryProvider

    var task = ExecuteAsync<TResult>(expression, CancellationToken.None).AsTask();
            return task.Result;

inf9144 avatar Jan 15 '24 12:01 inf9144

Closing this issue due to inactivity. Feel free to re-open if required.

6bee avatar Apr 23 '24 07:04 6bee