async-awaitentity-framework-core

Entity Framework Core CompileAsyncQuery syntax to do a query returning a list?


Documentation and examples online about compiled async queries are kinda sparse, so I might as well ask for guidance here.

Let's say I have a repository pattern method like this to query all entries in a table:

public async Task<List<ProgramSchedule>> GetAllProgramsScheduledList()
{
    using (var context = new MyDataContext(_dbOptions))
    {
        context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        return await context.ProgramsScheduledLists.ToListAsync();
    }
}

This works fine.

Now I want to do the same, but with an async compiled query.

One way I managed to get it to compile is with this syntax:

static readonly Func<MyDataContext, Task<List<ProgramSchedule>>> GetAllProgramsScheduledListQuery;

static ProgramsScheduledListRepository()
{
    GetAllProgramsScheduledListQuery = EF.CompileAsyncQuery<MyDataContext, List<ProgramSchedule>>(t => t.ProgramsScheduledLists.ToList());
}

public async Task<List<ProgramSchedule>> GetAllProgramsScheduledList()
{
    using (var context = new MyDataContext(_dbOptions))
    {
        context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
        return await GetAllProgramsScheduledListQuery(context);
    }
}

But then on runtime this exception get thrown:

System.ArgumentException: Expression of type 'System.Collections.Generic.List`1[Model.Scheduling.ProgramSchedule]' cannot be used for return type 'System.Threading.Tasks.Task`1[System.Collections.Generic.List`1[Model.Scheduling.ProgramSchedule]]'

The weird part is that if I use any other operator (for example SingleOrDefault), it works fine. It only have problem returning List.

Why?


Solution

  • EF.CompileAsync for set of records, returns IAsyncEnumrable<T>. To get List from such query you have to enumerate IAsyncEnumrable and fill List,

    private static Func<MyDataContext, IAsyncEnumerable<ProgramSchedule>> compiledQuery =
        EF.CompileAsyncQuery((MyDataContext ctx) =>
            ctx.ProgramsScheduledLists);
    
    public static async Task<List<ProgramSchedule>> GetAllProgramsScheduledList(CancellationToken ct = default)
    {
        using (var context = new MyDataContext(_dbOptions))
        {
            context.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking;
    
            var result = new List<ProgramSchedule>();
            await foreach (var s in compiledQuery(context).WithCancellation(ct))
            {
                result.Add(s);
            }
    
            return result;
        }
    }
    

    Package System.Linq.Async has predefined extensions for such case, ToListAsync(), ToArrayAsync(), etc.