简体   繁体   中英

C# Linq Filter IEnumerable dynamic property and value

public class Sample
{
public int Id { get; set; }
public string Description { get; set; }
public DateTime EffectiveDate { get; set; }
}

IEnumberable<Sample> sampleList;
//Populate the list

Now i want to filter the list some times by "Id" property, sometimes "Description" property. Just want to pass the property name (filterColumn) and property value (filterValue) both as strings .

I tried the following:

IEnumerable<Sample> result = sampleList.Where(x => x.GetType().GetProperty(filterColumn).Name == filterValue);

AND

string whereQuery = string.Format(" {0} = \"{1}\"", filterColumn, filterValue);
IEnumerable<Sample> result = sampleList.AsQueryable().Where(whereQuery);

The second option works, if i pass the filterColumn as "Description", but throws incomptable '=' operator between string and int error when "Id" is passed as filterColumn and some filterValue like "1".

Appreciate any help. Thanks

Your first approach can work. Expanding on Jon Skeet's comment, here's the adjusted statement.

IEnumerable<Sample> result = sampleList.Where(
  x => x.GetType().GetProperty(filterColumn).GetValue(x, null).Equals(filterValue)
);

To put a little context around this, you would have to allow for the differing data types. You can do this at least two ways: use a generic method or use the object data type. For illustrative purposes, I'll use the object approach.

public IEnumerable<Sample> GetFiltered(
  IEnumerable<Sample> samples, string filtercolumn, object filtervalue
{ 
   return samples.Where(
      x => x.GetType().GetProperty(filtercolumn).GetValue(x, null).Equals(filtervalue)
   );
}

IEnumberable<Sample> sampleList;

var byId = GetFiltered(sampleList, "Id", 100);
var byDescription = GetFiltered(sampleList, "Description", "Some Value");

This example is not really safe as there is no type checking to ensure that the property value will be of the same data type that you are passing in. For example, there is nothing stopping you from passing "Description" and 100 as parameters. You can't do a meaningful comparison between an integer and a string so you will always come up with an empty result. The Equals method does not throw an exception, it just sees that the two objects are different. As Jon pointed out, you'll always want to use Equals in this case rather than the "==" operator. The Equals method is intended to compare content while "==" compares references. Example:

Console.WriteLine(12 == 12); 
// True

object a = 12;
object b = 12;

Console.WriteLine(a == b); 
// False - because, due to boxing, a and b are separate objects
// that happen to contain the same value. (Check out "boxing" 
// if this doesn't make sense.)

Console.WriteLine(a.Equals(b)); 
// True - because the Equals method compares content (value)

Also, note that strings have some special behaviors when using the "==" operator. The important thing to remember is that there is a difference between references (containers) and content. You want to compare content and that means Equals. (I have noticed that the Immediate window in Visual Studio is inconsistent in its results regarding strings when using "==". I suspect this is because string references can be, but are not always, optimized in that window.)

You state that your second approach works. I have not seen this type of filter string in a standard IEnumerable.Where method. So I am guessing that you are using some extensions. Your example does not work as shown. The DataTable class uses filter strings that match your usage. In general, a filter string has to be constructed in different ways based on data type. For example, a string requires the quotes (which you have) but an integer value does not use quotes.

Another option that you have is to set up a dictionary with the required operations.

public IEnumerable<Sample> GetFiltered(
    IEnumerable<Sample> samples, string property, string value)
{
    var map = new Dictionary<string, Func<string, Func<Sample, bool>>>()
    {
        { "Description", v => s => s.Description == v },
        { "Id", v => s => s.Id == int.Parse(v) },
    };
    return samples.Where(map[property](value));
}

The advantage here is that you can perform a more complex comparison, such as adding custom filters by ranges of values, or those containing more than one property.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM