[英]Using async with Entity Framework select list of type IQueryable<T>
我正在尝试将数据访问同步查询转换为异步,到目前为止,我转换了除返回IQueryable<T>
选择列表之外的所有内容。
这是我到目前为止所做的:
[Dependency]
public SampleContext db { get; set; }
public async System.Threading.Tasks.Task<Profile> Add(Profile item)
{
db.Profiles.Add(item);
await db.SaveChangesAsync();
return item;
}
public async System.Threading.Tasks.Task<Profile> Get(string id)
{
return await db.Profiles.AsNoTracking().Where(i => i.Id == id).FirstOrDefaultAsync();
}
public async System.Threading.Tasks.Task Remove(string id)
{
Profile item = db.Profiles.Find(id);
item.IsDeleted = 1;
db.Entry(item).State = EntityState.Modified;
await db.SaveChangesAsync();
}
public async System.Threading.Tasks.Task<bool> Update(Profile item)
{
db.Set<Profile>().AddOrUpdate(item);
await db.SaveChangesAsync();
return true;
}
上面的代码运行良好,我一直在转换这段代码:
public IQueryable<Profile> GetAll()
{
return db.Profiles.AsNoTracking().Where(i => i.IsDeleted == 0);
}
如何将上面的代码转换为异步? 我尝试了 Stephen Cleary 的这个示例代码,但无法弄清楚什么是ProcessEventAsync
以及如何将其应用于我的代码。 另外,我不能使用.ToList()
,这对于将所有数据加载到内存中太昂贵了。
您必须了解查询的差异和查询的结果之间的差异。 IQueryable
包含执行查询的所有内容。 它不是查询本身,创建 IQueryable 不会执行查询。
如果您更仔细地查看 LINQ 语句,您会发现有两种类型:返回IQueryable
(和IEnumerable
)的类型,以及返回List<TResult>
、 TResults
、 TKey
等的类型,任何不是IQueryable/IEnumerable
。 如果返回值是一个IQueryable
,那么我们说该函数使用延迟执行(或延迟执行):执行查询的Expression
已创建,但查询尚未执行。
这样做的好处是您可以连接 LINQ 语句,而无需为每个语句执行查询。
当您要求 IQueryable 获取枚举器并且如果您开始枚举时,将执行查询,无论是通过使用foreach
隐式,还是通过使用IQueryable.GetEnumerator()
和IEnumerator.MoveNext()
(它们也由foreach
调用)。
所以只要你在创建一个查询并返回一个IQueryable,创建一个Task是没有用的。 连接 LINQ 语句只会更改IQueryable
的表达式,这不是您必须等待的。
只有当您创建一个实际执行查询的函数时,您才需要一个异步版本: ToListAsync
、 FirstOrDefaultAsync
、 MaxAsync
等。在内部,这些函数将GetEnumerator
和MoveNextAsync
<-- 这是实际的异步函数
结论:通常会返回
IQueryable<...>
所有函数都不需要 Async version ,所有返回实际获取数据的函数都需要 Async 版本
例子。 不需要异步:没有执行查询:
// Query customer addresses:
static IQueryable<Address> QueryAddresses(this IQueryable<Customer> customers)
{
return customers.Select(customer => customer.Address);
}
需要异步:
static async Task<List<Address>> FetchAddressesAsync (this IQueryable<Customer> customers)
{
var query = customers.QueryAddresses; // no query executed yet
return await query.ToListAsync(); // execute the query
// could of course be done in one statement
}
static async Task<Address> FetchAddressAsync(this.IQueryable<Customer> customers, int customerId)
{
var query = customers.Where(customer => customer.Id == customerId)
.QueryAddresses();
// no query executed yet!
// execute:
return await query.FirstOrDefaultAsync();
}
用法:
int customerId = ...
using (var dbContext = new InvoiceContext())
{
Address fetchedCustomerAddress = await dbContext.Customers
.FetchAddressAsync(customerId);
}
在极少数情况下,您必须枚举自己,您将在MoveNextAsync
等待:
IQueryable<Customer> myCustomers = ...
IEnumerator<Customer> customerEnumerator = myCustomers.GetEnumerator();
while (await customerEnumerator.MoveNextAsync())
{
Customer customer = customerEnumerator.Current;
Process(customer);
}
您希望一次从数据库中读取一条记录,而不将所有记录加载到内存中。 同步,那只是一个 foreach。 做同样的事情,但使用异步连接方法:
1) 保留您的签名,并使用ForeachAsync使用它
public IQueryable<Profile> GetAll()
然后像这样消费它:
await repository.GetAll().ForeachAsync(record => DoThingsWithRecord(record));
请注意,如果您将其设为异步,则此处传递的操作实际上并未等待,请参阅下面引用的问题,如果您使用此方法,如何处理此问题
2)更改签名,并像ForeachAsync
那样实现它(我从这个问题借用了示例,因为它提供了适当的等待。)
public async Task WithAll(Func<Profile, Task> profileAsync, CancellationToken cancellationToken) {
var asyncEnumerable = (IDbAsyncEnumerable<Profile>)db.Profiles.AsNoTracking()
.Where(i => i.IsDeleted == 0);
using (var enumerator = asyncEnumerable.GetAsyncEnumerator())
{
if (await enumerator.MoveNextAsync(cancellationToken)
.ConfigureAwait(continueOnCapturedContext: false))
{
Task<bool> moveNextTask;
do
{
var current = enumerator.Current;
moveNextTask = enumerator.MoveNextAsync(cancellationToken);
await profileAsync(current); //now with await
}
while (await moveNextTask.ConfigureAwait(continueOnCapturedContext: false));
}
}
}
恕我直言
保持这个方法不变。
public IQueryable<Profile> GetAll()
{
return db.Profiles.AsNoTracking().Where(i => i.IsDeleted == 0);
}
像这样使用这个方法。 每当您使用GetAll()
时,请在该点添加 async/await。
public async Task<List<Profile>> GetAllProfiles(int userId) {
return await GetAll().Where(u => u.User.Id == userId).ToListAsync();
}
IQueryable
不访问数据库,除非 ToList(),FirstOrDefault() ... 执行某些操作。
你不能向它添加 async/await。
相反,GetAllProfiles()
方法是一个 I/O 操作,它执行 db 操作,因此您可以等待它。
您可以使用以下示例代码选择异步
public async System.Threading.Tasks.Task<List<Profile>> GetAll()
{
return await db.Profiles.AsNoTracking().Where(i => i.IsDeleted == 0).ToListAsync();
}
但是这个实现将解析 SQL 语句并在调用时执行它
没有任何理由等待您正在尝试做的事情。 可查询是查询的表达式,这意味着只有当您调用ToList() 时,才真正对数据库起作用,这意味着查询是在数据库上执行的。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.