How to construct Where Expression dynamically in E

2020-02-12 05:09发布

问题:

I've taken a look at this answer on how to dynamically create an OrderBy expression in Entity Framework. But I'd like to also build a dynamic Where expression. Something along the lines of this:

public IEnumerable<InventoryItem> GetAll(string filterBy, object value)
{
  var results = new List<InventoryItem>();
  using (var db = new InventoryDb())
  {
    if (QueryHelper.PropertyExists<InventoryItem>(filterBy)) 
    {
      var query = db.rri_InventoryItems.WhereByProperty(filterBy, value); 

      foreach(var item in query.Where(expr))
      {
        results.Add(ConvertItem(item));
      }   
    }            
  }
  return results;
}

Passing in the property to filter by and a value as ab object. Queryable has two methods for Where that both take two parameters, so I am not even sure which is the proper one.

And it's at this point I get a little more lost. I'm not sure how to refactor the original OrderByProerty method to provide a WhereByProperty. I know what I have here is completely wrong. I'm not sure what to do with it.

Ideally, I'd want to extend this even more by providing a collection of objects that could be used to build a query with ands and or operators.

回答1:

Queryable has two methods for Where that both take two parameters, so I am not even sure which is the proper one.

You need the one that receives Expression<Func<T, bool>> predicate.

Here is how you can build dynamically a predicate similar to (T item) => item.Property == value:

public static partial class QueryableExtensions
{
    public static IQueryable<T> WhereEquals<T>(this IQueryable<T> source, string member, object value)
    {
        var item = Expression.Parameter(typeof(T), "item");
        var memberValue = member.Split('.').Aggregate((Expression)item, Expression.PropertyOrField);
        var memberType = memberValue.Type;
        if (value != null && value.GetType() != memberType)
            value = Convert.ChangeType(value, memberType);
        var condition = Expression.Equal(memberValue, Expression.Constant(value, memberType));
        var predicate = Expression.Lambda<Func<T, bool>>(condition, item);
        return source.Where(predicate);
    }
}

I've tried to write it in such a way so you can step over the code in order to understand what it does. The only line that might need some explanation is:

var memberValue = member.Split('.').Aggregate((Expression)item, Expression.PropertyOrField);

This is a simple way of handling nested properties like obj.Prop1.Prop2 etc. If you don't need such capability, you can simply use this instead:

var memberValue = Expression.PropertyOrField(item, member);


回答2:

I didn't need nested properties (yet). I modified your code slightly and have this that is working:

    public static IQueryable<T> WhereEquals<T>(
       this IQueryable<T> source, string propertyName, object value)
    {
        if (typeof(T).GetProperty(propertyName, BindingFlags.IgnoreCase |
            BindingFlags.Public | BindingFlags.Instance) == null)
        {
            return null;
        }

        ParameterExpression parameter = Expression.Parameter(typeof(T), "item");
        Expression whereProperty = Expression.Property(parameter, propertyName);
        Expression constant = Expression.Constant(value);
        Expression condition = Expression.Equal(whereProperty, constant);
        Expression<Func<T, bool>> lambda = Expression.Lambda<Func<T, bool>>(condition,parameter);
        return source.Where(lambda);
    }