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

F#泛型实现TSqlFragmentVisitor报FS3213错误的优雅方案

F# 封装 TSqlFragmentVisitor 简化访问器编写方案

报错原因

TSqlFragmentVisitor类的Visit不是参数为基类TSqlFragment的单虚方法,而是为每一种语法节点类型单独定义的重载方法。普通泛型版本即使加了基类约束,编译器也无法在泛型定义阶段确定要重写哪一个重载,因此会报匹配多个方法的错误。

实现方式

给泛型函数加inline关键字即可解决问题:内联函数会在每个调用点根据指定的具体节点类型生成专属实现,此时编译器可以明确匹配到对应类型的Visit重载完成重写,没有额外反射开销,和手写代码的性能完全一致。

open Microsoft.SqlServer.TransactSql.ScriptDom
open System.Collections.Generic

/// 生成指定TSql节点类型的访问器,返回收集结果的列表和访问器实例
let inline createVisitor<'T when 'T :> TSqlFragment> () =
    let collected = ResizeArray<'T>()
    let visitor = 
        { new TSqlFragmentVisitor() with
            member _.Visit(node: 'T) = collected.Add(node) }
    collected, visitor

使用时只需要指定要收集的节点类型即可,不需要重复写模板代码:

// 收集CREATE ASSEMBLY语句
let assemblyList, assemblyVisitor = createVisitor<CreateAssemblyStatement>()
// 收集EXECUTE语句
let executeList, executeVisitor = createVisitor<ExecuteStatement>()
// 收集SELECT语句
let selectList, selectVisitor = createVisitor<SelectStatement>()

如果要进一步简化解析流程,可以再封装一层直接提取节点的工具函数:

let inline parseAllNodes<'T when 'T :> TSqlFragment> (sqlText: string, sqlVersion: int = 160) =
    let parser = 
        match sqlVersion with
        | 160 -> TSql160Parser(false) :> TSqlParser
        | 150 -> TSql150Parser(false) :> TSqlParser
        | 140 -> TSql140Parser(false) :> TSqlParser
        | _ -> failwith "不支持的SQL Server版本"
    use reader = new System.IO.StringReader(sqlText)
    let mutable parseErrors = Unchecked.defaultof<_>
    let script = parser.Parse(reader, &parseErrors)
    if parseErrors.Count > 0 then 
        failwithf "SQL解析失败,错误数:%d" parseErrors.Count
    let nodes, visitor = createVisitor<'T>()
    script.Accept(visitor)
    nodes.ToArray()

调用示例:

let allCreateAssemblyStmts = parseAllNodes<CreateAssemblyStatement> "你的SQL脚本内容"

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 15:39:16