繁体   English   中英

在实体框架中使用三元条件运算符或表达式

[英]Using ternary conditional operator or expression in Entity Framework

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

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 Collection的循环中进行此操作(如果需要)

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();
    }
}

这样做是有效的,因为它仅在被告知时才进行求和,但是在主选择之外进行求和意味着整个查询现在所需时间是以前的两倍,因此显然不可行。

所以,我的问题是:

有谁知道是否有可能使Entity Framework仅运行将要使用的三元条件运算符的各个部分?

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

或者,如何将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:上面的简化版本没有给出null异常错误,但是完整版本(具有相同的代码)却...太奇怪了!

无论如何,在上面的工作缩减版本中,我尝试将其设置为true和fall,并且都花了相同的时间,因此无论哪种方式,Sum评估仍然

Linq将从两侧评估操作数,而不考虑三元运算符,因为它是在运行时进行评估的

您将必须在linq语句之外评估操作数,然后使用它。

例如:

var tst = from p in products join i in info on p.id equals i.pid

let status = p.type = "home" ? homestatus.Select(s=>s.status) :
             p.type = "offshore" ? offshorestatus.Select(s=>s.status) :
             p.type = "internal" ? internalestatus.Select(s=>s.status) : null
select new {
name = p.name,
status = status != null ? status.StatusText : string.Empty;
}

要么:

var tst = from p in products join i in info on p.id equals i.pid

let status = (p.type = "home" ? homestatus.Select(s=>s.status.StatusText) :
             p.type = "offshore" ? offshorestatus.Select(s=>s.status.StatusText) :
             p.type = "internal" ? internalestatus.Select(s=>s.status.StatusText) : null) ?? string.Empty
select new {
name = p.name,
status = status;
}

首先:@Barr的答案不正确。 不是问题在运行时进行评估(最后,根本不对Linq To Entities进行评估!),而是Linq2Entities的基础提供程序尝试执行的操作:

通过整个表达式树运行,并从中构建一些有效的SQL。 当然,找到与“ Invoke”等效的SQL。 好吧,它无法使用任何东西,因此它会LINQ to Entities does not recognize the method抛出LINQ to Entities does not recognize the method异常LINQ to Entities does not recognize the method

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

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

我可以给你两个建议:

  1. 看一下查询结果。 为此,安装SQL事件探查器(随SQL Server安装一起分发),在应用程序中进行调试,直到执行linq2entities语句为止。 希望您知道除非您在查询上调用ToList(),Any(),First()或其他任何内容,否则不会发生这种情况。 如果没有分析器,则还应该能够将整个linq查询存储在一个变量中(而无需调用toList()),并对其调用ToString()。 您还应该提供查询。

  2. 您是否考虑过检查查询的执行计划? 这听起来像表Transaction ClientId缺少索引。 也许您可以为我们提供SQL语句和/或执行计划,所以我们将能够为您提供更多帮助。

  3. 附加提示:检索查询后,可以在SQL Management Studio中执行它。 请让您显示实际的执行计划。 如果执行此操作,并且缺少某些索引,并且SQL Server检测到该丢失的索引,它将为您提供建议以加快查询速度。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM