将整个函数作为lambda表达式传递给LINQ

时间:2016-07-11 05:26:25

标签: c# linq lambda

我从我的数据库获得拍卖:

var auctions = from o in db.auctions select o;

我想将此函数作为我的linq的.Where子句中的lambda表达式传递来过滤我的拍卖结果:

    private bool wordsHasProductName(auction a, string[] words)
    {
        if (!a.product_name.Contains(' ')) // Auction product name is single word - check if 
        {
            if (words.Length > 1) return false; // array is passed, single word is searching
            else return a.product_name.Contains(words[0]); // check if product name consists passed string
        }
        else // Auction product name has more words check if passed string is partially in this product name
        {
            string[] productName = a.product_name.Split(' ');
            var list = new List<string>(words);
            foreach (string item in productName) 
            {
                if (list.Contains(item)) list.Remove(item);
            }
            return list.Count == 0;
        }
    }

我在我的代码中调用它,并且在调试时此行没有错误,这里是我在linq中调用.Where()子句的地方:

string[] words = searchName.Split(' ');
auctions = auctions.Where((a) => wordsHasProductName(a, words));

但是在return语句的最后我得到了异常错误:

return View(auctions.ToPagedList(pageNumber, pageSize));

错误代码:

  

发生了'System.NotSupportedException'类型的异常   EntityFramework.SqlServer.dll但未在用户代码中处理

     

其他信息:LINQ to Entities无法识别该方法   'Boolean wordsHasProductName(IEP_Projekat.Models.auction,   System.String [])'方法,这个方法无法翻译成一个   商店表达。

我怎样才能成功在我的linq .Where()中将完整的bool函数作为lambda表达式发送?

2 个答案:

答案 0 :(得分:1)

正如其他人已经提到的,方法调用不能在LINQ to Entities中使用,因为它们无法转换为SQL。因此,唯一可行的方法是,如果您可以将方法重写为兼容表达式。另外还有另一个问题 - 您使用的是不受支持的string.Split方法。

所以你运气不好。或者可能不是。这是诀窍。您可以使用以下(IMO等效)条件,而不是拆分product_name并检查它是否包含words中的每个单词:

words.All(word => (" " + product_name + " ").Contains(" " + word + " "))

用空格封闭product_nameword,可以匹配目标字符串开头,中间或末尾的整个单词。它仅使用EF支持的构造。

全部放在一起。功能如下:

private static Expression<Func<auction, bool>> wordsHasProductName(string[] words)
{
    if (words.Length == 1)
    {
        var word = words[0]; // Avoid ArrayIndex not supported
        return a => !a.product_name.Contains(" ") && a.product_name.Contains(word);
    }
    else
    {
        return a => words.All(word => (" " + a.product_name + " ").Contains(" " + word + " "));
    }
}

和用法:

string[] words = searchName.Split(' ');
auctions = auctions.Where(wordsHasProductName(words));

但是上面的实现并没有产生非常好的SQL查询,特别是对于多个单词。如果通过手动构建谓词表达式来重写它,则会生成更好的SQL:

private static Expression<Func<auction, bool>> wordsHasProductName(string[] words)
{
    Expression<Func<auction, string>> product_name;
    Expression condition;
    var containsMethod = typeof(string).GetMethod("Contains", new[] { typeof(string) });
    if (words.Length == 1)
    {
        // a => !a.product_name.Contains(" ") && a.product_name.Contains(words[0])
        product_name = a => a.product_name;
        condition = Expression.AndAlso(
            Expression.Not(Expression.Call(product_name.Body, containsMethod, Expression.Constant(" "))),
            Expression.Call(product_name.Body, containsMethod, Expression.Constant(words[0])));
    }
    else
    {
        // a => (" " + a.product_name + " ").Contains(" " + words[0] + " ")
        // && (" " + a.product_name + " ").Contains(" " + words[1] + " ")
        // ...
        // && (" " + a.product_name + " ").Contains(" " + words[N-1] + " ")
        product_name = a => " " + a.product_name + " ";
        condition = words
            .Select(word => Expression.Call(product_name.Body, containsMethod, Expression.Constant(" " + word + " ")))
            .Aggregate<Expression>(Expression.AndAlso);
    }
    return Expression.Lambda<Func<auction, bool>>(condition, product_name.Parameters);
}

欢迎来到EF和表达世界:)

答案 1 :(得分:0)

您不能将这个复杂的C#逻辑作为Expression Tree传递给EF提供程序解释,而提供程序最难的部分就是将其转换为SQL语句。

因此,您可以选择创建一个传入所需参数的存储过程,然后使用纯SQL编写逻辑。然后,将SP映射到EF并调用它以返回所需的对象列表。