问题
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