【问题标题】:Dynamically generate Linq Select动态生成 Linq Select
【发布时间】:2015-11-10 13:06:29
【问题描述】:

我有一个数据库,用户可以在该数据库上运行各种计算。计算在 4 个不同的列上运行,每个计算不一定使用每一列,即,calculation1 可能会变成类似 sql

SELECT SUM(Column1) 
FROM TABLE 
WHERE Column1 is not null

计算 2 是

SELECT SUM(Column2)
WHERE Column2 is null

我正在尝试通过 linq 生成它,我可以通过每次计算所有内容来获取正确的数据,例如

table.Where(x => x.Column1 != null)
     .Where(x => x.Column2 == null)
     .GroupBy(x => x.Date)
     .Select(dateGroup => new
             {
               Calculation1 = dateGroup.Sum(x => x.Column1 != null),
               Calculation2 = dateGroup.Sum(x => x.Column2 == null)
             }

问题是我的数据集非常大,所以除非用户请求,否则我不想执行计算。我研究了动态生成 Linq 查询。到目前为止,我发现的只是 PredicateBuilder 和 DynamicSQL,它们似乎只对动态生成 W​​here 谓词有用,并将 sql 查询本身硬编码为字符串,并在必要时插入 Sum(Column1) 或 Sum(Column2)。

如何将 Select 查询的不同部分动态添加到这样的匿名类型中?或者我应该寻找一种完全不同的方式来处理这个问题

【问题讨论】:

  • 除了动态创建查询之外,您能否将它们封装到自己的方法中并有条件地调用这些方法?
  • 看看 Dynamic LINQdynamiclinq.azurewebsites.net。它允许在许多 LINQ 操作中使用动态子句,包括 Select
  • 我对 Dynamic Linq 进行了一些研究。我在那里查看了您的链接,但摆脱类型安全似乎是我只想在必须时做出的选择

标签: c# sql-server linq dynamic


【解决方案1】:

您可以在不执行查询的情况下返回查询,这将允许您动态选择要返回的内容。

也就是说,您不能在运行时动态修改匿名类型。它们在编译时是静态类型的。但是,您可以使用不同的返回对象来允许动态属性,而无需外部库。

var query = table
    .Where(x => x.Column1 != null)
    .Where(x => x.Column2 == null)
    .GroupBy(x => x.Date);

然后,您可以使用以下任一方法动态解决查询:

  1. dynamic

    dynamic returnObject = new ExpandoObject();
    
    if (includeOne)
        returnObject.Calculation1 = groupedQuery.Select (q => q.Sum(x => x.Column1));
    
    if (includeTwo)
        returnObject.Calculation2 = groupedQuery.Select (q => q.Sum (x => x.Column2));
    
  2. 具体类型

    var returnObject = new StronglyTypedObject();
    if (includeOne)
        returnObject.Calculation1 = groupedQuery.Select (q => q.Sum(x => x.BrandId));
    
  3. Dictionary<string, int>

【讨论】:

  • 我知道如果我可以分别进行每个计算,那将如何工作。问题是我必须进行不同的计算分组,直到用户请求进来我才知道。我曾考虑对所有可能的组合进行硬编码,但由于已经有 14 种可能的计算并且可能还有更多,这是不可行的.你的建议有办法解决吗?
  • 我认为可能有,但如果您能描述请求将如何进入将会很有帮助。您有样本吗?
  • 当前,当后端获取请求列表时,有一些参数适用于每个计算,例如报告日期,这些参数在 where 子句中完成并像您演示的那样分组。然后是计算名称的 list 和一个用于获取所选计算数据的 switch 语句。问题是要让这些数据在 switch 语句中可用,我需要让 Linq 已经计算了所有内容。如果不使用 Dynamic Linq 中的硬编码字符串,我无法找到一种方法来动态执行不同的 Select 语句
  • 最终结果是,对于一个用户,我可能希望 Select 为 Select(x => new { calculation1 = Sum(x.column1), calculation2 = Count(x = >x. column2 > 0)}),但对于另一个用户,我希望 Select 为 Select(x => new { calculation3 = Sum(x.column2)}),这仅适用于 14 种不同的计算和用户可以选择从 0 到所有 14 个计算来运行
  • @AlexanderBurke 听起来您需要一个更丰富的系统来选择用户如何选择查询以及如何构建这些查询。这是可能的,尽管我怀疑在此过程中你会失去相当多的强类型,因为你最终可能需要构建一些非常动态的东西。也就是说,根据您迄今为止提供的内容,很难真正改进我的答案并使其对您更有用。
【解决方案2】:

我解决了这个问题,并通过使用一种 hacky 解决方法使自己不必失去 Dynamic Linq 的类型安全性。我有一个包含布尔值的对象,这些布尔值对应于我想要执行的计算,例如

public class CalculationChecks
{
  bool doCalculation1 {get;set;}
  bool doCalculation2 {get;set;}
}

然后在我的选择中检查我是否应该进行计算或返回一个常数,像这样

Select(x => new 
{
  Calculation1 = doCalculation1 ? DoCalculation1(x) : 0,
  Calculation2 = doCalculation2 ? DoCalculation2(x) : 0
}

但是,这似乎是 linq to sql 或 ef 的边缘情况,这会导致生成的 sql 仍然执行 DoCalculation1() 和 DoCalculation2 中指定的计算,然后使用 case 语句来决定是否执行把数据还给我。它的运行速度要慢得多,在测试中是 40-60%,执行计划表明它使用的查询效率要低得多。

此问题的解决方案是使用 ExpressionVisitor 遍历表达式并在相应的布尔值为 false 时删除计算。 @StriplingWarrior 在这个问题Have EF Linq Select statement Select a constant or a function

上提供了显示如何实现此 ExpressionVisitor 的代码

同时使用这两种解决方案仍然无法创建以 100% 纯 sql 速度运行的 sql。在测试中,无论测试集大小,都在 10s 内,并且执行计划的主要部分是相同的

【讨论】:

    猜你喜欢
    • 2011-08-18
    • 1970-01-01
    • 2011-08-22
    • 2017-09-02
    • 1970-01-01
    • 1970-01-01
    • 2012-06-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多