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

本文关键字:局部变量 表达式 | 更新日期: 2023-09-27 18:00:16

我有一个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)语句。问题是,我不知道如何重写它以允许查询提供程序适当地处理它。我知道它之所以有效是因为原始查询,只是不知道如何在表达式树中执行它。

有人能提供指导吗?

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

正如所怀疑的那样,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"));

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

示例:

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

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

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