使用List< T> .contains方法为LINQ构建表达式树 [英] Build expression tree for LINQ using List<T>.Contains method

查看:60
本文介绍了使用List< T> .contains方法为LINQ构建表达式树的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在为Web应用程序中的几个报告重构一些LINQ查询,并且试图将一些重复的查询谓词移到它们自己的IQueryable扩展方法中,以便我们可以将它们重新用于这些报告,并在将来进行报告.可以推断,我已经重构了组的谓词,但是代码的谓词给我带来了麻烦.这是到目前为止我拥有的一种报告方法的示例:

I'm working on refactoring some LINQ queries for several reports in our web application, and I'm attempting to move some duplicate query predicates into their own IQueryable exension methods so we can reuse them for these reports, and reports in the future. As you can probably infer, I've already refactored the predicate for groups, but the predicate for codes is giving me problems. This is an example of one of the report methods I have so far:

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>类型.我能想到的动态创建此谓词的唯一方法是使用两个参数,而这正是让我感到困扰的部分.

The problem I'm having is that Queryable.Where doesn't accept a type of Expression<Func<T, List<string>, bool>. The only way I can think of creating this predicate dynamically is to use two parameters, which is the part that is really stumping me.

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

What I'm not comprehending is the following method works. I can pass the exact lambda expression I am trying to create dynamically, and it correctly filters my data.

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. Can I implement my own Queryable.Where overload? If so, is it even feasible?
  2. If an overload isn't feasible, is there a way to dynamically construct the predicate p => codes.Contains(p.Code) without using two parameters?
  3. Is there an easier way to do this? I feel like I'm missing something.

推荐答案

  1. 您可以创建自己的扩展方法,将其命名为Where,接受IQueryable<T>,返回IQueryable<T>,否则使其模仿LINQ方法的形式.它不是LINQ方法,但看起来像一个.我不鼓励您编写这种方法,只是因为它可能会使其他人迷惑.即使您想使用新的扩展方法,也请使用LINQ中未使用的名称,以免造成混淆.简而言之,做您现在正在做的事情,创建新的扩展名而无需实际将其命名为Where.如果您真的想命名一个Where,尽管没有什么阻止您.

  1. You can create your own extension method, name it Where, accept an IQueryable<T>, return an IQueryable<T>, and otherwise make it emulate the form of LINQ methods. It wouldn't be a LINQ method, but it would look like one. I would discourage you from writing such a method simply because it would likely confuse others; even if you want to make a new extension method, use a name not used in LINQ to avoid confusion. In short, do what you're doing now, create new extensions without actually naming them Where. If you really wanted to name one Where though nothing's stopping you.

当然,只需使用lambda:

Sure, just use a 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);
}

如果您真的不能让您的实体实现接口(提示:您几乎可以确定),那么代码看上去将与您拥有的代码相同,但是将您传入的列表用作常量而不是新的参数:

If you really cannot have your entities implement an interface (hint: you almost certainly can), then the code would look identical to the code that you have, but using the list that you pass in as a constant rather than a new parameter:

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

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

I would strongly encourage use of the former method; this method loses static type safety, and is more complex and as such harder to maintain.

另一个说明,您在代码中的注释:// j => codes.Contains(j.Code)不正确. lambda实际上 的样子是:(j, codes) => codes.Contains(j.Code);实际上明显不同.

Another note, the comment you have in your code: // j => codes.Contains(j.Code) isn't accurate. What that lambda actually looks like is: (j, codes) => codes.Contains(j.Code); which is actually noticeably different.

请参阅#2的前半部分.

See the first half of #2.

这篇关于使用List&lt; T&gt; .contains方法为LINQ构建表达式树的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆