Is it possible to query a user-specified column name without resorting to dynamic SQL?

不打扰是莪最后的温柔 提交于 2019-12-22 17:39:20

问题


I am trying to implement an API with the following signature:

public static List<string> SearchDatabase(
    string column,
    string value);

The implementation of the API needs to construct an SQL SELECT query with a WHERE clause that uses a column name specified in the column parameter.

The query:

string query =
    string.Format(
        "SELECT Name, @Column " +
        "FROM Db1 " +
        "INNER JOIN Db2 ON Db1.Id = Db2.Id " +
        "WHERE @Column = @Value");

The SQL command and parameters:

SqlCommand selectCmd =
    new SqlCommand(
        query,
        connection);
selectCmd.CommandTimeout = SqlCommandTimeout;
selectCmd.Parameters.AddRange(
    new SqlParameter[]
{
    new SqlParameter("@Column", column),
    new SqlParameter("@Value", value)
});

And I execute it like this:

SqlDataReader sqlDataReader = selectCmd.ExecuteReader();
while (sqlDataReader.Read())
{
    // ...
}

The problem is that sqlDataReader doesn't return any rows, so I don't go into the while loop above.

However, if I change the last line of the query above from:

"WHERE @Column = @Value");

to

"WHERE Vendor = @Value");

(i.e. hardcode the column name to 'Vendor') then it works.

My understanding from research I have done is that it's not possible to pass column names as parameters, but only values we are querying on. But, it does appear to let me use the @Column parameter in the SELECT clause, just not the WHERE clause.

I don't want to resort to dynamic SQL because of the issues with SQL injection. Is there another way around this?


回答1:


Unfortunately table names, column names cannot be parametrized. Because you know the structure of the table you could have a whitelist of possible column names in this parameter and then use string concatenation for it to avoid SQL injection:

"WHERE " + Sanitize(column) + " = @Value");



回答2:


There's nothing wrong with dynamic SQL as long as you escape the column name correctly:

string query = string.Format(
    "SELECT Name, {0} " +
    "FROM Db1 " +
    "INNER JOIN Db2 ON Db1.Id = Db2.Id " +
    "WHERE {0} = @Value",
    Delimit(column));

where...

public static string Delimit(string name) {

    if(name == null) {
        throw new ArgumentNullException("name");
    } else if(name.Length == 0) {
        throw new ArgumentException("name");
    }

    return "[" + name.Replace("]", "]]") + "]";

}



回答3:


You can keep parameters and implement the select as a case statement matching on different values for column name. You may need to cast values if the columns are of different types.

 Select case 
         when @column = 'UserName' then username
         when @column = 'Email' then email 
         Else firstname End as Column
 From MyTable
 Where value = @vendor

However, I don't see the point in this, just return all columns and pick the one you are interested in using the result set in C# (if it's feasible).




回答4:


No, you cannot parametrise column names like that, not in SQL Server.

To avoid building a dynamic query, you could use a CASE expression like this:

SELECT
  Name,
  CASE @Column
    WHEN 'Age' THEN Age
    WHEN 'Weight' THEN Weight
    …
  END
…

Note, however, that the various possible columns' types should be implicitly compatible with one another in this case (poor pun intended).




回答5:


Presumably the column name has already been validated? If not you can add extra validation by adding a select 1 from <column-names-table> where <column-name-column> = @column.

If your predicate is always = then you don't need to select @Column. The extra effort required in populating client side data structure is trivial.

EDIT: Instead of validating column name on each call, @Darin's comment to "2011-09-16 07:24:34Z" here of one time initialization is better.



来源:https://stackoverflow.com/questions/7441344/is-it-possible-to-query-a-user-specified-column-name-without-resorting-to-dynami

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!