EF Core query using String.Contains

GETah

We have a requirement to searches a given term within a comma-separated string. The query is built so that it ignores possible leading and trailing spaces in the comma-separated string. I came up with the following query which is running fine with EF 6.0

var trimmedTags = tags.Select(t => t.Trim()); // List of tags we need to look for    
return products.Where(p => trimmedTags.Any(t => ("," + p.Categories + ",").Contains("," + t + ",")) ||
                               trimmedTags.Any(t => ("," + p.Categories + ",").Contains(", " + t + ",")) ||
                               trimmedTags.Any(t => ("," + p.Categories + ",").Contains("," + t + " ,")) ||
                               trimmedTags.Any(t => ("," + p.Categories + ",").Contains(", " + t + " ,")));

This query is no longer running in EF Core 3.1 and throws the following error:

System.InvalidOperationException: 'The LINQ expression 'DbSet<Product>
    .Where(p => __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains("," + t + ",")) || __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains(", " + t + ",")) || __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains("," + t + " ,")) || __trimmedTags_1
        .Any(t => ("," + p.Categories + ",").Contains(", " + t + " ,")))' could not be translated. Either rewrite the query in a form that can be translated, or switch to client evaluation explicitly by inserting a call to either AsEnumerable(), AsAsyncEnumerable(), ToList(), or ToListAsync(). See https://go.microsoft.com/fwlink/?linkid=2101038 for more information.'

My target table has millions of rows so client evaluation is unfortunately not an option. The EF Core team claims that string.Contains is supported but I can't figure out why my query is suddenly failing in EF Core.

Ivan Stoev

A different variations of this question often appear on SO, and the problem is always one and the same - even at the most current version (5.x) EF Core does not support operators on in-memory collections other than simple Contains with primitive value (or Any that can be turned into Contains like x => memValues.Any(v => v == SomeExpr(x)), with == operator being the essential).

The workaround is also one and the same - building dynamically expression - || (or) based for Any and && (and) based for All.

This case requires ||, and is similar to How to simplify repetitive OR condition in Where(e => e.prop1.contains() || e.prop2.contains() || ...) but with value and field roles exchanged, so following is the helper method I would use:

public static partial class QueryableExtensions
{
    public static IQueryable<T> WhereAnyMatch<T, V>(this IQueryable<T> source, IEnumerable<V> values, Expression<Func<T, V, bool>> match)
    {
        var parameter = match.Parameters[0];
        var body = values
            // the easiest way to let EF Core use parameter in the SQL query rather than literal value
            .Select(value => ((Expression<Func<V>>)(() => value)).Body)
            .Select(value => Expression.Invoke(match, parameter, value))
            .Aggregate<Expression>(Expression.OrElse);
        var predicate = Expression.Lambda<Func<T, bool>>(body, parameter);
        return source.Where(predicate);
    }
}

Note that this works only for top level query expressions. If you need something like this for something which is part of a query expression tree (like collection navigation property), you'd need different type of helper function or some library which allows expression injection.

Luckily that's not the case here, so the above helper method can be used directly by passing the trimmedTags and the condition for each tag value, e.g.

return products.WhereAnyMatch(trimmedTags, (p, t) => 
    ("," + p.Categories + ",").Contains("," + t + ",") ||
    ("," + p.Categories + ",").Contains(", " + t + ",") ||
    ("," + p.Categories + ",").Contains("," + t + " ,") ||
    ("," + p.Categories + ",").Contains(", " + t + " ,"));

Collected from the Internet

Please contact [email protected] to delete if infringement.

edited at
0

Comments

0 comments
Login to comment

Related

From Dev

Optimize EF query using Any instead of contains

From Dev

EF Core relationships query

From Dev

EF Core - complex query

From Dev

EF Core Includes query

From Dev

LINQ does not produce COUNT() query on group using EF Core

From Dev

Linq Query using Contains and not contains

From Dev

EF Core MySQL Contains(variable) like operator

From Dev

EF core query could not be translated

From Dev

EF LINQ spatial query times out using Contains() method in SQL Server

From Dev

EF LINQ spatial query times out using Contains() method in SQL Server

From Dev

.Contains() in .Where() EF Core doesn't work .NET Core 3.1.8

From Dev

Possible SQL Injection when using contains with EF?

From Dev

EF6 - using Contains with a list

From Dev

EF6 - using Contains with a list

From Dev

XPath query using contains function

From Dev

Running a query over all entities in EF Core

From Dev

How to query data asynchronously with Ef Core?

From Dev

Disable .NET Core EF 2.0 Query Logging

From Java

Ef core: Sequence contains no element when doing MaxAsync

From Dev

EF7 / Linq - "Contains" query with nested object throws Exception

From Dev

Why does the htaccess rewritecond using %{query_string} only work when the URL contains index.php?

From Java

How to query by text string which contains html tags using React Testing Library?

From Dev

String#contains using Pattern

From Dev

EF query with string as date filtering, how?

From Dev

EF query with string as date filtering, how?

From Dev

Query String issue when it contains Arabic text

From Dev

Realm query where member array contains string

From Dev

Parse Query Contains Non-Exact String

From Java

htaccess send 404 if query string contains keyword

Related Related

HotTag

Archive