在表达式树中使用局部变量

时间:2014-04-25 16:35:49

标签: c# linq entity-framework-5 expression-trees

我有这个LINQ表达式,可以找到给定客户的CreditBalance的所有历史变化:

var history = GetHistory(id);
var changes = history.Where(x => history.Where(y => y.AuditId < x.AuditId)
                                        .OrderByDescending(y => y.AuditId)
                                        .Select(y => y.CreditBalance)
                                        .FirstOrDefault() != x.CreditBalance);

此功能按预期工作。我想要做的是更改此功能,以允许用户查询对任何历史字段的更改。我选择解决这个问题的方式是表达树。

到目前为止,我已经提出了这个解决方案:

var history = GetHistory(id);
var c = Expression.Parameter(typeof(Customer_history), "c");
var d = Expression.Parameter(typeof(Customer_history), "d");
var cAudit = Expression.Property(c, typeof(Customer_history).GetProperty("AuditId"));
var dAudit = Expression.Property(d, typeof(Customer_history).GetProperty("AuditId"));

var whereBody = Expression.LessThan(dAudit, cAudit);
var whereLambda = Expression.Lambda(whereBody, d);
var where = Methods.QueryableWhere.MakeGenericMethod(typeof(Customer_history));

var whereCall = Expression.Call(null, where, **Expression.Constant(history)**, whereLambda);

var orderByLambda = Expression.Lambda(dAudit, d);

var orderBy = Methods.QueryableOrderByDescending.MakeGenericMethod(typeof(Customer_history), orderByLambda.Body.Type);
var orderByCall = Expression.Call(null, orderBy, whereCall, orderByLambda);

var dProp = Expression.Property(d, typeof(Customer_history).GetProperty(field));
var selectLambda = Expression.Lambda(dProp, d);

var select = Methods.QueryableSelect.MakeGenericMethod(typeof(Customer_history), selectLambda.Body.Type);
var selectCall = Expression.Call(null, select, orderByCall, selectLambda);

var firstOrDefault = Methods.QueryableFirstOrDefault.MakeGenericMethod(selectLambda.Body.Type);
var firstOrDefaultCall = Expression.Call(null, firstOrDefault, selectCall);

var cProp = Expression.Property(c, typeof(Customer_history).GetProperty(field));
var comparison = Expression.NotEqual(firstOrDefaultCall, cProp);
var lambda = Expression.Lambda<Func<Customer_history, bool>>(comparison, c);

var changes = history.Where(lambda);

问题是,我在执行查询时遇到此异常:

  

无法创建类型的常量值   'Namespace.Customer_history'。只有原始类型或枚举   在这种情况下支持类型。

现在我假设问题是基于异常消息的 Expression.Constant(history)语句。问题是,我不知道如何重写它以允许查询提供程序适当地处理它。我知道它的工作原理是因为原始查询,我只是不知道如何在表达式树中执行它。

任何人都可以提供任何指示吗?

2 个答案:

答案 0 :(得分:1)

如所怀疑的那样,似乎ConstantExpression不是从局部变量获取值的方法。

我需要做的是创建一个私有类来存储变量,然后我可以使用字段MemberExpression访问它

private class ValueHolder<T>
{
      public IQueryable<T> History;
}

然后在我的方法中,我能够使用以下方法评估表达式:

var valueHolder = new ValueHolder<T>
{
      History = data
};

var c = Expression.Parameter(typeof(T), "c");
var constantEx = Expression.Constant(valueHolder);
var fieldEx = Expression.Field(constantEx, valueHolder.GetType().GetField("History"));

答案 1 :(得分:0)

您可以随时尝试使用动态linq,它允许您将字符串用作表达式而不是lambda。

示例:

var query = history.Where("MyField = MyFilter");

https://www.nuget.org/packages/System.Linq.Dynamic.Library/

https://github.com/NArnott/System.Linq.Dynamic