Why can't I use ToListAsync() when using a parameter for the predicate in EF7?

asp.net-core c# entity-framework-core

Question

I'm currently implementing the repository pattern for my ASP.VNext application. I would like the methods to be asynchronous and filterable. So I have devised the following interface method:

Task<TEntity> GetOneAsync(Func<TEntity,bool> predicate);

and would like to implement it like this (with a private DbContext instance ctx):

public async Task<MyEntity> GetOneAsync(Func<MyEntity,bool> predicate) 
{
    // compiler error
    return await ctx.MyEntities.Where(predicate).FirstOrDefaultAsync();
}

However I can only use FirstOrDefaultAsync() when hardcoding the predicate like this:

return await ctx.MyEntites.Where(e => e.Id == 1).FirstOrDefaultAsync();

When passing the predicate i only get the FirstOrDefault() without the async option, so in order to make my method asynchronous I have to write

public async Task<MyEntity> GetOneAsync(Func<MyEntity,bool> predicate) 
{
    //save to a local variable to prevent calling a disposed DbContext
    var entities = await Task.Run(() => ctx.Contracts.Where(predicate).FirstOrDefault());
    return entities;
}

I have two questions regarding this:

  1. Why is it not possible to access the FirstOrDefaultAsync() method when passing a predicate?

  2. Does my solution using await Task.Run(synchronousMethod) achieve the same behavior as a call to FirstOrDefaultAsync() would?

Accepted Answer

FirstOrDefaultAsync is defined as an extension method for IQueryable<T>.

ctx.MyEntities.Where(e => e.Id == 1) returns IQueryable<MyEntity>.

ctx.MyEntities.Where(predicate) returns IEnumerable<MyEntity>, because you're calling the Enumerable.Where extension method, not the Queryable.Where one.

To make it work, change predicate from Func<MyEntity, bool> to Expression<Func<MyEntity, bool>>. This means predicate is no longer just a function that gives the result you want, but a description of that function, that Entity Framework can then translate to SQL.

And no, using Func<MyEntity, bool> within a task would not have the same behaviour. That would load rows from the db server without any filtering, and evaluate each and every one at the db client until a match is found. That would add a lot of overhead.



Related

Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why
Licensed under: CC-BY-SA with attribution
Not affiliated with Stack Overflow
Is this KB legal? Yes, learn why