2

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 learn.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.

johnnyRose
  • 7,310
  • 17
  • 40
  • 61
Lightning3
  • 375
  • 7
  • 18
  • Possible duplicate of [Query a collection using PropertyInfo object in LINQ](https://stackoverflow.com/questions/11431546/query-a-collection-using-propertyinfo-object-in-linq) – Amit Kumar Singh Sep 27 '17 at 14:47

3 Answers3

5

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.

Backs
  • 24,430
  • 5
  • 58
  • 85
5

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.

poke
  • 369,085
  • 72
  • 557
  • 602
  • 2
    good answer, instead of ToLower you could use `OrdinalIgnoreCase` – M.kazem Akhgary Sep 27 '17 at 14:51
  • Yes [`IndexOf` and Ignore case is going to be more efficient](https://stackoverflow.com/a/15464440/542251) than `ToLower`(ing) everything. – Liam Sep 27 '17 at 15:30
  • Thanks! I Didn't expect that simple reflection will allow me to accomplish the task. I Marked your answer as accepted because its strict in typecasting and doesn't require extension methods – Lightning3 Sep 28 '17 at 07:36
0

You can use GetProperty combined with GetValue

List<Company> FilterCompanies(List<Company> unfilteredList, string fieldToQueryOn, string query)
{
   return unfilteredList
       .Where(x => x.GetType.GetProperty(fieldToQueryOn).GetValue(x)
       .ToString().ToLower().Contains(query.ToLower())).ToList();
}

OR: property accessors using string (same as javascript obj[property])

You can modify your class:

public class Company
{
    // just add this code block to all your classes that would need to access
    // your function
    public object this[string propertyName] 
    {
        get{
            Type myType = typeof(Company);                   
            PropertyInfo myPropInfo = myType.GetProperty(propertyName);
            return myPropInfo.GetValue(this, null);
        }
        set{
            Type myType = typeof(Company);                   
            PropertyInfo myPropInfo = myType.GetProperty(propertyName);
            myPropInfo.SetValue(this, value, null);
        }

    }

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

and then you can change your function like this:

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

Check this Demo

NOTE:

In order for your function to work, you need to add this code to your classes:

public object this[string propertyName] 
{
    get{
        Type myType = typeof(<YOUR CLASS HERE>);                   
        PropertyInfo myPropInfo = myType.GetProperty(propertyName);
        return myPropInfo.GetValue(this, null);
    }
    set{
        Type myType = typeof(<YOUR CLASS HERE>);                   
        PropertyInfo myPropInfo = myType.GetProperty(propertyName);
        myPropInfo.SetValue(this, value, null);
    }

}

Bonus: you can now retrieve values using myObject["someproperty"] and you can even set their values!

xGeo
  • 2,149
  • 2
  • 18
  • 39