Sql/Dapper: 如何在值数组的 WHERE 子句中执行 LIKE?

Sql/Dapper: How do perform LIKE in WHERE clause for array of values?

我有一个 table 里面有很多员工,每个人都有一个姓名栏,里面有他们的全名。

然后我想在搜索人的时候做一个类似这样的查询:

SELECT * FROM Employees WHERE Name LIKE '%' + @value1 + '%' AND Name LIKE '%' + @value2 +'%' AND so forth...

对于任意值数组。

我的 Dapper 代码看起来像这样:

public IEnumerable<Employee> Search(string[] words)
{
  using var connection = CreateConnection();
  connection.Query<Employee>("SELECT * etc.", words);
}

有没有什么方法可以在不求助于字符串连接的情况下使用 SQL 做到这一点,以及随之而来的 SQL 注入攻击的风险?

警告:我不知道 Dapper 实际上是如何将数组传递给查询的,这限制了我解决这个问题的创意:-D

而且:不幸的是,更改 Table 结构是不可能的。而且我宁愿避免将每个人都提取到 .Net 内存中并在那里进行过滤。

Is there ANY way to do this with SQL without resorting to string concatenation, and the risk of SQL Injection attacks that follows?

由于 where 条件的集合不固定,您需要动态构建查询。但这并不意味着您不能对查询进行参数化,您只需在构建查询的同时构建参数列表。每次将列表中的一个词添加到条件并添加一个参数。

由于 Dapper 不直接包含任何需要 DbParameter 集合的内容,请考虑使用 ADO.NET 获取 IDataReader,然后是 Dappter 的

IEnumerable<T> Parse<T>(this IDataReader reader)

用于映射。

这样的builder会很粗略

var n = 0;
for (criterion in cirteria) {
  var cond = $"{crition.column} like @p{n}";
  var p = new SqlPatameter($"@p{n}", $"%{crition.value}%";
  conditions.Add(cond);
  cmd.Parameters.Add(p);
}

var sql = "select whetever from table where " + String.Join(" and ", conditions);
cmd.CommandText = sql;
var reader  = await cmd.ExecuteReaderAsync();
var res = reader.Parse<TResult>();

出于性能原因,最好将其作为基于集合的操作执行。

您可以将数据table 作为Table 值参数传递,然后以LIKE 作为条件加入该数据。在这种情况下,您希望 所有 值匹配,因此您需要一点关系除法。

首先创建您的 table 类型:

CREATE TYPE dbo.StringList AS TABLE (str varchar(100) NOT NULL);

你的SQL如下:

SELECT *
FROM Employees e
WHERE NOT EXISTS (SELECT 1
    FROM @words w
    WHERE e.Name NOT LIKE '%' + w.str + '%' ESCAPE '/'   -- if you want to escape wildcards you need to add ESCAPE
);

然后你通过列表​​如下:

public IEnumerable<Employee> Search(string[] words)
{
  var table = new DataTable{ Columns = {
    {"str", typeof(string)},
  } };
  foreach (var word in words)
    table.Rows.Add(SqlLikeEscape(word));    // make a function that escapes wildcards

  using var connection = CreateConnection();
  return connection.Query<Employee>(yourQueryHere, new
    {
      words = table.AsTableValuedParameter("dbo.StringList"),
    });
}