【问题标题】:Can't use LINQ in EF Where expression for lambda property无法在 EF Where 表达式中使用 LINQ 用于 lambda 属性
【发布时间】:2020-04-20 23:04:10
【问题描述】:

我像这样更改了 LINQ 表达式。

public async Task<IEnumerable<Thing>> Get(bool all)
{
  List<Thing> output = await Context.Things
    //.Where(_ => all || _.DeletedOn == null && _.Deletedon < DateTime.Now)
    .Where(_ => all || _.Active)
    .ToListAsync();
  return output;
}

显然,它导致了以下错误。

InvalidOperationException:无法翻译 LINQ 表达式“DbSet .Where(l => False || l.Active)”。以可翻译的形式重写查询,或通过插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用显式切换到客户端评估。

好吧,我确实调用了 AsListAsync(),但我有点困惑,因为 .NET Core EF 无法解释如此简单的条件。我怀疑我可能遗漏了其他东西。

Thing看起来像这样。

class Thing
{
  ...
  public DateTime? DeletedOn { get; set; }
  public Active => DeletedOn == null && DeletedOn < DateTime.Now.
}

检查provided link 什么也没给我。

【问题讨论】:

  • 你有一个逻辑问题,因为条件 Active => DeletedOn == null && DeletedOn
  • @lior 正确。但这是异常的原因吗?!
  • 这句话是什么意思。Where(_ =&gt; all
  • EF 在使用计算属性时有限制,因为很难找到它们背后的“坐”。它基本上尝试将属性名称(即 Active)映射到数据库列,这会失败并导致错误。简而言之,我认为您必须坚持第一个选项。
  • 怎么能比现在既空少呢?至于您的错误消息,linq 无法翻译您的 bool Active 属性,因为它不是表达式。

标签: c# .net-core .net-core-3.0


【解决方案1】:

正如其他人所提到的,表达式体成员getter实际上被视为方法调用,can't be parsed 由 Linq QueryProvider 转换为原生 SQL,因此您需要再次撤消“DRY”代码并展开Active 属性中的谓词逻辑直接进入您的 Linq 查询,或者您需要将整个表达式树或 IQueryable 传递给 Active 之类的方法,以便它可以将谓词组合到其中(这似乎有点矫枉过正)。

此外,根据 cmets,还有一个逻辑问题 - _.DeletedOn == null &amp;&amp; _.Deletedon &lt; DateTime.Now 将返回 false,这意味着根据 all 参数的值,查询是全有或全无。猜测一下,您正在寻找一个有条件应用的“逻辑删除”过滤器,它还允许将来删除日期,即任何尚未删除的记录都将被视为活动记录,或者任何具有空值的记录删除日期根本没有被删除。

最后,对于大多数 RDBMS,尤其是 SQL Server,将硬编码谓词(如 all)添加到 WHERE 过滤器中通常不是一个好主意,因为这会与 query plan caching 混淆。在您的情况下,all 标志过滤逻辑只是决定您是否要在逻辑上包含 Deleted 数据。 IMO,我会改为 conditionally compose the query 如下所示,以避免在 SQL 中进行不必要的过滤。

将所有这些放在一起,查询将是:

var query = Context.Things;
if (!all)
{
   query = query.Where(t => t.DeletedOn == null || t.Deletedon > DateTime.Now)
}
var output = await query
   .ToListAsync();
return output;

【讨论】:

  • 最后评论 - 按照惯例,OP 不应使用 _ 作为使用的 lambda 参数,因为它在许多 FP 语言中重新用于指示 discarded 参数。
  • 您应该将评论移动到您的回复中。我完全不知道这个约定(顺便说一句很棒的链接),并且由于缺乏想象力而使用下划线。下划线是新的黑色(即变量命名)。或者我是这么想的……
  • 是的。最不惊讶的原则 - 确保下一个人可以用最少的白发阅读你的代码。迄今为止我发现的最佳约定是 Sql 表别名 - 实体名称的第一个字母,因此 t 表示事物。那么......关于那个逻辑删除逻辑......?
  • 啊,你的意思是人们发现的“错误”?在实际代码中,它实际上是由多个条件组成的复杂得多的表达式(所有这些条件都与删除、创建和最新更改的日期有关。我在尝试排除故障时将其缩短,并错过了我引入了一个“错误”。那是什么你指的是?
  • 哦,我刚刚意识到 lambda 表达式中的 首字母约定 有两个奇怪的地方。我坐下来重构,适应惯例。然后我打了两个……嗯……不太理想的地方。在我的项目中,我有一个名为 Licence 的实体。当我去 .Where(l=>...) 时,它看起来很糟糕。这是第一件事。第二个是我还有一个名为Licensee的实体,嵌套了前者。所以在我的代码中我有 .Where(l1=>l1.Select(l2=>...))。您对如何以最少的白发方式处理它有什么想法?
【解决方案2】:

breaking changes in .Net core 3.0

一种解决方案(虽然不建议)可能如下:

如果无法完全翻译查询,则要么以可翻译的形式重写查询,要么使用 AsEnumerable()、ToList() 或类似方法将数据显式带回客户端使用 LINQ-to-Objects 进行进一步处理。

就像之前发生的那样,使用可翻译的查询获取 dbset,稍后使用您想要的任何 C# linq to Objects 魔法进行过滤。

未经测试,我相信 EF 无法翻译您的Expression bodied member。当您尝试以下操作时它是否有效?

   .Where(_ => all || _.DeletedOn == null && _.Deletedon < DateTime.Now) 

【讨论】:

  • .Where(_ =&gt; all) 的 SQL 是什么
  • @ShahidManzoorBhat 我猜1 = 1 如果all = true
  • @ShahidManzoorBhat 它会返回所有元素,无论它们是否被软删除。 SQL中:select *,即没有where条件。
  • 这是一个真正的陷阱。我理解它背后的原理。虽然,我更愿意让我仍然以可能昂贵的方式使用我的东西。在许多情况下,DRY 会受到影响,而不会或几乎没有性能增益。我不喜欢。但是为链接和现场信息 +1。
【解决方案3】:

问题出在公共字段 Active 中的表达式主体中。 EF 无法翻译此表达式Active =&gt; DeletedOn == null &amp;&amp; DeletedOn &lt; DateTime.Now

【讨论】:

    【解决方案4】:

    尝试在过滤记录之前插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用,即在这种情况下,在 Where 条件之前。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多