C# 是否将数组匹配到SqlParser.Parser的输出? 出身背景

C# 是否将数组匹配到SqlParser.Parser的输出? 出身背景,c#,sql-server,C#,Sql Server,作为面向数据库的应用程序的一部分,我们发现有必要构造参数化查询并将其传递到其他EntityFramework应用程序中,通常用于桥接多个客户端数据库。我们通过非常标准的语法来实现这一点: context.Database.SqlQuery<ReturnModel>(queryString, sqlParameters) 并确保我们没有在参数化查询字符串中引入任何语法错误 我还希望能够验证生成的参数列表中没有任何缺少的参数;查询字符串中需要参数,但未提供。是否有任何方法可以将sqlP

作为面向数据库的应用程序的一部分,我们发现有必要构造参数化查询并将其传递到其他EntityFramework应用程序中,通常用于桥接多个客户端数据库。我们通过非常标准的语法来实现这一点:

context.Database.SqlQuery<ReturnModel>(queryString, sqlParameters)
并确保我们没有在参数化查询字符串中引入任何语法错误

我还希望能够验证生成的参数列表中没有任何缺少的参数;查询字符串中需要参数,但未提供。是否有任何方法可以将sqlParameters与parseResults中的某些内容进行匹配,或者利用Microsoft.SqlServer.Management库的其他功能进行匹配?

SqlParser是一种经典的解析器,具有用于处理结果的访问者界面。唯一的美中不足之处是公开文档,它似乎与发布不同步,而且通常缺乏任何和所有示例,以至于几乎毫无用处。我假设这里使用的解析器就是您可以在。早期版本作为独立安装分发,它们不使用相同的类型

首先,假设一个简单的查询,我们可以通过访问所有变量声明和引用并消除对本地声明变量的引用(这些必须是参数)来获得所有参数的列表

class ParameterVisitor : SqlCodeObjectRecursiveVisitor {
    HashSet<string> referencedVariables = new HashSet<string>();
    public override void Visit(SqlScalarVariableRefExpression codeObject) {
        referencedVariables.Add(codeObject.VariableName);
    }

    HashSet<string> declaredVariables = new HashSet<string>();
    public override void Visit(SqlVariableDeclaration codeObject) {
        declaredVariables.Add(codeObject.Name);
    }

    public override void Visit(SqlBatch codeObject) {
        base.Visit(codeObject);
        parameters = referencedVariables.Except(declaredVariables).ToList();
    }

    List<string> parameters;
    public IEnumerable<string> Parameters => parameters;
}
用信息量更大的断言来调味

令人沮丧的是,SqlScalarVariableRefExpression有一个BoundVariable属性,它似乎能够将引用链接到它们的声明。不幸的是,使用它需要使用BinderProvider,它从实际数据库中提取一整套元数据,以便将标识符绑定到数据库对象。关于这方面的文档太少,我无法解释如果您只想进行一些本地解析,如何恰当地使用它


这段代码也不完整,因为它只处理标量变量-添加对表变量的支持留给读者作为练习。

这太小了,无法给出答案,但这里有一点效率低下:HashSet没有实现Except,just ExceptWith,所以我们得到了默认的可枚举项,除了实现,它包含自己的集合逻辑。这在实践中并不重要,因为您不太可能使用太多的参数,这会成为一个瓶颈,而且SQL Server只允许2100个参数。我很欣赏这种结构,这非常接近我想要的!但这次访问似乎遇到了一个SqlNullScalarExpression:从DataTable中选择Top1 Id,其中@ParamList0中的Id和FkId=a.FkId按Id排序,0,并且找不到任何子项进行深入研究。我可能必须重新构造that.codeObject.Tokens.Wheret=>t.Type==Tokens.Tokens\u VARIABLE.ToString.Select t=>t。父SqlBatch访问中的文本似乎提供了一个引用变量列表,而无需跳转到解析树中,尽管我意识到Tokens枚举被认为是不安全的。@Glazius:Yes,具体地说,COALESCE和NULLIF而非ISNULL中的子查询似乎没有进一步分解,或者至少在解析树中没有出现。对我来说这看起来像个虫子。使用其他一些函数(例如REVERSE)会生成一个SqlBuiltinScalarFunctionCallExpression,该函数确实有子函数,即使您使用错误数量的参数调用该函数,或者即使实际上没有此类函数(例如UNICORNSELECT…)。。!这就提出了各种解决办法;使用令牌列表可能与其他方法一样好。
class ParameterVisitor : SqlCodeObjectRecursiveVisitor {
    HashSet<string> referencedVariables = new HashSet<string>();
    public override void Visit(SqlScalarVariableRefExpression codeObject) {
        referencedVariables.Add(codeObject.VariableName);
    }

    HashSet<string> declaredVariables = new HashSet<string>();
    public override void Visit(SqlVariableDeclaration codeObject) {
        declaredVariables.Add(codeObject.Name);
    }

    public override void Visit(SqlBatch codeObject) {
        base.Visit(codeObject);
        parameters = referencedVariables.Except(declaredVariables).ToList();
    }

    List<string> parameters;
    public IEnumerable<string> Parameters => parameters;
}
internal static class ParseResultsExtensions {
    public static IEnumerable<string> GetParameters(this ParseResult p) {
        var pv = new ParameterVisitor();
        p.Script.Accept(pv);
        return pv.Parameters;
    }
}
string queryString = @"DECLARE @notAParameter INT; SELECT @c, @b, @a, @notAParameter";
var myParameterCollection = new[] {
    new SqlParameter("@a", SqlDbType.Int),
    new SqlParameter("@b", SqlDbType.Int),
    new SqlParameter("@c", SqlDbType.Int),
};

ParseResult parseResults = Parser.Parse(queryString);
Assert.That(parseResults.Errors, Is.Empty);

var expected = myParameterCollection.Select(p => p.ParameterName);
var actual = parseResults.GetParameters();
Assert.That(actual, Is.EquivalentTo(expected));