【问题标题】:Wildcard search for LINQLINQ 的通配符搜索
【发布时间】:2010-11-05 15:19:06
【问题描述】:

我想知道是否可以使用 LINQ 进行通配符搜索。

我看到 LINQ 有 Contains、StartsWith、EndsWith 等。

如果我想要 %Test if%it work% 之类的东西怎么办?

问候

【问题讨论】:

    标签: c# sql linq wildcard


    【解决方案1】:

    您可以使用SqlMethods.Like()

    用法示例:

    var results =
            from u in users
            where SqlMethods.Like(u.FirstName, "%John%")
            select u;
    

    【讨论】:

    • 这只适用于 LinqToSql 查询(使用的类应该很明显)。
    • 很好,我也不知道有没有+1
    • 哇,我从来不知道有一个 SqlMethods 类。
    • 太糟糕了,它不能在 .NET Core 上运行。 (仅供参考,以防有人试图为他们的 c# UWP 找到修复程序)
    【解决方案2】:

    我会使用正则表达式,因为您可能并不总是使用 Linq to SQL。

    就像这个 Linq to Objects 的例子

    List<string> list = new List<string>();
    list.Add("This is a sentence.");
    list.Add("This is another one.");
    list.Add("C# is fun.");
    list.Add("Linq is also fun.");
    
    System.Text.RegularExpressions.Regex regEx = new System.Text.RegularExpressions.Regex("This");
    
    var qry = list
        .Where<string>(item => regEx.IsMatch(item))
        .ToList<string>();
    
    // Print results
    foreach (var item in qry)
    {
        Console.WriteLine(item);
    }
    

    【讨论】:

    • 有点啰嗦-他可以只使用SqlMethods.Like
    • 您总是假设您正在调用 SQL 数据库。如果您要查询在内存中创建的列表怎么办?
    • 他的问题是用SQL标记的
    • 为特定问题提供通用解决方案并不是一件坏事。
    • @Dementic 除非它没有回答原始问题
    【解决方案3】:

    将 System.Data.Linq.SqlClient 添加到您的使用或导入列表中,然后尝试:

    var results= from x in data
                 where SqlMethods.Like(x.SearchField, “%something%like%this%”)
                 select x;
    

    【讨论】:

      【解决方案4】:

      对于Entity Framework Core 2.0,有LIKE 运算符(announced in August 2017):

      var query = from e in _context.Employees
                          where EF.Functions.Like(e.Title, "%developer%")
                          select e;
      

      【讨论】:

        【解决方案5】:

        看问题

        如果我想要 %Test if%it work% 之类的东西怎么办?

        那么我期待一些东西

        LIKE '%Test if%it work%'
        

        表示字符串必须包含“Test if”和“it work”,按此顺序

        这不起作用:

        context.SomeTable.Where(s => s.Name.Contains("Test if%it work")).ToList();
        

        如果我使用:

        context.SomeTable.Where(s => s.Name.Contains("Test if") && s.Name.Contains("it work")).ToList();
        

        然后我会找到所有同时包含“Test if”和“it work”的记录,但不按这个顺序

        所以使用 Contains 这是不可能的。但是使用 IndexOf 就是这样。

        IndexOf 将定位搜索字符串并返回它在字符串中的位置。可以按正确的顺序找到单词。

        -- 更新--

        根据我的原始答案,我的目标不是提供通用解决方案,而是另一种不依赖 sql 的方法的示例。因此,原始示例仅回答字面问题是正确的。但是由于如果它是通用的答案可能会更有用,我编写了一个 IQuerable 扩展,它允许向查询中添加一个 like 语句,就像 where 语句一样简单。该扩展适用于 Linq 作为 Linq-Sql。

        这将按顺序查找所有带有“Test if”和“it work”的记录。

        context.SomeTable.Like("test if%it work", "Name").ToList();
        
        listOfString.Like("test if%it work").ToList();
        

        扩展,允许任意数量的通配符:

        /// <summary>
        /// Allow to search the string with wildcards.
        /// </summary>
        /// <typeparam name="T">String or an object with a string member.</typeparam>
        /// <param name="q">Original query</param>
        /// <param name="searchstring">The searchstring</param>
        /// <param name="memberName">The name of the field or null if not a field.</param>
        /// <returns>Query filtered by 'LIKE'.</returns>
        public static IQueryable<T> Like<T>(this IQueryable<T> q, string searchstring, string memberName = null)
        {
            // %a%b%c% --> IndexOf(a) > -1 && IndexOf(b) > IndexOf(a) && IndexOf(c) > IndexOf(b)
        
            var eParam = Expression.Parameter(typeof(T), "e");
        
            MethodInfo methodInfo;
        
            // Linq (C#) is case sensitive, but sql isn't. Use StringComparison ignorecase for Linq.
            // Sql however doesn't know StringComparison, so try to determine the provider.
            var isLinq = (q.Provider.GetType().IsGenericType && q.Provider.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>));
            if (isLinq)
                methodInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) });
            else
                methodInfo = typeof(string).GetMethod("IndexOf", new[] { typeof(string) });
        
            Expression expr;
            if (string.IsNullOrEmpty(memberName))
                expr = eParam;
            else
                expr = Expression.Property(eParam, memberName);
        
            // Split the searchstring by the wildcard symbol:
            var likeParts = searchstring.Split(new char[] { '%' }, StringSplitOptions.RemoveEmptyEntries);
        
            for (int i = 0; i < likeParts.Length; i++)
            {
                MethodCallExpression e;
                if (isLinq)
                    e = Expression.Call(expr, methodInfo, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) });
                else
                    e = Expression.Call(expr, methodInfo, Expression.Constant(likeParts[i], typeof(string)));
        
                if (i == 0)
                {
                    // e.IndexOf("likePart") > -1
                    q = q.Where(Expression.Lambda<Func<T, bool>>(Expression.GreaterThan(e, Expression.Constant(-1, typeof(int))), eParam));
                }
                else
                {
                    // e.IndexOf("likePart_previous")
                    MethodCallExpression ePrevious;
                    if (isLinq)
                        ePrevious = Expression.Call(expr, methodInfo, new Expression[] { Expression.Constant(likeParts[i - 1], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) });
                    else
                        ePrevious = Expression.Call(expr, methodInfo, Expression.Constant(likeParts[i - 1], typeof(string)));
        
                    // e.IndexOf("likePart_previous") < e.IndexOf("likePart")
                    q = q.Where(Expression.Lambda<Func<T, bool>>(Expression.LessThan(ePrevious, e), eParam));
                }
            }
            return q;
        }
        

        由于它不需要 SqlMethods,我假设您可以将它用于任何数据库,例如 MySql 或 Postgresql。但我不确定。我确实使用 Entity Framework 6 对 Sql Server 进行了测试。上面的语句在 Sql Server 中生成了以下代码。

        SELECT [Extent1].* FROM SomeTable AS [Extent1]
        WHERE ((( CAST(CHARINDEX(N'test if', [Extent1].[Name]) AS int)) - 1) > -1)
        AND ((( CAST(CHARINDEX(N'test if', [Extent1].[Name]) AS int)) - 1) < 
             (( CAST(CHARINDEX(N'it work', [Extent1].[Name]) AS int)) - 1))
        

        关于性能,似乎有一些关于“更好”的讨论:LIKE 或 CHARINDEX。从我读过的内容来看,CHARINDEX 似乎是最喜欢的。

        【讨论】:

        • 您的答案不包含对如何在 LINQ 中实现通配符搜索的一般问题的答案,它只是回答了具体示例,不适用于任何其他情况。因此,它没有为问题提供有用的答案。
        • 实际上我一直在寻找特定于 SQL 的答案,因为不得不编写额外的通用代码让我很恼火,因为我们将始终使用 SQL Server。不幸的是,当你展示了为什么 contains 不起作用时,你没有展示任何简单的东西。我要进行的比较是包含字符串之一。所以我希望能在那个字符串中添加一些简单的东西。看起来必须使用 SQLMethods.Like。
        【解决方案6】:

        我知道这是一个老话题,但这是我非常简单的解决方案:

        string s=Regex.Escape("pattern - escaped for sanity").Replace("%", ".*").Replace("_", ".?");
        user => Regex.IsMatch(user.FullName, s, RegexOptions.CultureInvariant | RegexOptions.IgnoreCase);
        

        在这段代码中,我使用 SQL 语言的常见转义字符。 如果你想使用*?,转义字符串将包含\*\?,请确保在.Replace(...)语句中包含反斜杠字符. 当然,如果您想让您的用户能够进行 RexEx 搜索,请不要转义模式字符串。

        搜索正则表达式教程以获取其他选项。

        我相信通常% 将匹配至少一个字符,而RegEx .* 将匹配零个或多个 个字符。所以实际上,% 通配符更像.+(贪婪)而不是.*(懒惰)。

        希望这会有所帮助。

        【讨论】:

          【解决方案7】:
          .Where( column LIKE "Pattern")
          

          【讨论】:

            【解决方案8】:
            var result = (from x in db.Members
                          where x.IDNumber.Contains(idnumber)
                          && x.InstitutionIdentifier == institution.Identifier
                          select x).ToList();
            return result;
            

            适用于内存中的 Linq to SQL 和 Linq。

            【讨论】:

              【解决方案9】:

              不确定你说的是 LinqToSql 还是 linq……但你可以像这样的正则表达式:

              .Where(dto => System.Text.RegularExpressions.Regex.IsMatch(dto.CustomerName, @"Ad"));
              

              【讨论】:

                【解决方案10】:

                在包括 LINQ to Objects 的 .Net 代码中,我正在使用线程 Using Regex to create a SQL's "like" like function. 中的 IsSqlLikeMatch 函数的实现。

                使用示例

                bool ret = message.IsSqlLikeMatch(pattern);
                

                更多细节在我的帖子中 SQL's "like" patterns to compare in .Net

                【讨论】:

                  【解决方案11】:

                  你也可以使用“包含”

                  var myresult = db.MyItems.Where(x=>x.MyField.Contains(mysearchstring));
                  

                  【讨论】:

                    【解决方案12】:

                    您是在谈论 LINQ to objects 还是 LINQ to SQL?

                    对于 LINQ to 对象,您必须求助于 regular expressions 我认为。

                    【讨论】:

                      【解决方案13】:

                      我用它来支持用户搜索中的“*”通配符过滤器。 (顺序无关紧要):

                       if (!string.IsNullOrEmpty(SearchString))
                          {
                           List<String> containValues = new List<String>();
                           if (SearchString.Contains("*"))
                              {
                      
                              String[] pieces = SearchString.Split("*");
                      
                              foreach (String piece in pieces)
                                      {
                                      if (piece != "")
                                         {
                                         containValues.Add(piece);
                                         }
                                       }
                                 }
                      
                             if (containValues.Count > 0)
                                {
                                foreach(String thisValue in containValues)
                                   {
                                   Items = Items.Where(s => s.Description.Contains(thisValue));
                                   }
                                 }
                                 else
                                 {
                                 Items = Items.Where(s => s.Description.Contains(SearchString));
                                 }
                             }
                      

                      【讨论】:

                        【解决方案14】:

                        我扩展了 Ruard van Elburg 的示例来支持我的需求,并认为我会分享。它处理通配符,例如“a%”(startswith(a))、“%b”(endswith(b))、“a%b”(startswith(a) && endswith(b))和“a%b% c" (startwith(a), indexof(a)

                            public static class LinqLikeExtension
                        {
                            /// <summary> Permits searching a string value with any number of wildcards. This was written 
                            /// to handle a variety of EF wildcard queries not supported because the current version is 
                            /// less tan EFv6.2, which has a .Like() method.
                            /// like in EFv6.</summary>
                            /// <typeparam name="T">String or an object with a string member.</typeparam>
                            /// <param name="query">Original query</param>
                            /// <param name="searchstring">The searchstring</param>
                            /// <param name="columnName">The name of the db column, or null if not a column.</param>
                            /// <returns>Query filtered by 'LIKE'.</returns>
                            /// <example>return iQueryableRows.Like("a", "ReferenceNumber");</example>
                            /// <example>return iQueryableRows.Like("a%", "ReferenceNumber");</example>
                            /// <example>return iQueryableRows.Like("%b", "ReferenceNumber");</example>
                            /// <example>return iQueryableRows.Like("a%b", "ReferenceNumber");</example>
                            /// <example>return iQueryableRows.Like("a%b%c", "ReferenceNumber");</example>
                            /// <remarks>Linq (C#) is case sensitive, but sql isn't. Use StringComparison ignorecase for Linq.
                            /// Keep in mind that Sql however doesn't know StringComparison, so try to determine the provider.</remarks>
                            /// <remarks>base author -- Ruard van Elburg from StackOverflow, modifications by dvn</remarks>
                            /// <seealso cref="https://stackoverflow.com/questions/1040380/wildcard-search-for-linq"/>
                            public static IQueryable<T> Like<T>(this IQueryable<T> query, string searchstring, string columnName = null)
                            {
                                var eParam = Expression.Parameter(typeof(T), "e");
                                var isLinq = (query.Provider.GetType().IsGenericType && query.Provider.GetType().GetGenericTypeDefinition() == typeof(EnumerableQuery<>));
                        
                                MethodInfo IndexOf, StartsWith, EndsWith, Equals;
                                MethodCallExpression mceCurrent, mcePrevious;
                        
                                Expression method = string.IsNullOrEmpty(columnName) ? eParam : (Expression)Expression.Property(eParam, columnName);
                        
                                var likeParts = searchstring.Split(new char[] { '%' });
                        
                                for (int i = 0; i < likeParts.Length; i++)
                                {
                                    if (likeParts[i] == string.Empty) continue; // "%a"
                        
                                    if (i == 0)
                                    {
                                        if (likeParts.Length == 1) // "a"
                                        {
                                            Equals = isLinq
                                                ? Equals = typeof(string).GetMethod("Equals", new[] { typeof(string), typeof(StringComparison) })
                                                : Equals = typeof(string).GetMethod("Equals", new[] { typeof(string) });
                                            mceCurrent = isLinq
                                                ? Expression.Call(method, Equals, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                                                : Expression.Call(method, Equals, Expression.Constant(likeParts[i], typeof(string)));
                                        }
                                        else // "a%" or "a%b"
                                        {
                                            StartsWith = isLinq
                                                ? StartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string), typeof(StringComparison) })
                                                : StartsWith = typeof(string).GetMethod("StartsWith", new[] { typeof(string) });
                                            mceCurrent = isLinq
                                                ? Expression.Call(method, StartsWith, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                                                : Expression.Call(method, StartsWith, Expression.Constant(likeParts[i], typeof(string)));
                                        }
                                        query = query.Where(Expression.Lambda<Func<T, bool>>(mceCurrent, eParam));
                                    }
                                    else if (i == likeParts.Length - 1)  // "a%b" or "%b"
                                    {
                                        EndsWith = isLinq
                                            ? EndsWith = typeof(string).GetMethod("EndsWith", new[] { typeof(string), typeof(StringComparison) })
                                            : EndsWith = typeof(string).GetMethod("EndsWith", new[] { typeof(string) });
                                        mceCurrent = isLinq
                                            ? Expression.Call(method, EndsWith, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                                            : Expression.Call(method, EndsWith, Expression.Constant(likeParts[i], typeof(string)));
                                        query = query.Where(Expression.Lambda<Func<T, bool>>(mceCurrent, eParam));
                                    }
                                    else // "a%b%c"
                                    {
                                        IndexOf = isLinq
                                            ? IndexOf = typeof(string).GetMethod("IndexOf", new[] { typeof(string), typeof(StringComparison) })
                                            : IndexOf = typeof(string).GetMethod("IndexOf", new[] { typeof(string) });
                                        mceCurrent = isLinq
                                            ? Expression.Call(method, IndexOf, new Expression[] { Expression.Constant(likeParts[i], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                                            : Expression.Call(method, IndexOf, Expression.Constant(likeParts[i], typeof(string)));
                                        mcePrevious = isLinq
                                            ? Expression.Call(method, IndexOf, new Expression[] { Expression.Constant(likeParts[i - 1], typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase) })
                                            : Expression.Call(method, IndexOf, Expression.Constant(likeParts[i - 1], typeof(string)));
                                        query = query.Where(Expression.Lambda<Func<T, bool>>(Expression.LessThan(mcePrevious, mceCurrent), eParam));
                                    }
                                }
                        
                                return query;
                            }
                        }
                        

                        我知道这真的很晚了,我知道 EFv6.2+ 支持 Like() 方法。但也许你和我一样,在一家小商店里有大量遗留应用程序,很难简单地升级 .Net 和 EF 版本。

                        【讨论】:

                          猜你喜欢
                          • 2021-11-25
                          • 1970-01-01
                          • 1970-01-01
                          • 2013-05-02
                          • 1970-01-01
                          • 1970-01-01
                          • 1970-01-01
                          • 2019-09-25
                          相关资源
                          最近更新 更多