动态生成 Linq Select

3

我有一个数据库,用户可以在上面运行各种计算。这些计算涉及4个不同的列,但并不是每个计算都必须使用所有列,例如,计算1可能会转换为如下的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,它们似乎只对动态生成Where谓词有用,并在必要时将Sum(Column1)或Sum(Column2)作为字符串插入硬编码的SQL查询本身。
如何动态添加不同部分的Select查询到像这样的匿名类型中?还是我应该寻找一种完全不同的处理方式?

你可以将查询语句封装到它们自己的方法中,并有条件地调用这些方法,而不是动态创建查询语句。 - Jerreck
看一下 Dynamic LINQ: http://dynamiclinq.azurewebsites.net/。它允许在许多LINQ操作中使用动态子句,包括 Select - Nathan A
我稍微研究了一下动态 Linq。我更仔细地看了你的链接,但是放弃类型安全似乎只有在必要的情况下才会做出这样的选择。 - Alexander Burke
2个回答

0

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

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

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

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

  1. 动态

    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. 字典<string,int>


您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Alexander Burke
我认为可能有,但如果您能描述请求的方式将会很有帮助。您有样例吗? - David L
目前,当后端获取请求列表时,有一些适用于每个计算的参数,例如报告日期,在where子句中完成并像您演示的那样分组。然后有一个calculation names的list<string>和一个switch语句,用于获取所选计算的数据。问题是为了使这些数据在switch语句中可用,我需要让Linq已经计算出所有内容。我找不到一种方法来动态执行不同的Select语句,而不必诉诸于Dynamic Linq中的硬编码字符串。 - Alexander Burke
最终结果可能是,对于一个用户,我希望选择 Select(x => new { calculation1 = Sum(x.column1), calculation2 = Count(x = >x. column2 > 0)}),但对于另一个用户,我希望选择 Select(x => new { calculation3 = Sum(x.column2)})。这仅涉及14个不同的计算,用户可以选择运行从0到所有14个计算。 - Alexander Burke
@AlexanderBurke 看起来你需要一个更丰富的系统来选择用户如何选择查询以及如何构建这些查询。虽然这是可能的,但我怀疑在这个过程中你可能会失去很多强类型的特性,因为你可能需要构建一些非常动态的东西。话虽如此,基于你目前提供的信息,很难真正改进我的答案并使其对你更有用。 - David L

0

我通过使用一个巧妙的解决方法,解决了这个问题,并避免了使用动态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遍历表达式,并在相应的bool为false时删除计算。实现此ExpressionVisitor的代码由@StriplingWarrior在此问题Have EF Linq Select statement Select a constant or a function中提供。

将这两个解决方案结合使用仍无法创建以100%纯sql运行的sql。在测试中,无论测试集的大小如何,它都在10秒内接近纯sql,并且执行计划的主要部分相同。


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