use*_*893 5 c# sql validation roslyn
如果我有一个像这样的 Dapper.NET 查询:
conn.Execute("insert into My_Table values ('blah', 'blah, 'blah', 'blah')");
Run Code Online (Sandbox Code Playgroud)
如何强制 Visual Studio 针对某个数据库架构对此查询进行编译时验证?我知道有一些库可以进行查询验证(提供字符串和连接),但是这里的工作正确的工具是什么?
扩展 Roslyn 以检查我标记为查询字符串的字符串(使用类似于熟悉的 @"Unescaped string" 的语法)?自定义预处理?
还是我问错了问题?将所有查询逻辑包装在我的数据库项目中的存储过程中会更安全吗(这可以让我验证查询本身)?现在我把它写下来,我想我实际上会采用那个解决方案,但我仍然对上面的内容感到好奇。我希望能够写:
conn.Execute(#"insert into My_Table values ('blah',
'blah, 'blah', 'blah')"); //Hashtag to mark as query
Run Code Online (Sandbox Code Playgroud)
并让编译器根据给定的数据库模式验证字符串。
一种选择是编写一个 Roslyn 分析器。分析器要做的就是找到对函数的所有调用Execute()
,如果它们的参数是常量字符串,则使用您提到的库进行验证。
在实现分析器时,您将遇到的一个问题是如何指定要验证的数据库模式。除非您想以某种方式在分析器中对其进行硬编码,否则似乎这样做的方法是使用“附加文件”(当前需要手动编辑您想要使用分析器的任何项目的 csproj)。
分析器可能如下所示(请注意,您可能需要修改代码以使其更加健壮):
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
=> ImmutableArray.Create(BadSqlRule, MissingOptionsFileRule);
public override void Initialize(AnalysisContext context)
{
context.RegisterCompilationStartAction(AnalyzeCompilationStart);
context.RegisterCompilationAction(AnalyzeCompilation);
context.RegisterSyntaxNodeAction(AnalyzeNode, SyntaxKind.InvocationExpression);
}
private Config config = null;
private void AnalyzeCompilationStart(CompilationStartAnalysisContext context)
{
var configFile = context.Options.AdditionalFiles
.SingleOrDefault(f => Path.GetFileName(f.Path) == "myconfig.json");
config = configFile == null
? null
: new Config(configFile.GetText(context.CancellationToken).ToString());
}
private void AnalyzeCompilation(CompilationAnalysisContext context)
{
if (config == null)
context.ReportDiagnostic(Diagnostic.Create(MissingOptionsFileRule, Location.None));
}
private void AnalyzeNode(SyntaxNodeAnalysisContext context)
{
if (config == null)
return;
var node = (InvocationExpressionSyntax) context.Node;
var symbol = (IMethodSymbol) context.SemanticModel.GetSymbolInfo(
node, context.CancellationToken).Symbol;
// TODO: properly check it's one of the methods we analyze
if (symbol.ToDisplayString().Contains(".Execute(string"))
{
var arguments = node.ArgumentList.Arguments;
if (arguments.Count == 0)
return;
var firstArgument = arguments.First().Expression;
if (!firstArgument.IsKind(SyntaxKind.StringLiteralExpression))
return;
var sqlString = (string)((LiteralExpressionSyntax) firstArgument).Token.Value;
if (Verify(config, sqlString))
return;
context.ReportDiagnostic(
Diagnostic.Create(BadSqlRule, firstArgument.GetLocation(), sqlString));
}
}
Run Code Online (Sandbox Code Playgroud)