当你请求另一个进程为你执行某些操作时,几乎总是使用异步等待:请求读取文件、执行数据库查询或从互联网获取一些信息。
通常,在这个任务正在执行的过程中,你无法做任何事情来加速它。你只能等待这个进程完成它的任务,或者四处看看是否可以在此期间做其他事情。
如果你仔细观察IQueryable LINQ语句,你会发现有两组:返回IQueryable<TResult>
和不返回的函数。第一组是Where、GroupBy、Select等函数。后一组包含ToList()、ToDictionary()、Count()、Any()、FirstOrDefault()等函数。
第一组函数(返回IQueryable)只是改变必须执行的查询的表达式。查询本身没有被执行,数据库也没有被联系。这些函数使用延迟执行。
只有在你使用后一组函数之一,或者开始枚举自己,使用foreach,甚至在更低的级别上:GetEnumerator()和MoveNext(),表达式才会被转换并发送到必须执行查询的进程。
当另一个进程正在执行查询时,你可以无所事事地等待该进程完成,或者可以使用async await来做其他事情,比如保持UI的响应性(典型的例子),但当然你也可以通过命令另一个进程为你做点事情来加速处理。因此,在查询数据库的同时,你可以读取文件或从互联网获取一些信息。
因为IQueryable函数不执行查询,只更改查询中的表达式,所以你不会看到像WhereAsync、SelectAsync等函数。
你会发现像ToListAsync()、AnyAsync()、FirstOrDefaultAsync()这样的函数。
但这与我的问题有什么关系呢?
你应该决定:我的GetBalanceItems函数应该返回什么?它应该返回平衡项目吗?还是应该返回查询平衡项目的可能性。差别微妙,但非常重要。
如果你返回平衡项目,那么你已经执行了查询。如果你返回查询平衡项目的可能性,你就返回了一个IQueryable:类似于LINQ函数Where()和Select(),查询没有被执行。
你应该返回什么取决于你的调用者想要做什么:他们想要所有获取的数据,还是可能希望将数据与其他LING函数连接起来。例如:
// Get the OfficeId and OfficeName of all balance items with a zero total:
var balanceItemsWithZeroTotal = GetBalanceItems()
// keep only those balnceItems with zero total
.Where(balanceItem => balanceItem.Total == 0)
// from the remaining balanceItems select the OfficeId and OfficeName
.Select(balanceItem => new
{
OfficeId = balanceItem.OfficeId,
OfficeName = balanceItem.OfficeName,
});
如果您的呼叫者想要执行类似此操作的事情,那么如果您执行查询并获取所有BalanceItems,然后您的呼叫者会丢弃大部分已获取的数据,这将是一种浪费。
在使用LINQ时,最好尽可能保持查询IQueryable。让您的呼叫者执行查询(ToList()、Any()、FirstOrDefault()等)
因此,在您的情况下:不要调用枚举函数(如ToList()),也不要使您的函数异步并返回IQueryable:
public IQueryable<BalanceItemResource>> QueryBalanceItems(int fyId)
{
return from r1 in _context.Requests
join u1 in _context.Users
on r1.ApproverId equals u1.Id
...
select new BalanceItemResource() {...};
}
这个规则有一个例外!
您的函数使用了_context
。在查询未执行时,必须保持此上下文的活动状态:您的调用者必须确保在执行查询之前不会Dispose上下文。通常情况下,可以按照以下方式完成此操作:
class MyRepository : IDisposable
{
private readonly MyDbContext dbContext = ...
public void Dispose()
{
Dispose(true)
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
this.dbContext.Dispose();
}
}
public IQueryable<BalanceItemResource>> QueryBalanceItems(int fyId) {...}
...
}
使用方法:
using (var repository = new MyRepository(...))
{
var queryBalanceItemsWithZeroTotal = GetBalanceItems()
.Where(balanceItem => balanceItem.Total == 0)
.Select(balanceItem => new
{
OfficeId = balanceItem.OfficeId,
OfficeName = balanceItem.OfficeName,
});
var balancesWithZeroTotal = await queryBalanceItemsWithZeroTotal.ToListAsync();
var queryTask = queryBalanceItemsWithZeroTotal.ToListAsync();
DoSomethingElse();
var var balancesWithZeroTotal = await queryTask;
Process(balancesWithZeroTotal);
}
所以一定要在 using 语句结束之前执行查询。如果您不这样做,将会出现运行时错误,提示您的上下文已被处理。
ConfigureAwait
?如果不是至关重要,那么默认情况下它的效果很好。此外,在需要使用相同上下文的情况下,它会导致错误(例如 UI 中的跨线程访问)。你不知道上下文,所以我不会走得太远 :) - Michał TurczynConfigureAwait
很危险,因为我不知道它会产生什么影响。除此之外,我完全同意你的看法 :) - Michał TurczynConfigureAwait(true)
作为有意 不 使用ConfigureAwait(false)
的指示。我建议其他人也这样做。我不知道为什么我对这个观点会受到如此多的抨击 D: - Dai