简体   繁体   English

LINQ to Entities基于字符串的动态Where

[英]LINQ to Entities string based dynamic Where

I have an MVC site that utilizes the Kendo Grid and I'm attempting to implement dynamic filters. 我有一个利用Kendo Grid的MVC网站,我正在尝试实现动态过滤器。 The data I'm displaying contains several 1-to-many tables. 我正在显示的数据包含几个1对多表。 For example, I have a row of people, each person can have 0 or more Items assigned to them. 例如,我有一排人,每个人可以分配0个或更多项。 I'm displaying a flattened list in the grid: 我在网格中显示一个扁平列表:

Bob  | Item 1, Item 2
Jane | Item 3

If I were to hard-code the filter on the Items column, it would look like: 如果我要在Items列上对过滤器进行硬编码,它看起来像:

people.Where(p=> p.Items.Any(i=>i.name.contains("Item 1"))).ToList()

I want to come up with a generic way to build the expression tree so I can filter on different 1-to-many fields and also perform different comparisons (eg contains, startswith, equals, etc.). 我想提出一种通用的方法来构建表达式树,这样我就可以过滤不同的1对多字段并执行不同的比较(例如contains,startswith,equals等)。 Ideally I would have an extension method with the following syntax: 理想情况下,我会使用以下语法的扩展方法:

public static IQueryable<TEntity> Where( this IQueryable<TEntity> source, string tableName, string fieldName, string comparisonOperator, string searchVal) where TEntity : class

Then I could query on multiple one-to-many tables: 然后我可以查询多个一对多表:

if(searchOnItems)
    persons = persons.Where("Items", "name", "Contains", "item 1);
if(searchOnOtherTableName)
    persons = persons.Where("OtherTableName", "name", "Equals", "otherSearchValue);
persons.ToList();

I'm attempting to use LINQ to Entities string based dynamic OrderBy as a starting point since the concept is similar, but I can't figure out how to modify the GenerateSelector method. 我试图使用基于LINQ to Entities字符串的动态OrderBy作为起点,因为概念类似,但我无法弄清楚如何修改GenerateSelector方法。 Any ideas would be greatly appreciated. 任何想法将不胜感激。

Edit - My code is on a closed network, so I'll do my best replicating what I'm trying. 编辑 - 我的代码在一个封闭的网络上,所以我会尽我所能复制我正在尝试的东西。 Here is the code I'm attempting to modify. 这是我试图修改的代码。 The comment block is where I'm stuck. 评论块是我被困的地方。 The examples on calling the "Where" extension method above are still valid. 调用上述“Where”扩展方法的示例仍然有效。

public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source, string tableName, string fieldName, string comparisonOperator, string searchVal) where TEntity : class
{
    MethodCallExpression resultExp = GenerateMethodCall<TEntity>(source, "Where", tableName, fieldName, comparisonOperator, searchVal);
    return source.Provider.CreateQuery<TEntity>(resultExp) as IOrderedQueryable<TEntity>;
}

private static MethodCallExpression GenerateMethodCall<TEntity>(IQueryable<TEntity> source, string methodName, string tableName, String fieldName, string comparisonOperator, string searchVal) where TEntity : class
{
    Type type = typeof(TEntity);
    Type selectorResultType;
    LambdaExpression selector = GenerateSelector<TEntity>(tableName, fieldName, comparisonOperator, searchVal, out selectorResultType);
    MethodCallExpression resultExp = Expression.Call(typeof(Queryable), methodName,
                    new Type[] { type, selectorResultType },
                    source.Expression, Expression.Quote(selector));
    return resultExp;
}

private static LambdaExpression GenerateSelector<TEntity>(string tableName, String fieldName, string comparisonOperator, string searchVal, out Type resultType) where TEntity : class
{
    // Create a parameter to pass into the Lambda expression (Entity => Entity.OrderByField).
    var parameter = Expression.Parameter(typeof(TEntity), "Entity");

    PropertyInfo property = typeof(TEntity).GetProperty(tableName, BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public);;
    Expression propertyAccess = Expression.MakeMemberAccess(parameter, property);;

    /************************************************/
    //property is now "TEntity.tableName"
    //how do I go another step further so it becomes "TEntity.tableName.comparisonOperator(searchVal)"
    /************************************************/

    resultType = property.PropertyType;
    // Create the order by expression.
    return Expression.Lambda(propertyAccess, parameter);
}       

I'm attempting to use LINQ to Entities string based dynamic OrderBy as a starting point since the concept is similar, but I can't figure out how to modify the GenerateSelector method. 我试图使用基于LINQ to Entities字符串的动态OrderBy作为起点,因为概念类似,但我无法弄清楚如何修改GenerateSelector方法。

There is a significant difference between methods that expect selector like Select , OrderBy , ThenBy etc. versus the methods that expect predicate like Where , Any etc. The later cannot use the above GenerateMethodCall because it assumes 2 generic arguments ( new Type[] { type, selectorResultType } ) while the predicate methods use just 1 generic argument. 期望选择器SelectOrderByThenBy等的方法与期望谓词的方法(如WhereAny等)之间存在显着差异。后者不能使用上述GenerateMethodCall因为它假设有2个泛型参数( new Type[] { type, selectorResultType } )而谓词方法只使用1个泛型参数。

Here is how you can achieve your goal. 以下是如何实现目标的方法。 I've tried to make it in a way so you can follow each step of the expression building. 我试图以某种方式制作它,以便您可以按照表达式构建的每个步骤进行操作。

public static IQueryable<TEntity> Where<TEntity>(this IQueryable<TEntity> source, string collectionName, string fieldName, string comparisonOperator, string searchVal) where TEntity : class
{
    var entity = Expression.Parameter(source.ElementType, "e");
    var collection = Expression.PropertyOrField(entity, collectionName);
    var elementType = collection.Type.GetInterfaces()
        .Single(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEnumerable<>))
        .GetGenericArguments()[0];
    var element = Expression.Parameter(elementType, "i");
    var elementMember = Expression.PropertyOrField(element, fieldName);
    var elementPredicate = Expression.Lambda(
        GenerateComparison(elementMember, comparisonOperator, searchVal),
        element);
    var callAny = Expression.Call(
        typeof(Enumerable), "Any", new[] { elementType },
        collection, elementPredicate);
    var predicate = Expression.Lambda(callAny, entity);
    var callWhere = Expression.Call(
        typeof(Queryable), "Where", new[] { entity.Type },
        source.Expression, Expression.Quote(predicate));
    return source.Provider.CreateQuery<TEntity>(callWhere);
}

private static Expression GenerateComparison(Expression left, string comparisonOperator, string searchVal)
{
    var right = Expression.Constant(searchVal);
    switch (comparisonOperator)
    {
        case "==":
        case "Equals":
            return Expression.Equal(left, right);
        case "!=":
            return Expression.NotEqual(left, right);
    }
    return Expression.Call(left, comparisonOperator, Type.EmptyTypes, right);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM