Linq to SQL Where子句基于运行时选择的字段

时间:2013-11-04 15:51:10

标签: c# linq-to-sql

我正在尝试使用LINQ to SQL创建一个简单的可重用搜索。

我传入一个在搜索框中输入的单词列表。然后根据此标准过滤结果。

  private IQueryable<User> BasicNameSearch(IQueryable<User> usersToSearch, ICollection<string> individualWordsFromSearch)
  {
        return usersToSearch
            .Where(user => 
            individualWordsFromSearch.Contains(user.Forename.ToLower()) 
            || individualWordsFromSearch.Contains(user.Surname.ToLower()));
  }

现在,我希望在不同的数据源上使用相同的搜索功能,并希望动态选择要应用搜索的字段。例如,代替用户的IQueryable,我可能有一个IQueryable of Cars而不是名字和姓氏,搜索从Make和Model开始。基本上,目标是通过动态选择在运行时搜索的内容来重用搜索逻辑。

2 个答案:

答案 0 :(得分:2)

您可以创建一个扩展方法,将string选择器一起编译为一个表达式:

public static class CompileExpressions
{
    public static IQueryable<T> SearchTextFieldsOr<T>(this IQueryable<T> source,
        ICollection<string> wordsFromSearch, params Func<T, string>[] stringSelectors)
    {
        Expression<Func<T, bool>> compiledExpression = t => false;

        foreach (var filter in stringSelectors)
        {
            compiledExpression = compiledExpression.Or(t => wordsFromSearch.Contains(filter(t)));
        }

        var compiled = compiledExpression.Compile();

        return source.Where(t => compiled(t));
    }

    public static IQueryable<T> SearchTextFieldsAnd<T>(this IQueryable<T> source,
        ICollection<string> wordsFromSearch, params Func<T, string>[] stringSelectors)
    {
        foreach (var filter in stringSelectors)
        {
            source = source.Where(t => wordsFromSearch.Contains(filter(t)));
        }

        return source;
    }

    //Taken from http://www.albahari.com/nutshell/predicatebuilder.aspx
    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1,
                                                Expression<Func<T, bool>> expr2)
    {
        var invokedExpr = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>
              (Expression.OrElse(expr1.Body, invokedExpr), expr1.Parameters);
    }
}

如何使用它的一个例子:

public class Entity
{
    public string Name { get; set; }
    public string Type { get; set; }
    public string Model { get; set; }
    public string Colour { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        var source = new[]{
            new Entity { Colour = "Red", Model = "New", Name="Super", Type="Filter"},
            new Entity { Colour = "Green", Model = "New", Name="Super", Type="Filter"},
            new Entity { Colour = "Green", Model = "New", Name="Super", Type="Filter"},
            new Entity { Colour = "Green", Model = "New", Name="Super", Type="Filter"},
            new Entity { Colour = "Green", Model = "New", Name="Super", Type="Filter"},
            new Entity { Colour = "Green", Model = "New", Name="Super", Type="Amazing"},
        };

        var filters = new[] {"Red", "Amazing" };

        var filteredOr = source
               .AsQueryable()
               .SearchTextFieldsOr(filters, t => t.Colour, t => t.Type)
               .ToList();

        //2 records found because we're filtering on "Colour" OR "Type"

        var filteredAnd = source
               .AsQueryable()
               .SearchTextFieldsAnd(filters, t => t.Colour, t => t.Type)
               .ToList();

         //1 record found because we're filtering on "Colour" AND "Type"

    }
}

由于您的string选择参数的类型为params Func<T, string>[],因此您可以添加任意数量的string选择器,以便包含在查询中。

答案 1 :(得分:0)

您的问题与this thread's类似(但不是同一个问题)。

简而言之,当您编写linq-to-sql请求时,它只构建一个System.Linq.Expression,对应于您输入的实际代码,该代码将提供给“provider”,后者将其转换为sql(您可以通过将您的请求转发给具有Provider属性的IQueryable来获得此提供程序。

实际上,形成你请求的代码永远不会被“执行”,实际上甚至不会像你传递给Linq-to-objects函数的委托那样编译成IL。 (使用System.Linq.Enumerable扩展方法,而linq-to-sql使用System.Linq.Queryable个扩展方法

因此,您也可以“手动”创建linq表达式(而不是让c#编译器为您构建它),并将它们传递给提供程序,提供程序将解析并执行它们,就像您使用常规创建它们一样方式。

参见上面给出的主题中的例子。

编辑或者您可以查看Oliver的回答,他给了您一个复制粘贴运行的示例:)