Why DbSet<TEntity> doesn't implement EnumerableAsync

喜欢而已 提交于 2019-12-10 12:39:03

问题


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


回答1:


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.)




回答2:


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();



回答3:


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.




回答4:


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.



来源:https://stackoverflow.com/questions/25204336/why-dbsettentity-doesnt-implement-enumerableasync

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!