我有一个使用Dapper的c#mvc应用程序。有一个列表表页面,它有几个可选的过滤器(以及分页)。用户可以选择(或不选择)几个(现在大约8个但可能会增长)过滤器中的任何一个,每个过滤器都有一个从值到值的下拉列表。因此,例如,用户可以选择类别"价格"从价值过滤" $ 100"价值200美元"。但是,我不知道用户过滤了多少类别,并且并非所有过滤器类别都是相同的类型(某些int,一些十进制/双精度,一些DateTime,尽管它们都以{{{{ 1}} string
}。
我试图为此构建一个(相对)简单但可持续的Dapper查询。到目前为止,我有这个:
FilterRange
我继续收到错误说" ... filterRanges不能用作参数值"
甚至可以在Dapper中做到这一点吗?我看到的所有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();
}
}
示例都是IEnumerable
,并不适合这种情况。任何帮助表示赞赏。
答案 0 :(得分:1)
您可以使用动态列值列表,但除了使用可能导致SQL注入的字符串格式之外,您也不能对列名称执行此操作。
您必须验证列表中的列名,以确保它们在SQL查询中使用之前确实存在。
这是您可以动态使用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();
}
答案 1 :(得分:0)
我能够找到解决方案。关键是将List
转换为Dictionary
。我创建了一个私有方法:
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;
}
然后建立我的查询,
var ranges = " WHERE 1 = 1 ";
for(var i = 0; i < filterRanges.Count; i++)
ranges += $" AND {filter[$"column_{i}"]} BETWEEN @fromVal_{i} AND @toVal_{i} ";
特别注意:这里要特别小心,因为列名不是参数,并且您可以对注入攻击敞开大门(正如@Popa在他的回答中指出的那样)。就我而言,这些值来自枚举类,而不是用户输入的,所以我很安全。
其余的内容很简单:
using (var conn = OpenConnection())
{
string query = @"Select * from Sales "
+ ranges
+ skipTake;
return conn.Query<Sale>(query, filter).AsList();
}
答案 2 :(得分:0)
您可以将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);
答案 3 :(得分:0)
您可以使用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();
}
或者没有魔术词/**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();
}
即使看起来我们正在执行纯字符串连接,输出也是经过完全参数化的SQL。
免责声明:我是该库的作者之一