构建复杂的 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 表达式(在对象列表上选择属性)

构造允许您使用 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);
    }
}