从用户获取 SQL 查询并 运行 它针对 SQL 服务器

Get SQL query from user and run it against SQL Server

我想用 C# 开发非常小的应用程序,它从用户那里获取一些 SQL 查询并针对指定的 SQL 服务器执行它。

SQL 服务器和数据库必须由用户指定,因此一切都可以改变。 我的问题是用户可以输入各种类型的 SQL 查询,并且它的每个国王都应该以自己的方式 运行。

例如

SELECT * FROM mytable

UPDATE mytable 
SET city = "NY" 
WHERE name = "tom"

不能以同样的方式执行。

我想我需要在我的代码中识别用户查询类型,有什么方法可以识别它或更好的方法来 运行 任何可能的查询吗?

(此答案假定您使用 System.Data.SqlClient 命名空间中可用的 类。)

最简单但并非万无一失的方法是检查第一个单词 - 如果它(不区分大小写)等于 SELECT,你可以使用 ExecuteReader() - 如果不是, 您可以使用 ExecuteNonQuery().

另一种选择就是ExecuteNonQuery()。如果您将 SELECT 语句作为 CommandText 传递,这将 return -1。检测到这一点后,您可以调用 ExecuteReader()。但是,其他查询如 BEGIN TRANSACTION 也会 return -1.

我会简单地给用户2个执行按钮,一个用于非查询,一个用于reader。
如果您信任用户编写 sql 语句,则可以信任用户知道要单击哪个按钮。

不尝试自己分析 sql 语句的原因很简单: 可以写成 exec stp_doesThisProcedureSelectsOrUpdates 之类的东西,所以你根本无法分辨...

您不需要在代码中创建不同的查询类型。 SqlCommand ExecuteReader 方法可用于所有 DMLDDL 语句。如果语句 return 没有结果,则 SqlDataReader Read 方法将为 return false。您还需要调用 SqlDataReader.NextResult 方法,直到它 return 对 return 多个语句的结果为假。

var reader = command.ExecuteReader();
do{
    while(reader.Read())
    {
        //process resultset here
    }
}while(reader.NextResult());

我会使用 Microsoft.SqlServer.TransactSql.ScriptDom 命名空间 (reference | download #1 - Nuget download #2 - Microsoft® SQL Server® 2014 Transact-SQL ScriptDom) 中的 TSqlPerser.ParseStatementList 方法:

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

namespace ConsoleApp1
{
    class Program
    {
        static void Main(string[] args)
        {
            TextReader rdr = new StreamReader(new MemoryStream(
                Encoding.UTF8.GetBytes(
                    @"/* comment */ 
                    WITH CteUpdate 
                    AS
                    (
                        SELECT * FROM Table1 
                        WHERE Col1 = 1
                    )
                    UPDATE CteUpdate SET city = ""NY"" WHERE name = ""tom""")
                ));
            TSql110Parser parser = new TSql110Parser(true);

            IList<ParseError> errors;

            StatementList stmtList = parser.ParseStatementList(rdr, out errors);
            // Process errors
            foreach(TSqlStatement stmt in stmtList.Statements)
            {
                Console.WriteLine("Statement type {0}", stmt.GetType());
                if (stmt is SelectStatement)
                {
                    //Process SELECT statment
                }
                else if (stmt is UpdateStatement)
                {
                    //Process UPDATE statment
                    UpdateStatement stmtUpdate = (UpdateStatement)stmt;
                    NamedTableReference tabRef = (NamedTableReference)stmtUpdate.UpdateSpecification.Target;
                    Console.Write(" > UPDATE statement > target object {0}", tabRef.SchemaObject.BaseIdentifier.Value);
                }
                else //Process other statments
                {
                    throw new NotImplementedException();
                }

            }
        }
    }

}

输出:

Statement type Microsoft.SqlServer.TransactSql.ScriptDom.UpdateStatement > UPDATE statement > target object CteUpdate