构建复杂的Lambda表达式(在对象上选择对象列表上的属性)

时间:2014-12-08 20:28:31

标签: c# lambda

我需要在运行时构建一个复杂的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接口,它应该根据CostCenterId和项目的CostCenterId列表过滤项目,这是表达式建:

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);
}

还有第二个中心,而这个我无法解决,它发生在对象包含实现IHasCostCenter接口的对象列表时,就像上面描述的类一样,在这种情况下lambda表达式应该选择Contract_Rate合同中的列表,然后为列表中的每个Contract_Rate选择CostCenterId,并查看是否有任何CostCenterId位于User.CostCenters.Select(x =&gt; x.CostCenterId).ToList()。

任何帮助都将不胜感激。

1 个答案:

答案 0 :(得分:-1)

构建使用lambdas编写此查询所需的工具比尝试手动创建整个表达式更容易。

最简单的情况是查询中的项目实现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来转换{{1}在确定它是否是有效ID的查询中,您可以IHasCostCenter将这两个lambdas放在一起。如果您编写这样的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);
}