考虑以下具有2个可选变量的函数
public List<T> SelectSqlItems<T>(
string settingsgroup = null,
int? state = null)
{
SqlCommand selectCommand = null;
if (settingsgroup == null)
{
selectCommand = new SqlCommand(
"select * from ApplicationSettings ", con);
}
else
{
selectCommand = new SqlCommand(
string.Format(
"select * from ApplicationSettings where settingsgroup='{0}' ",
settingsgroup),
con);
}
if (state != null)
{
selectCommand.CommandText +=
!selectCommand
.CommandText
.ToLower()
.Contains("where")
? string.Format("where state={0}", state)
: string.Format("and state={0}", state);
}
//etc..
}
我有4种可能性:
settingsgroup==null && state==null
settingsgroup==null && state!=null
settingsgroup!=null && state==null
settingsgroup!=null && state!=null
从上面的每个案例中都必须生成不同的SQL命令。 C#中的内置功能可以帮助我在没有大量条件语句的情况下实现这些功能,如果您要编写上述内容,除了必须将函数重载4次之外,您将如何编写它?
答案 0 :(得分:10)
这是SQL中的常见问题,可以在查询本身中有效处理,从而允许事先创建查询,使用参数,并通过存储过程进行访问。
使用参数是一项重要建议,不应视为可选。 SQL参数将有助于防止SQL注入攻击。例如,假设某人使用以下参数值调用您的方法:
SelectSqlItems<T>("' OR settingsgroup <> '", null);
您的查询现在将成为:
select * from ApplicationSettings where settingsgroup='' OR settingsgroup<>''
这当然会返回表中的所有行,并可能泄露私人信息。但是,存在更糟糕的可能性,例如插入可能删除整个表的DELETE
子句,甚至删除整个数据库(尽管希望您的用户权限配置为至少可以防止这些最坏情况)。 / p>
为了防止这种情况发生,您可以将SelectSqlItems
方法重新设置为以下内容:
public List<T> SelectSqlItems<T>(
string settingsgroup = null,
int? state = null)
{
var cmdText = "..."; // See Query Below
var selectCommand = new SqlCommand(cmdText, con);
// Set the values of the parameters
selectCommand.Parameters.AddWithValue("@settingsgroup", settingsgroup);
selectCommand.Parameters.AddWithValue("@state", state);
// etc...
}
您的查询现在可以说明如下:
SELECT
*
FROM
ApplicationSettings
WHERE
((@settingsgroup IS NULL) OR (settingsgroup=@settingsgroup))
AND
((@state IS NULL) OR (state=@state))
如果参数值为null,则由OR
连接的条件语句的左侧将始终具有值TRUE
,因此将匹配所有行。但是,如果参数值不是NULL
,则条件的左侧将具有值FALSE
,并且将检查右侧。如果行的值与参数值匹配,则右侧将仅具有值TRUE
,因此仅返回与参数值匹配的行。可以根据需要使用尽可能多的参数重复此概念。
答案 1 :(得分:3)
为什么不切换到两个参数都为optional的SQL存储过程,并将传递给SelectSqlItems
的参数直接传递给它?
答案 2 :(得分:2)
如果您切换到像Entity Framework这样的ORM解决方案,您可以轻松地使用函数动态构建查询。
public List<T> SelectSqlItems<T>(string settingsgroup=null,int? state=null)
{
using(var context = new MyContext())
{
IQueyable<ApplicationSettings> query = context.ApplicationSettings;
if(settingsgroup != null)
query = query.Where(row => row.settingsgroup = settingsgroup);
if(state != null)
query = query.Where(row => row.state = state.Value)
Expression<Func<ApplicationSettings, T>> selectExpression = GetSelectExpression<T>();
return query.Select(selectExpression).ToList();
}
}
答案 3 :(得分:1)
这可能会奏效。如果未传入参数,它也不会强制执行null。如果您担心注入攻击,则应该查看使用Parameters。这不是向查询添加参数的安全方法。
string stateCompare = state.HasValue ? "state = " + state.Value : "";
string settingsgroupCompare = String.IsNullOrEmpty(settingsgroup) ? "IS NULL" : "= " + settingsgroup;
string whereCondition = !String.IsNullOrEmpty(stateCompare) || !String.IsNullOrEmpty(settingsgroupCompare)?"WHERE":"";
SqlCommand selectCommand = new SqlCommand(String.Format("select * from ApplicationSettings {0} {1} {2}",whereCondition, settingsgroupCompare, stateCompare);
答案 4 :(得分:1)
强制SQL注入警告:不要直接使用源自用户输入的字符串常量。参数化您的查询。
如果您坚持动态构建SQL语句(而不是使用.NET中提供的内置或开源ORM解决方案构建它),您可以使用假的{{来简化代码。条件,动态SQL构建器的常见技巧,或者将状态“编码”为数字,并在WHERE 1=1
中处理它们。
“技巧”解决方案就像这样开始:
switch
这看起来类似于你所拥有的,除了你不再需要检查现有字符串是否存在if (settingsgroup == null) {
selectCommand = new SqlCommand("select * from ApplicationSettings WHERE 1=1 ", con);
} else {
selectCommand = new SqlCommand(string.Format("select * from ApplicationSettings where settingsgroup='{0}' ", settingsgroup), con);
}
子句:它始终存在。您可以继续使用简化代码:
WHERE
另一种方法是在状态编号中明确地“编码”状态,并在if (state != null) {
selectCommand.CommandText += string.Format("and state={0}",state);
} ... // and so on
中使用它,如下所示:
switch
现在int conditionForm = 0;
if (settingsgroup != 0) conditionForm |= 1;
if (state != 0) conditionForm |= 2; // Use powers of two
变量可以包含范围为0..3(包括0和3)的四个值之一。您可以编写一个switch语句,分别处理每个条件:
conditionForm