14

In Entity framework 6.1.1 an IDbSet represents the collection of entities which can be queried from the database and its concrete implementation is DbSet as described in

DbSet

How come this interface or its concrete implementation doesn't contain any definition for ToEnumerableAsync or AsEnumerableAsync but ToListAsync,ToArrayAsync,ToDictionaryAsync?

To give you an idea of why I came across this question I have the following piece of code which I wanted to make async:

public IQueryable<TEntity> GetQuery<TEntity>() where TEntity : class
{
    string entityName = GetEntityName<TEntity>();
    return ((IObjectContextAdapter)DbContext).ObjectContext.CreateQuery<TEntity>(entityName);
}


public IEnumerable<TEntity> Get<TEntity, TOrderBy>(Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
    if (sortOrder == SortOrder.Ascending)
    {
        return GetQuery<TEntity>().OrderBy(orderBy).Skip((pageIndex - 1) * pageSize).Take(pageSize).AsEnumerable();
    }
    return
        GetQuery<TEntity>()
            .OrderByDescending(orderBy)
            .Skip((pageIndex - 1) * pageSize)
            .Take(pageSize)
            .AsEnumerable();
}

The only implementation which comes to my mind is as follows:

public async Task<IEnumerable<TEntity>> GetAsync<TEntity, TOrderBy>(Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
    if (sortOrder == SortOrder.Ascending)
    {
        return await GetQuery<TEntity>().OrderBy(orderBy).Skip((pageIndex - 1) * pageSize).Take(pageSize).ToListAsync();
    }
    return
        await GetQuery<TEntity>()
            .OrderByDescending(orderBy)
            .Skip((pageIndex - 1) * pageSize)
            .Take(pageSize)
            .ToListAsync();
}

Is this the correct approach in regards to making a method asynchronous?

Above methods belong to the generic repository implemented in the following links: Entity Framework POCO, Repository and Specification Pattern To dig deeper you can have a look at the originating blog: Entity Framework POCO, Repository and Specification Pattern

MHOOS
  • 5,146
  • 11
  • 39
  • 74

4 Answers4

11

The design of IEnumerable doesn't allow it to be used with async/await. IEnumerator<T>.MoveNext() cannot return any Task object that the caller can await, because it has got a fixed return type of bool.

The async-aware version of IEnumerable is IDbAsyncEnumerable, and that's already implemented by DbQuery<T>, from which DbSet<T> derives, so no AsDbAsyncEnumerable() extension method is necessary for that to work.

Your Get version, IMO, does not need an Async version. It already doesn't block, because it doesn't do anything. Only when the caller starts using the returned enumerable, will the database be queried. I'd just change the return type to DbQuery<TEntity>. (This requires a cast, but should already be the concrete type that gets returned.) The caller can then decide whether to use synchronous methods, or asynchronous methods.

(Actually, on closer inspection, I see that although your question is about DbSet<T>, you're actually using the backing ObjectContext instead of the DbContext. This will likely give you ObjectQuery<T> queryables rather than DbQuery<T> queryables, for which the answer will be different. You'll make things easier on yourself if you stop using the ObjectContext except when you really need to.)

  • Re-thinking the question I think you have the more correct answer, I did not think about the author wanting a lazy loaded async enumerable. – Scott Chamberlain Aug 08 '14 at 13:21
  • @hvd ,I updated the question and added some code. Do you reckon there is a better way of making the non-sync method asynchronous? – MHOOS Aug 08 '14 at 13:38
  • @MHOOS I don't think you need a `GetAsync` at all, I think you can trivially modify your `Get` method, and have added that to my answer. –  Aug 08 '14 at 13:54
  • Nitpick: “MoveNext() has no return type” Actually, it returns `bool`. – svick Aug 08 '14 at 14:00
  • @svick Oops! Yes, I didn't quite say what I meant to say. :) –  Aug 08 '14 at 14:03
  • @hvd these methods belong to a generic repository as implemented by https://github.com/Serenyrddraig/efprs . I use these repositories and need to have the async version of them in my web api controllers. – MHOOS Aug 08 '14 at 14:12
  • @MHOOS According to [this file](https://github.com/Serenyrddraig/efprs/blob/master/net45/Infrastructure.Data/GenericRepository.cs), the `GetQuery` code originally used `DbContext.Set()`, but back in 4.1, that started causing problems. We're at version 6.1.x now. You may investigate whether that still causes problems, and if not, switch back to that version. If it works, then as I attempted to explain, your `Get` method can already be made `async`-friendly easily without a negative impact on existing callers. –  Aug 08 '14 at 14:25
  • @hvd, I amended the GetQuery method to return DbContext.Set() rather than ((IObjectContextAdapter)DbContext).ObjectContext.CreateQuery(entityName) as you suggested. Thanks for spotting this. – MHOOS Aug 08 '14 at 16:19
6

Because you can't have a Enumerable, that class is static. You can have a IEnumerable, but List, Array, and Dictionaries all implement it so what would your underlying type be in your ToEnumerableAsync()?

You can easily do

IEnumerable<TEntity> result = await yourDbSet.ToListAsync();
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
  • 5
    That would probably defeat the purpose of IEnumerable, which is to be a pipeline minimizing peak memory usage. – Kind Contributor Apr 22 '15 at 01:31
  • the purpose of `IEnumerable` is simply to define an interface. It's the specific implementation what matters and there are many classes that implement `IEnumerable`, including `List`. I think it's a perfectly valid way to query a list in an asynchronous way. – diegosasw Feb 26 '16 at 01:52
1

Although the question has been answered, this thread might also be found if you want covariance with IEnumerable for different reasons.

For example, you might want to create an interface which expicitly does not expose the concrete list implementation. Also migrating from a synchronous implementation, heavily using IList or IEnumerable instead of List or [].

In that case you could do something like this:

query.ToListAsync().ContinueWith(task => task.Result.AsEnumerable())

Keep in mind, that Result is dangerous and may lead to deadlocks with await. But if you're using await, you should not have this problem in the first place, since IEnumerable e = await query.ToListAsync() works just fine.

Oliver Schimmer
  • 387
  • 2
  • 14
0

The answer to question, "why.." can be found by exploring the correct way to iterate over an async source.

You cannot use IEnumerable through foreach, because foreach is not async aware. So until c# includes a foreachAsync statement or the like, you must use a special function with delegate to an iteration handler.

var qry = (from x in db.myTable select f);
await qry.ForEachAsync(async (myRecord) => {
   await DoSomethingAsync(myRecord);
});

NOTE: You need using System.Data.Entity to have ForEachAsync available

Your generic implementation would need to be (note it's not an async method):

public IQueryable<TEntity> PagedQuery<TEntity, TOrderBy>(Expression<Func<TEntity, TOrderBy>> orderBy, int pageIndex, int pageSize, SortOrder sortOrder = SortOrder.Ascending) where TEntity : class
{
    if (sortOrder == SortOrder.Ascending)
    {
        return GetQuery<TEntity>().OrderBy(orderBy).Skip((pageIndex - 1) * pageSize).Take(pageSize)();
    }
    return
        GetQuery<TEntity>()
            .OrderByDescending(orderBy)
            .Skip((pageIndex - 1) * pageSize)
            .Take(pageSize)();
}

And to use:

var qry = PagedQuery(...);
await qry.ForEachAsync(async (myRecord) => {
   await DoSomethingAsync(myRecord);
});

So your function just becomes a convenience wrapper, but not one that makes foreach over async any more convenient.

Kind Contributor
  • 17,547
  • 6
  • 53
  • 70