Dynamic Linq query Contains List

11,622

Solution 1

You could write something like this that builds your query function dynamically:

public static Func<ObjT, bool> PropertyCheck<ObjT, PropT>(string propertyName, Expression<Func<PropT, bool>> predicate)
{
    var paramExpr = Expression.Parameter(typeof(ObjT));
    var propExpr = Expression.Property(paramExpr, propertyName);
    return Expression.Lambda<Func<ObjT, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile();
}

Then, it could be used like this:

foos.Where(PropertyCheck<Foo, int>("MyId", x => idList.Contains(x)));

Of course, you could also just provide your own Where extension method that does all that at once:

public static IEnumerable<T> Where<T, PropT>(this IEnumerable<T> self, string propertyName, Expression<Func<PropT, bool>> predicate)
{
    var paramExpr = Expression.Parameter(typeof(T));
    var propExpr = Expression.Property(paramExpr, propertyName);
    return self.Where<T>(Expression.Lambda<Func<T, bool>>(Expression.Invoke(predicate, propExpr), paramExpr).Compile());
}
foos.Where<Foo, int>("MyId", x => idList.Contains(x));

Solution 2

You could use the expressions to do this dynamic query, try something like this, for sample:

import these namespaces:

using System.Linq;
using System.Linq.Expressions;
using System.Reflection;

And try this:

// a reference parameter
var x = Expression.Parameter(typeof (YourType), "x");

// contains method
var containsMethod = typeof (string).GetMethod("Contains", new[] {typeof (string)});

// reference a field
var fieldExpression = Expression.Property(instance, "PropertyName");

// your value
var valueExpression = Expression.Constant(yourId);

// call the contains from a property and apply the value
var containsValueExpression = Expression.Call(fieldExpression, containsMethod, valueExpression);

// create your final lambda Expression
var filterLambda = Expression.Lambda<Func<YourType, bool>>(containsValueExpression, x);

// apply on your query
q = q.Where(finalLambda);

Obs: make sure your property has a method called contains.

Solution 3

If you look at the source of Dynamic LINQ then you can see that parsing in many cases depends on variable predefinedTypes.

In your case you need change this variable like this

static readonly Type[] predefinedTypes = {
    ....
    ,typeof(List<int>)
};

after that next code will be work

List<int> idList = new List<int> { 1, 5, 6};
....
string someId = "CustomId";
q = q.Where("@0.Contains(" + someId + ")", idList);

Solution 4

@Felipe Oriani in his 90% answer used the string.Contains method and the yourId single value, but asked was:

q = q.Where(a => idList.Contains(a.MyId));

which is member (property) access to the a.

So here is the final tested extension method:

/// <summary>
/// Creates lambda expression predicate: (TEntity entity) => collection.Contains(entity.property)
/// </summary>
public static Expression<Func<TEntity, bool>> ContainsExpression<TEntity, TProperty, TCollection>(
    this TCollection collection, 
    Expression<Func<TEntity, TProperty>> property
)
    where TCollection : ICollection<TProperty>
{
    // contains method
    MethodInfo containsMethod = typeof(TCollection).GetMethod(nameof(collection.Contains), new[] { typeof(TProperty) });

    // your value
    ConstantExpression collectionInstanceExpression = Expression.Constant(collection);

    // call the contains from a property and apply the value
    var containsValueExpression = Expression.Call(collectionInstanceExpression, containsMethod, property.Body);

    // create your final lambda Expression
    Expression<Func<TEntity, bool>> result = Expression.Lambda<Func<TEntity, bool>>(containsValueExpression, property.Parameters[0]);

    return result;
}

The example:

List<int> idList = new List<int> { 1, 5, 6 };

Expression<Func<MyEntity,int>> idExpression = entity => entity.Id;
var contains = idList.ContainsExpression(idExpression)

IQueryable<MyEntity> q = DbContext.Set<MyEntity>().Where(contains);
Share:
11,622

Related videos on Youtube

borisdj
Author by

borisdj

IT consultant, .NET developer, believer in science and technology

Updated on June 30, 2022

Comments

  • borisdj
    borisdj almost 2 years

    I am using dynamic Linq for generic search. I have list of Ids:

    List<int> idList = new List<int> { 1, 5, 6};
    

    In plain Linq, I would write:

    q = q.Where(a => idList.Contains(a.MyId));
    

    But now I have to use System.Linq.Dynamic because I don't know in advance name of the column.

    string someId = "CustomId";
    q = q.Where("@0"+ ".Contains(" + someId + ")", idList.ToArray());
    

    But this gives error:

    "No applicable method 'Contains' exists in type 'Int32'"

    How can I achieve this?

    Is there some extension library that implements Contains for dynamic Linq or some other way.

  • borisdj
    borisdj about 10 years
    Where would put these predefinedTypes. Did you mean to override DynamicLinq.cs class or to extended these types somehow?
  • Grundy
    Grundy about 10 years
    @Boris i mean edit this variable in DynamicLinq.cs
  • borisdj
    borisdj about 10 years
    I have installed Linq.Dynamic via nuget and it is a library so I can't edit code. I know I can download it's source from github and then change it but I would prefer not having to do that.
  • Grundy
    Grundy about 10 years
    @Boris in some cases it may be useful :-)
  • borisdj
    borisdj about 10 years
    that I agree. +1 for usefulness.
  • Franki1986
    Franki1986 almost 7 years
    Is there a way to create it in a string only way ? Something like q = q.Where("(new List<int>{ 1, 5, 6}).Contains(outerIt.CustomId));
  • Grundy
    Grundy almost 7 years
    @Franki1986, not sure, that DynamicLinq can parse all language construction, so possibly you can do this with a bit another syntax, or can't. But you can try :) I not watch to this library, so possibly even this workaround not needed
  • Franki1986
    Franki1986 almost 7 years
    Sorry for the bad question, I tried it but this does not work, I mean is this possible with another syntax.. Theretically this is nothing but a static array..
  • Grundy
    Grundy almost 7 years
    @Franki1986, but to parse it, library can be more complicated, so seems, workaround with parameter is one way, to pass constant list to query. But, as i say before, possibly something has changed now :)
  • Robin Ding
    Robin Ding over 3 years
    I like this solution more. But it has problem when I use string[] instead of List<string>, because it cant find the Contains method. I fix the issue by changing to var containsMethod = typeof(ICollection<TProperty>).GetMethod(nameof(ICollection<‌​TProperty>.Contains)‌​, new[] { typeof(TProperty) });