You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

EF Core 7 自定义CTE扩展方法如何转换为SQL?

你已经完成了CTE扩展方法和自定义SQL表达式的基础定义,接下来需要通过EF Core的查询翻译体系,把自定义方法调用转换成对应的SQL表达式,最终生成合法的CTE语句,具体步骤如下:

1. 完善自定义SQL表达式(补全CTE别名支持)

你的CteSqlDefinitionExpression缺少CTE别名(生成合法CTE SQL的必要项),先修改该类:

namespace CTE.EntityFrameworkCore.Query.Expressions;

public class CteSqlDefinitionExpression : SqlExpression
{
    public string Alias { get; }
    public SqlExpression Definition { get; }
    public CteSqlDefinitionExpression? Parent { get; private set; }
    public CteSqlDefinitionExpression? Root
    {
        get
        {
            var root = Parent;
            while (root?.Parent != null)
            {
                root = root.Parent;
            }
            return root;
        }
    }

    public override bool CanReduce => false;

    // 修改构造函数,新增Alias参数
    public CteSqlDefinitionExpression(string alias, SqlExpression definition, RelationalTypeMapping? typeMapping, CteSqlDefinitionExpression? parent = null)
         : base(definition.Type, typeMapping)
    {
        Alias = alias;
        Definition = definition;
        Parent = parent;
    }

    public CteSqlExpression ToCteExpression()
    {
        List<CteSqlDefinitionExpression> definitions = new();
        var parent = this;

        while (parent != null)
        {
            definitions.Add(parent);
            parent = parent.Parent;
        }

        definitions.Reverse();
        return new CteSqlExpression(definitions, TypeMapping);
    }

    protected override Expression VisitChildren(ExpressionVisitor visitor)
    {
        var visitedParent = Parent == null ? null : (CteSqlDefinitionExpression)visitor.Visit(Parent);
        var visitedDefinition = (SqlExpression)visitor.Visit(Definition);
        if (ReferenceEquals(Parent, visitedParent) && ReferenceEquals(Definition, visitedDefinition))
            return this;
        return new CteSqlDefinitionExpression(Alias, visitedDefinition, TypeMapping, visitedParent);
    }

    // 完善Print方法,输出CTE别名与定义结构
    protected override void Print(ExpressionPrinter expressionPrinter)
    {
        expressionPrinter.Append(Alias);
        expressionPrinter.Append("\n AS \n(");
        expressionPrinter.Visit(Definition);
        expressionPrinter.Append(")");
    }

    public override bool Equals(object? obj)
        => base.Equals(obj) && obj is CteSqlDefinitionExpression node && Equals(node);

    private bool Equals([NotNull] CteSqlDefinitionExpression node)
        => Alias == node.Alias && Equals(Parent, node.Parent) && Definition.Equals(node.Definition);

    public override int GetHashCode()
    {
        var hash = new HashCode();
        hash.Add(base.GetHashCode());
        hash.Add(Alias);
        hash.Add(Parent == null ? 0 : Parent.GetHashCode());
        hash.Add(Definition.GetHashCode());
        return hash.ToHashCode();
    }
}

2. 实现自定义方法调用翻译器

创建CteMethodCallTranslator,将AsCte/ThenAs方法调用转换成自定义SQL表达式:

using System.Linq.Expressions;
using System.Reflection;
using CTE.Extensions;
using CTE.EntityFrameworkCore.Query.Expressions;
using Microsoft.EntityFrameworkCore.Query;
using Microsoft.EntityFrameworkCore.Query.SqlExpressions;

namespace CTE.EntityFrameworkCore.Query.Translation;

public class CteMethodCallTranslator : IMethodCallTranslator
{
    private readonly ISqlExpressionFactory _sqlExpressionFactory;
    private readonly MethodInfo _asCteMethod = CteQueryableExtensions.asCteMethod;
    private readonly MethodInfo _thenAsMethod = CteQueryableExtensions.thenAsMethod;

    public CteMethodCallTranslator(ISqlExpressionFactory sqlExpressionFactory)
    {
        _sqlExpressionFactory = sqlExpressionFactory;
    }

    public SqlExpression? Translate(SqlExpression? instance, MethodInfo method, IReadOnlyList<SqlExpression> arguments)
    {
        // 处理AsCte方法
        if (method.IsGenericMethod && method.GetGenericMethodDefinition() == _asCteMethod)
        {
            var definition = arguments[0];
            // 生成默认CTE别名,也可扩展为用户指定别名的重载
            var alias = $"CTE_{definition.Type.Name}";
            return new CteSqlDefinitionExpression(alias, definition, definition.TypeMapping);
        }

        // 处理ThenAs链式CTE方法
        if (method.IsGenericMethod && method.GetGenericMethodDefinition() == _thenAsMethod)
        {
            var source = arguments[0] as CteSqlDefinitionExpression;
            var definitionLambda = arguments[1] as LambdaExpression;
            if (source == null || definitionLambda == null) return null;

            // 替换Lambda参数,解析出CTE定义查询
            var parameter = definitionLambda.Parameters[0];
            var substituted = ReplacingExpressionVisitor.Replace(parameter, source, definitionLambda.Body);
            var definition = _sqlExpressionFactory.ApplyDefaultTypeMapping(substituted as SqlExpression);
            
            var alias = $"CTE_{definition.Type.Name}";
            return new CteSqlDefinitionExpression(alias, definition, definition.TypeMapping, source);
        }

        return null;
    }
}

3. 注册自定义翻译组件

创建自定义翻译器提供者,并注册到EF Core服务体系:

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Query;
using CTE.EntityFrameworkCore.Query.Translation;

public class YourDbContext : DbContext
{
    // 你的DbSet定义
    public DbSet<User> Users { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer("YourConnectionString") // 根据数据库类型调整,如UseNpgsql
                      .ReplaceService<IMethodCallTranslatorProvider, CteMethodCallTranslatorProvider>()
                      .ReplaceService<IQueryTranslationPostprocessor, CteQueryTranslationPostprocessor>();
    }
}

// 自定义翻译器提供者,添加CTE翻译器
public class CteMethodCallTranslatorProvider : RelationalMethodCallTranslatorProvider
{
    public CteMethodCallTranslatorProvider(RelationalMethodCallTranslatorProviderDependencies dependencies)
        : base(dependencies)
    {
        AddTranslator(new CteMethodCallTranslator(dependencies.SqlExpressionFactory));
    }
}

4. 实现查询后处理器,整合CTE到SQL头部

EF Core需要将CTE放在SELECT语句最前面,因此需要自定义后处理器调整查询结构:

using Microsoft.EntityFrameworkCore.Query;
using CTE.EntityFrameworkCore.Query.Expressions;

public class CteQueryTranslationPostprocessor : RelationalQueryTranslationPostprocessor
{
    public CteQueryTranslationPostprocessor(QueryTranslationPostprocessorDependencies dependencies, RelationalQueryTranslationPostprocessorDependencies relationalDependencies)
        : base(dependencies, relationalDependencies)
    {
    }

    public override Expression Process(Expression query)
    {
        query = base.Process(query);
        
        var visitor = new CteExpressionVisitor();
        var visitedQuery = visitor.Visit(query);
        
        // 如果找到CTE定义,将其包裹到查询最外层
        if (visitor.FoundCteDefinition != null)
        {
            var cteExpression = visitor.FoundCteDefinition.ToCteExpression();
            return new SelectExpression(
                cteExpression.TypeMapping,
                new[] { cteExpression },
                visitedQuery as SelectExpression);
        }
        
        return visitedQuery;
    }

    private class CteExpressionVisitor : ExpressionVisitor
    {
        public CteSqlDefinitionExpression? FoundCteDefinition { get; private set; }

        protected override Expression VisitExtension(Expression extensionExpression)
        {
            if (extensionExpression is CteSqlDefinitionExpression cteDef)
            {
                FoundCteDefinition = cteDef;
                return cteDef.Definition;
            }
            return base.VisitExtension(extensionExpression);
        }
    }
}

5. 测试CTE查询

现在可以直接使用你的扩展方法编写CTE查询:

var adultUsersCte = dbContext.Users
    .Where(u => u.Age > 18)
    .AsCte();

var filteredUsers = adultUsersCte
    .ThenAs(cte => cte.Where(u => u.Name.StartsWith("A")))
    .Select(u => new { u.Id, u.Name });

var results = filteredUsers.ToList();

生成的SQL会自动包含WITH CTE结构:

WITH CTE_User
AS
(
    SELECT [u].[Id], [u].[Name], [u].[Age]
    FROM [Users] AS [u]
    WHERE [u].[Age] > 18
),
CTE_User
AS
(
    SELECT [cte].[Id], [cte].[Name], [cte].[Age]
    FROM [CTE_User] AS [cte]
    WHERE [cte].[Name] LIKE N'A%'
)
SELECT [cte].[Id], [cte].[Name]
FROM [CTE_User] AS [cte]

内容的提问来源于stack exchange,提问作者Simbarashe Mukodzani

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 04:42:03