SelectMany on ICollection property throws ArgumentException on EF Core 7
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!
@jbhelm I'm also able to reproduce this using .NET 6 and EF Core 6...
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.
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
...or even better, use AsEnumerable():
context.Roots.Select("Children.SelectMany(Grandchildren.AsEnumerable())").ToDynamicArray(); // <-- works