【问题标题】:EF Core: The LINQ expression could not be translated for a nested List with Generic StartsWith() ExpressionEF Core:无法为具有通用 StartsWith() 表达式的嵌套列表翻译 LINQ 表达式
【发布时间】:2020-11-04 17:22:34
【问题描述】:

我正在尝试在名为“OtherListEntities”的嵌套列表中构建一种通用的“StartsWith”表达式。托管实体如下所示:

public class MyEntity
    {
        [System.ComponentModel.DataAnnotations.Key] // key just for the sake of having a key defined, not relevant for the question
        public string TopLevelString { get; set; }
        public IList<AnotherEntity> OtherListEntities { get; set; }
    }
    public class AnotherEntity
    {
        [System.ComponentModel.DataAnnotations.Key]  // key just for the sake of having a key defined, not relevant for the question
        public string NestedString { get; set; }
    }

我把它放在这样的上下文中:

public class MyDbContext : DbContext
{
    public DbSet<MyEntity> MyEntities { get; set; }
    public MyDbContext(DbContextOptions<MyDbContext> options) {}
    protected override void OnConfiguring(DbContextOptionsBuilder options) => options.UseSqlite("Data Source=sqlitedemo.db");
}

并尝试在测试类中使用此上下文:

public partial class MyTestClass
    {
        private List<MyEntity> testExampleList = new List<MyEntity>()
        {
            new MyEntity()
            {
                TopLevelString = "ABC",
                OtherListEntities = new List<AnotherEntity>()
                {
                    new AnotherEntity(){ NestedString = "ASD"},
                    new AnotherEntity(){ NestedString = "DEF"},
                    new AnotherEntity(){ NestedString = "GHI"},
                }
            },
            new MyEntity()
            {
                TopLevelString = "XYZ",
                OtherListEntities = new List<AnotherEntity>()
                {
                    new AnotherEntity(){ NestedString = "asd"},
                    new AnotherEntity(){ NestedString = "XXX"},
                    new AnotherEntity(){ NestedString = "FHJ"},
                }
            }
        };

        MyDbContext context;
        public MyTestClass()
        {
            var options = new DbContextOptions<MyDbContext>();
            this.context = new MyDbContext(options);

            context.Database.EnsureDeleted();
            context.Database.EnsureCreated();
            this.context.MyEntities.AddRange(testExampleList);
            this.context.SaveChanges();
        }
    }

这是我想做的普通 Where 过滤器:

 public partial class MyTestClass
    {
        [Fact]
        public void TestFilteringWithoutOwnExpression()
        {
            Assert.Equal(1, context.MyEntities.Where(x => x.TopLevelString.StartsWith("A")).Count()); // works fine
            Assert.Equal(1, context.MyEntities.Where(x => x.OtherListEntities.Where(e => e.NestedString.StartsWith("XXX")).Any()).Count());
        }
    }

由于在应用实际的 where 子句之前应该发生一些其他的魔法,我尝试将它包装成一个自己的表达式,如下所示:

public partial class MyTestClass
    {
        [Fact]
        public void TestFilteringWithExpression()
        {
            Assert.Equal(1, context.MyEntities.MyWhere<MyEntity>(x => x.TopLevelString, "A").Count()); // works
        }
        [Fact]
        public void TestFilteringWithExpressionAny()
        {
            Assert.Equal(1, context.MyEntities.Where(x => x.OtherListEntities.AsQueryable().MyAny(e => e.NestedString, "XXX")).Count()); // doesn't work, why?
        }
    }

在扩展类中定义了 MyWhere() 和 MyAny():

public static class IQueryableExtension
    {
        public static IQueryable<TEntity> MyWhere<TEntity>(this IQueryable<TEntity> query, Expression<Func<TEntity, string>> stringSelector, string searchString)
        {
            ParameterExpression entityParameter = stringSelector.Parameters.First(); //Expression.Parameter(typeof(TEntity), stringSelector.Parameters.First().Name);
            MemberExpression memberExpr = (MemberExpression)(stringSelector.Body);
            var searchConstant = Expression.Constant(searchString, typeof(string));

            var filterExpression = Expression.Lambda<Func<TEntity, bool>>(
                                             Expression.Call(
                                                memberExpr,
                                                typeof(string).GetMethod(nameof(string.StartsWith), new Type[] { typeof(string) }),
                                                searchConstant),
                                             entityParameter);
            query = query.Where(filterExpression);
            return query;
        }
        public static bool MyAny<TEntity>(this IQueryable<TEntity> query, Expression<Func<TEntity, string>> stringSelector, string searchString)
        {
            return query.MyWhere(stringSelector, searchString).Any();
        }
    }

代码因 InvalidOperationException 失败:

LINQ 表达式 'DbSet .Where(m => DbSet .Where(a => EF.Property(m, "TopLevelString") != null && EF.Property(m, "TopLevelString") == EF.Property(a, "MyEntityTopLevelString")) .MyAny( 查询:e => e.NestedString, stringSelector: "XXX"))' 无法翻译。要么以可翻译的形式重写查询,要么切换 通过插入对任何一个的调用来明确地进行客户评估 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync()。看 https://go.microsoft.com/fwlink/?linkid=2101038 了解更多信息。

如何在嵌套列表中设置通用且可翻译的 StartsWith 表达式?

【问题讨论】:

  • 您不能在 EF 核心查询中使用“自定义”方法,除非您知道 explained 如何将其转换为 SQL。或者您需要为Where 子句生成整个表达式。
  • @GuruStron 正如我在 unnittest 上所说的那样 MyWhere() 对 TopLevelString 工作正常,但嵌套的 List 属性需要有 .any() 才能将其项目与 searchString 进行比较!
  • 因为你没有在表达式内部使用它,所以EF不需要将MyWhere翻译成SQL,只需要filterExpression

标签: c# entity-framework linq .net-core sql-to-linq-conversion


【解决方案1】:

如果您只需要计算您的MyEntities 中的AnotherEntityXXX 开头,您可以简单地写:

Assert.Equal(1, context.MyEntities.SelectMany(x => x.OtherListEntities).Where(o => o.NestedString.StartsWith("XXX")).Count();

要了解为什么您的扩展方法不起作用,请参阅this answer

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-09-10
    • 2022-11-11
    • 1970-01-01
    • 2022-11-11
    • 2020-04-11
    • 1970-01-01
    • 2021-12-28
    相关资源
    最近更新 更多