与 Linq-to-Entities 一起使用的可重用函数

Reusable functions for use with Linq-to-Entities

我有一些统计代码,我想在不同的地方使用它来计算计划的成功/失败百分比 Results。我最近在代码中发现了一个错误,这是因为它在每个 LINQ 语句中都被复制了,然后我决定最好使用通用代码来执行此操作。当然,问题是在 SQL 服务器上执行时,正常函数会抛出 NotSupportedException,因为 SQL 服务器中不存在该函数。

如何编写在 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 */);

您不能将函数传递给 SQL - 您需要在实际的 SQL 数据库上声明该函数,然后从您的代码中调用它。

你可以 do/try 是这样的:

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:

为了使其适用于单个结果(或任何情况下),您需要将其传递到接受表达式的子句中。示例是 SelectWhereAverage(显然),任何 而不是 return 的结果。

从我的头顶开始(我确定我遗漏了一些):

  • 列表:ToArrayToDictionaryToListToLookup

  • 单个结果:FirstFirstOrDefaultSingleSingleOrDefaultLastLastOrDefault

  • 计算:CountSumMaxMin

由于上述条款 return 结果,他们(据我所知)只接受 Predicates (一个只能 return 'true' 或 'false')

您可能恰巧用 .Average(CalculateCoveragePercentage)

做对了

因此,如果您要使用 .FirstOrDefault() 获得单个结果,您将在 select 子句之前的 select 子句中传递表达式:.Select(CalculateCoveragePercentage).FirstOrDefault()。也就是说,如果您不需要实际实体而只需要计算。请注意,如果没有 Result 结果,此特定示例将 return 0。您可能想要也可能不想要这种行为。

当然,如果您已经有了结果(它不再是 IQueryable),那么您可以简单地执行以下操作:

var coveragePercentage = CalculateCoveragePercentage().Compile().Invoke(result);

但这会有点违背表达式的目的 - 对于这种情况,您应该在 Result class 中添加一个方法来计算给定实例的 CoveragePercentage。