使用List <t>为LINQ构建表达式树.Contains方法

时间:2015-05-11 16:58:54

标签: c# entity-framework linq expression-trees

问题

我正在为我们的Web应用程序中的几个报告重构一些LINQ个查询,并且我正在尝试将一些重复的查询谓词移动到他们自己的IQueryable exension方法中,以便我们可以重用它们这些报告和未来的报告。正如您可能推断的那样,我已经为组重构了谓词,但是代码的谓词给了我一些问题。这是我到目前为止报告方法之一的一个例子:

DAL方法:

public List<Entities.QueryView> GetQueryView(Filter filter)
{
    using (var context = CreateObjectContext())
    {
        return (from o in context.QueryViews
                    where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
                    && (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
                    select o)
                .WithCode(filter)
                .InGroup(filter)
                .ToList();
    }
}

IQueryable扩展名:

public static IQueryable<T> WithCode<T>(this IQueryable<T> query, Filter filter)
{
    List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);

    if (codes.Count > 0)
        return query.Where(Predicates.FilterByCode<T>(codes));

    return query;
}

谓词:

public static Expression<Func<T, List<string>, bool>> FilterByCode<T>(List<string> codes)
{
    // Method info for List<string>.Contains(code).
    var methodInfo = typeof(List<string>).GetMethod("Contains", new Type[] { typeof(string) });

    // List of codes to call .Contains() against.
    var instance = Expression.Variable(typeof(List<string>), "codes");

    var param = Expression.Parameter(typeof(T), "j");
    var left = Expression.Property(param, "Code");
    var expr = Expression.Call(instance, methodInfo, Expression.Property(param, "Code"));

    // j => codes.Contains(j.Code)
    return Expression.Lambda<Func<T, List<string>, bool>>(expr, new ParameterExpression[] { param, instance });
}

我遇到的问题是Queryable.Where不接受某种Expression<Func<T, List<string>, bool>。我能想到动态创建这个谓词的唯一方法是使用两个参数,这是真正让我感到难过的部分。

我不理解的是以下方法有效。我可以传递我想要动态创建的确切lambda表达式,并且它正确地过滤了我的数据。

public List<Entities.QueryView> GetQueryView(Filter filter)
{
    // Get the codes here.
    List<string> codes = DAL.GetCodesByCategory(filter.CodeCategories);

    using (var context = CreateObjectContext())
    {
        return (from o in context.QueryViews
                    where (!filter.FromDate.HasValue || o.RepairDate >= EntityFunctions.TruncateTime(filter.FromDate))
                    && (!filter.ToDate.HasValue || o.RepairDate <= EntityFunctions.TruncateTime(filter.ToDate))
                    select o)
                .Where(p => codes.Contains(p.Code)) // This works fine.
                //.WithCode(filter)
                .InGroup(filter)
                .ToList();

        }

    }

问题

  1. 我可以实现自己的Queryable.Where重载吗?如果是这样,它甚至可行吗?
  2. 如果过载不可行,有没有办法动态构建谓词p => codes.Contains(p.Code)而不使用两个参数?
  3. 有更简单的方法吗?我觉得我错过了什么。

1 个答案:

答案 0 :(得分:15)

  1. 您可以创建自己的扩展方法,将其命名为Where,接受IQueryable<T>,返回IQueryable<T>,然后使其模拟LINQ方法的形式。它不会一个LINQ方法,但它看起来像一个。我不鼓励你写这样的方法只是因为它可能会混淆他人;即使您想要创建一个新的扩展方法,也可以使用LINQ中未使用的名称来避免混淆。简而言之,做你现在正在做的事情,创建新的扩展而不实际命名它们Where。如果你真的想给一个Where命名,虽然没有什么可以阻止你。

  2. 当然,只需使用lambda:

    public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
        where T : ICoded //some interface with a `Code` field
    {
        return p => codes.Contains(p.Code);
    }
    

    如果你真的不能让你的实体实现一个接口(提示:你几乎可以肯定),那么代码看起来与你拥有的代码相同,但是使用你作为常量而不是新传递的列表参数:

    public static Expression<Func<T, bool>> FilterByCode<T>(List<string> codes)
    {
        var methodInfo = typeof(List<string>).GetMethod("Contains", 
            new Type[] { typeof(string) });
    
        var list = Expression.Constant(codes);
    
        var param = Expression.Parameter(typeof(T), "j");
        var value = Expression.Property(param, "Code");
        var body = Expression.Call(list, methodInfo, value);
    
        // j => codes.Contains(j.Code)
        return Expression.Lambda<Func<T, bool>>(body, param);
    }
    

    我强烈鼓励使用前一种方法;这种方法失去了静态类型的安全性,并且更加复杂,因此难以维护。

    另一个注意事项,您在代码中的评论:// j => codes.Contains(j.Code)并不准确。 lambda 实际的外观如下:(j, codes) => codes.Contains(j.Code);实际上明显不同。

  3. 见#2的前半部分。