Fsharp: Query builder with EF core can't return fsharp tuples (and sometimes new objects) using async methods

Created on 20 Oct 2017  路  5Comments  路  Source: dotnet/fsharp

I'm getting an ex褋eption when using this code

    [<CLIMutable>]
    type Product =
        {
            Id: string
            Name: string
        }

    let db = new MyContext()
    let q = query {
        for p in db.Products do
        select (p.Id, p.Name)
    }
    let result = q.ToListAsync().Result

If I choose select p or select p.Id insead of select (p.Id, p.Name) , this works fine. If I write ToList instead of ToListAsync this works fine as well. C# version with Tuples works well. With provided F# code it fails with this exception:

Unhandled Exception: System.InvalidOperationException: The source IQueryable doesn't implement IAsyncEnumerable<System.Tuple`2[System.String,System
.String]>. Only sources that implement IAsyncEnumerable can be used for Entity Framework asynchronous operations.
   at Microsoft.EntityFrameworkCore.Extensions.Internal.QueryableExtensions.AsAsyncEnumerable[TSource](IQueryable`1 source)
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToke
n)

Also noticed that select { Id = p.Id; Name = p.Name } works, while select { Name = p.Name; Id = p.Id } doesn't and fails with exception:

Unhandled Exception: System.AggregateException: One or more errors occurred. (When called from 'VisitLambda', rewriting a node of type 'System.Linq
.Expressions.ParameterExpression' must return a non-null value of the same type. Alternatively, override 'VisitLambda' and change it to not visit c
hildren of this type.) ---> System.InvalidOperationException: When called from 'VisitLambda', rewriting a node of type 'System.Linq.Expressions.Par
ameterExpression' must return a non-null value of the same type. Alternatively, override 'VisitLambda' and change it to not visit children of this
type.
   at System.Linq.Expressions.ExpressionVisitor.VisitAndConvert[T](ReadOnlyCollection`1 nodes, String callerName)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.ExpressionVisitorBase.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.Expression`1.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalProjectionExpressionVisitor.Visit(Expression expression)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalProjectionExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallE
xpression)
   at System.Linq.Expressions.MethodCallExpression.Accept(ExpressionVisitor visitor)
   at System.Linq.Expressions.ExpressionVisitor.Visit(Expression node)
   at Microsoft.EntityFrameworkCore.Query.ExpressionVisitors.RelationalProjectionExpressionVisitor.Visit(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitSelectClause(SelectClause selectClause, QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitSelectClause(SelectClause selectClause, QueryModel queryModel)
   at Remotion.Linq.Clauses.SelectClause.Accept(IQueryModelVisitor visitor, QueryModel queryModel)
   at Remotion.Linq.QueryModelVisitorBase.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.RelationalQueryModelVisitor.VisitQueryModel(QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.EntityQueryModelVisitor.CreateAsyncQueryExecutor[TResult](QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileAsyncQuery[TResult](QueryModel queryModel)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileAsyncQueryCore[TResult](Expression query, INodeTypeProvider nodeTypeProvide
r, IDatabase database)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass24_0`1.<CompileAsyncQuery>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQueryCore[TFunc](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddAsyncQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileAsyncQuery[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.ExecuteAsync[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.System.Collections.Generic.IAsyncEnumerable<TResult>.GetEnumerator()
   at System.Linq.AsyncEnumerable.<Aggregate_>d__6`3.MoveNext()
   --- End of inner exception stack trace ---
   at System.Threading.Tasks.Task`1.GetResultCore(Boolean waitCompletionNotification)
  • Windows 10
  • .NET Core 2.0.0
  • Microsoft.EntityFrameworkCore 2.0
  • FSharp.Core 4.2.3
  • FSharp.NET.Sdk 1.0.5
Area-Library Severity-Medium bug

Most helpful comment

The example is already in the main post right above

All 5 comments

Notes

  1. This works:

    let db = new MyContext()
    let q1 = query {
        for p in db.Products do
        select p.Id
    }
    let result = q1.ToListAsync().Result
    

    I.e. only the code with tuples (like select (p.Id, p.Name)) won't work.

  2. It seems it doesn't work because q1 (from my sample) has Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1[System.String] type (EF-friendly) while q (from @Lanayx sample) has System.Linq.EnumerableQuery`1[System.Tuple`2[System.String,System.String]] type (non EF-efiendly?).
  3. This also works:

    let db = new MyContext()
    let q1 = query {
        for p in db.Products do
        select (Tuple<string, string>(p.Id, p.Name))
    }
    let result = q1.ToListAsync().Result
    

There is another lingering bug in the treatment of tuples in query { ... } expressions e.g. #47, I assume this may be related

I assume using an intermediate record type will work.

There are two bugs here
1) The tuple doesn't work
2) The order of intermediate record fields matters, which shouldn't

@Lanayx could you please provide an example when the order matters? It'll be useful.

The example is already in the main post right above

Was this page helpful?
0 / 5 - 0 ratings