Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Ef core 3 casting could not be translated

i want to cast my IQueryable to an Interface like this:

public static IQueryable<T> _enableFilter<T>(this IQueryable<T> queryable) => queryable.Where(x => (x as IEnable).Enable);
_newsRepository.BaseQuery.EnableFilter().FirstOrDefaultAsync(x => x.Id == model.Id);

its work on EF core 2.2 but in 3 i give this error:

System.InvalidOperationException : The LINQ expression 'Where<News>(
    source: DbSet<News>, 
    predicate: (n) => (n as IEnable).Enable)' 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.

like image 561
Mo3in Avatar asked Oct 10 '19 15:10

Mo3in


2 Answers

This query didn't work in EF Core 2.2 either. That cast has no meaning in SQL - there are no interfaces or inheritance in SQL. EF Core before 3 had an ... unfortunate feature, client side evaluation. If EF Core couldn't translate something to SQL, it would pull everything to the client and try to filter the data there. Needless to say, this is disastrous for performance. Worse, it did so without any warning. A warning or exception would allow you to recognize and fix the problem. At least, in EF Core 2.2 it was possible to disable client-side evaluation. In EF Core 3, this went away for good.

As for the method itself, you wouldn't need that cast at all if you used a type constraint, eg :

public static IQueryable<T> _enableFilter<T>(this IQueryable<T> queryable) 
    where T:IEnable 
{
    return queryable.Where(x => (x as IEnable).Enable);
}

I'm not sure EF Core will accept this - this is unusual syntax. It's a lot easier to just add that extra condition, eg :

_newsRepository.BaseQuery.EnableFilter().FirstOrDefaultAsync(x => x.Id == model.Id && x.Enabled);

Global Queries and Soft deletes

If you want to apply a filter condition to all queries using a specific entity, eg to implement soft-deletes, you can use global filters. In fact, soft-deletes is the first scenario mentioned in the docs.

In you context's OnModelCreating() method you can add :

modelBuilder.Entity<SomeEntity>().HasQueryFilter(p => p.IsEnabled);
like image 80
Panagiotis Kanavos Avatar answered Nov 03 '22 00:11

Panagiotis Kanavos


The reason behind this is a change in how EF Core 3 handles situations like this.

In EF core 2.x this was pulled into memory and the operation done there automatically, due to the potential performance issues of doing things like this the EF core team took the decision to move it from being the default behaviour to an error being the default behaviour.

Essentially, if you want the same behaviour as 2.x had, you now have to explicitly pull it into memory before doing it - however, this can often be a sign that its probably better to change how your query is done - unless you really do explicitly need this behaviour

For more specifics on the change see Here

like image 4
Gibbon Avatar answered Nov 03 '22 02:11

Gibbon