参数化查询中的WHERE子句

本文关键字:WHERE 子句 查询 参数 | 更新日期: 2023-09-27 18:29:46

环境:

  • C#
  • Visual Studio 2012
  • .NET Framework 3.5

我可以参数化SQL Server中的where子句吗?

在我的场景中,一旦输入WHERE子句String,应用程序就会将其连接到查询的其他部分,并在SQL Server中执行,然后返回结果。

例如,

  • 用户输入"[烹饪时间]<30和[成本]<20"
  • 应用程序创建查询"从[Recipes]中选择[RecipeID],其中[CookingTime]<30和[Cost]<20",并在SQL Server中执行
  • 应用程序将结果返回给用户

出于安全考虑,我想将整个WHERE子句作为参数。但我不知道如何实现。

提前谢谢。

参数化查询中的WHERE子句

就是这样做的

string commandText = "UPDATE Sales.Store SET Demographics = @demographics "
    + "WHERE CustomerID = @ID;";
using (SqlConnection connection = new SqlConnection(connectionString))
{
    SqlCommand command = new SqlCommand(commandText, connection);
    command.Parameters.Add("@ID", SqlDbType.Int);
    command.Parameters["@ID"].Value = customerID;
    // Use AddWithValue to assign Demographics. 
    // SQL Server will implicitly convert strings into XML.
    command.Parameters.AddWithValue("@demographics", demoXml);
    try
    {
        connection.Open();
        Int32 rowsAffected = command.ExecuteNonQuery();
        Console.WriteLine("RowsAffected: {0}", rowsAffected);
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
}

作为参数的整个WHERE子句无论如何都将成为sql注入的牺牲品。为了防止这种情况,你最好:

设置正确的权限。因此,即使在注入sql的情况下,用户也不能访问任何未经授权的内容。在这种情况下,@Dhaval的示例更好,因为封装在存储过程中的动态sql生成需要较少的执行权限。

检查sql注入的语句。最简单的方法是检查分号,以避免批处理中出现其他语句。更复杂、更精确的方法是使用t-sql DOM解析器。例如:

using Microsoft.SqlServer.TransactSql.ScriptDom;
TSql110Parser parser = new TSql110Parser(true);
IList<ParseError> errors = null;
var condition = "a > 100; delete from [Recipes]";
var script = parser.Parse(new StringReader("select [RecipeID] from [Recipes] where " + condition), out errors) as TSqlScript;
if (errors.Count > 0)
{
    throw new Exception(errors[0].Message);
}
foreach (var batch in script.Batches)
{
    if (batch.Statements.Count == 1)
    {
        var select = batch.Statements[0] as SelectStatement;
        if (select != null)
        {
            QuerySpecification query = select.QueryExpression as QuerySpecification;
            if (query.WhereClause is BooleanBinaryExpression)
            {
                ...
            }
        }
        else
        {
            throw new Exception("Select statement only allowed");
        }
    }
    else
    {
        throw new Exception("More than one statement detected");
    }
}

您可以在sql server中创建一个动态查询,并从C#传递参数

像这样的

Create Procedure usp_Test    
    @WhereCond Varchar(max)
AS
Bgein
    Set NoCount ON
    Declare @SQLQuery AS Varchar(max)
    Set @SQLQuery = 'Select * From tblEmployees where ' + @WhereCond
    Execute sp_Executesql @SQLQuery
End

C#执行程序的代码

DataSet ds = new DataSet();
    using(SqlConnection conn = new SqlConnection("ConnectionString"))
    {               
            SqlCommand sqlComm = new SqlCommand("usp_Test", conn);               
            sqlComm.Parameters.AddWithValue("@WhereCond", WhereCond);
            sqlComm.CommandType = CommandType.StoredProcedure;
            SqlDataAdapter da = new SqlDataAdapter();
            da.SelectCommand = sqlComm;
            da.Fill(ds);
     }

我想最初的问题是想知道如何根据用户的输入动态生成它,然后使用适当的sql参数进行查询。

对于sql参数的使用,通常我所做的是使用一个通用的helper方法,一个快速示例(未测试):

public static class SqlHelpers
{
    public static IEnumerable<T> ExecuteAdhocQuery<T>(SqlConnection con, string sql, CommandType cmdType, Func<SqlDataReader, T> converter, params SqlParameter[] args)
    {
        try
        {
            using (SqlCommand cmd = new SqlCommand(sql, con) { CommandType = cmdType })
            {
                cmd.Parameters.AddRange(args);
                if (con.State != ConnectionState.Open) { con.Open(); }
                var ret = new List<T>();
                using (SqlDataReader rdr = cmd.ExecuteReader())
                {
                    while (rdr.Read())
                    {
                        ret.Add(converter.Invoke(rdr));
                    }
                }
                return ret;
            }
        }
        catch (Exception e)
        {
            // log error?
            Console.WriteLine(e.Message);
            Console.WriteLine(e.StackTrace);
            throw e; // handle exception...
        }
    }
    public void Test()
    {
        using (SqlConnection con = new SqlConnection("connection string here"))
        {
            var data = ExecuteAdhocQuery(con,
                "SELECT ID, Name FROM tblMyTable WHERE ID = @Id and Status = @Status;",
                CommandType.Text, (x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
                new SqlParameter("@Id", SqlDbType.Int) { Value = 1 },
                new SqlParameter("@Status", SqlDbType.Bit) { Value = true });
            Console.WriteLine(data.Count());
        }
    }
}

当然,这只是Reading,对于Insert/Update,也可以创建类似的方法。

但复杂的部分是如何使其在未知数量的条件下具有动态性以及它们之间的关系。因此,一个快速的建议是使用委托的方法或类来完成这项工作。样品(未测试):

    public static Dictionary<string, SqlParameter> GetParamsFromInputString(string inputString)
    {
        var output = new Dictionary<string, SqlParameter>();
        // use Regex to translate the input string (something like "[CookingTime] < 30 and [Cost] < 20" ) into a key value pair
        // and then build sql parameter and return out
        // The key will be the database field while the corresponding value is the sql param with value
        return output;
    }
    public void TestWithInput(string condition)
    {
        var parameters = GetParamsFromInputString(condition);
        // first build up the sql query:
        var sql = "SELECT Id, Name from tblMyTable WHERE " + parameters.Select(m => string.Format("{0}={1}", m.Key, m.Value.ParameterName)).Aggregate((m,n) => m + " AND " + n);
        using (SqlConnection con = new SqlConnection("connection string here"))
        {
            var data = ExecuteAdhocQuery(con,
                sql,
                CommandType.Text,
                (x) => new { Id = x.GetInt32(0), Name = x.GetString(1) },
                parameters.Select(m => m.Value).ToArray());
        }
    }

对于静态函数GetParamsFromInputString,它只是一个示例。实际上,根据您的需要,它可能非常复杂。

例如,您可能希望包含运算符(无论是>、<还是<>,…)。

你可能还想包括条件之间的连词,无论是and还是or。

如果工作非常复杂,则构建委托类来完成该工作。