EF Core 无法翻译表达式来比较两个集合,而 EF 6 可以

EF Core can't translate an expression to compare two collections which EF 6 could

我在旧 Entity Framework (.NET Framework) 中有以下查询:

db.ProductVariations
    .Where(pv => pv.Product.Categories
        .Any(cat => categorySearchStrings
            .Any(categorySearchString => cat.SearchTree.StartsWith(categorySearchString))));

我意识到这不太好,但我正在重构一个遗留应用程序,我们必须选择我们的战斗。

所以你可以传递一个搜索字符串列表(categorySearchStrings),例如:

"38.54.", "45."

这基本上是 search tree 的一个实现,其中我们数据库中的每个类别都有一个 SearchTree 属性。因此具有搜索树 38.54.99 的类别会匹配,但 38. 不会匹配。

一个产品可以有多个类别,我们可以将多个搜索树字符串传递给查询。所以我们正在比较两个集合。

这被翻译成

SELECT 
    [GroupBy1].[A1] AS [C1]
    FROM ( SELECT 
        COUNT(1) AS [A1]
        FROM [dbo].[ProductVariation] AS [Extent1]
        WHERE  EXISTS (SELECT 
            1 AS [C1]
            FROM ( SELECT 
                [Extent3].[SearchTree] AS [SearchTree]
                FROM  [dbo].[ProductCategory] AS [Extent2]
                INNER JOIN [dbo].[Category] AS [Extent3] ON [Extent2].[CategoryId] = [Extent3].[Id]
                WHERE [Extent1].[ProductId] = [Extent2].[ProductId]
            )  AS [Project1]
            WHERE  EXISTS (SELECT 
                1 AS [C1]
                FROM  ( SELECT 1 AS X ) AS [SingleRowTable1]
                WHERE ( CAST(CHARINDEX(N'38.', [Project1].[SearchTree]) AS int)) = 1
            )
        )
    )  AS [GroupBy1]

我正在尝试迁移到 Entity Framework Core(6,.NET 6 上的 运行),但现在出现以下错误:

System.InvalidOperationException : The LINQ expression 'categorySearchString => categorySearchString == "" || EntityShaperExpression: 
        Company.Data.Models.Category
        ValueBufferExpression: 
            ProjectionBindingExpression: Inner
        IsNullable: False
    .SearchTree != null && categorySearchString != null && EntityShaperExpression: 
        Company.Data.Models.Category
        ValueBufferExpression: 
            ProjectionBindingExpression: Inner
        IsNullable: False
    .SearchTree.StartsWith(categorySearchString)' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to 'AsEnumerable', 'AsAsyncEnumerable', 'ToList', or 'ToListAsync'. See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.

我认为切换到客户评估并不是一个真正的选择,因为要检索的数据太多了。另外,除了这个 Where 子句之外,还有更多的事情要做。我简化了。

我也试过改写成这样:

.Where(pv => pv.Product.Categories.Select(c => c.SearchTree).Any(st => categorySearchStrings.Any(ss => st.StartsWith(ss))));

但是我得到了同样的错误。

是否可以使用 EF Core 执行此操作?

我倾向于构建一个动态 expression tree 来表示过滤器:

var cat = Expression.Parameter(typeof(Category), "cat");
var parts = new List<Expression>(categorySearchStrings.Count);
var startsWithMethod = typeof(string).GetMethod(nameof(string.StartsWith), new[] { typeof(string) });

foreach (string categorySearchString in categorySearchStrings)
{
    var searchTree = Expression.Property(cat, nameof(Category.SearchTree));
    var value = Expression.Constant(categorySearchString);
    var startsWith = Expression.Call(searchTree, startsWithMethod, value);
    parts.Add(startsWith);
}

var body = parts.Aggregate(Expression.OrElse);
var categoryFilter = Expression.Lambda<Func<Category, bool>>(body, cat);

var pv = Expression.Parameter(typeof(ProductVariation), "pv");
var product = Expression.Property(pv, nameof(ProductVariation.Product));
var categories = Expression.Property(product, nameof(Product.Categories));
var any = Expression.Call(typeof(Enumerable), nameof(Enumerable.Any), new[] { typeof(Category) }, categories, categoryFilter);
var finalFilter = Expression.Lambda<Func<ProductVariation, bool>>(any, pv);

db.ProductVariations
    .Where(finalFilter)
    ...

您还应该将此报告为 an issue on the efcore repository,以查看它是否可以在未来的版本中修复。

更新:问题是 created but was a duplicate of an existing issue