Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to construct Where Expression dynamically in Entity Framework?

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.

like image 358
Greg Kopp Avatar asked Aug 27 '16 15:08

Greg Kopp


People also ask

Can we use dynamic in lambda expression?

In 2010, the Dynamic Type was introduced and that gave us the ability to create dynamic lambda expressions.

What is Dynamic LINQ?

The Dynamic LINQ library exposes a set of extension methods on IQueryable corresponding to the standard LINQ methods at Queryable, and which accept strings in a special syntax instead of expression trees.

What is LINQ expression in C#?

Language-Integrated Query (LINQ) is the name for a set of technologies based on the integration of query capabilities directly into the C# language. Traditionally, queries against data are expressed as simple strings without type checking at compile time or IntelliSense support.


2 Answers

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);
like image 185
Ivan Stoev Avatar answered Sep 24 '22 16:09

Ivan Stoev


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);
    }
like image 34
Greg Kopp Avatar answered Sep 21 '22 16:09

Greg Kopp