简体   繁体   中英

How to modify expression-based filters to avoid client-side evaluation in Entity Framework Core 3.0

I have the following code that used to convert Func -based filters to Expression and filter the data in Entity Framework Core 2.2 :

public async Task<TType> GetDataAsync<TType>(Func<TType, bool> filtering = null) where TType : class
{
  Expression<Func<TType, bool>> filteringExpression = (type) => filtering(type);
  if (filtering != null)
    //return await myContext.Set<TType>().FirstOrDefaultAsync(filteringExpression);
    return await myContext.Set<TType>().Where(filteringExpression ).FirstOrDefaultAsync();
  return await myContext.Set<TType>().FirstOrDefaultAsync();
}

This is how I use it:

public async Task<DataLog> GetDataLogByID(Guid dataLogID) => await GetDataAsync<DataLog>(dataLog => dataLog.ID == dataLogID);

(Un)fortunately, when I upgraded to Entity Framework Core 3.0 , the code threw an InvalidOperationException as the expression can't be turned into SQL query (although it filters only a property that matches a database column):

System.InvalidOperationException: 'The LINQ expression 'Where( source: DbSet, predicate: (f) => Invoke(__filtering_0, f[DataLog]) )' 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.

So can you tell me, how I should modify the code to make sure that all (most) of the processing stay on the server side? What is the best practice to keep the generic code yet comply with the standards?

Congratulations, you've discovered one of the breaking changes in EF Core 3.0 - LINQ queries are no longer evaluated on the client

Old behavior

Before 3.0, when EF Core couldn't convert an expression that was part of a query to either SQL or a parameter, it automatically evaluated the expression on the client. By default, client evaluation of potentially expensive expressions only triggered a warning.

New behavior

Starting with 3.0, EF Core only allows expressions in the top-level projection (the last Select() call in the query) to be evaluated on the client. When expressions in any other part of the query can't be converted to either SQL or a parameter, an exception is thrown.

See the docs (link above) for more info, but the warnings you experienced before upgrading are now generating InvalidOperationExceptions and has nothing to do with SQLite, you'd get the same issues with SQL Server.

Only way around this is to ensure that your filtering expression/func can be converted to appropriate SQL... or revert to EF Core < 3.0

UPDATE

You could try not wrapping the passed Func and change the parameter type to Expression<Func<TType, bool>> (it shouldn't require any changes to code calling the method)

public async Task<TType> GetDataAsync<TType>(Expression<Func<TType, bool>> filter = null)
    where TType : class
{
    var query = myContext.Set<TType>();

    if (filter != null)
        query = query.Where(filter);

    return await query.FirstOrDefaultAsync();
}

Just noticed that the call to GetDataAsync appears to be incorrect and has an extra type parameter Guid which should be removed from this example.

public async Task<DataLog> GetDataLogByID(Guid dataLogID) =>
    await GetDataAsync<DataLog>(dataLog => dataLog.ID == dataLogID);

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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