6

I'm trying to create a dynamic filter for various classes. We would only know at runtime what type we're dealing with. I need the ColumnName to be the actual column (not a string value).

Is there an easy way to convert the string into a column?

public static List<T> Filter<T>
    (this List<T> Source, string ColumnName, 
    string TypeOfCompare, string CompValue)
{
    IQueryable<T> matches = Source.AsQueryable();

    if (ColumnName.Length > 0)
    {
        matches = (IEnumerable)matches.Where(a => ColumnName == CompValue)
    }

    List<T> ReturnList2 = new List<T>();
    ReturnList2 = matches.ToList();
    return ReturnList2;
}
Andrew Hare
  • 344,730
  • 71
  • 640
  • 635
John K.
  • 5,426
  • 1
  • 21
  • 20

3 Answers3

13

Basically you need to build an expression tree. It's not terribly hard, fortunately, using Expression.Property. You can either pass that to Queryable.Where, or compile it and pass it to Enumerable.Where. (Obviously you'll need to use something like Expression.Equal as well, depending on the type of comparison you're trying to make.)

Is CompValue meant to be an actual value? What's TypeOfCompare meant to be?

I'm not sure where LINQ to Entities fits into this, either... you're only using LINQ to Objects really, as far as I can see.

EDIT: Okay, here's a sample. It assumes you want equality, but it does what you want if so. I don't know what the performance impact of compiling an expression tree every time is - you may want to cache the delegate for any given name/value combination:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

static class Extensions
{
    public static List<T> Filter<T>
        (this List<T> source, string columnName, 
         string compValue)
    {
        ParameterExpression parameter = Expression.Parameter(typeof(T), "x");
        Expression property = Expression.Property(parameter, columnName);
        Expression constant = Expression.Constant(compValue);
        Expression equality = Expression.Equal(property, constant);
        Expression<Func<T, bool>> predicate =
            Expression.Lambda<Func<T, bool>>(equality, parameter);

        Func<T, bool> compiled = predicate.Compile();
        return source.Where(compiled).ToList();
    }
}

class Test
{
    static void Main()
    {
        var people = new[] {
            new { FirstName = "John", LastName = "Smith" },
            new { FirstName = "John", LastName = "Noakes" },
            new { FirstName = "Linda", LastName = "Smith" },
            new { FirstName = "Richard", LastName = "Smith" },
            new { FirstName = "Richard", LastName = "Littlejohn" },
        }.ToList();

        foreach (var person in people.Filter("LastName", "Smith"))
        {
            Console.WriteLine(person);
        }
    }
}
Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • Right... it's Linq to Objects... We have classes containing data like (firstname, lastname, phone) and CompValue is the actual value that we're looking to match...so an example would be... personCollection = personCollection.Filter("LastName", "Smith"); And we'd be returning the "filtered" collection of Smiths... Thanks – John K. Aug 07 '09 at 20:18
  • Perfect... Thanks Jon. John K. – John K. Aug 10 '09 at 13:24
  • I think it would be better if you change type of parameter compValue from 'string' to 'object', in case you need to Equal integers or strings. public static List Filter (this List source, string columnName, object compValue) – Delmonte Nov 13 '18 at 18:18
  • @Delmonte: I was taking the signature from the question. I'd quite possibly make it more generic (add a type parameter) rather than accepting just object. – Jon Skeet Nov 13 '18 at 18:49
0

Instead of a string ColumnName, can't you pass a selector for that column (I realize this isn't always possible, but just in case it is …)? This you could use, then.

By the way, the code is way too complicated. This should work:

public static List<T> Filter<T>(
    this List<T> Source, Func<T, string> selector, string CompValue)
{
    return Source.Where(a => selector(a) == CompValue).ToList();
}

You could then call the code as follows:

var result = myList.Filter(x => x.ColumnName, "foo");
Konrad Rudolph
  • 530,221
  • 131
  • 937
  • 1,214
0

Are you looking for something like this: http://naspinski.net/post/Writing-Dynamic-Linq-Queries-in-Linq-to-Entities.aspx Otherwise you have to handle each case separately like in the following:

if (columnName == "blah")
  matches = matches.Where(i => i.Blah == Value);
if (columnName == "name")
  matches = matches.Where(i => i.Name == Value);
ollifant
  • 8,576
  • 10
  • 35
  • 45