简体   繁体   中英

Parameterize WHERE Clause in Query

Environment:

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

Hi

Could I parameterize where clause in SQL Server?

In my scenario, once a WHERE clause String is input, application will concatenate it to other part of query and execute in SQL Server then return the result.

For example,

  • User inputs "[CookingTime] < 30 and [Cost] < 20"
  • Application creates query "select [RecipeID] from [Recipes] where [CookingTime] < 30 and [Cost] < 20" and executes in SQL Server.
  • Application returns result to user.

For security reason, I would like to make whole WHERE CLAUSE as parameter. But I have no idea how to achieve.

Thanks in advance.

This is how it can be done

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

The whole WHERE clause as parameter will be a victim of sql injection in any way. To prevent this you'd better to:

Setup proper permissions. So even in case of sql injected user can't access anything not granted. In this case sample of @Dhaval is better, because dymanic sql generation incapsulated in stored procedure requires less permissions to execute.

Check the statement for sql injection. The simplest way is to check for semicolons in order to avoid another statements in the batch. More complex and more precise way is to use t-sql DOM parser. For example:

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

You can create a dynamic query in sql server and pass the parameter from C#

Something like this

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# Code to execute the procedure

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

I guess the original question wanted to find out how to make it dynamically from user's input and then use proper sql parameter to do the query.

For the usage of sql parameter, normally what I do is to use a generic helper method, a quick example (not tested):

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

of course, this is only Reading, for Insert/Update, similar methods could be created too.

But the complicated part is how to make it dynamic with unknown number of conditions and the relationship between them. So a quick suggestion is use a delegated method or class to do the work. sample (not tested):

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

for the static function GetParamsFromInputString, it's just a sample. actually it could be very complicated depending on your needs.

for example, you might want to include the operator (whether it's >, < or <>,...).

and you might also want to include the conjunctions between the conditions, whether it's AND or OR.

Build delegated classes to do the job if it's very complicated.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM