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

后端 未结 4 453
半阙折子戏
半阙折子戏 2020-12-06 06:38

I am trying to search a database to see if a string contains elements of a list of search terms.

var searchTerms = new List { \"car\", \"232\"          


        
相关标签:
4条回答
  • 2020-12-06 06:55

    You could use the Any function from Linq to see if any term is contained into the data.

    var result = context.Data.Where(data => searchTerms.Any(term => data.Name.Contains(term) ||
                                            searchTerms.Any(term => data.Code.Contains(term));
    
    0 讨论(0)
  • 2020-12-06 06:57

    I'm late to the party but using the SearchExtensions nuget package you could do something like the following

    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

    0 讨论(0)
  • 2020-12-06 07:14

    Following is a fully functinal 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.

    It does something similar to the library @NinjaNye named.


    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). This is what @Mohsen Esmailpour suggested.

    It generates SQL similar to the following:

    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. It returns only results, that contain all the keyword (and possibly orther words as well).

    It generates SQL similar to the following:

    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. It constructs the body of the WHERE expression of multiple nested OR expressions. This is what you want.

    It generates SQL similar to the following:

    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 functinal 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");
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-06 07:16

    You can try using Any method, I'm not sure whether it's supported but it's worth trying:

    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.

    0 讨论(0)
提交回复
热议问题