是否可以在不诉诸动态SQL的情况下查询用户指定的列名?

时间:2011-09-16 07:16:38

标签: c# sql sql-server-2005

我正在尝试使用以下签名实现API:

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

API的实现需要使用SELECT子句构造SQL WHERE查询,该子句使用column参数中指定的列名。

查询:

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

SQL命令和参数:

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

我按照这样执行:

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

问题是sqlDataReader没有返回任何行,所以我不会进入上面的while循环。

但是,如果我从上面更改上面查询的最后一行:

"WHERE @Column = @Value");

"WHERE Vendor = @Value");

(即将列名硬编码为'Vendor')然后它可以工作。

我从研究中得到的理解是,不可能将列名作为参数传递,而只传递我们要查询的值。但是,它似乎让我在@Column子句中使用SELECT参数,而不是WHERE子句。

由于SQL注入问题,我不想求助于动态SQL。还有另一种解决方法吗?

5 个答案:

答案 0 :(得分:7)

不幸的是,表名,列名不能参数化。因为您知道表的结构,所以可以在此参数中使用可能列名称的白名单,然后使用字符串连接来避免SQL注入:

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

答案 1 :(得分:1)

只要正确转义列名,动态SQL就没有问题:

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

其中...

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("]", "]]") + "]";

}

答案 2 :(得分:1)

您可以保留参数并将select实现为匹配列名的不同值的case语句。如果列的类型不同,则可能需要转换值。

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

但是,我没有看到这一点,只返回所有列并使用C#中的结果集选择您感兴趣的列(如果可行的话)。

答案 3 :(得分:0)

不,你不能像这样参数化列名,而不是SQL Server。

要避免构建动态查询,可以使用如下的CASE表达式:

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

但请注意,在这种情况下,各种可能的列类型应该隐式地相互兼容(预期的双关语很差)。

答案 4 :(得分:0)

据推测,列名已经过验证?如果没有,您可以通过添加select 1 from <column-names-table> where <column-name-column> = @column添加额外的验证。

如果您的谓词始终为=,那么您无需选择@Column。填充客户端数据结构所需的额外工作是微不足道的。

编辑:@ Darin对一次初始化的“2011-09-16 07:24:34Z”here的评论不是在每次调用时验证列名,而是更好。