将Array <sqlparameter>匹配到SqlParser.Parser的输出?

时间:2019-01-30 21:42:19

标签: c# sql-server

背景

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

context.Database.SqlQuery<ReturnModel>(queryString, sqlParameters)

queryString是一个字符串,sqlParameters是一个SqlParameter对象的数组。

测试

当我们在应用程序中构建其他自动化测试时,我发现将SqlParser用作“离线”测试器会很有帮助。它不会捕获所有内容,但我可以:

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

并确保我们不会在参数化查询字符串中引入任何语法错误。

我还希望能够验证所生成的参数列表中没有缺少的参数;查询字符串中需要的参数,但未提供。我有什么办法可以将sqlParameters与parseResults中的某项匹配以执行此操作,或者利用Microsoft.SqlServer.Management库的其他功能?

1 个答案:

答案 0 :(得分:1)

SqlParser是带有访问者界面的经典解析器,用于处理结果。美中不足的是公开文档,它似乎与发行版不一致,并且通常缺少任何和所有示例,以至于几乎没有用。我假设这里使用的解析器是您可以在Microsoft.SqlServer.SqlParser NuGet package中找到的解析器。早期版本是作为独立安装分发的,它们使用的类型不同。

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

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));

以更多有益的断言来品尝美食。

令人沮丧的是,SqlScalarVariableRefExpression具有一个BoundVariable属性,该属性似乎能够将引用链接到它们的声明。不幸的是,使用此方法涉及使用BinderProvider,它从实际数据库中提取了完整的元数据集(因此它可以将标识符绑定到数据库对象)。如果您只想进行一些本地解析,那么有关此文档的文档太少了,我无法解码如何正确使用它。

此代码也不完整,因为它仅处理标量变量-留给读者增加对表变量的支持。