我有一些我想在不同地方使用的统计代码来计算时间表Results
的成功/失败百分比。我最近在代码中发现了一个错误,这是因为它在每个LINQ语句中被复制,然后我决定使用通用代码执行此操作会更好。当然,问题在于,在SQL服务器上执行时,正常函数会抛出NotSupportedException
,因为SQL Server中不存在这种功能。
如何编写可在SQL服务器上执行的可重用统计代码,或者这是不可能的?
以下是我为Result
public class Result
{
public double CalculateSuccessRatePercentage()
{
return this.ExecutedCount == 0 ? 100 : ((this.ExecutedCount - this.FailedCount) * 100.0 / this.ExecutedCount);
}
public double CalculateCoveragePercentage()
{
return this.PresentCount == 0 ? 0 : (this.ExecutedCount * 100.0 / this.PresentCount);
}
}
它的使用方式如此(结果为IQueryable
,并抛出异常):
schedule.SuccessRatePercentage = (int)Math.Ceiling(results.Average(r => r.CalculateSuccessRatePercentage()));
schedule.CoveragePercentage = (int)Math.Ceiling(results.Average(r => r.CalculateCoveragePercentage()));
或者像这样(有效,因为我们在单个结果上这样做)
retSchedule.SuccessRatePercentage = (byte)Math.Ceiling(result.CalculateSuccessRatePercentage());
retSchedule.CoveragePercentage = (byte)Math.Ceiling(result.CalculateCoveragePercentage());
修改
根据@ Fred的回答,我现在有以下代码,适用于IQueryable
schedule.SuccessRatePercentage = (int)Math.Ceiling(scheduleResults.Average(ScheduleResult.CalculateSuccessRatePercentageExpression()));
schedule.CoveragePercentage = (int)Math.Ceiling(scheduleResults.Average(ScheduleResult.CalculateCoveragePercentageExpression()));
唯一的问题,虽然是一个小问题,但这个代码不适用于个别结果,即
retSchedule.SuccessRatePercentage = (byte)Math.Ceiling(/* How do I use it here for result */);
答案 0 :(得分:4)
您无法将函数传递给SQL - 您需要在实际的SQL数据库上声明该函数,然后从您的代码中调用该函数。
你可以做/尝试的是:
Expression<Func<Result, double>> CalculateCoveragePercentage()
{
return r => r.PresentCount == 0 ? 0 : (r.ExecutedCount * 100.0 / r.PresentCount);
}
需要解释而不是执行,以便EF可以将其转换为SQL。问题是,当它直接传递到where
子句时,我只听说过这种情况。
由于您可以在LINQ查询中直接应用它们时进行这些计算,我倾向于认为应该可以将这些计算声明为Expression<Func<..., ...>>
并将它们传递给它们。
唯一可以确定的方法是尝试(除非你想看看EF的ExpressionBuilder)
<强>更新强>:
我应该提到,如果这样做,你需要将这个表达式传递给Select
语句:
// Assuming you have Results declared as a DbSet or IDbSet, such as:
DbSet<Result> Results
// You could do something like this (just to illustrate that
// it would be interpreted rather than executed):
List<double> allCoveragePercentages = Results.Select(CalculateCoveragePercentage)
.ToList();
更新#2 :
为了使其能够处理单个结果(或者无论如何),您需要将其传递给接受表达式的子句。示例包括Select
,Where
,Average
(显然),任何不都会返回结果。
从头顶(我确定我错过了一些):
列表:ToArray
,ToDictionary
,ToList
,ToLookup
单个结果:First
,FirstOrDefault
,Single
,SingleOrDefault
,Last
,LastOrDefault
计算:Count
,Sum
,Max
,Min
由于上述条款返回结果,因此(据我所知)它们只接受Predicates
(只能返回'true'或'false'的函数)
您可能巧合地使用了.Average(CalculateCoveragePercentage)
因此,如果您要使用.FirstOrDefault()
获得单个结果,则应在其前面的select子句中传递表达式:.Select(CalculateCoveragePercentage).FirstOrDefault()
。也就是说,如果您不需要实际实体而只需要计算。请注意,如果没有0
结果,此特定示例将返回Result
。您可能想要也可能不想要这种行为。
当然,如果你已经有了结果(它不再是IQueryable)那么你可以这么做:
var coveragePercentage = CalculateCoveragePercentage().Compile().Invoke(result);
但是这会破坏表达式的目的 - 对于这种情况,你应该只为你的Result
类添加一个计算给定实例的CoveragePercentage的方法。