Home > OS >  Should I return Task<IEnumerable<T>> or IAsyncEnumerable<T> from repository?
Should I return Task<IEnumerable<T>> or IAsyncEnumerable<T> from repository?

Time:10-28

What is the correct way of repository implementation in EF Core?

public IAsyncEnumerable<Order> GetOrder(int orderId)
{
    return blablabla.AsAsyncEnumerable();
}

or

public Task<IEnumerable<Order>> GetOrder(int orderId)
{
    return blablabla.ToListAsync();
}

Is it performance wise to call AsAsyncEnumerable()? Is this approach safe? From one hand it doesn't create List<T> object so it should be slightly faster. But from the order hand the query is not materialized so we deffer the SQL execution and the result can change in the meantime.

CodePudding user response:

According to source .ToListAsync will use IAsyncEnumerable internally anyway, so there's not much of performance benefits in one or another. But one important feature of .ToListAsync or .ToArrayAsync is cancellation.

public static async Task<List<TSource>> ToListAsync<TSource>(
    this IQueryable<TSource> source,
    CancellationToken cancellationToken = default)
{
    var list = new List<TSource>();
    await foreach (var element in source.AsAsyncEnumerable().WithCancellation(cancellationToken))
    {
        list.Add(element);
    }
    return list;
}

List will basically hold everything in memory but it might be a serious performance concern only if the list is really big. In this case you might consider paging your big response.

public Task<List<Order>> GetOrders(int orderId, int offset, int limit)
{
    return blablabla.Skip(offset).Take(limit).ToListAsync();
}

CodePudding user response:

If you want to buffer the results, use ToList() or ToListAsync().
If you want to stream the results, use AsEnumerable() or AsAsyncEnumerable().

From the docs:

Buffering refers to loading all your query results into memory, whereas streaming means that EF hands the application a single result each time, never containing the entire resultset in memory. In principle, the memory requirements of a streaming query are fixed - they are the same whether the query returns 1 row or 1000; a buffering query, on the other hand, requires more memory the more rows are returned. For queries that result large resultsets, this can be an important performance factor.

In the question, you mentioned:

But from the order hand the query is not materialized so we defer the SQL execution

This is not true. Calling AsEnumerable() will case EF to execute a SQL query against the DB. Anything after AsEnumerable will be executed on the client.

For instance:

var students = db.Students
                 .Where(s => s.DepartmentId == 1)
                 .AsEnumerable()
                 .Where(s => Age > 16);

In the above example, the first Where() is run on the database. The second Where is run on the client, because AsEnumerable() was called before it.


Related:

  • Related