Linq query - "Where" on List Where reflected property Contains text/value

c# expression-trees linq

Question

I would like to build a Function where user could search if certain property from list contains value

Let say we will have List, and Company will be defined as a class with properties like :

public class Company
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string CompanyAddress1 { get; set; }
    public string CompanyPostCode { get; set; }
    public string CompanyCity { get; set; }
    public string CompanyCounty { get; set; }
}

Now - Ideally I would like to have function with this parameters

List<Company> FilterCompanies(List<Company> unfilteredList, string fieldToQueryOn, string query)
{
    // linq  version what ideally would like to archeve
    return unfilteredList.Where(x => x."fieldToQueryOn".ToString().ToLower().Contains(query.ToLower())).ToList();
}

and call :

var variable = FilterCompanies(NotNullFilledUnfilteredList, "CompanyCity", "New York")

I tried to follow the tutorial at docs.microsoft.com and it's easy, but I don't have clue how to extend that solution with reflection on Type and use it in an expression tree.

Accepted Answer

You can use Type.GetProperty to find a property by name using reflection, and then use GetValue to retrieve the value:

List<Company> FilterCompanies(List<Company> list, string propertyName, string query)
{
    var pi = typeof(Company).GetProperty(propertyName);

    query = query.ToLower();
    return list
        .Where(x => pi.GetValue(x).ToString().ToLower().Contains(query))
        .ToList();
}

You should probably add some error handling though in case someone uses a property that is invalid. For example, you could do (pi?.GetValue(x) ?? string.Empty).ToString().ToLower()… to be on the safe side.

I’ve also moved the query.ToLower() out of the lambda expression to make sure it only runs once. You can also try other case-insensitive ways to check whether query is a substring of the value to avoid having to convert any string. Check out the question “Case insensitive 'Contains(string)'” for more information.

Btw. if you are generally interested in running dynamic queries, you should take a look at dynamic LINQ.


Popular Answer

Generics and lambda:

namespace WhereTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var companies = new[] { new Company { Id = 1, Name = "abc" }, new Company { Id = 2, CompanyAddress1 = "abc" } };
            foreach (var company in FilterCompanies(companies, "abc", x => x.Name, x => x.CompanyCity))
            {
                Console.WriteLine(company.Id);
            }
        }

        static List<Company> FilterCompanies(IEnumerable<Company> unfilteredList, string query, params Func<Company, string>[] properties)
        {
            return unfilteredList.Where(x => properties.Any(c => c.Invoke(x) == query)).ToList();
        }
    }

    public class Company
    {
        public int Id { get; set; }
        public string Name { get; set; }
        public string CompanyAddress1 { get; set; }
        public string CompanyPostCode { get; set; }
        public string CompanyCity { get; set; }
        public string CompanyCounty { get; set; }
    }
}

Advantages: no reflection, strongly typed code.



Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why