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

Nested collection queries: System.InvalidOperationException: Unable to resolve generated expression #94

Closed
benbristow opened this issue Nov 7, 2023 · 2 comments
Labels
bug Something isn't working

Comments

@benbristow
Copy link

benbristow commented Nov 7, 2023

Some very odd behaviour I've found recently whilst using this library on a project. Rather hard to explain.

I've put an example project together which when ran will trigger an exception just like in our project's code:

https://github.com/benbristow/entityframework-projectables-bug

The main affected code:

Console.WriteLine("test 1");
await context.Students
    .Select(s =>
        new
        {
            CommentCount = s.Comments.SearchComment("foo").Count(),
            CourseCount = s.Courses.SearchCourse("bar").Count(),
        })
    .ToListAsync();
Console.WriteLine("pass 1");

Console.WriteLine("test 2");
await context.Students
    .Select(s =>
        new
        {
            CommentCount = s.Comments.Search("foo").Count(),
            CourseCount = s.Courses.Search("bar").Count(),
        })
    .ToListAsync();
Console.WriteLine("pass 2");
System.InvalidOperationException: Unable to resolve generated expression for ProjectablesExample.Extensions.IHasNameExtensions.SearchComment.
   at EntityFrameworkCore.Projectables.Services.ProjectionExpressionResolver.FindGeneratedExpression(MemberInfo projectableMemberInfo)
   at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.TryGetReflectedExpression(MemberInfo memberInfo, LambdaExpression& reflectedExpression)
   at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitNew(NewExpression node)
   at System.Linq.Expressions.ExpressionVisitor.VisitLambda[T](Expression`1 node)
   at System.Linq.Expressions.ExpressionVisitor.VisitUnary(UnaryExpression node)
   at System.Dynamic.Utils.ExpressionVisitorUtils.VisitArguments(ExpressionVisitor visitor, IArgumentProvider nodes)
   at System.Linq.Expressions.ExpressionVisitor.VisitMethodCall(MethodCallExpression node)
   at EntityFrameworkCore.Projectables.Services.ProjectableExpressionReplacer.VisitMethodCall(MethodCallExpression node)
   at EntityFrameworkCore.Projectables.Infrastructure.Internal.CustomQueryCompiler.Expand(Expression expression)
   at EntityFrameworkCore.Projectables.Infrastructure.Internal.CustomQueryCompiler.ExecuteAsync[TResult](Expression query, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.ExecuteAsync[TResult](Expression expression, CancellationToken cancellationToken)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetAsyncEnumerator(CancellationToken cancellationToken)
   at System.Runtime.CompilerServices.ConfiguredCancelableAsyncEnumerable`1.GetAsyncEnumerator()
   at Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions.ToListAsync[TSource](IQueryable`1 source, CancellationToken cancellationToken)
   at Program.<Main>$(String[] args) in C:\Users\BenjaminBristow\dev\misc\ProjectablesExample\ProjectablesExample\Program.cs:line 29
   at Program.<Main>(String[] args)

Now if I remove the [Projectable] attribute from all of the extension methods in the below file, the program runs to completion with no issues (I don't think these examples actually need this library as EF seems to compile queries OK, but is different in our project where this library is required but to seems to be the same issue).

https://github.com/benbristow/entityframework-projectables-bug/blob/master/ProjectablesExample/Extensions/IHasNameExtensions.cs

If I leave the [Projectable] attribute on the uniquely named methods as below, the program also runs to completion with no issues, e.g.

    public static IEnumerable<Comment> Search(this IEnumerable<Comment> source, string search)
        => source.Where(s => s.Text == search);

    [Projectable]
    public static IEnumerable<Comment> SearchComment(this IEnumerable<Comment> source, string search)
        => source.Where(s => s.Text == search);

    public static IEnumerable<Course> Search(this IEnumerable<Course> source, string search)
        => source.Where(s => s.Name == search);

    [Projectable]
    public static IEnumerable<Course> SearchCourse(this IEnumerable<Course> source, string search)
        => source.Where(s => s.Name == search);

However, if I swap this around, it generates the same exception

    [Projectable]
    public static IEnumerable<Comment> Search(this IEnumerable<Comment> source, string search)
        => source.Where(s => s.Text == search);

    public static IEnumerable<Comment> SearchComment(this IEnumerable<Comment> source, string search)
        => source.Where(s => s.Text == search);

    [Projectable]
    public static IEnumerable<Course> Search(this IEnumerable<Course> source, string search)
        => source.Where(s => s.Name == search);

    public static IEnumerable<Course> SearchCourse(this IEnumerable<Course> source, string search)
        => source.Where(s => s.Name == search);

My hypothesis here is that I think there's an issue where the library isn't picking up the generic argument as being a different method and is getting confused.

Example run:
https://github.com/benbristow/entityframework-projectables-bug/actions/runs/6788027570/job/18452129581

@koenbeuk
Copy link
Owner

Thanks for reporting this and apologies for not coming back to this earlier!

The problem is due to method overloading. This is currently not supported by this library. Each method projected with a Projectable attribute with generate a companion expression in a new source file. The name of his generated source file is currently derived roughly as: {Namespace}_{ClassName}_{MethodName}. When overloading methods, these generated source files are no longer unique which will cause generation to fail in which case during runtime, we'll be unable to locate the generated expression.

We should ensure that the generated file names are unique. One way would be to include the line / column of actual method that we generate a projectable expression for as part of the file name.

Meanwhile the workaround would be to not use method overloading and give each method a unique name.

@koenbeuk koenbeuk added the bug Something isn't working label Nov 19, 2023
@koenbeuk
Copy link
Owner

koenbeuk commented Mar 8, 2024

This is tracked by #58

@koenbeuk koenbeuk closed this as completed Mar 8, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants