【发布时间】:2018-02-16 18:00:44
【问题描述】:
我有一个系统,允许将与销售有关的不同标准存储在数据库中。加载条件后,它们用于构建查询并返回所有适用的销售额。条件对象如下所示:
ReferenceColumn(他们适用的销售表中的列)
MinValue(参考列必须是最小值)
MaxValue(引用列必须是最大值)
使用上述条件的集合完成对销售的搜索。相同类型的 ReferenceColumns 被 OR'd 在一起,不同类型的 ReferenceColumns 被 AND'd 在一起。例如,如果我有三个标准:
ReferenceColumn:'Price',MinValue:'10',MaxValue:'20'
ReferenceColumn:'Price',MinValue:'80',MaxValue:'100'
ReferenceColumn:'Age',MinValue:'2',MaxValue:'3'
查询应返回价格在 10-20 或 80-100 之间的所有销售,但前提是这些销售的年龄在 2 到 3 岁之间。
我使用 SQL 查询字符串实现它并使用 .FromSql 执行:
public IEnumerable<Sale> GetByCriteria(ICollection<SaleCriteria> criteria)
{
StringBuilder sb = new StringBuilder("SELECT * FROM Sale");
var referenceFields = criteria.GroupBy(c => c.ReferenceColumn);
// Adding this at the start so we can always append " AND..." to each outer iteration
if (referenceFields.Count() > 0)
{
sb.Append(" WHERE 1 = 1");
}
// AND all iterations here together
foreach (IGrouping<string, SaleCriteria> criteriaGrouping in referenceFields)
{
// So we can always use " OR..."
sb.Append(" AND (1 = 0");
// OR all iterations here together
foreach (SaleCriteria sc in criteriaGrouping)
{
sb.Append($" OR {sc.ReferenceColumn} BETWEEN '{sc.MinValue}' AND '{sc.MaxValue}'");
}
sb.Append(")");
}
return _context.Sale.FromSql(sb.ToString();
}
事实上这在我们的数据库中运行良好,但它与其他集合不兼容,特别是我们用于 UnitTesting 的 InMemory 数据库,所以我尝试使用表达式树重写它,我已经以前从未使用过。到目前为止,我已经得到了这个:
public IEnumerable<Sale> GetByCriteria(ICollection<SaleCriteria> criteria)
{
var referenceFields = criteria.GroupBy(c => c.ReferenceColumn);
Expression masterExpression = Expression.Equal(Expression.Constant(1), Expression.Constant(1));
List<ParameterExpression> parameters = new List<ParameterExpression>();
// AND these...
foreach (IGrouping<string, SaleCriteria> criteriaGrouping in referenceFields)
{
Expression innerExpression = Expression.Equal(Expression.Constant(1), Expression.Constant(0));
ParameterExpression referenceColumn = Expression.Parameter(typeof(Decimal), criteriaGrouping.Key);
parameters.Add(referenceColumn);
// OR these...
foreach (SaleCriteria sc in criteriaGrouping)
{
Expression low = Expression.Constant(Decimal.Parse(sc.MinValue));
Expression high = Expression.Constant(Decimal.Parse(sc.MaxValue));
Expression rangeExpression = Expression.GreaterThanOrEqual(referenceColumn, low);
rangeExpression = Expression.AndAlso(rangeExpression, Expression.LessThanOrEqual(referenceColumn, high));
innerExpression = Expression.OrElse(masterExpression, rangeExpression);
}
masterExpression = Expression.AndAlso(masterExpression, innerExpression);
}
var lamda = Expression.Lambda<Func<Sale, bool>>(masterExpression, parameters);
return _context.Sale.Where(lamda.Compile());
}
当我调用 Expression.Lamda 时,它当前正在抛出 ArgumentException。 Decimal 不能在那里使用,它说它需要类型 Sale,但我不知道该为 Sales 放什么,而且我不确定我是否在正确的轨道上。我还担心我的 masterExpression 每次都在复制自己,而不是像我对字符串生成器所做的那样追加,但也许这无论如何都会起作用。
我正在寻求有关如何将此动态查询转换为表达式树的帮助,如果我不在此处,我愿意采用完全不同的方法。
【问题讨论】:
-
您的原始代码有效吗?这不应该工作,你为什么使用 1 = 1 和 1 = 0?
-
是的,如果集合是使用 SQL Server 的 DbContext 的一部分,它就可以工作。 1=1 和 1=0 在那里,所以我总是可以将 'AND'/'OR' 附加到查询字符串,而不必处理第一次迭代的特殊情况等。
-
尝试使用 LINQKit (albahari.com/nutshell/linqkit.aspx),它会更容易。该页面说:使用 LINQKit,您可以: ...动态构建谓词
-
我可能会再看看 LINQKit。我记得当我第一次处理这个问题时看过它,但选择使用 SQL 查询以避免引入另一个第三方库。
-
好吧,如果您可以在数据库中使用 LINQ,那么使用 LINQKit 应该没问题。我多次使用它来构建动态查询(用于数据库和 SharePoint),如果没有它,我什至不会尝试 :)
标签: c# entity-framework linq expression-trees