【问题标题】:Is there a way to reference the same CTE inside a compiled query more than once in Linq2Db?有没有办法在 Linq2Db 中多次引用编译查询中的相同 CTE?
【发布时间】:2021-03-04 04:48:41
【问题描述】:

考虑以下 C# 代码:

CompiledQuery.Compile<IDataContext, int>((ctx, someId) =>
  ctx
    .GetTable<SomeTable>()
    .Where(x => x.SomeId == someId /* complex filtering here */)
    .AsCte("filtered")
    .Join(
      ctx.GetTable<AnotherTable>(),
      SqlJoinType.Left,
      (filtered, another) => filtered.Id == another.SomeId,
      (filtered, another) => new { filtered.Id, another.SomeInteger }
    )
    .GroupBy(x => x.Id, x => x.SomeInteger)
    .Select(x => new { x.Key, Sum = DataExtensions.AsNullable(x.Sum()) })
    .AsCte("grouped")
)

假设这部分查询产生了如下的SQL(使用PostgreSQL方言):

WITH filtered AS (
  SELECT "Id", "IntegerValue" FROM "SomeTable"
  WHERE "SomeId" = @some_id
), grouped AS (
  SELECT filtered."Id", SUM(another."SomeInteger") as "Sum"
  FROM filtered
  LEFT JOIN "AnotherTable" another
    ON filtered."Id" = another."SomeId"
  GROUP BY filtered."Id"
)

我想要的是继续这个查询以生成最终的 CTE,比如

SELECT filtered."Id" "FilteredId", grouped."Id" "GroupedId"
FROM grouped
INNER JOIN filtered /*LINQ problem here: "filtered" is not saved to a variable to reference it one more*/
  ON filtered."SomeInteger" = grouped."Sum" OR grouped."Sum" IS NULL

从上例中的评论可以看出,filtered 已经被使用后,似乎无法引用它。所以问题是:有没有办法在查询的最后部分(分组后)引用filtered

不包括第二个 CTE 用法(如窗口函数或子查询用法)的变通方法不在此问题的范围内。

由于Compile 方法接受表达式,因此适用 System.Linq.Expressions 限制:没有 ref/out/async/await/ValueTuple 等。不过,可以通过 F# 元组解决 ValueTuple 限制。

如果有办法可以帮助表达式树 AST 重写,则可以考虑(无论如何,我正在将嵌套的 lambdas 从 F# 表示转换为 linq2db 期望的表示)。

【问题讨论】:

    标签: c# .net-core f# abstract-syntax-tree linq2db


    【解决方案1】:

    linq2db 中的 CTE 功能使用表达式树比较来检测查询中的相同 CTE,因此您可以通过 ExpressionMethod 将 lambda 主体注入原始查询的帮助器重复此操作。

    private static Func<IQueryable<SomeTable>, int, IQueryable<SomeTable>> _complexFilterFunc;
    
    [ExpressionMethod(nameof(ComplexFilterImpl))]
    private static IQueryable<SomeTable> ComplexFilter(IQueryable<SomeTable> query, int someId)
    {
        _complexFilterFunc ??= ComplexFilterImpl().Compile();
        return _complexFilterFunc(query, someId);
    }
    
    private static Expression<Func<IQueryable<SomeTable>, int, IQueryable<SomeTable>>> ComplexFilterImpl()
    {
        return (query, someId) => query
            .Where(x => x.SomeId == someId /* complex filtering here */)
            .AsCte("filtered");
    }
    
    

    并在您编译的查询中使用此方法。如果输入参数相同,则只会产生一个 CTE。

    CompiledQuery.Compile<IDataContext, int>((ctx, someId) =>
      ComplexFilter(ctx.GetTable<SomeTable>(), someId)
        .Join(
          ctx.GetTable<AnotherTable>(),
          SqlJoinType.Left,
          (filtered, another) => filtered.Id == another.SomeId,
          (filtered, another) => new { filtered.Id, another.SomeInteger }
        )
        .GroupBy(x => x.Id, x => x.SomeInteger)
        .Select(x => new { x.Key, Sum = DataExtensions.AsNullable(x.Sum()) })
        .AsCte("grouped")
    )
    

    在常规查询中测试,然后您可以转到已编译的查询实现。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-09
      • 1970-01-01
      相关资源
      最近更新 更多