【问题标题】:How do you check if a string contains any strings from a list in Entity Framework?如何检查字符串是否包含实体框架列表中的任何字符串?
【发布时间】:2022-01-14 22:22:45
【问题描述】:

我正在尝试搜索数据库以查看字符串是否包含搜索词列表的元素。

var searchTerms = new List<string> { "car", "232" };
var result = context.Data.Where(data => data.Name.Contains(searchTerms) ||
                                        data.Code.Contains(searchTerms));

如果 searchTerms 是一个字符串,这将起作用,但我一直试图让它与字符串列表一起使用。

基本上我需要 SQL 来说明

SELECT * FROM Data
WHERE Name LIKE '%car%'
OR Name LIKE '%232%'
OR Code LIKE '%car%'
OR Code LIKE '%232%'

linq where list contains any in list 似乎是我能找到的最接近这种情况的东西。

Where(data =&gt; searchTerms.Contains(data.Name) || searchTerms.Contains(data.Code) 只会将完全匹配的内容带回搜索词列表。

我也尝试在实体框架中搜索多个关键字搜索,并且已经用尽了努力。有什么方法可以实现我的目标吗?

【问题讨论】:

    标签: c# entity-framework


    【解决方案1】:

    您可以尝试使用Any 方法,我不确定它是否支持但值得尝试:

    var result = context.Data.Where(data => searchTerms.Any(x => data.Name.Contains(x)) ||
                                            searchTerms.Any(x => data.Code.Contains(x));
    

    如果这给了您NotSupportedException,您可以在Where 之前添加AsEnumerable 以获取所有记录并在内存而不是数据库中执行查询。

    【讨论】:

    • 如果Data有100万条记录怎么办?
    • @hunter 如果 DB 有太多记录并且此解决方案影响性能,则创建一个 DB 函数并让数据库执行数据库所做的事情 ;-)。以上对我来说很好用
    【解决方案2】:

    我迟到了,但使用 SearchExtensions nuget package 您可以执行以下操作

    var result = context.Data.Search(x => x.Name, x => x.Code).Containing(searchTerms);
    

    这会构建一个表达式树,因此仍将在服务器上(而不是在内存中)执行查询,并且基本上会运行您在上面想要的 SQL

    【讨论】:

    • 比使用任何会生成嵌套太深的查询并导致异常的更好。
    • @NinjaNye 这对 EF Core 5 有效吗?
    【解决方案3】:

    以下是一个功能齐全的示例,说明如何使用多个关键字实现不同类型的搜索。

    这个例子特别针对@Hamza Khanzada 的similar question 关于Pomelo.EntityFrameworkCore.MySql

    它的功能类似于 @NinjaNye 命名的库。


    EqualsQuery() 使用了最简单的方法,它只是根据关键字的精确匹配来测试数据库字段(尽管大小写无关紧要)。这就是@Mohsen Esmailpour 的建议。

    它生成类似于以下的 SQL:

    SELECT `i`.`IceCreamId`, `i`.`Name`
    FROM `IceCreams` AS `i`
    WHERE LOWER(`i`.`Name`) IN ('cookie', 'berry')
    ORDER BY `i`.`IceCreamId`
    

    但是,这对于您的情况可能还不够,因为您不是在寻找 exact 匹配项,而是希望返回具有仅 包含 关键字的字段的行 (可能还有其他词)。


    AndContainsQuery() 使用了第二种方法,它仍然非常简单,但做的事情略有不同。它只返回包含 all 关键字(可能还有其他词)的结果。

    它生成类似于以下的 SQL:

    set @__keyword_0 = 'Cookie';
    set @__keyword_1 = 'choco';
    
    SELECT `i`.`IceCreamId`, `i`.`Name`
    FROM `IceCreams` AS `i`
    WHERE
        (LOCATE(LCASE(@__keyword_0), LCASE(`i`.`Name`)) > 0) AND
        (LOCATE(LCASE(@__keyword_1), LCASE(`i`.`Name`)) > 0)
    ORDER BY `i`.`IceCreamId`;
    

    这不是你想要的,但我认为它也可以展示出来,因为它非常简单,无需手动构建表达式树即可完成。


    最后,orContainsQuery()使用了第三种方法,手动构建了部分表达式树。它构造了多个嵌套OR 表达式的WHERE 表达式的主体。 这你想要的。

    它生成类似于以下的 SQL:

    set @__keyword_0 = 'berry';
    set @__keyword_1 = 'Cookie';
    
    SELECT `i`.`IceCreamId`, `i`.`Name`
    FROM `IceCreams` AS `i`
    WHERE
        (LOCATE(LCASE(@__keyword_0), LCASE(`i`.`Name`)) > 0) OR
        (LOCATE(LCASE(@__keyword_1), LCASE(`i`.`Name`)) > 0)
    ORDER BY `i`.`IceCreamId`;
    

    这是功能齐全的控制台项目:

    using System;
    using System.Diagnostics;
    using System.Linq;
    using System.Linq.Expressions;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Extensions.Logging;
    using Pomelo.EntityFrameworkCore.MySql.Storage;
    
    namespace IssueConsoleTemplate
    {
        public class IceCream
        {
            public int IceCreamId { get; set; }
            public string Name { get; set; }
        }
    
        public class Context : DbContext
        {
            public DbSet<IceCream> IceCreams { get; set; }
    
            protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
            {
                optionsBuilder
                    .UseMySql(
                        "server=127.0.0.1;port=3306;user=root;password=;database=so60914868",
                        b => b.ServerVersion(new ServerVersion("8.0.20-mysql")))
                    .UseLoggerFactory(
                        LoggerFactory.Create(
                            b => b
                                .AddConsole()
                                .AddFilter(level => level >= LogLevel.Information)))
                    .EnableSensitiveDataLogging()
                    .EnableDetailedErrors();
            }
    
            protected override void OnModelCreating(ModelBuilder modelBuilder)
            {
                modelBuilder.Entity<IceCream>(
                    entity =>
                    {
                        entity.HasData(
                            new IceCream {IceCreamId = 1, Name = "Vanilla"},
                            new IceCream {IceCreamId = 2, Name = "Berry"},
                            new IceCream {IceCreamId = 3, Name = "Strawberry"},
                            new IceCream {IceCreamId = 4, Name = "Berry & Fruit"},
                            new IceCream {IceCreamId = 5, Name = "cookie"},
                            new IceCream {IceCreamId = 6, Name = "Chocolate chip cookie"},
                            new IceCream {IceCreamId = 7, Name = "Choco-Cookie & Dough"});
                    });
            }
        }
    
        internal class Program
        {
            private static void Main()
            {
                using (var context = new Context())
                {
                    context.Database.EnsureDeleted();
                    context.Database.EnsureCreated();
                }
    
                EqualsQuery();
                AndContainsQuery();
                OrContainsQuery();
            }
    
            private static void EqualsQuery()
            {
                //
                // This will find only matches that match the word exactly (though case-insensitive):
                //
    
                using var context = new Context();
                
                var keywords = new[] {"Cookie", "berry"}
                    .Select(s => s.ToLower())
                    .ToArray();
    
                var equalsResult = context.IceCreams
                    .Where(i => keywords.Contains(i.Name.ToLower()))
                    .OrderBy(i => i.IceCreamId)
                    .ToList();
    
                Debug.Assert(equalsResult.Count == 2);
                Debug.Assert(
                    equalsResult[0]
                        .Name == "Berry");
                Debug.Assert(
                    equalsResult[1]
                        .Name == "cookie");
            }
    
            private static void AndContainsQuery()
            {
                //
                // This will find matches, that contain ALL keywords (and other words, case-insensitive):
                //
    
                using var context = new Context();
                
                var keywords = new[] {"Cookie", "choco"};
    
                var andContainsQuery = context.IceCreams.AsQueryable();
    
                foreach (var keyword in keywords)
                {
                    andContainsQuery = andContainsQuery.Where(i => i.Name.Contains(keyword, StringComparison.CurrentCultureIgnoreCase));
                }
    
                var andContainsResult = andContainsQuery
                    .OrderBy(i => i.IceCreamId)
                    .ToList();
    
                Debug.Assert(andContainsResult.Count == 2);
                Debug.Assert(
                    andContainsResult[0]
                        .Name == "Chocolate chip cookie");
                Debug.Assert(
                    andContainsResult[1]
                        .Name == "Choco-Cookie & Dough");
            }
    
            private static void OrContainsQuery()
            {
                //
                // This will find matches, that contains at least one keyword (and other words, case-insensitive):
                //
    
                using var context = new Context();
                
                var keywords = new[] {"Cookie", "berry"};
    
                // The lambda parameter.
                var iceCreamParameter = Expression.Parameter(typeof(IceCream), "i");
    
                // Build the individual conditions to check against.
                var orConditions = keywords
                    .Select(keyword => (Expression<Func<IceCream, bool>>) (i => i.Name.Contains(keyword, StringComparison.OrdinalIgnoreCase)))
                    .Select(lambda => (Expression) Expression.Invoke(lambda, iceCreamParameter))
                    .ToList();
    
                // Combine the individual conditions to an expression tree of nested ORs.
                var orExpressionTree = orConditions
                    .Skip(1)
                    .Aggregate(
                        orConditions.First(),
                        (current, expression) => Expression.OrElse(expression, current));
                
                // Build the final predicate (a lambda expression), so we can use it inside of `.Where()`.
                var predicateExpression = (Expression<Func<IceCream, bool>>)Expression.Lambda(
                    orExpressionTree,
                    iceCreamParameter);
    
                // Compose and execute the query.
                var orContainsResult = context.IceCreams
                    .Where(predicateExpression)
                    .OrderBy(i => i.IceCreamId)
                    .ToList();
    
                Debug.Assert(orContainsResult.Count == 6);
                Debug.Assert(orContainsResult[0].Name == "Berry");
                Debug.Assert(orContainsResult[1].Name == "Strawberry");
                Debug.Assert(orContainsResult[2].Name == "Berry & Fruit");
                Debug.Assert(orContainsResult[3].Name == "cookie");
                Debug.Assert(orContainsResult[4].Name == "Chocolate chip cookie");
                Debug.Assert(orContainsResult[5].Name == "Choco-Cookie & Dough");
            }
        }
    }
    

    【讨论】:

    • 太棒了!在 ef core 5 而不是 i.Name.Contains(keyword, StringComparison.CurrentCultureIgnoreCase) 中,您必须使用 i.Name.Contains(keyword) 才能获得 IQuearyable ,否则它将是客户评估。微软文档go.microsoft.com/fwlink/?linkid=2101038
    【解决方案4】:

    这个请求会在数据库端执行

    var searchTerms = new List<string> { "car", "232" };
    Expressions<Func<Data, bool>> expression = it => false;
    foreach(var searchTerm in searchTerms)
    {
        expression = expression.Or(it => it.Name.Contains(searchTerm));
        //you can implement your own 'Or' extensions method, 
        //or use third-party libraries, i.e. LinqKit
    }
    var result = context.Data.Where(expression);
    

    您还可以使用规范模式,以实现代码纯度

    【讨论】:

      猜你喜欢
      • 2015-03-24
      • 2012-11-15
      • 1970-01-01
      • 1970-01-01
      • 2014-08-09
      • 2013-01-01
      • 1970-01-01
      • 2014-07-08
      • 2015-11-14
      相关资源
      最近更新 更多