如何为我的linq查询创建自定义商店表达式

时间:2015-02-14 19:40:39

标签: c# sql-server asp.net-mvc linq entity-framework-6

首先让我解释一下我想要完成的事情。

我正在使用C#ASP.NET MVC 5项目,使用Entity Framework与SQL Server数据库进行通信。大多数查询都使用linq进行查询。在前端站点的各个地方,我正在显示记录列表,需要提供通过搜索栏搜索这些记录的方法。现在最初的想法是允许用户输入搜索短语,其中关键字用空格分隔,并且这些关键字用于匹配表记录中的任何字段组合。

例如,假设我的搜索是针对用户表的“John Doe”。考虑这些是此表中的记录:

uFirstName    uLastName
----------    ----------
Johnny        Doe
John          Doe
Jane          Doe

应返回前两个记录。

这是我要调用的示例方法,用于返回我期望的结果:

public static List<UserModel> GetUserList(string terms)
{
    using (DBConnection dbcontext = new DBConnection())
    {
        var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
        var linqList = (from u in dbcontext.Users
                        where 
                        (
                            (terms == "") ||
                            (termlist.Any(_s => u.uLastName.Contains(_s))) ||
                            (termlist.Any(_s => u.uFirstName.Contains(_s)))
                        )
                        select new { u.uLastName, u.uFirstName });
        return linqList.ToList().ConvertAll<UserModel> ( u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName } );
    }
}

在我的项目中,我在各个地方使用这个搜索栏,用于搜索明显具有不同字段的各种表格。我想要做的是创建一个帮助方法,允许我传入“terms”字符串并使其与linq语句中的字段值列表进行匹配。这是一个示例伪方法,显示我想将上述方法更改为:

public static List<UserModel> GetUserList(string terms)
{
    using (DBConnection dbcontext = new DBConnection())
    {
        var linqList = (from u in dbcontext.Users
                        where SearchTermMatch(terms, new List<string>() { u.uLastName, u.uFirstName }) == true
                        select new { u.uLastName, u.uFirstName });
        return linqList.ToList().ConvertAll<UserModel>(u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName });
    }
}

这就是辅助方法的样子:

public static bool SearchTermMatch(string terms, List<string> fieldvalues)
{
    if (terms == "") return true;
    else
    {
        var termlist = terms.Split(' ').ToList();
        var foundlist = new List<bool>();
        foreach (string value in fieldvalues)
            foundlist.Add(termlist.Any(s => value.Contains(s)));
        return foundlist.Any(f => f == true);
    }
}

即使编译得很好,但在运行时会产生以下错误:

  

LINQ to Entities无法识别方法'Boolean SearchTermMatch(System.String,System.Collections.Generic.List`1 [System.String])'方法,并且此方法无法转换为商店表达式。

从我所有关于如何使其工作的搜索中,很明显我需要使用表达式,但我不能在我的生活中理解这些是如何工作的。我理解的是,实体框架想要将linq语句转换为SQL可以理解的查询,并且我的帮助方法没有这样做。

最终我想要完成的是构建一个帮助器方法,我可以稍后使用更高级的搜索技术进行扩展。我想如果我从基于关键字拆分的所有相关字段的搜索开始变得简单,我可以稍后添加更多的复杂性,我只需要对这个帮助方法做,并且我的所有搜索栏都将从这些进步中受益。

所以我想我正在寻找的是我如何创建这个帮助方法的帮助,我可以在我的项目中使用各种linq语句。

1 个答案:

答案 0 :(得分:3)

好的,我找到了解决问题的方法。它并不完全理想,但它完成了工作。

首先让我参考我正在使用的解决方案的来源。我首先提到这个答案作为起点: https://stackoverflow.com/a/27993416/4566281

这个答案提到了我最终在我的项目中使用的来源。如果您使用的是Visual Studio,可以在NuGet中找到该软件包,只需搜索“neinlinq”,或者从这个GitHub存储库中获取它: https://github.com/axelheer/nein-linq

我不认为这是我理想的解决方案的唯一原因是我希望完全坚持.NET / MVC中的库。使用第三方库没有任何问题,在这种情况下,它完成了我的工作。但我希望尽可能以原生的方式实现这一目标。

关于我的代码解决方案,因为我希望这会以某种身份帮助其他人。

我的“助手”功能最终成为了这一点(不要忘记包含“使用NeinLinq;”)

    [InjectLambda]
    public static bool SearchTermMatch(List<string> termlist, List<string> fieldvalues)
    {
        throw new NotImplementedException();
    }
    public static Expression<Func<List<string>, List<string>, bool>> SearchTermMatch()
    {
        return (t,f) => 
        (
            (t.Count() == 0) ||
            (t.Count(_t => f.Any(_f => _f.Contains(_t)) || _t == "") == t.Count())
        );
    }

而且,我的linq声明最终成为以下内容:

    public static List<UserModel> GetUserList(string terms)
    {
        using (DBConnection dbcontext = new DBConnection())
        {
            var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
            var linqList = (from u in dbcontext.Users
                            where SearchTermMatch(termlist, new List<string>() { u.uLastName, u.uFirstName })
                            select new { u.uLastName, u.uFirstName });
            return linqList.ToList().ConvertAll<UserModel>(u => new UserModel { LastName = u.uLastName, FirstName = u.uFirstName });
        }
    }

我也不喜欢我必须在linq语句之前构建“termlist”才能进行我想要的比较。理想情况下,我希望使用“SearchTermMatch”表达式通过类似于Split的方式构造列表,所以我所要做的就是传入字符串“terms”,但我无法弄清楚如何在表达式中完成。如果有人知道如何做到这一点,请告诉我。然后,我可以灵活地在表达式中建立我自己的搜索规则集,而不是让调用linq语句成为列表。

因此,为了全面了解这是如何实现我的情境的,我现在可以将SearchTermMatch重新用于我的所有搜索栏场景。以此声明为例:

            var linqList = (from p in Person 
                            join a in Address on p.AddressID equals a.AddressID 
                            select new { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode });

我现在可以轻松地将其更新为以下内容来处理我的搜索栏调用:

            var termlist = (terms == "") ? new List<string>() : terms.Split(' ').ToList();
            var linqList = (from p in Person 
                            join a in Address on p.AddressID equals a.AddressID
                            where SearchTermMatch(termlist, new List<string>() { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode })
                            select new { p.ContactName, p.EmailAddress, a.Street, a.City, a.State, a.Zipcode });