筆記目錄

Skip to content

升級至 .NET 6 時多餘 AsQueryable() 呼叫的原因

TLDR

  • 舊版 .NET Core 專案中殘留的 AsQueryable() 呼叫,通常是為了處理 IQueryableIAsyncEnumerable 擴充方法衝突的問題。
  • 當專案同時安裝 System.Linq.Async 與舊版 EF Core 時,編譯器因無法判斷 Where()Select() 應套用至哪個介面而產生歧義。
  • 升級至 EF Core 6 後,DbSet 不再直接實作 IAsyncEnumerable,因此可安全移除多餘的 AsQueryable() 呼叫。

擴充方法衝突的原因分析

什麼情況下會遇到這個問題:當專案同時引用了 System.Linq.Async 套件,且使用 EF Core 5 或更早的版本時。

在 EF Core 5 以前,DbSet<TEntity> 同時實作了 IQueryable<TEntity>IAsyncEnumerable<TEntity> 介面:

csharp
public abstract class DbSet<TEntity> : 
    Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<IServiceProvider>, 
    System.Collections.Generic.IAsyncEnumerable<TEntity>, 
    System.Collections.Generic.IEnumerable<TEntity>, 
    System.ComponentModel.IListSource, 
    System.Linq.IQueryable<TEntity> where TEntity : class

此時,System.Linq.Async 提供的 AsyncEnumerable 擴充方法與 System.Linq 提供的 Queryable 擴充方法(如 WhereSelect)會發生衝突。編譯器無法判斷開發者意圖呼叫的是同步的 IQueryable 擴充方法,還是非同步的 IAsyncEnumerable 擴充方法。為了明確指定型別,開發者必須顯式呼叫 AsQueryable()

升級至 EF Core 6 的改善

什麼情況下可以移除 AsQueryable():當專案已經升級至 EF Core 6 或更高版本時。

在 EF Core 6 中,DbSet<TEntity> 的介面實作已經調整,移除了對 IAsyncEnumerable<TEntity> 的直接實作:

csharp
public abstract class DbSet<TEntity> : 
    Microsoft.EntityFrameworkCore.Infrastructure.IInfrastructure<IServiceProvider>, 
    System.Collections.Generic.IEnumerable<TEntity>, 
    System.ComponentModel.IListSource, 
    System.Linq.IQueryable<TEntity> where TEntity : class

由於介面定義的改變,編譯器不再面臨擴充方法選擇的歧義。因此,即便專案中仍安裝有 System.Linq.Async,也不會再發生衝突,開發者可以安全地移除程式碼中多餘的 AsQueryable() 呼叫。

異動歷程

  • 2024-07-16 初版文件建立。