LINQ表达式无法翻译。要么以可翻译的形式重写查询,要么切换到客户端评估。

32

我有一个C#应用程序(.NET Core 3.1),并且我编写了以下的LINQ表达式。

public ActionResult<bool> GetItems(string title)
{
     var items = _service.All.GetItems().OrderByDescending(o => o.Id).Where(w => w.Portal_Id == 1);

     if (!string.IsNullOrWhiteSpace(title))
     {
            var terms = title.Split(' ').ToList();
            items = items.Where(w => terms.Any(a => w.ItemName.Contains(a)));
     }
     // Some Other code
     return Ok();
}

每当执行这个表达式时,我都会收到以下错误。

The LINQ expression 'DbSet<PosItem>\r\n    .Where(p => !(p.IsDeleted))\r\n    
.OrderByDescending(p => p.CreatedAt)\r\n    .Where(p => p.Portal_Id == 1)\r\n    .Where(p => __terms_1\r\n      
.Any(a => p.ItemName.Contains(a)))' 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 either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync().
See https://go.microsoft.com/fwlink/?linkid=2101038 for more information." 

由于数据集过大,我无法添加ToList()并转换为客户端评估,请指导我如何解决此问题而不转换为客户端评估。

谢谢


1
根据此表格,某些函数调用可以转换为SQL。在您的特定情况下,无需任何编码,我认为您需要解决的问题是items.Where(w => terms.Any(a => w.ItemName.Contains(a)))。您正在测试客户端数组。 - Daniel Dearlove
2
很可能无法翻译 terms.Any()。您可以使用 ExpressionBuilder 和 for/foreach 创建一个 Expression,然后在 items.Where(your_new_expression) 中使用新创建的 Expression - Cleptus
1
这个答案提供了一个示例,展示如何在.NET中创建表达式生成器How to create an Expression builder in .NET - Cleptus
很可能它不起作用,因为包含方法的原因。 - Piotr Szuflicki
您提到数据很大,因此希望避免进行任何客户端处理。正如在多个地方指出的那样,使用Contains()将导致最终SQL查询中出现一个或多个LIKE '%item%'条件。反过来,这可能会在服务器上轻松执行表扫描。这可能是值得付出的代价,也可能不是。 - Daniel Dearlove
2个回答

30
问题在于你正在尝试在一个Any表达式中使用string.Contains,EF将无法将其降低到SQL。Cleptus非常准确,要为Where子句构建谓词,对术语进行OR比较。否则,你的代码应该会正常工作,而不是包含检查,而是进行相等性检查:

没有包含:Contains:(相等性检查而不是LIKE %name%

var terms = title.Split(' ').ToList();
items = items.Where(w => terms.Contains(w.ItemName)); // IN clause.

构建的表达式:

var terms = title.Split(' ').ToList();
Expression<Func<Item, bool>> predicate = (Item) => false;
foreach(var term in terms)
    predicate = predicate.Or(x => x.ItemName.Contains(term));

items = items.Where(predicate);

因此,对于标题中的每个术语,我们会在ItemName上使用LIKE %term%进行匹配。


感谢您的时间和建议。 我稍微修改了您的解决方案,并使用了 var predicate = PredicateBuilder.New<Item>(false); 然后它起作用了。 - Sheheryar Sajid

1

当在一个表达式中使用多个ParameterExpression时,也可能出现此问题。请始终将ParameterExpression实例传递给所有Expression.Property(argPara, "Name")值。


3
与问题无关:他们在这里不构建表达式。 - Gert Arnold
我不确定你的回答在你预期之外是否有太多意义。请考虑添加更多细节来修订你的回答。 - Michael Earls

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接