'Nested F# lambdas do not work in Linq Expressions
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
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|
