Dynamic Linq query Contains List

爷,独闯天下 提交于 2019-11-29 10:16:29

问题


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.


回答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));



回答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.




回答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);



回答4:


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.




回答5:


@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);


来源:https://stackoverflow.com/questions/22913540/dynamic-linq-query-contains-list

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!