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

How to create a custom store expression for my linq queries

让我先解释一下我要完成的任务。

我正在使用 C# ASP.NET MVC 5 项目,使用 Entity Framework 与 SQL 服务器数据库进行通信。大多数查询都使用 linq 进行查询。在前端站点的不同位置,我正在显示记录列表,并且需要提供通过搜索栏搜索这些记录的方法。现在的初步想法是让用户输入一个搜索短语,关键字之间用空格分隔,这些关键字用于匹配 table 记录中的任意字段组合。

例如,假设我的搜索是 "John Doe" 针对用户 table。考虑这些是 table:

中的记录
uFirstName    uLastName
----------    ----------
Johnny        Doe
John          Doe
Jane          Doe

前两条记录应该 returned。

这是一个示例方法,我会调用它来获得 return 我期望的结果:

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 } );
    }
}

在我的项目中,我在不同的地方使用这个搜索栏来搜索显然具有不同字段的各种 table。我想做的是创建一个辅助方法,允许我传入 "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 does not recognize the method 'Boolean SearchTermMatch(System.String, System.Collections.Generic.List`1[System.String])' method, and this method cannot be translated into a store expression.

从我对如何让它工作的所有搜索中,很明显我需要使用表达式,但我终其一生都无法理解它们是如何工作的。我所理解的是 Entity Framework 想要将 linq 语句转换为 SQL 可以理解的查询,而我的辅助方法不具备这样做的能力。

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

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

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

首先让我参考我用于我的解决方案的资源。我首先提到这个答案作为起点:

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

我不认为这是我理想的解决方案的唯一原因是我希望完全坚持使用 .NET/MVC 中的库。使用第 3 方库没有任何问题,在这种情况下,它为我完成了工作。但我希望在合理的范围内尽可能地做到这一点。

继续我的代码解决方案,因为我希望这能在某种程度上帮助其他人。

我的 "helper" 函数最终变成了这个(别忘了包含 "using 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 });