【问题标题】:Code to validate SQL Scripts验证 SQL 脚本的代码
【发布时间】:2011-03-17 14:29:46
【问题描述】:

如何在使用 .net 2.0 和 c# 执行 sql 脚本之前验证它们?

如果 sql 无效,我想返回错误行。

【问题讨论】:

标签: c# .net sql sql-server


【解决方案1】:

如果您正在创建一个允许用户手动输入一些 sql 代码的工具,并且您想在 sql server 上执行之前验证使用 C# 代码输入的代码,您可以创建这样的方法:

using Microsoft.Data.Schema.ScriptDom;
using Microsoft.Data.Schema.ScriptDom.Sql;

public class SqlParser
{
        public List<string> Parse(string sql)
        {
            TSql100Parser parser = new TSql100Parser(false);
            IScriptFragment fragment;
            IList<ParseError> errors;
            fragment = parser.Parse(new StringReader(sql), out errors);
            if (errors != null && errors.Count > 0)
            {
                List<string> errorList = new List<string>();
                foreach (var error in errors)
                {
                    errorList.Add(error.Message);
                }
                return errorList;
            }
            return null;
        }
}

截至 2018 年和新的数据库版本,这可能是更新的版本:

using Microsoft.SqlServer.TransactSql.ScriptDom;

(使用 npm 下载:PM> Install-Package Microsoft.SqlServer.TransactSql.ScriptDom -Version 14.0.3811.1)

public bool IsSQLQueryValid(string sql, out List<string> errors)
{
    errors = new List<string>();
    TSql140Parser parser = new TSql140Parser(false);
    TSqlFragment fragment;
    IList<ParseError> parseErrors;

    using (TextReader reader = new StringReader(sql))
    {
        fragment = parser.Parse(reader, out parseErrors);
        if (parseErrors != null && parseErrors.Count > 0)
        {
            errors = parseErrors.Select(e => e.Message).ToList();
            return false;
        }
    }
    return true;
}

【讨论】:

【解决方案2】:

我知道这个问题是关于 .NET 2.0 的,但对某些人来说可能很有趣。在最新版本的 Microsoft SQL Server 中,查询的验证略有变化。 命名空间是 Microsoft.SqlServer.TransactSql.ScriptDom 而不是 Microsoft.Data.Schema.ScriptDom

在哪里可以找到这个库?

库的路径是%programfiles(x86)%\Microsoft SQL Server\120\SDK\Assemblies 如果找不到该库且安装了 Microsoft SQL Server,请尝试将120 更改为110100 并使用相应的解析器(分别为TSql110ParserTSql100Parser)。

如何使用?

我有两个扩展:第一个扩展检查输入字符串是否是有效的 SQL 查询,第二个可用于从解析中获取错误。

using Microsoft.SqlServer.TransactSql.ScriptDom;
using System.Collections.Generic;
using System.IO;
using System.Linq;

public static class SqlStringExtensions
{
    public static bool IsValidSql(this string str)
    {
        return !str.ValidateSql().Any();
    }

    public static IEnumerable<string> ValidateSql(this string str)
    {
        if (string.IsNullOrWhiteSpace(str))
        {
            return new[] { "SQL query should be non empty." };
        }
        var parser = new TSql120Parser(false);
        IList<ParseError> errors;
        using (var reader = new StringReader(str))
        {
            parser.Parse(reader, out errors);
        }
        return errors.Select(err => err.Message);
    }
}

另外,我检查输入 SQL 查询不为 null 或空,因为解析器认为空字符串是完全有效的(我不判断它)。

如何测试?

有三个 NUnit 测试展示了如何使用这个扩展。

using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;

[TestFixture]
public class SqlStringExtensionsTests
{
    [Test]
    public void ValidateSql_InvalidSql_ReturnsErrorMessages()
    {
        // this example doesn't contain "," between the field names
        string invalidSql = "SELECT /*comment*/ " +
            "CustomerID AS ID CustomerNumber FROM Customers";
        IEnumerable<string> results = invalidSql.ValidateSql();
        Assert.AreNotEqual(0, results.Count());
    }

    [Test]
    public void IsValidSql_ValidSql_ReturnsTrue()
    {
        string validSql = "SELECT /*comment*/ " +
            "CustomerID AS ID, CustomerNumber FROM Customers";
        bool result = validSql.IsValidSql();
        Assert.AreEqual(true, result);
    }

    [Test]
    public void IsValidSql_InvalidSql_ReturnsFalse()
    {
        // this example doesn't contain "," between the field names
        string invalidSql = "SELECT /*comment*/ "+
            " CustomerID AS ID CustomerNumber FROM Customers";
        bool result = invalidSql.IsValidSql();
        Assert.AreEqual(false, result);
    }
}

【讨论】:

  • 该文件夹中有三个 DLL。
  • @AndersLindén 你能详细描述一下这个问题吗?
  • 好吧,我刚刚选择了一个 DLL,它就可以工作了。然而,选择正确的不是一项火箭科学任务。
  • 正如 Onur 在他的评论中提到的,您也可以通过 nuget 在nuget.org/packages/Microsoft.SqlServer.TransactSql.ScriptDom 获得此信息
【解决方案3】:

SSMS 有办法做到这一点。

如果您使用 SQL Profiler,您将看到它执行 SET PARSEONLY ON,然后是 SQL,然后是 SET PARSEONLY OFF,并且在不编译或执行查询的情况下会出现任何错误。

SET PARSEONLY ON;
SELECT * FROM Table; --Query To Parse
SET PARSEONLY OFF; 

PARSEONLY

我从未在 c# 中尝试过这个,但我看不出它为什么不能工作,毕竟它可以在 SSMS 中工作。

正如 Martin Smith 在 cmets 中指出的那样,您可以使用 SET NOEXEC ON

MSDN 对这两个命令进行了以下说明。

当 SET NOEXEC 为 ON 时,SQL Server 编译每批 Transact-SQL 语句但不执行它们。 当 SET NOEXEC 为 OFF 时,所有批次 编译后执行。

当 SET PARSEONLY 为 ON 时,SQL Server 只解析语句。什么时候 SET PARSEONLY 为 OFF,SQL Server 编译并执行语句。

这表明NOEXEC 也将编译PARSEONLY 不会的查询。所以NOEXEC 可能会捕捉到PARSEONLY 没有捕捉到的错误。用法是一样的。

SET NOEXEC ON;
SELECT * FROM Table; --Query To Parse
SET NOEXEC OFF; 

NOEXEC

【讨论】:

  • 我认为NO EXEC 捕捉到了更多的东西。 stackoverflow.com/questions/3084387/…
  • 根据 Martin Smith 链接的帖子,您可能还想尝试 SET FMTONLY ON。这将识别丢失的表等。
【解决方案4】:

“有效”SQL 是什么意思?语法还是结果?

验证语法的唯一可靠方法是在 SQL Server 中执行 SQL。您是否考虑过在事务中运行 SQL,然后在最后进行回滚?

Begin Transaction

--execute your code between the 'Begin Transaction' and the 'rollback' keywords.
...

--example
Insert into mytable(ID)Values(2)

...

Rollback

MSDN Documentation 回滚

【讨论】:

  • 我该怎么做?能不能写个例子。谢谢。
  • @Tamifist:使用 TransactionScope 并且永远不要调用 Transaction.Complete。在 stackoverflow 上有很多 TransactionScope 的示例(但请记住,您必须先创建 TransactionScope,然后在 SqlConnection 内部创建。
猜你喜欢
  • 1970-01-01
  • 2011-06-08
  • 1970-01-01
  • 2015-11-13
  • 1970-01-01
  • 1970-01-01
  • 2014-04-21
  • 2013-10-24
  • 1970-01-01
相关资源
最近更新 更多