表达式树:导航属性上的过滤计数

时间:2016-07-12 17:15:04

标签: c# asp.net-mvc linq expression-trees

我制作了一个动态报表构建器,允许用户从预定义类(通过Entity Framework映射到数据库表)中选择字段作为其数据的过滤器。为了构建我的LINQ to Entities查询,由于查询的动态特性,我使用了表达式树。我让它适用于几乎所有非自定义场景,但我真的很难让它适用于一些自定义场景。

我的一个自定义查询的模型的缩写版本如下所示:

public class Attendee {
    public int ID { get; set; }
    public DateTime? CancelledOn { get; set; }

    [ForeignKey("Event")]
    public int Event_ID { get; set; }
    public virtual Event Event { get; set; }        
}

public class Event {
    public int ID { get; set; }
    public virtual ICollection<Attendee> Attendees { get; set; }        
}

用户想要运行的示例查询是过滤以仅显示具有超过10个未被取消的与会者的事件。如果我在正常的IQueryable查询中写这个,我会把它写成

db.Event.Where(s => s.Attendees.Count(a => a.CancelledOn == null) > 10);

我已经设置了表达式树框架,我可以处理&#34;&gt; 10&#34;部分已经,但我无法弄清楚如何动态生成&#34; s.Attendees.Count(a =&gt; a.CancelledOn == null)&#34;部分。我已经阅读过有关在顶级属性上执行Count或Sum的SO帖子,但我无法破解任何这些解决方案来处理过滤导航属性。帖子示例:Dynamic LINQ, Select function, works on Enumerable, but not Queryable

下面的屏幕截图是使用表达式树构建的不同过滤器的示例,因此您可以看到我工作的示例。 &#34; PE&#34;是传入的Type的ParameterExpression,&#34; Event&#34;。 &#34;表达&#34;是我试图创造和评估的东西。 http://grab.by/RoYm

上面运行的LINQ查询是

db.Event.Where(s=> s.StartDate >= '1/1/2016 12:00 am')

如果我需要包含任何其他代码段,请随时与我们联系。

2 个答案:

答案 0 :(得分:3)

不确定您正在寻找的方法的输入参数是什么,但以下内容应该为您提供一个起点。关键部分是构建对Enumerable.Count(predicate)方法的方法调用。

static Expression<Func<TSource, bool>> MakeCountPredicate<TSource>(string collectionName, string itemName, ExpressionType itemComparison, string itemValue, ExpressionType countComparison, int countValue)
{
    var source = Expression.Parameter(typeof(TSource), "s");
    var collection = Expression.Property(source, collectionName);
    var itemType = collection.Type.GetInterfaces()
        .Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .GetGenericArguments()[0];
    var item = Expression.Parameter(itemType, "e");
    var itemProperty = Expression.Property(item, itemName);
    var itemPredicate = Expression.Lambda(
        Expression.MakeBinary(itemComparison, itemProperty, Expression.Constant(
            string.IsNullOrEmpty(itemValue) || itemValue.Equals("null", StringComparison.OrdinalIgnoreCase) ? null :
            Convert.ChangeType(itemValue, itemProperty.Type))),
        item);
    var itemCount = Expression.Call(
        typeof(Enumerable), "Count", new[] { itemType },
        collection, itemPredicate);
    var predicate = Expression.Lambda<Func<TSource, bool>>(
        Expression.MakeBinary(countComparison, itemCount, Expression.Constant(countValue)),
        source);
    return predicate;
}

所以样本谓词表达式

Expression<Func<Event, bool>> predicate =
    s => s.Attendees.Count(a => a.CancelledOn == null) > 10

可以像这样动态构建

var predicate = MakeCountPredicate<Event>("Attendees", 
    "CancelledOn", ExpressionType.Equal, "null", ExpressionType.GreaterThan, 10);

答案 1 :(得分:0)

要为Expression生成Count树,您应该为相应的Call生成Method

这是代码的初稿......

Expression callExpr = Expression.Call(
    Expression.Constant(s.Attendees), 
    typeof(ICollection<Attendee>).GetMethod("get_Count")); // + 2 Arguments

当然,你必须详细说明并将其合并到主程序中。

基本用法示例是

// Print out the expression.
Debug.WriteLine(callExpr.ToString());

// The following statement first creates an expression tree,
// then compiles it, and then executes it.  
Debug.WriteLine(Expression.Lambda<Func<int>>(callExpr).Compile()());

最后, Count Expression(NodeType Call)必须包含2个参数(作为第三个参数,未在上面显示):

  • 收藏品
  • Lambda的{​​{1}}