简体   繁体   中英

Can't use LINQ in EF Where expression for lambda property

I changed the LINQ expression like this.

public async Task<IEnumerable<Thing>> Get(bool all)
{
  List<Thing> output = await Context.Things
    //.Where(_ => all || _.DeletedOn == null && _.Deletedon < DateTime.Now)
    .Where(_ => all || _.Active)
    .ToListAsync();
  return output;
}

Apparently, it caused the following error.

InvalidOperationException: The LINQ expression 'DbSet .Where(l => False || l.Active)' 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().

Well, I do have a call to AsListAsync() and I'm a bit buffeled that such a simple conditional wasn't interpretable by .NET Core EF. I suspect I might be missing something else.

The class Thing looks like this.

class Thing
{
  ...
  public DateTime? DeletedOn { get; set; }
  public Active => DeletedOn == null && DeletedOn < DateTime.Now.
}

Checking the provided link gave me nothing.

As others have mentioned, the expression bodied member getter is actually regarded a method call, which can't be parsed by a Linq QueryProvider into native SQL, so you'll need to again undo your 'DRY' code and instead expand the predicate logic in the Active property directly into your Linq Query, or alternately you would need to pass the entire Expression Tree or IQueryable to a method like Active so that it could compose the predicate into it (which seems overkill).

Also, as per comments, there's a logical issue - _.DeletedOn == null && _.Deletedon < DateTime.Now will return false, meaning the query is then all or nothing based on the value of the all parameter. At a guess, you're looking for a conditionally applied 'logical delete' filter, which also allows for future dated deletion, ie any record which hasn't yet been deleted is to be regarded as active, or any record which has a null deletion date hasn't been deleted at all.

And finally, for most RDBMS, especially SQL Server, it is usually not a good idea to add a hard coded predicate (like all ) into a WHERE filter, as this can mess with the query plan caching . In your case, the all flag filtering logic simply decides whether you want to include logically Deleted data or not. IMO, I would instead conditionally compose the query like follows to avoid doing unnecessary filtering in SQL.

Putting this all together, the query would be:

var query = Context.Things;
if (!all)
{
   query = query.Where(t => t.DeletedOn == null || t.Deletedon > DateTime.Now)
}
var output = await query
   .ToListAsync();
return output;

See breaking changes in .Net core 3.0

One solution (though not advised) could be the following:

If a query can't be fully translated, then either rewrite the query in a form that can be translated, or use AsEnumerable(), ToList(), or similar to explicitly bring data back to the client where it can then be further processed using LINQ-to-Objects.

So as it would happen before, get the dbset with a translatable query and filter later on with whatever C# linq to Objects magic you'd like.

Without having tested it, I believe that EF can't translate your Expression bodied member . Did it work when you tried with the following?

   .Where(_ => all || _.DeletedOn == null && _.Deletedon < DateTime.Now) 

The problem is in the expression body in the public field Active. EF could not translate this expression Active => DeletedOn == null && DeletedOn < DateTime.Now .

尝试在过滤记录之前插入对 AsEnumerable()、AsAsyncEnumerable()、ToList() 或 ToListAsync() 的调用,即在这种情况下在Where条件之前。

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