簡體   English   中英

將異步與實體框架選擇列表 IQueryable 一起使用<T>

[英]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>TResultsTKey等的類型,任何不是IQueryable/IEnumerable 如果返回值是一個IQueryable ,那么我們說該函數使用延遲執行(或延遲執行):執行查詢的Expression已創建,但查詢尚未執行。

這樣做的好處是您可以連接 LINQ 語句,而無需為每個語句執行查詢。

當您要求 IQueryable 獲取枚舉器並且如果您開始枚舉時,將執行查詢,無論是通過使用foreach隱式,還是通過使用IQueryable.GetEnumerator()IEnumerator.MoveNext() (它們也由foreach調用)。

所以只要你在創建一個查詢並返回一個IQueryable,創建一個Task是沒有用的。 連接 LINQ 語句只會更改IQueryable的表達式,這不是您必須等待的。

只有當您創建一個實際執行查詢的函數時,您才需要一個異步版本: ToListAsyncFirstOrDefaultAsyncMaxAsync等。在內部,這些函數將GetEnumeratorMoveNextAsync <-- 這是實際的異步函數

結論:通常會返回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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM