简体   繁体   English

如何检查字符串是否包含实体框架中列表中的任何字符串?

[英]How do you check if a string contains any strings from a list in Entity Framework?

I am trying to search a database to see if a string contains elements of a list of search terms.我正在尝试搜索数据库以查看字符串是否包含搜索词列表的元素。

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

This would work if searchTerms was a string but I've been trying to get it to work with a list of strings.如果 searchTerms 是一个字符串,这将起作用,但我一直试图让它与字符串列表一起使用。

Essentially I need SQL which would say本质上我需要 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 seems to be the closes thing I could find to the situation. linq list contains any in list似乎是我能找到的最接近这种情况的东西。

Where(data => searchTerms.Contains(data.Name) || searchTerms.Contains(data.Code) only brings back exact matches to the search terms list. Where(data => searchTerms.Contains(data.Name) || searchTerms.Contains(data.Code)仅将完全匹配项带回搜索词列表。

I have tried searching for multiple keyword search in Entity Framework also and have exhausted that effort.我也尝试在实体框架中搜索多个关键字搜索,并且已经用尽了努力。 Is there any way to achieve what I am aiming for?有什么方法可以实现我的目标吗?

You can try using Any method, I'm not sure whether it's supported but it's worth trying:您可以尝试使用Any方法,我不确定它是否受支持,但值得尝试:

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

If this gives you NotSupportedException you can add AsEnumerable before Where to fetch all records and execute the query in memory rather than DB.如果这给您NotSupportedException您可以在Where之前添加AsEnumerable以获取所有记录并在内存而不是数据库中执行查询。

I'm late to the party but using the SearchExtensions nuget package you could do something like the following我迟到了,但是使用SearchExtensions nuget 包,您可以执行以下操作

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

This builds an expression tree so will still perform the query on the server (and not in memory) and will essentially run the SQL you desire above这将构建一个表达式树,因此仍将在服务器上(而不是在内存中)执行查询,并且本质上将运行您想要的上述 SQL

Following is a fully functional example, of how to implement different kind of searches, using multiple keywords.以下是一个功能齐全的示例,说明如何使用多个关键字实现不同类型的搜索。

This example was especially directed at @Hamza Khanzada 's similar question regarding Pomelo.EntityFrameworkCore.MySql .这个例子特别针对@Hamza Khanzada 关于Pomelo.EntityFrameworkCore.MySql类似问题

It does something similar to the library @NinjaNye named.它执行类似于名为@NinjaNye 的库的功能。


The simplest approach is used by EqualsQuery() , which just tests the database field against an exact match of a keyword (though casing doesn't matter). EqualsQuery()使用了最简单的方法,它只是根据关键字的精确匹配来测试数据库字段(尽管大小写无关紧要)。 This is what @Mohsen Esmailpour suggested.这是@Mohsen Esmailpour 的建议。

It generates SQL similar to the following:它生成类似于以下的 SQL:

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

However, this is probably not sufficient for your case, because you are not looking for exact matches, but want to return rows with fields that just contain the keyword (and possibly other words as well).但是,对于您的情况,这可能还不够,因为您不是在寻找完全匹配的内容,而是希望返回包含包含关键字(以及可能还有其他词)的字段的行。


A second approach is used by AndContainsQuery() , which is still very simple, but does something slightly different. AndContainsQuery()使用了第二种方法,它仍然非常简单,但做了一些稍微不同的事情。 It returns only results, that contain all the keyword (and possibly orther words as well).它只返回包含所有关键字(可能还有其他词)的结果。

It generates SQL similar to the following:它生成类似于以下的 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`;

This is not what you want, but I though it would be nice to show as well, because it is very simple and can be done without manually building expression trees.这不是您想要的,但我认为展示也很好,因为它非常简单,无需手动构建表达式树即可完成。


Finally, the third approach is used by orContainsQuery() , and builds part of the expression tree manually.最后,第三种方法orContainsQuery() ,并手动构建部分表达式树。 It constructs the body of the WHERE expression of multiple nested OR expressions.它构造多个嵌套OR表达式的WHERE表达式的主体。 This is what you want.就是你想要的。

It generates SQL similar to the following:它生成类似于以下的 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`;

Here is the fully functional console project:这是功能齐全的控制台项目:

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

You could use the Any function from Linq to see if any term is contained into the data. 您可以使用Linq中的Any函数来查看数据中是否包含任何术语。

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

This request will be executed on the database side该请求将在数据库端执行

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

you can also use the Specification pattern, for code purity您还可以使用规范模式,以实现代码纯度

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM