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

Gar*_*tes 5 c# linq entity-framework expression-trees

问题

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

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

谓语:

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 });
}
Run Code Online (Sandbox Code Playgroud)

我遇到的问题是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();

        }

    }
Run Code Online (Sandbox Code Playgroud)

问题

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

Ser*_*rvy 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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    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);
    }
    
    Run Code Online (Sandbox Code Playgroud)

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

    另请注意,您的代码中的注释:// j => codes.Contains(j.Code)不准确.lambda 实际上是什么样的: (j, codes) => codes.Contains(j.Code);实际上明显不同.

  3. 见#2的前半部分.

  • 出色的!这非常有帮助。我为“QueryView”实体创建了一个接口,并将其用作扩展方法的类型约束,一切正常。感谢您的帮助。 (2认同)