10

我正在为我的实体框架应用程序编写一个简单的搜索查询。我需要检查一堆字段是否为空,如果不是,则对它们调用 ToLower() 并与搜索查询进行比较。LINQ 查询如下所示:

public IQueryable<Store> SearchStores(string q, IQueryable<Store> source)
{
    q = q.ToLower();

    return (
        from s in source
        where (
            (s.Name != null && s.Name.ToLower().Contains(q)) ||
            (s.Description != null && s.Description.ToLower().Contains(q)) ||
            ...
}

有很多这样的行,所以我很想写一个辅助方法来清理它:

public static bool SafeSearch(this string s, string q)
{
    return s == null ? false : s.ToLower().Contains(q);
}

但是,这当然不起作用,因为 LINQ to entity 不了解 SafeSearch 功能是什么:

LINQ to Entities 无法识别方法 'Boolean SafeSearch(System.String, System.String)' 方法,并且此方法无法转换为存储表达式。

有没有一种简单的方法来编写这样一个简单的自定义函数?

谢谢!

4

2 回答 2

4

由于 linq 使用在您实际调用数据库之前不会执行的表达式,因此您需要将函数包装在谓词中。

private static Func<Country, bool> Predicate(string q)
{
    return x => (
        q.SafeSearch(x.Name) ||
        q.SafeSearch(x.Description)
        );
}

此外,通过在查询中调用 SafeSearch 扩展方法来反转它,将处理 x.Name 为空的情况。

public static class SearchExt
{
    public static bool SafeSearch(this string q, string param)
    {
        return param == null ? false : param.ToLower().Contains(q);
    }
}

然后你可以将它与扩展方法一起使用

return source.Where(Predicate(q));

或使用 linq 表达式

return from p in source
       where Predicate(q).Invoke(p)
       select p;
于 2012-05-07T16:26:40.777 回答
4

有一种方法可以准备动态查询和条件,也可以使用函数来构建它们的一部分。语法也是可读的,这适用于问题的“简单”部分。可以通过组合 Linq 表达式来实现。有几篇关于如何做到这一点的文章,但我想我想出了一种新方法。至少我没有在网上找到它。

要继续,您需要一个包含 3 个简单函数的库。它们用于System.Linq.Expressions.ExpressionVisitor动态修改表达式。关键特性是统一表达式内的参数,以便将两个同名的参数设为相同 ( UnifyParametersByName)。剩下的部分是用给定的表达式 ( ReplacePar) 和辅助方法 ( NewExpr) 替换命名参数。该库在 github 上通过 MIT 许可证提供:LinqExprHelper,但您可以快速自己编写一些东西。

首先,您定义一些方法,这些方法稍后可能用于创建动态查询。

public class Store
{
    ...

    public static Expression<Func<Store, bool>>
        SafeSearchName(string sWhat)
    {
        return LinqExprHelper.NewExpr(
            (Store s) => s.Name != null && s.Name.ToLower().Contains(sWhat)
        );
    }

    public static Expression<Func<Store, bool>>
        SafeSearchDesc(string sWhat)
    {
        return LinqExprHelper.NewExpr(
            (Store s) => s.Description != null && s.Description.ToLower().Contains(sWhat)
        );
    }
}

然后你这样查询:

    // Define a master condition, using named parameters.
    var masterExpr = LinqExprHelper.NewExpr(
        (Store s, bool bSearchName, bool bSearchDesc)
        => (bSearchName && bSearchDesc));

    // Replace stub parameters with some real conditions.
    var combExpr = masterExpr
        .ReplacePar("bSearchName", Store.SafeSearchName("b").Body)
        .ReplacePar("bSearchDesc", Store.SafeSearchDesc("p").Body);
        // Sometimes you may skip a condition using this syntax:
        //.ReplacePar("bSearchDesc", Expression.Constant(true));

    // It's interesting to see how the final expression looks like.
    Console.WriteLine("expr: " + combExpr);

   // Execute the query using combined expression.
   db.Stores
        .Where((Expression<Func<Store, bool>>)combExpr)
        .ToList().ForEach(i => { Console.WriteLine(i.Name + ", " + i.Description); });

我还没有在生产中使用它,但是通过了一些简单的测试。我认为以这种方式组合查询没有任何限制。如果我们需要更多参数,我们可以附加额外的组合级别。这种方法的优点是可以使用内联的 lambda 表达式,读起来很舒服,同时动态表达式的创建和组合非常有能力。

毕竟它“简单”吗?如果您认为Linq 的方法语法很简单,那么这几乎就是那么简单。它不允许您创建自定义 Linq 函数,但为您提供了类似的功能。

于 2016-10-16T18:58:51.383 回答