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

SelectMany on ICollection property throws ArgumentException on EF Core 7 #701

Open
jbhelm opened this issue May 1, 2023 · 4 comments
Open
Assignees

Comments

@jbhelm
Copy link

jbhelm commented May 1, 2023

1. Description

When calling SelectMany() within a Dynamic LINQ expression for an ICollection<> property, an ArgumentException is thrown when executed on EF Core 7 LINQ to Entities. Note that it does work on LINQ to Objects, LINQ to Entities with EF 6 (classic), and on EF Core 7 if the properties are declared as IEnumerable<> instead of ICollection<>.

2. Exception

System.ArgumentException
  HResult=0x80070057
  Message=Expression of type 'System.Linq.Expressions.Expression`1[System.Func`2[DynamicLinqEfCoreExample.Program+Child,System.Collections.Generic.ICollection`1[DynamicLinqEfCoreExample.Program+Grandchild]]]' cannot be used for parameter of type 'System.Linq.Expressions.Expression`1[System.Func`2[DynamicLinqEfCoreExample.Program+Child,System.Collections.Generic.IEnumerable`1[DynamicLinqEfCoreExample.Program+Grandchild]]]' of method 'System.Linq.IQueryable`1[DynamicLinqEfCoreExample.Program+Grandchild] SelectMany[Child,Grandchild](System.Linq.IQueryable`1[DynamicLinqEfCoreExample.Program+Child], System.Linq.Expressions.Expression`1[System.Func`2[DynamicLinqEfCoreExample.Program+Child,System.Collections.Generic.IEnumerable`1[DynamicLinqEfCoreExample.Program+Grandchild]]])' (Parameter 'arg1')
  Source=System.Linq.Expressions
  StackTrace:
   at System.Dynamic.Utils.ExpressionUtils.ValidateOneArgument(MethodBase method, ExpressionType nodeKind, Expression arguments, ParameterInfo pi, String methodParamName, String argumentParamName, Int32 index)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, Expression arg0, Expression arg1)
   at System.Linq.Expressions.Expression.Call(Expression instance, MethodInfo method, IEnumerable`1 arguments)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.TryConvertEnumerableToQueryable(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   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 Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.VisitMethodCall(MethodCallExpression methodCallExpression)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryableMethodNormalizingExpressionVisitor.Normalize(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.NormalizeQueryableMethod(Expression expression)
   at Microsoft.EntityFrameworkCore.Query.QueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.InMemory.Query.Internal.InMemoryQueryTranslationPreprocessor.Process(Expression query)
   at Microsoft.EntityFrameworkCore.Query.QueryCompilationContext.CreateQueryExecutor[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Storage.Database.CompileQuery[TResult](Expression query, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.CompileQueryCore[TResult](IDatabase database, Expression query, IModel model, Boolean async)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.<>c__DisplayClass9_0`1.<Execute>b__0()
   at Microsoft.EntityFrameworkCore.Query.Internal.CompiledQueryCache.GetOrAddQuery[TResult](Object cacheKey, Func`1 compiler)
   at Microsoft.EntityFrameworkCore.Query.Internal.QueryCompiler.Execute[TResult](Expression query)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryProvider.Execute[TResult](Expression expression)
   at Microsoft.EntityFrameworkCore.Query.Internal.EntityQueryable`1.GetEnumerator()
   at System.Collections.Generic.LargeArrayBuilder`1.AddRange(IEnumerable`1 items)
   at System.Collections.Generic.EnumerableHelpers.ToArray[T](IEnumerable`1 source)
   at System.Linq.Dynamic.Core.DynamicEnumerableExtensions.CastToArray[T](IEnumerable source)
   at System.Linq.Dynamic.Core.DynamicEnumerableExtensions.ToDynamicArray(IEnumerable source)
   at DynamicLinqEfCoreExample.Program.Main(String[] args) in C:\Users\JoshuaHelm\dev\DynamicLinqEfCoreExample\Program.cs:line 40

3. Fiddle or Project

using Microsoft.EntityFrameworkCore;
using System.Linq.Dynamic.Core;

namespace DynamicLinqEfCoreExample
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var options = new DbContextOptionsBuilder<ExampleContext>().UseInMemoryDatabase(databaseName: "Example").Options;

            using (var context = new ExampleContext(options))
            {
                Root root = new()
                {
                    Id = 1,
                    Children = new Child[] {
                        new() {
                            Id = 2,
                            Grandchildren = new Grandchild[]
                            {
                                new()
                                {
                                    Id = 3,
                                }
                            }
                        }
                    }
                };
                context.Roots.Add(root);
                context.SaveChanges();

                // LINQ to Objects (works)
                var data = new[] { root }.AsQueryable();
                var a = data.Select(r => r.Children.SelectMany(c => c.Grandchildren)).ToArray();    // <-- works
                var b = data.Select("Children.SelectMany(Grandchildren)").ToDynamicArray();         // <-- works

                // LINQ to Entities (fails)
                var c = context.Roots.Select(r => r.Children.SelectMany(c => c.Grandchildren)).ToArray();    // <-- works
                var d = context.Roots.Select("Children.SelectMany(Grandchildren)").ToDynamicArray();         // <-- throws error (works if Children and Grandchildren properties are defined as IEnumerable instead of ICollection)
            }

        }


        public class Root
        {
            public int Id { get; set; }
            public ICollection<Child> Children { get; set; } = new HashSet<Child>();    // <-- dynamic LINQ query works if this and Child.Grandchildren are IEnumerable instead of ICollection
        }

        public class Child
        {
            public int Id { get; set; }
            public ICollection<Grandchild> Grandchildren { get; set; } = new HashSet<Grandchild>(); // <-- dynamic LINQ query works if this and Root.Children are IEnumerable instead of ICollection
        }

        public class Grandchild
        {
            public int Id { get; set; }
        }


        public class ExampleContext : DbContext
        {
            public ExampleContext() : base()
            {
            }

            public ExampleContext(DbContextOptions<ExampleContext> options)
                : base(options)
            {
            }

            public DbSet<Root> Roots { get; set; }

        }
    }
}

4. Any further technical details

  • System.Linq.Dynamic.Core 1.3.2
  • Microsoft.EntityFrameworkCore 7.0.5
  • .NET 7.0

Thanks!

@StefH StefH self-assigned this May 4, 2023
@StefH
Copy link
Collaborator

StefH commented May 5, 2023

@jbhelm
I'm also able to reproduce this using .NET 6 and EF Core 6...

@jbhelm
Copy link
Author

jbhelm commented May 5, 2023

Hi @StefH

I didn't try it with any earlier versions of EF Core. However, I can say it does work with "classic" EntityFramework 6.4.4 on .NET 7.

@jbhelm
Copy link
Author

jbhelm commented Aug 21, 2024

Just want to add to this that I've found, as a work around, that adding Select(it) to the SelectMany() expression works. For example change:

context.Roots.Select("Children.SelectMany(Grandchildren)").ToDynamicArray()  // <-- throws

to

context.Roots.Select("Children.SelectMany(Grandchildren.Select(it))").ToDynamicArray();  // <-- works

@jbhelm
Copy link
Author

jbhelm commented Aug 22, 2024

...or even better, use AsEnumerable():

context.Roots.Select("Children.SelectMany(Grandchildren.AsEnumerable())").ToDynamicArray();  // <-- works

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

2 participants