Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamic Linq query Contains List

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.

like image 291
borisdj Avatar asked Apr 07 '14 13:04

borisdj


4 Answers

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));
like image 164
poke Avatar answered Nov 15 '22 09:11

poke


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.

like image 32
Felipe Oriani Avatar answered Nov 15 '22 10:11

Felipe Oriani


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);
like image 37
Grundy Avatar answered Nov 15 '22 11:11

Grundy


@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);
like image 1
Motlicek Petr Avatar answered Nov 15 '22 11:11

Motlicek Petr