有没有一种简单的方法在LINQ to Entities中编写自定义函数?

时间:2012-05-07 15:26:25

标签: c# linq entity-framework linq-to-entities

我正在为我的Entity Framework应用程序编写一个简单的搜索查询。我需要检查一堆字段是否为空,如果没有,请调用它们上的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实体不理解SafeSearch功能是什么:

  

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

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

谢谢!

2 个答案:

答案 0 :(得分:2)

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

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

通过在查询时调用SafeSearch扩展方法来反转它将处理x.Name为null的情况。

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

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

return source.Where(Predicate(q));

或使用linq表达式

return from p in source
       where Predicate(q).Invoke(p)
       select p;

答案 1 :(得分:1)

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

要继续,您需要一个包含3个简单函数的库。他们使用System.Linq.Expressions.ExpressionVisitor动态修改表达式。关键特性是统一表达式中的参数,以使具有相同名称的2个参数相同(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函数,但为您提供了可比较的功能。