有没有一种有效的方法在Linq to Entities中进行中间计算?

4

我有一个查询,它使用LinqToEntities在后台运行。

(...)
.GroupBy(x => x.FahrerID)
.Select(x => new FahrerligaEintrag()
{
    FahrerID = x.Key,
    FahrerFullName = string.Empty,
    VollgasKmAufHundertKm = (100m / x.Sum(y => y.BasisMeter)) * (x.Sum(y => y.Gas100ProzentInMeter.Value) + x.Sum(y => y.Gas90ProzentInMeter.Value)),
    LeerlaufInProzent = (100m / x.Sum(y => y.BasisSekunden)) * x.Sum(y => y.LeerlaufInSekunden.Value),
    VerbrauchLiterAufHundertKm = (100m / x.Sum(y => y.BasisMeter)) * x.Sum(y => y.VerbrauchInLiter.Value) * 1000,
    RollenKmAufHundertKm = (100m / x.Sum(y => y.BasisMeter)) * x.Sum(y => y.RollenInMeter.Value),
    TempomatKmAufHundertKm = (100m / x.Sum(y => y.BasisMeter)) * x.Sum(y => y.TempomatInMeter.Value),
    GeschwindigkeitsuebertretungenAnzahlAufHundertKm = (100m / x.Sum(y => y.BasisMeter)) * 1000 * x.Sum(y => y.UebertretungenAnzahl.Value),
    GangwechselAnzahlAufHundertKm = (100m / x.Sum(y => y.BasisMeter)) * 1000 * x.Sum(y => y.GangwechselAnzahl.Value)
});

正如您所看到的,这部分内容被重复多次(100m / x.Sum(y => y.BasisMeter))

在Linq to Objects中,首先将投影到匿名类以计算因子以避免重复计算是很自然的感觉。就像这样:

.GroupBy(x => x.FahrerID)
.Select(x => new
{
    Grouping = x,
    BasisMeterFaktor = 100m / x.Sum(y => y.BasisMeter),
    BasisSekundenFaktor = 100m /x.Sum(y => y.BasisSekunden)
})
.Select(x => new FahrerligaEintrag()
{
    FahrerID = x.Grouping.Key,
    FahrerFullName = string.Empty,
    VollgasKmAufHundertKm = x.BasisMeterFaktor * (x.Grouping.Sum(y => y.Gas100ProzentInMeter.Value) + x.Grouping.Sum(y => y.Gas90ProzentInMeter.Value)),
    LeerlaufInProzent = x.BasisSekundenFaktor * x.Grouping.Sum(y => y.LeerlaufInSekunden.Value),
    VerbrauchLiterAufHundertKm = x.BasisMeterFaktor * x.Grouping.Sum(y => y.VerbrauchInLiter.Value) * 1000,
    RollenKmAufHundertKm = x.BasisMeterFaktor * x.Grouping.Sum(y => y.RollenInMeter.Value),
    TempomatKmAufHundertKm = x.BasisMeterFaktor * x.Grouping.Sum(y => y.TempomatInMeter.Value),
    GeschwindigkeitsuebertretungenAnzahlAufHundertKm = x.BasisMeterFaktor * 1000 * x.Grouping.Sum(y => y.UebertretungenAnzahl.Value),
    GangwechselAnzahlAufHundertKm = x.BasisMeterFaktor * 1000 * x.Grouping.Sum(y => y.GangwechselAnzahl.Value)
});

然而,在LinqToEntities中这会导致性能较差的SQL代码。至少在我使用的这个Oracle后端中(我无法进行分析以实际显示SQL)。因此,我想知道是否还有其他方法可以避免重复计算,或者这是我能得到的最快的方法。

请原谅所有那些德国变量名。我相信您仍然可以理解其含义。

更新

我能够使用建议的ToTraceString()。有趣的是,在投影方面,SQL包含18(!!!)SELECT语句。如果没有它,它只包含2个。

3个回答

2
< p >在查询上执行的.ToTraceString()是否提供了可以进行性能分析的SQL查询?在所有这些计算中很容易迷失方向,但我相信,如果您想在单个查询中进行所有这些计算,则性能将会受到影响。减少计算重复的另一种方法是使用let关键字(它没有扩展方法,因此您必须使用“传统”的LINQ)。这使您可以分配一个变量,该变量可在查询中重复使用。但我怀疑它的性能是否比您的分组方法更好。< /p >
from f in Fahrer
let meterFaktor = 100m / x.Sum(y =>.BasisMeter)
select new FahrerLigaEintrag()
{
    ...
}

感谢您的回答。 "Let" 只是查询语法糖,会转换为 Select() 投影。编译器根本不知道查询语法。所有这些都归结为编译器级别上的扩展方法。但是,我能够像您建议的那样使用 ToTraceString()。我不知道,几个月前我尝试过它,但没有成功,现在可能由于底层 devart oracle 驱动程序的更新而可以使用了。SQL 看起来很糟糕,但我正在进一步调查它。不幸的是,我不能在这里发布它。 - Christoph
我更新了问题。事实证明,由于投影的存在,SQL变得更加复杂。不确定是否有另一种方法可以在一个查询中解决它。 - Christoph
@Christoph,我看不出去掉投影会将SELECT的数量减少到2个,因为您需要为每个.Sum()都提供一个。不妨将每个.Sum()调用放入投影中?我认为这样做并不能获得太多性能优化,但至少可以清晰地概述所有涉及的属性。我数了有10个.Sum(),这是很多的。 - J. Tihon
由于Sum()是SQL Group By运算符中的自然聚合方法,因此不需要在第一次执行SELECT时进行SELECT,因此它减少到了2个SELECT。但是,由于投影已经就位,他不再依赖于该方法,而是针对每个聚合使用一个子查询。 - Christoph
@Christoph,查询可能会更加“复杂”,但性能不就是问题吗?你比较过这两个查询了吗? - Dustin Davis
@DustinDavis 当然没问题,加入更复杂的查询后速度慢了40倍(!!!)。然而我意识到我应该为这个性能关键部分创建一个服务器端视图。 - Christoph

1

我想在这里添加一些内容,而不是直接在Twitter上发给Christoph。我认为要求LINQ进行翻译是很困难的。虽然它可以完成,但正如他所看到的那样,这并不美观,因为LINQ to Entities必须使用通用算法来处理您提供的任何内容。如果他有可能向数据库添加存储过程或视图,我建议他采用这种方法。

我还推荐(尽我所能在140个字符内)他查看EFProfiler(efprof.com)或LLBLGen的新分析器(llblgen.com),以便在Oracle中分析EF查询。


0

使用通用委托 Func<Tx, Ty, TResult> 怎么样?

// declare out of query and provide valid types for x, y, result
Func<TX, TY, TResult> basisMeterFaktorAlgo;

// set formula once
basisMeterFaktorAlgo = (x, y) => 100m / x.Sum(y => y.BasisMeter);

// call it in query
basicMeterFactor = basisMeterFaktorAlgo(xValue, yValue)

应该如何将其翻译成 SQL 呢?我感到困惑。 - Christoph
@Christoph:看起来我错过了什么,因为标题包含“Linq to Entities”(到对象?) - sll
整个查询都是基于IQueryable构建的。IQueryableProvider负责将表达式树转换为适当的SQL查询。因此,任何重构都必须导致一个查询,IQueryableProvider可以将其转换为适当的SQL。 - Christoph
在这种情况下,我不确定如何将其翻译成SQL,你能试试吗? - sll

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