简体   繁体   English

创建要在IQueryable上应用的动态搜索的最简单方法

[英]Simplest way to create a dynamic search to be applied on IQueryable

Consider the case of having a single table (called Car) with the following columns and the corresponding entity 考虑只有一个表(称为Car)的情况,该表包含以下各列和相应的实体

string Make
string Model
string Owner

Now I want to create a search where the user can choose (by the use of checkboxes) which properties the search should be targetted to. 现在,我想创建一个搜索,用户可以在其中选择(通过使用复选框)搜索应针对的属性。 If more than one is chosen, then it should be sufficient if the search string is found in at least one of them. 如果选择多个,则至少在其中一个中找到搜索字符串就足够了。

Moreover, if multiple search strings are given (separated by a space), the search should match only if every word is found (so for example, given search string "ter mist", a car with an owner "mister" would match). 此外,如果给出了多个搜索字符串(用空格分隔),则仅当找到每个单词时,搜索才应匹配(例如,给定搜索字符串“ ter mist”,则拥有者“ mister”的汽车将匹配)。

After doing some research, I figured I'd create a list of Expression<Func<Car, bool>> for each of the chosen properties, add one for each word in the search string and then And all these together to create a single Expression<Func<Car, bool>> . 经过一些研究,我想为每个选定的属性创建一个Expression<Func<Car, bool>>的列表,为搜索字符串中的每个单词添加一个,然后将所有这些共同创建一个Expression<Func<Car, bool>> Once I had these for all the chosen propeties, I would Or them together, to create the final filter. 一旦为所有选定的属性获取了这些属性,我将与它们一起创建最终的过滤器。 This, however, is where I am struggling. 但是,这就是我在努力的地方。

In the end, the furthest I got was a NotSupportedException saying The LINQ expression node type 'Invoke' is not supported in LINQ to Entities. 最后,我得到的最远的是一个NotSupportedException,它说The LINQ expression node type 'Invoke' is not supported in LINQ to Entities.

Here are the helper functions I have for doing the combining (found from http://social.msdn.microsoft.com/Forums/en-US/linqprojectgeneral/thread/60a1f4c0-d4d9-4143-91aa-79d29dde7a7c/ ): 这是我用于合并的辅助函数(可从http://social.msdn.microsoft.com/Forums/en-US/linqprojectgeneral/thread/60a1f4c0-d4d9-4143-91aa-79d29dde7a7c/找到):

public static Expression<Func<T, bool>> Or<T>(params Expression<Func<T, bool>>[] predicates)
    {
        if (predicates.Length == 1)
            return predicates[0];

        Expression<Func<T, bool>> result = predicates[0];
        for (int i = 1; i < predicates.Length; i++)
        {
            result = OrTwo(result, predicates[i]);
        }

        return result;
    }
private static Expression<Func<T, bool>> OrTwo<T>(Expression<Func<T, Boolean>> expr1, Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return (Expression.Lambda<Func<T, Boolean>>(Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters));
    }

This is all getting surprisingly confusing as well, so I'm starting to think that there must be an easier way to go about this. 这一切也令人感到困惑,所以我开始认为必须有一种更简单的方法来解决这个问题。 So, what would be the easiest way to solve this? 那么,解决这个问题的最简单方法是什么?


Solution

After trying several things (LINQKit, Albahari's PredicateBuilder , fiddling with the expression trees myself), I finally ended up here . 在尝试了几种方法(LINQKit, Albarhari的PredicateBuilder ,自己摆弄表达式树)之后,我终于到了这里 This universal version of the PredicateBuilder works without any other external dependencies, and is fully compatible with EF. PredicateBuilder的此通用版本无需任何其他外部依赖项即可工作,并且与EF完全兼容。 It made solving the problem really trivial. 这使得解决这个问题确实不容易。

I decided to solve this problem myself a few weeks ago. 几周前,我决定自己解决此问题。

Check out my blog post: 查看我的博客文章:

http://jnye.co/Posts/5/generic-repository-search-function-with-expression-trees http://jnye.co/Posts/5/generic-repository-search-function-with-expression-trees

Link Update: I have now created a new post which adds the search functionality as a extension method to IQueryable: 链接更新:我现在创建了一个新帖子,它将搜索功能作为扩展方法添加到IQueryable:

http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable http://jnye.co/Posts/6/c%23-generic-search-extension-method-for-iqueryable

You should be able to adapt it to your needs. 您应该能够使其适应您的需求。

Updated 更新

Below is an extract of the Search function I created. 以下是我创建的搜索功能的摘录。

/// <summary>  
/// Performs a search on the supplied string property  
/// </summary>  
/// <param name="stringProperty">Property to search upon</param>  
/// <param name="searchTerm">Search term</param>  
public virtual IQueryable<T> Search(Expression<Func<T, string>> stringProperty, string searchTerm)  
{  
    var source = this.RetrieveAll();  

    if (String.IsNullOrEmpty(searchTerm))  
    {  
        return source;  
    }  

    //Create expression to represent T.[property] != null  
    var isNotNullExpression = Expression.NotEqual(stringProperty.Body, Expression.Constant(null));  

    //Create expression to represent T.[property].Contains(searchTerm)  
    var searchTermExpression = Expression.Constant(searchTerm);  
    var checkContainsExpression = Expression.Call(stringProperty.Body, typeof(string).GetMethod("Contains"), searchTermExpression);  

    //Join not null and contains expressions  
    var notNullAndContainsExpression = Expression.AndAlso(isNotNullExpression, checkContainsExpression);  

    //Build final expression  
    var methodCallExpression = Expression.Call(typeof (Queryable),   
                                               "Where",   
                                               new Type[] {source.ElementType},   
                                               source.Expression,   
                                               Expression.Lambda<Func<Club, bool>>(notNullAndContainsExpression, stringProperty.Parameters));  

    return source.Provider.CreateQuery<T>(methodCallExpression);  
}  

You should be able to refactor the code that generates the methodCallExpression to create multiple search expressions which you can then combine using Expression.OrElse() . 您应该能够重构生成methodCallExpression的代码,以创建多个搜索表达式,然后可以使用Expression.OrElse()进行组合。

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

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