7

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
  • 2,201
  • 3
  • 24
  • 31
  • Use the expression tree. – Hamlet Hakobyan Apr 07 '14 at 13:21
  • Duplicate http://stackoverflow.com/questions/13651080/dyanmic-linq-failing-when-using-contains-against-int-field – Vladimir Apr 07 '14 at 13:32
  • The expression tree is solution as described in accepted answer below. @pil0t question may be duplicate but the answer is not. There the only given solution required changes to the library, while here I got answer how to just extend it easily with Expression. – borisdj Apr 08 '14 at 10:58

5 Answers5

6

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));
Hamlet Hakobyan
  • 32,965
  • 6
  • 52
  • 68
poke
  • 369,085
  • 72
  • 557
  • 602
4

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.

Felipe Oriani
  • 37,948
  • 19
  • 131
  • 194
1

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);
Grundy
  • 13,356
  • 3
  • 35
  • 55
  • Where would put these predefinedTypes. Did you mean to override DynamicLinq.cs class or to extended these types somehow? – borisdj Apr 08 '14 at 08:56
  • @Boris i mean edit this variable in `DynamicLinq.cs` – Grundy Apr 08 '14 at 09:20
  • 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. – borisdj Apr 08 '14 at 10:21
  • @Boris in some cases it may be useful :-) – Grundy Apr 08 '14 at 10:29
  • that I agree. +1 for usefulness. – borisdj Apr 08 '14 at 10:38
  • Is there a way to create it in a string only way ? Something like q = q.Where("(new List{ 1, 5, 6}).Contains(outerIt.CustomId)); – Franki1986 May 11 '17 at 09:31
  • @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 – Grundy May 11 '17 at 09:33
  • 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.. – Franki1986 May 11 '17 at 09:34
  • @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 :) – Grundy May 11 '17 at 09:38
1

@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);
Motlicek Petr
  • 767
  • 9
  • 10
  • 1
    I like this solution more. But it has problem when I use string[] instead of List, because it cant find the Contains method. I fix the issue by changing to ```var containsMethod = typeof(ICollection).GetMethod(nameof(ICollection.Contains), new[] { typeof(TProperty) });``` – Robin Ding Jan 19 '21 at 05:43
0

Another way to skin this cat would be to convert the contains to ORs.

someArray.Constains(someField) is equivalent to:

someField == someArray[0] or someField == someArray[1] and so on.

It's not ideal but if the array is small it could work.

boggy
  • 3,674
  • 3
  • 33
  • 56