Build expression tree for LINQ using List.Contains method

c# entity-framework expression-trees linq

Question

Problem

I'm trying to refactor a few things.LINQ I'm trying to transfer certain redundant query predicates into their own to make our web application's requests for several reports more efficient.IQueryable exension techniques so we may apply them for this report and future reports. I've previously refactored the predicate for groups, as you can probably guess, but I'm having trouble with the predicate for codes. Here is an example of one of the report formats I now use:

DAL approach:

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 Extension:

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

Predicate:

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

The issue I'm experiencing is thatQueryable.Where does not recognize a kind ofExpression<Func<T, List<string>, bool> . The thing that really confounds me is the fact that the only method I can think of to dynamically create this predicate is to utilize two arguments.

What I don't understand is how the next approach works. My data is appropriately filtered when I provide the precise lambda expression I'm attempting to dynamically build.

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

        }

    }

Questions

  1. Can I put my own in place?Queryable.Where overload? If so, is it really possible?
  2. Is there a method to generate the predicate dynamically if an overload is not possible?p => codes.Contains(p.Code) Missing the use of two parameters?
  3. Is there a simpler method to do this? I feel as if something is missing.
1
5
7/25/2017 8:13:18 PM

Accepted Answer

  1. You are able to develop your own extension technique.Where accede to anIQueryable<T> , give back aIQueryable<T> , and otherwise make it mimic LINQ method syntax. Although it wouldn't be a LINQ method, it would seem to be one. Simply put, I would advise against developing such a method since it is likely to generate confusion in others. If you must create a new extension method, choose a name that is not present in LINQ to prevent misunderstanding. Simply continue creating new extensions as you are now doing, but without actually giving them names.Where If you had to choose, choose one.Where nevertheless, nothing can stop you.

  2. Yes, simply 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);
    }
    

    The code would be similar to the current code, but utilizing the list you send in as a constant rather than a new parameter: If you truly can't have your entities implement an interface (hint: you almost surely can), then

    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 highly advise against using the latter way since it lacks static type safety, is more complicated, and hence more difficult to maintain.

    Another thing to consider is the code comment you have:// j => codes.Contains(j.Code) is incorrect. The appearance of the lambda actually is:(j, codes) => codes.Contains(j.Code); it truly differs significantly.

  3. See #2's opening paragraph.

15
5/11/2015 5:12:24 PM


Related Questions





Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow