Home > database >  EF.Functions.Like method not working after upgrading to .NET 6
EF.Functions.Like method not working after upgrading to .NET 6

Time:09-11

I was using EF.Functions.Like method for my query on my ASP.NET Core 3.1 SPI but as soon as I upgraded it to .NET 6 along with with all nugets, this particular query started to throw following error

InvalidOperationException : The Like method is not supported because the query has switched to client-evaluation

I have done some research and I know InMemory databases can cause this issue, but thats not the case I've tried localhosting the API and also published SPI and in both cases the database is always on SQL Server (not local on my machine) so its not an inmemory database, also the fact it worked fine on asp.net core 3.1

Example query

enter image description here

Query Method

public static async Task<IQueryable<Evidence>> QueryEvidencesAsync(this EvidenceContext Context, EvidenceQuery query)
    {

            var predicate = PredicateBuilder.True<Evidence>();
            predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.ReleasedToOwner && x.EvidenceStatus != EvidenceStatus.Transfer);
            if (query.EvidenceStatus is not EvidenceStatus.Disposed)
            {
                predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.Disposed);
            }
            if (query.EvidenceStatus is not EvidenceStatus.Incoming)
            {
                predicate = predicate.And(x => x.IsActive && x.EvidenceStatus != EvidenceStatus.Incoming);
            }

            var predicateAndOr = query.IsOr ? PredicateBuilder.False<Evidence>() : PredicateBuilder.True<Evidence>();

            if (!string.IsNullOrWhiteSpace(query.CaseNumber))
            {
                predicateAndOr = predicateAndOr.AndOr(x => EF.Functions.Like(x.CaseFileNumber, $"%{query.CaseNumber}%"), query.IsOr);
            }

            predicate = predicate.And(predicateAndOr);
            var predicateCompiled = predicate.Compile();

            return query.IncludeRequests
                ? Context.Evidences.Include(x => x.Requests).AsNoTracking().Where(predicateCompiled).AsQueryable().OrderByDescending(x => x.Modified)
                : Context.Evidences.Where(predicateCompiled).AsQueryable().OrderByDescending(x => x.Modified);
    }

PredicateBuilder

public static class PredicateBuilder
{
    public static Expression<Func<T, bool>> True<T>()
    {
        return (T _) => true;
    }

    public static Expression<Func<T, bool>> False<T>()
    {
        return (T _) => false;
    }

    public static Expression<Func<T, bool>> Or<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        InvocationExpression right = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.OrElse(expr1.Body, right), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> And<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2)
    {
        InvocationExpression right = Expression.Invoke(expr2, expr1.Parameters.Cast<Expression>());
        return Expression.Lambda<Func<T, bool>>(Expression.AndAlso(expr1.Body, right), expr1.Parameters);
    }

    public static Expression<Func<T, bool>> AndOr<T>(this Expression<Func<T, bool>> expr1, Expression<Func<T, bool>> expr2, bool or = false)
    {
        if (!or)
        {
            return expr1.And(expr2);
        }

        return expr1.Or(expr2);
    }
}

in this particular example, query.IsOr will always be false so predicate will always build with And predicate. Also query.CaseNumber will have the string value for caseNumber

I have removed many more conditions and if statement all of them just checked for one property and add that check to predicate, but as I said they are not relevant here as in this scenario only the if statements I've shown here are true and hence validated.

CodePudding user response:

You're building a predicate as an Expression<Func<Evidence, bool>>, but then you're .Compile()ing it into a Func<Evidence, bool>. That means when you call .Where() you're no longer calling Queryable.Where<T>(this IQueryable<T> source, Expression<Func<Evidence, bool>> criteria), but rather Enumerable.Where(this IEnumerable<T> source, Func<Evidence, bool> criteria).

The difference there is that the method you're calling will treat the query as an IEnumerable<T>, and rather than allowing Entity Framework to issue a query against the database with your criteria as a WHERE clause, it will bring all of the objects back from the database, and then pass them into the criteria function (which has been compiled into memory) to determine which ones to keep.

In other words, your code in the past was terribly inefficient, and you didn't realize it. EF.Functions.Like was never meant to be invoked: it was only supposed to be evaluated as part of a query. Earlier versions of EF.Functions.Like did their best to mimic the behavior of SQL's LIKE when they were called, whereas newer versions will just throw an exception to warn you that you're doing it wrong.

You need to avoid .Compile(), and pass predicate directly into your .Where() call.

            predicate = predicate.And(predicateAndOr);

            return query.IncludeRequests
                ? Context.Evidences.Include(x => x.Requests).AsNoTracking().Where(predicate).OrderByDescending(x => x.Modified)
                : Context.Evidences.Where(predicate).OrderByDescending(x => x.Modified);

That may lead to other issues, but it'll get you one step closer to doing it right.

  • Related