有没有办法在 Linq2Db 中多次引用已编译查询中的相同 CTE?
Is there a way to reference the same CTE inside a compiled query more than once in Linq2Db?
考虑以下 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 用法(如 window 函数或子查询用法)的解决方法超出了这个问题的范围。
由于 Compile
方法接受表达式,因此适用 System.Linq.Expressions 限制:无 ref/out/async/await/ValueTuple 等。但可以通过 F# 元组解决 ValueTuple 限制。
表达式树 AST 重写,如果有办法让它有所帮助,可以考虑重写(我正在这样做,将嵌套的 lambda 从 F# 表示转换为 linq2db 期望的表示)。
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")
)
在常规查询中测试,然后您可以转到编译查询实现。
考虑以下 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 用法(如 window 函数或子查询用法)的解决方法超出了这个问题的范围。
由于 Compile
方法接受表达式,因此适用 System.Linq.Expressions 限制:无 ref/out/async/await/ValueTuple 等。但可以通过 F# 元组解决 ValueTuple 限制。
表达式树 AST 重写,如果有办法让它有所帮助,可以考虑重写(我正在这样做,将嵌套的 lambda 从 F# 表示转换为 linq2db 期望的表示)。
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")
)
在常规查询中测试,然后您可以转到编译查询实现。