When using LINQ with F#, queries that have nested lambda expressions don't seem to work
/// helper from http://www.fssnip.net/g0/title/QuickLinq-Helpers
/// this works fine for a SelectMany without a nested lambda
type IQueryable<'T> with
/// A version of Queryable.Select which does not require a type annotation on the argument when used from F#
member x.SelectQ(selector : Expression<Func<'T,'TResult>>) = System.Linq.Queryable.Select(x,selector)
context.Members
.SelectManyQ(fun m -> context.Roles.Where(fun r -> m.OrganizationId = r.OrganizationId).DefaultIfEmpty())
.ToListAsync()
Or with query {} syntax (note that this doesn't need the SelectManyQ):
let q = query {
for m in context.Members do
for r in context.Roles.Where(fun r -> m.OrganizationId = r.OrganizationId).DefaultIfEmpty() do
select (m, r.Id, r.Permission)
}
q.ToListAsync()
Stack Trace
Unable to cast object of type 'System.Linq.Expressions.MethodCallExpression1' to type 'System.Linq.Expressions.LambdaExpression'.
Stack Trace:
ExpressionExtensions.UnwrapLambdaFromQuote(Expression expression)
NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
MethodCallExpression.Accept(ExpressionVisitor visitor)
ExpressionVisitor.Visit(Expression node)
NavigationExpandingExpressionVisitor.ExpandNavigationsForSource(NavigationExpansionExpression source, Expression expression)
NavigationExpandingExpressionVisitor.ProcessSelectMany(NavigationExpansionExpression source, LambdaExpression collectionSelector, LambdaExpression resultSelector)
NavigationExpandingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
MethodCallExpression.Accept(ExpressionVisitor visitor)
ExpressionVisitor.Visit(Expression node)
NavigationExpandingExpressionVisitor.Expand(Expression query)
<5 more frames...>
CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
QueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
IAsyncEnumerable<T>.GetAsyncEnumerator(CancellationToken cancellationToken)
ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
F# query that does work:
context.Members
.SelectManyQ(fun m -> context.Roles.DefaultIfEmpty())
.ToListAsync()
The equivalent C# works as expected
public static IQueryable<MemberWithRoleQueryResult> MemberQuery(UsersContext context, IQueryable<MemberRecord> members)
=> from m in members
from maybeRole in context.Roles.Where(r => m.OrganizationId == r.OrganizationId).DefaultIfEmpty()
select new MemberWithRoleQueryResult(
m,
maybeRole == null ? null : (Guid?)maybeRole.Id,
maybeRole == null ? null : maybeRole.Permission,
);
I've tried using LinqKit but that doesn't seem to detect this case, and using its workarounds like Compile (possibly due to this issue) didn't help. I also tried various explicit expressions but these resulted in the same error (or a different one involving using Func<,> in IQueryable)
type Expr =
static member Quote(e:Expression<System.Func<'T, IEnumerable<'TResult>>>) = e
static member Quote'(e:Expression<System.Func<'T, 'TResult>>) = e
let exp = fun (pl: MemberRecord) -> Expr.Quote'(fun (r: RoleRecord) -> pl.OrganizationId = r.OrganizationId)
let expr = Expr.Quote(fun (pl: MemberRecord) -> context.Roles.Where(exp(pl).Compile()).DefaultIfEmpty())
context.Members
.SelectMany(expr)
.ToListAsync()
I suspect this is a bug with F#, possibly related to or duplicating this issue, though workarounds or fixes here would be appreciated