构建复杂的 lambda 表达式(在对象列表上选择属性)
本文关键字:列表 选择 属性 对象 复杂 lambda 表达式 构建 | 更新日期: 2023-09-27 18:33:30
我需要在运行时构建一个复杂的lambda表达式,这里是cenario:
我有一个名为 IHasCostCenter 的接口,其中包含一个名为 CostCenterId 的字符串属性:
public interface IHasCostCenter
{
string CostCenterId { get; set; }
}
下面是实现此接口的类的示例:
public class Contract_Rate: Entity, IHasCostCenter
{
public int ContratoId { get; set; }
public string CostCenterId { get; set; }
public int Percentage { get; set; }
}
在两种情况下,我需要构建一个动态 lambda 表达式,第一种是,如果类包含 IHasCostCenter 接口,它应该根据 CostCenter ID 和项目的 CostCenter ID 列表过滤项目,这是构建的表达式:
if (Interfaces.Any(x => x == typeof(IHasCostCenter)))
{
var sValues = User.CostCenters.Select(x => x.CostCenterId).ToList();
var sValuesType = sValues.GetType().GetGenericArguments().FirstOrDefault();
ParameterExpression Parameter = Expression.Parameter(typeof(T), "x");
Expression Property = Expression.Property(Parameter, typeof(T).GetProperty("CostCenterId"));
Expression Contains = Expression.Call(typeof(Enumerable), "Contains", new[] { sValuesType }, Expression.Constant(sValues, sValues.GetType()), Property);
MethodCallExpression whereExpression = Expression.Call(
typeof(Queryable),
"Where",
new Type[] { items.ElementType },
items.Expression,
Expression.Lambda(Contains, Parameter));
items = items.Provider.CreateQuery<T>(whereExpression);
}
还有第二个cenario,这个我无法解决,当对象包含实现IHasCostCenter接口的对象列表时,就会发生这种情况,如上所述,在这种情况下,lambda表达式应从合约中选择Contract_Rate列表,然后为列表中的每个Contract_Rate选择CostCenterId,并查看是否有任何CostCenterId在User.CostCenters.Select(x => x.CostCenterId(。ToList((。
任何帮助将不胜感激。
构造允许您使用 lambda 编写此查询所需的工具比尝试手动创建整个表达式要容易得多。
最简单的情况是查询本身中的项实现IHasCostCenter
。 在这种情况下,您可以简单地将泛型参数限制为IHasCostCenter
,然后使用 lambda:
public static IQueryable<T> Foo<T>(
this IQueryable<T> query)
where T : IHasCostCenter
{
var validIDs = User.CostCenters.Select(cc => cc.CostCenterId);
return query.Where(item => validIDs.Contains(item.CostCenterId));
}
如果您将选择相关项目的选择器作为输入 IHasCostCenter
,假设查询具有该类型的属性,然后编写自己的 lambda 以将IHasCostCenter
转换为确定它是否为有效 ID 的查询,则可以将这两个 lambda Compose
在一起。 如果您编写这样的Compose
方法,则现有代码的实现将变为:
public static IQueryable<T> Foo<T>(
this IQueryable<T> query,
Expression<Func<T, IHasCostCenter>> selector)
{
var validIDs = User.CostCenters.Select(cc => cc.CostCenterId);
return query.Where(selector.Compose(
item => validIDs.Contains(item.CostCenterId)));
}
将其转换为接受IEnumerable<IHasCostCenter>>
反而变得非常简单:
public static IQueryable<T> Foo<T>(
this IQueryable<T> query,
Expression<Func<T, IEnumerable<IHasCostCenter>>> selector)
{
var validIDs = User.CostCenters.Select(cc => cc.CostCenterId);
return query.Where(selector.Compose(list =>
list.Any(item => validIDs.Contains(item.CostCenterId))));
}
那么我们如何编写Compose
方法呢? 我们需要做的是接受两个表达式,并将组合表达式参数的所有实例替换为组合表达式的主体:
public static Expression<Func<TFirstParam, TResult>>
Compose<TFirstParam, TIntermediate, TResult>(
this Expression<Func<TFirstParam, TIntermediate>> first,
Expression<Func<TIntermediate, TResult>> second)
{
var param = Expression.Parameter(typeof(TFirstParam), "param");
var newFirst = first.Body.Replace(first.Parameters[0], param);
var newSecond = second.Body.Replace(second.Parameters[0], newFirst);
return Expression.Lambda<Func<TFirstParam, TResult>>(newSecond, param);
}
这将使用以下方法将一个表达式的所有实例替换为另一个表达式:
public static Expression Replace(this Expression expression,
Expression searchEx, Expression replaceEx)
{
return new ReplaceVisitor(searchEx, replaceEx).Visit(expression);
}
internal class ReplaceVisitor : ExpressionVisitor
{
private readonly Expression from, to;
public ReplaceVisitor(Expression from, Expression to)
{
this.from = from;
this.to = to;
}
public override Expression Visit(Expression node)
{
return node == from ? to : base.Visit(node);
}
}