Dynamic Linq query Contains List
Asked Answered
M

5

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.

Magnus answered 7/4, 2014 at 13:14 Comment(3)
Use the expression tree.Bernice
Duplicate #13651580Syllogistic
The expression tree is solution as described in accepted answer below. @Syllogistic 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.Magnus
H
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));
Halle answered 7/4, 2014 at 13:42 Comment(0)
C
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.

Candice answered 7/4, 2014 at 13:21 Comment(0)
C
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);
Considered answered 7/4, 2014 at 15:10 Comment(9)
Where would put these predefinedTypes. Did you mean to override DynamicLinq.cs class or to extended these types somehow?Magnus
@Boris i mean edit this variable in DynamicLinq.csConsidered
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.Magnus
@Boris in some cases it may be useful :-)Considered
that I agree. +1 for usefulness.Magnus
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));Mm
@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 neededConsidered
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..Mm
@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 :)Considered
E
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);
Epilimnion answered 21/2, 2019 at 11:0 Comment(1)
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) });Turbo
B
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.

Barretter answered 29/11, 2017 at 1:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.