【问题标题】:Reusable functions for use with Linq-to-Entities与 Linq-to-Entities 一起使用的可重用函数
【发布时间】:2015-04-08 10:15:48
【问题描述】:

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

如何编写可在 SQL Server 上执行的可重用统计代码,或者这不可能?

这是我为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 */);

【问题讨论】:

  • 能否包含变量的声明,以便我们查看它们的数据类型?

标签: c# linq linq-to-sql linq-to-entities


【解决方案1】:

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

你可以做/尝试的是:

Expression<Func<Result, double>> CalculateCoveragePercentage()
{
    return r => r.PresentCount == 0 ? 0 : (r.ExecutedCount * 100.0 / r.PresentCount);
}

需要解释而不是执行,以便 EF 可以将其翻译成 SQL。问题是,我只听说当它直接传递到where 子句时才有可能。

因为当您直接在 LINQ 查询中应用这些计算时,您可以进行这些计算,所以我倾向于认为也应该可以将这些计算声明为 Expression&lt;Func&lt;..., ...&gt;&gt; 并将它们传递进去。

确定的唯一方法是尝试(除非您想查看 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(显然),任何都会返回结果。

从我的脑海中(我确定我错过了一些):

  • 列表:ToArrayToDictionaryToListToLookup

  • 单个结果:FirstFirstOrDefaultSingleSingleOrDefaultLastLastOrDefault

  • 计算:CountSumMaxMin

由于上述子句返回结果,它们(据我所知)只接受Predicates(一个只能返回“真”或“假”的函数)

您的.Average(CalculateCoveragePercentage) 可能碰巧弄对了

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

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

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

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

【讨论】:

  • 这行得通,谢谢,非常好的解决方案。虽然我无法让相同的代码为单个结果工作(有什么想法吗?),但至少现在所有代码都在一个地方
  • 能否将您尝试执行的代码添加到您的问题中?
  • 不确定您是否看到我的编辑。你用我提供的Select(CalculateCoveragePercentage) 语法试过了吗?
  • 嘿 Fred,它在没有编辑的情况下适用于 LINQ,它只是不适用于单个 (POCO) 结果。我用更多信息更新了我的问题
  • 抱歉,我搞砸了。它再次更新 - 'UPDATE #2' 下面的信息应该可以帮助您解决问题。
猜你喜欢
  • 1970-01-01
  • 2010-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多