提问人:Dave 提问时间:11/3/2023 最后编辑:CharliefaceDave 更新时间:11/3/2023 访问量:76
在运行时,从 C 在原始 SQL 中添加列#
At runtime add columns in raw SQL from C#
问:
我有一个SQL查询,其中子句将在运行时决定。它可以是带有 和 运算符的列的任意组合。此数据库中的存储过程受到限制,因此将使用原始 SQL。WHERE
OR
AND
从 postman 获取 ris OData 可以是以下任何一种。
eq Equals /Employees?$filter=Name eq 'John'
ne Does not equal /Employees?$filter=Name ne 'John'
gt Greater than /Employees?$filter=Age gt 15
lt Less than /Employees?$filter=Age lt 15
ge Greater than or equal to /Employees?$filter=Age ge 15
and /Employees?$filter=Name eq 'John' and Age gt 65
or /Employees?$filter=Age gt 65 or Age lt 11
not /Employees?$filter=not(Name eq 'John')
然后,在构造和执行查询后,将使用 a 从数据库中获取数据。SqlDataReader
public ActionResult Get(Columns columns =null)
{
StringBuilder str = new StringBuilder();
str.Append("select firstNmae,lastname,gender,city from dbo.customer where ");
if (columns != null)
{
if (columns.firstName != null || columns.firstName != "")
{
str.Append(String.Format("firstname = '{0}' ", columns.Name));
}
if (columns.lastName != null || columns.lastName != "")
{
str.Append(String.Format("lastName = '{0}' ", columns.lastName));
}
}
}
我怎样才能将上述内容包含在此 SQL 中?
select firstName, lastname, gender, city
from dbo.customer
where + columns which comes at runtime 1 or more with or and conditions
答:
在构建这样的 SQL 时,你必须非常小心地去做。很容易意外地对 SQL 注入问题敞开心扉。
为了安全地做到这一点(这是非常重要的领域之一,即使在学习/实践/概念验证/私人工作中也是如此),您必须做两件事:
- 预先验证列名(和运算符)与数据库中的实际内容。为此,可以使用视图运行快速(安全)SQL 语句,或者直接在应用程序中包含允许的列集。然后,自行提供这些项的文本,而不是直接从用户输入中提供文本。
sys.columns
- 对筛选器数据使用查询参数而不是字符串连接。
这样,用户提供的文本就不会成为最终 SQL 语句的一部分。这很重要!
我在这里不会做的是谈论你如何解析条件。这不是一个代码编写服务,这里描述的语法很快就会超出你的预期,类似于一个功能齐全的SQL解析器。columns
但是一旦完成,你就需要得到这样的结果:
public ActionResult Get(Columns columns =null)
{
StringBuilder str = new StringBuilder("select firstNmae,lastname,gender,city from dbo.customer where 1=1 ");
var parameters = new List<SqlParameter>();
SqlParameter p;
// Assuming you already extracted your columns details
// from the input into separate variables used below
// (again: not a code writing service, you'll need to do this part)
if (NameFilter.Length > 0)
{
str.Append(" AND Name = @Name");
// use actual database types/lengths here
p = new SqlParameter("@Name", SqlDbType.NVarChar, 20);
p.Value = firstNameFilter;
parameters.Add(p);
}
if (AgeFilter.Length > 0)
{
//simplified
var opMap = new Dictionary<string, string>() {{"lt", "<"}, {"le","<="},{"eq","="},{"ne","<>"},{"gt",">"},{"ge",">="}};
var op = opMap[AgeOperator];
str.Append($" AND Age {op} @Age");
// use actual database types/lengths here
p = new SqlParameter("@Age", SqlDbType.Int);
p.Value = Convert.ToInt32(AgeFilter);
parameters.Add(p);
}
// ...
using var conn = new SqlConnection("connection string here");
using var cmd = new SqlCommand(str.ToString(), conn);
foreach(var parameter in parameters)
{
cmd.Parameters.Add(parameter);
}
conn.Open();
using var rdr = cmd.ExecuteReader();
// do something with your reader ...
}
请注意,在上面的代码中,从输入解析的文本仅通过对象的属性使用。SQL 命令中使用的所有其他文本都是从代码中的字符串文本中提取的。columns
SqlParameter
.Value
要重点介绍其中一个示例:
if (AgeFilter.Length > 0)
{
//simplified
var opMap = new Dictionary<string, string>() {{"lt", "<"}, {"le","<="},{"eq","="},{"ne","<>"},{"gt",">"},{"ge",">="}};
var op = opMap[AgeOperator];
str.Append($" AND Age {op} @Age");
// use actual database types/lengths here
p = new SqlParameter("@Age", SqlDbType.Int);
p.Value = Convert.ToInt32(AgeFilter);
parameters.Add(p);
}
在上面的代码中,在解析输入后,我们通过条件语句确定列上有一个过滤器。columns
Age
if()
在此块中,我们不仅接受用户提供的列名。相反,我们单独提供列名称。if()
同样,我们允许用户告诉我们使用哪个运算符,但我们不接受直接提供的内容。相反,我们通过字典将其转换为已知的良好值。opMap
最后,我们确实完全接受了用户给我们的实际价值......但仅通过参数将其包含在查询中,因此它仍然与 SQL 命令语句隔离。age
然后我们自己把它们放在一起,这样我们就可以控制结果的结构。
这是生成动态查询的唯一安全方法。
评论