首页 > 解决方案 > 在实体框架中使用三元条件运算符或表达式

问题描述

我有一个继承的实体框架查询,其中包括几个总和,缩减示例:-

from c in db.Clients
where {where clauses}
select new
{
ClientID = c.ClientID,
ClientName = c.ClientName,
CurrentBalance = c.ClientTransactions.Select(ct => ct.Amount)
                .DefaultIfEmpty(0m).Sum(),
}).ToList();

随着客户端数量和事务数量的增长,这个查询显然变得越来越慢。

理想情况下,我想存储余额而不是每次都计算它们,但目前系统并没有这样做,而且实施起来将是一个非常大的变化,所以现在我只是尝试一个创可贴修复。

我试图实施的解决方法是不为对它们不感兴趣的人进行 Sum 计算(有几个,上面的例子只有一个)。

我的第一次尝试是简单地使用三元条件运算符来确定是否进行计算:-

from c in db.Clients
where {where clauses}
select new
{
ClientID = c.ClientID,
ClientName = c.ClientName,
CurrentBalance = ClientSearchExcludeCurrentBalance ? 0m : 
                 c.ClientTransactions.Select(ct => ct.fAmount).DefaultIfEmpty(0m).Sum(),
}).ToList();

事实证明,这样做的问题在于,无论条件 (ClientSearchExcludeCurrentBalance) 的值如何,双方仍然会计算,然后三元组决定使用哪一个。因此,即使将条件设置为 false,Sum 仍会得到处理,并且查询时间过长。

注释掉总和,如下...

from c in db.Clients
where {where clauses}
select new
{
ClientID = c.ClientID,
ClientName = c.ClientName,
CurrentBalance = ClientSearchExcludeCurrentBalance ? 0m : 0m,
                 //c.ClientTransactions.Select(ct => ct.fAmount).DefaultIfEmpty(0m).Sum(),
}).ToList();

...现在又好又快,所以即使不使用三元也肯定会运行它。

所以,有了这个想法,我尝试使用一个表达式来代替:-

Expression<Func<Client, Decimal>> currentBalance = c => 0m;
if (!ClientSearchExcludeCurrentBalance)
{
    currentBalance = c => c.ClientTransactions
                          .Select(ct => ct.Amount).DefaultIfEmpty(0m).Sum();
}

from c in db.Clients
where {where clauses}
select new
{
ClientID = c.ClientID,
ClientName = c.ClientName,
CurrentBalance = currentBalance.Invoke(c),
}).ToList();

这因未知的表达式错误而失败:-

LINQ to Entities does not recognize the method 'System.Decimal Invoke[Client,Decimal](System.Linq.Expressions.Expression`1[System.Func`2[RPM.DAO.UI.Client,System.Decimal]], RPM.DAO.UI.Client)' method, and this method cannot be translated into a store expression

我也尝试使用 Expand()

CurrentBalance = currentBalance.Expand().Invoke(c)

但仍然得到未知的表达式错误。

只是为了看看,我尝试将 Sum 值默认为 0m,然后在将结果分配给 DTO 集合的循环中进行求和,如果需要的话

foreach (var client in Clients) 
{
    if (!ClientSearchExcludeCurrentBalance) {
        var c = db.Clients.FirstOrDefault(cl => cl.ClientID == client.ClientID);
        client.CurrentBalance = c.ClientTransactions.Select(ct => ct.fAmount)
                                .DefaultIfEmpty(0m).Sum();
    }
}

这是可行的,因为它只在被告知时才进行求和,但在主选择之外进行意味着整个查询现在需要的时间是过去的两倍,因此显然不可行。

所以,我的问题是: -

有谁知道是否可以让实体框架只运行将要使用的三元条件运算符的部分?

有谁知道是否可以使用表达式在实体框架中返回值?

或者,或者,如何将 IF 语句添加到实体框架查询中?

对于(非工作)示例:-

from c in db.Clients
where {where clauses}
select new
{
ClientID = c.ClientID,
ClientName = c.ClientName,
CurrentBalance = if (ClientSearchExcludeCurrentBalance)
                     return 0m;
                 else 
                     return c.ClientTransactions.Select(tf => tf.fAmount)
                           .DefaultIfEmpty(0m).Sum(),
}).ToList();

谢谢!

编辑:

我尝试了 Barr J 的解决方案:-

from c in db.Clients
let currentBalance = ClientSearchExcludeCurrentBalance ? 0m : 
                     c.ClientTransactions.Select(ct => ct.Amount).DefaultIfEmpty(0m).Sum()
where {where clauses}
select new
{
ClientID = c.ClientID,
ClientName = c.ClientName,
CurrentBalance = currentBalance
}).ToList();

我得到一个空引用异常:

System.NullReferenceException: 'Object reference not set to an instance of an object.'

编辑#2:上面的精简版没有给出空异常错误,但完整版(具有相同的代码)确实......很奇怪!

无论如何,对于上面的工作缩减版本,我尝试将设置设置为 true 和 fall,并且两者都花费了相同的时间,所以它仍然以任何方式进行 Sum 评估

标签: c#entity-frameworkexpressionternary-operator

解决方案


首先:@Barr 的答案不正确。问题不是运行时的评估(最后,根本没有对 Linq To Entities 进行评估!),而是 Linq2Entities 的底层提供者试图做的事情:

运行整个表达式树并从中构建一些有效的 SQL。当然,还可以找到与“Invoke”等效的 SQL。好吧,它没有什么可以使用的,所以它抛出了异常LINQ to Entities does not recognize the method

您必须避免必须在运行时评估的此类 linq2entity 语句中的所有内容。例如,访问 DateTimeOffset.Now 也将不起作用。

目前我无法测试您的查询,所以我无法告诉您为什么三元运算符不能按预期工作。这可能取决于 SQL 的外观。

我可以给你两个建议:

  1. 看看查询结果。为此,请安装 SQL 探查器(随 SQL Server 安装分发),调试到您的应用程序,直到执行您的 linq2entities 语句。希望你知道这不会发生,除非你在查询中调用 ToList()、Any()、First() 或其他东西。如果您没有分析器,您还应该能够将整个 linq 查询存储在一个变量中(无需调用 toList())并对其调用 ToString()。这应该你也给出查询。

  2. 你有没有想过检查查询的执行计划?这听起来像是ClientIdtable上缺少索引Transaction。也许您可以向我们提供 SQL 语句和/或执行计划,以便我们为您提供更多帮助。

  3. 附加提示:检索查询后,您可以在 SQL Management Studio 中执行它。请让你展示真正的执行计划。如果您这样做并且缺少一些索引并且 SQL Server 检测到此缺少的索引,它将建议您可以采取哪些措施来加快查询速度。


推荐阅读