[英]Dapper query with dynamic list of filters
I have ac# mvc app using Dapper.我有使用 Dapper 的 ac# mvc 应用程序。 There is a list table page which has several optional filters (as well as paging).
有一个列表页面,它有几个可选的过滤器(以及分页)。 A user can select (or not) any of several (about 8 right now but could grow) filters, each with a drop down for a from value and to value.
用户可以选择(或不选择)几个(现在大约有 8 个,但可能会增加)过滤器中的任何一个,每个过滤器都有一个下拉列表,用于显示起始值和终止值。 So, for example, a user could select category "price" and filter from value "$100" to value "$200".
因此,例如,用户可以选择类别“价格”并从值“$100”过滤到值“$200”。 However, I don't know how many categories the user is filtering on before hand and not all of the filter categories are the same type (some int, some decimal/double, some DateTime, though they all come in as
string
on FilterRange
).但是,我不知道用户事先过滤了多少个类别,并且并非所有的过滤器类别都是相同的类型(有些是整数,有些是十进制/双
FilterRange
,有些是 DateTime,尽管它们都以string
在FilterRange
) .
I'm trying to build a (relatively) simple yet sustainable Dapper query for this.我正在尝试为此构建一个(相对)简单但可持续的 Dapper 查询。 So far I have this:
到目前为止,我有这个:
public List<PropertySale> GetSales(List<FilterRange> filterRanges, int skip = 0, int take = 0)
{
var skipTake = " order by 1 ASC OFFSET @skip ROWS";
if (take > 0)
skipTake += " FETCH NEXT @take";
var ranges = " WHERE 1 = 1 ";
for(var i = 0; i < filterRanges.Count; i++)
{
ranges += " AND @filterRanges[i].columnName BETWEEN @filterRanges[i].fromValue AND @filterRanges[i].toValue ";
}
using (var conn = OpenConnection())
{
string query = @"Select * from Sales "
+ ranges
+ skipTake;
return conn.Query<Sale>(query, new { filterRanges, skip, take }).AsList();
}
}
I Keep getting an error saying "... filterRanges cannot be used as a parameter value"我不断收到错误消息“... filterRanges 不能用作参数值”
Is it possible to even do this in Dapper?甚至可以在 Dapper 中做到这一点吗? All of the
IEnumerable
examples I see are where in _
which doesn't fit this situation.我看到的所有
IEnumerable
示例都where in _
中不适合这种情况。 Any help is appreciated.任何帮助表示赞赏。
You can use a list of dynamic column values but you cannot do this also for the column name other than using string format which can cause a SQL injection.您可以使用动态列值列表,但除了使用可能导致 SQL 注入的字符串格式之外,您也不能对列名执行此操作。
You have to validate the column names from the list in order to be sure that they really exist before using them in a SQL query.在 SQL 查询中使用它们之前,您必须验证列表中的列名,以确保它们确实存在。
This is how you can use the list of filterRanges dynamically :这是您可以动态使用 filterRanges 列表的方法:
const string sqlTemplate = "SELECT /**select**/ FROM Sale /**where**/ /**orderby**/";
var sqlBuilder = new SqlBuilder();
var template = sqlBuilder.AddTemplate(sqlTemplate);
sqlBuilder.Select("*");
for (var i = 0; i < filterRanges.Count; i++)
{
sqlBuilder.Where($"{filterRanges[i].ColumnName} = @columnValue", new { columnValue = filterRanges[i].FromValue });
}
using (var conn = OpenConnection())
{
return conn.Query<Sale>(template.RawSql, template.Parameters).AsList();
}
You can use DynamicParameters class for generic fields.您可以将 DynamicParameters 类用于通用字段。
Dictionary<string, object> Filters = new Dictionary<string, object>();
Filters.Add("UserName", "admin");
Filters.Add("Email", "admin@admin.com");
var builder = new SqlBuilder();
var select = builder.AddTemplate("select * from SomeTable /**where**/");
var parameter = new DynamicParameters();
foreach (var filter in Filters)
{
parameter.Add(filter.Key, filter.Value);
builder.Where($"{filter.Key} = @{filter.Key}");
}
var searchResult = appCon.Query<ApplicationUser>(select.RawSql, parameter);
You can easily create that dynamic condition using DapperQueryBuilder :您可以使用DapperQueryBuilder轻松创建该动态条件:
using (var conn = OpenConnection())
{
var query = conn.QueryBuilder($@"
SELECT *
FROM Sales
/**where**/
order by 1 ASC
OFFSET {skip} ROWS FETCH NEXT {take}
");
foreach (var filter in filterRanges)
query.Where($@"{filter.ColumnName:raw} BETWEEN
{filter.FromValue.Value} AND {filter.ToValue.Value}");
return conn.Query<Sale>(query, new { filterRanges, skip, take }).AsList();
}
Or without the magic word /**where**/
:或者没有魔法词
/**where**/
:
using (var conn = OpenConnection())
{
var query = conn.QueryBuilder($@"
SELECT *
FROM Sales
WHERE 1=1
");
foreach (var filter in filterRanges)
query.Append($@"{filter.ColumnName:raw} BETWEEN
{filter.FromValue.Value} AND {filter.ToValue.Value}");
query.Append($"order by 1 ASC OFFSET {skip} ROWS FETCH NEXT {take}");
return conn.Query<Sale>(query, new { filterRanges, skip, take }).AsList();
}
The output is fully parametrized SQL, even though it looks like we're doing plain string concatenation.输出是完全参数化的 SQL,即使看起来我们正在执行纯字符串连接。
Disclaimer: I'm one of the authors of this library免责声明:我是这个库的作者之一
I was able to find a solution for this.我能够为此找到解决方案。 The key was to convert the
List
to a Dictionary
.关键是将
List
转换为Dictionary
。 I created a private method:我创建了一个私有方法:
private Dictionary<string, object> CreateParametersDictionary(List<FilterRange> filters, int skip = 0, int take = 0)
{
var dict = new Dictionary<string, object>()
{
{ "@skip", skip },
{ "@take", take },
};
for (var i = 0; i < filters.Count; i++)
{
dict.Add($"column_{i}", filters[i].Filter.Description);
// some logic here which determines how you parse
// I used a switch, not shown here for brevity
dict.Add($"@fromVal_{i}", int.Parse(filters[i].FromValue.Value));
dict.Add($"@toVal_{i}", int.Parse(filters[i].ToValue.Value));
}
return dict;
}
Then to build my query,然后建立我的查询,
var ranges = " WHERE 1 = 1 ";
for(var i = 0; i < filterRanges.Count; i++)
ranges += $" AND {filter[$"column_{i}"]} BETWEEN @fromVal_{i} AND @toVal_{i} ";
Special note: Be very careful here as the column name is not a parameter and you could open your self up to injection attacks (as @Popa noted in his answer).特别注意:这里要非常小心,因为列名不是参数,你可以打开自己的注入攻击(正如@Popa 在他的回答中指出的那样)。 In my case those values come from an enum class and not from user in put so I am safe.
在我的情况下,这些值来自枚举类而不是用户输入,所以我很安全。
The rest is pretty straight forwared:其余的很直接:
using (var conn = OpenConnection())
{
string query = @"Select * from Sales "
+ ranges
+ skipTake;
return conn.Query<Sale>(query, filter).AsList();
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.