简体   繁体   English

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

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

I have an entity framework query that I inherited which includes several Sums, cutdown example:- 我有一个继承的实体框架查询,其中包括几个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();

As the number of clients and the number of transactions has grown, this query has obviously become slower and slower. 随着客户数量和交易数量的增长,此查询的速度显然越来越慢。

Ideally I'd want to store balances rather than calculate them every time, but currently the system doesn't do that, and it would be a very large change to implement, so for now I'm just attempting a band-aid fix. 理想情况下,我想存储余额而不是每次都进行计算,但是当前系统不这样做,实现起来将是非常大的变化,因此,现在我只是尝试使用创可贴修复。

The fix I'm attempting to implement is to simply not do the Sum calculations (there are several, example above just has one) for people that aren't interested in them. 我尝试实现的解决方法是,对不感兴趣的人不做Sum计算(上面的例子中有几个)。

My first attempt was simply to use ternary conditional operators to determine whether or not to do the calculation:- 我的第一次尝试只是使用三元条件运算符来确定是否进行计算:

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

The problem with this, it turns out, is that regardless of the value of the condition (ClientSearchExcludeCurrentBalance) both sides are still calculated, and then the ternary decides which one to use. 事实证明,与此相关的问题是,无论条件(ClientSearchExcludeCurrentBalance)的值如何,都仍会计算双方,然后由三元决定使用哪一个。 So even setting the condition to false, the Sum still gets processed and the query takes too long. 因此,即使将条件设置为false,Sum仍将得到处理,查询花费的时间也太长。

Commenting out the sum, as below... 注释掉总和,如下所示...

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

... is now nice and fast, so the ternary is definitely running it even when it's not used. ...现在非常好且快速,因此即使不使用三元组,它也绝对可以运行它。

So, with that idea out the window, I tried using an expression instead:- 因此,有了这个想法,我尝试使用一个表达式代替:

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

This fell over with an unknown expression error:- 这陷入了未知的表达错误:-

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

I also tried using Expand() 我也尝试使用Expand()

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

but still got the unknown expression error. 但仍然出现未知的表达错误。

Just to see, I tried it with defaulting the Sum values to 0m, and then in the loop that assigns the results to the DTO Collection doing the sum there if needed 只是看到,我尝试将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();
    }
}

This works, in that it only does the sum if told to, but doing it outside the main select means the entire query now takes twice as long as it used to, so is clearly not viable. 这样做是有效的,因为它仅在被告知时才进行求和,但是在主选择之外进行求和意味着整个查询现在所需时间是以前的两倍,因此显然不可行。

So, my questions are:- 所以,我的问题是:

Does anyone know if it's possible to make Entity Framework only run the parts of a ternary conditional operator that will be used? 有谁知道是否有可能使Entity Framework仅运行将要使用的三元条件运算符的各个部分?

Does anyone know if it's possible to use an Expression to return a value in Entity Framework? 有人知道在实体框架中是否可以使用表达式返回值吗?

Or, alternatively, how to add an IF statement into an Entity Framework query? 或者,如何将IF语句添加到实体框架查询中?

For (non-working) example:- 对于(非工作)示例:-

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

Thanks! 谢谢!

Edit: 编辑:

I tried Barr J's solution:- 我尝试了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();

I get a null reference exception: 我得到一个空引用异常:

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

Edit #2: The cut-down version above doesn't give the null exception error, but the full version (with identical code) does... weird! 编辑#2:上面的简化版本没有给出null异常错误,但是完整版本(具有相同的代码)却...太奇怪了!

Anyway, with the working cut-down version above I tried it with the setting set to true and fall, and both took the same time, so it still does the Sum evaluation either way 无论如何,在上面的工作缩减版本中,我尝试将其设置为true和fall,并且都花了相同的时间,因此无论哪种方式,Sum评估仍然

Linq will evaluate the operand from both sides regardless of the ternary operator, because it is being evaluated at run-time . Linq将从两侧评估操作数,而不考虑三元运算符,因为它是在运行时进行评估的

You will have to evaluate the operands outside of your linq statement and then use it. 您将必须在linq语句之外评估操作数,然后使用它。

for example: 例如:

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

or: 要么:

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

first of all: @Barr 's answer ist not correct. 首先:@Barr的答案不正确。 Not the evaluation at runtime is the problem (in the end, it is not evaluated at all for Linq To Entities!), but what the underlying provider of Linq2Entities tries to do: 不是问题在运行时进行评估(最后,根本不对Linq To Entities进行评估!),而是Linq2Entities的基础提供程序尝试执行的操作:

Run though the whole expresion tree and build some valid SQL out of it. 通过整个表达式树运行,并从中构建一些有效的SQL。 And of course, Find a SQL equivalent of "Invoke". 当然,找到与“ Invoke”等效的SQL。 Well there is nothing it can use so it throws the exception LINQ to Entities does not recognize the method 好吧,它无法使用任何东西,因此它会LINQ to Entities does not recognize the method抛出LINQ to Entities does not recognize the method异常LINQ to Entities does not recognize the method

You have to avoid everything within such linq2entity statements that MUST be evaluated at runtime. 您必须避免在linq2entity语句中必须在运行时进行评估的所有内容。 Eg access to DateTimeOffset.Now will also not work. 例如,访问DateTimeOffset.Now也不起作用。

Currently I am not able to test your query so I can not tell you why the ternary operator does not work as expected. 目前,我无法测试您的查询,因此无法告诉您为什么三元运算符无法按预期工作。 It may depend on how the SQL looks like. 这可能取决于SQL的外观。

I can give you two advices: 我可以给你两个建议:

  1. take a look at the query outcome. 看一下查询结果。 To do this, install SQL profiler (distributed with SQL Server installation), debug into your application until your linq2entities statement is executed. 为此,安装SQL事件探查器(随SQL Server安装一起分发),在应用程序中进行调试,直到执行linq2entities语句为止。 Hopefully you know that this will not happen unless you call ToList(), Any(), First() or something else onto the query. 希望您知道除非您在查询上调用ToList(),Any(),First()或其他任何内容,否则不会发生这种情况。 If you have no profiler, you should also be able to store the whole linq query in a variable (without calling toList()) and call ToString() onto it. 如果没有分析器,则还应该能够将整个linq查询存储在一个变量中(而无需调用toList()),并对其调用ToString()。 This should you also give the query. 您还应该提供查询。

  2. Have you thought about checking the execution plan of the query? 您是否考虑过检查查询的执行计划? This sounds like a missing index on ClientId of the table Transaction . 这听起来像表Transaction ClientId缺少索引。 Maybe you can provide us with the SQL statement and/or the execution plan, so we will be able to help you more. 也许您可以为我们提供SQL语句和/或执行计划,所以我们将能够为您提供更多帮助。

  3. Additional hint: After retriving the query, you can execute it in SQL Management Studio. 附加提示:检索查询后,可以在SQL Management Studio中执行它。 Please let you show the real execution plan. 请让您显示实际的执行计划。 If you do this and there are some indices missing and SQL Server detects this missing index, it will suggest you what you can do to speed up the query. 如果执行此操作,并且缺少某些索引,并且SQL Server检测到该丢失的索引,它将为您提供建议以加快查询速度。

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

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