[英]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.