问题
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