升級至 .NET 6 時多餘 AsQueryable() 呼叫的原因
TLDR
- 在 .NET 6 專案中發現多餘的
AsQueryable()呼叫,通常是因為從舊版專案升級而來。 - 舊版 EF Core (5 以前) 的
DbSet<TEntity>同時實作了IQueryable<TEntity>與IAsyncEnumerable<TEntity>,導致編譯器在處理 LINQ 擴充方法時產生歧義。 - 呼叫
AsQueryable()是為了明確指定型別為IQueryable<TEntity>,以避開System.Linq.Async帶來的擴充方法衝突。 - EF Core 6 移除了
DbSet<TEntity>對IAsyncEnumerable<TEntity>的直接實作,解決了此衝突,因此在 .NET 6 環境下可安全移除多餘的AsQueryable()。
問題情境與成因分析
在升級至 .NET 6 的專案中,開發者常會發現程式碼中充斥著 DbContext.Table.AsQueryable()。由於 DbSet<TEntity> 本身已經實作了 IQueryable<TEntity>,理論上無需額外呼叫 AsQueryable()。
此現象主要發生在專案中同時引用了 System.Linq.Async 套件,且原始專案基於 EF Core 5 或更早版本的情況下。
擴充方法衝突的技術細節
在 EF Core 5 以前的版本,DbSet<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 時:
Queryable類別定義了IQueryable<TEntity>的擴充方法(如Where、Select)。AsyncEnumerable類別定義了IAsyncEnumerable<TSource>的擴充方法(如Where、Select)。
由於 DbSet<TEntity> 同時實作了這兩個介面,編譯器無法判斷開發者意圖呼叫的是哪一個擴充方法,導致編譯錯誤。為了強制指定使用 IQueryable 的擴充方法,開發者必須顯式呼叫 AsQueryable()。
EF Core 6 的改進
在 EF Core 6 及以後的版本中,DbSet<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結論與建議
由於 EF Core 6 移除了對 IAsyncEnumerable<TEntity> 的直接實作,編譯器不再面臨擴充方法選擇的歧義。即便專案中仍安裝有 System.Linq.Async,也不會再發生衝突。因此,在升級至 .NET 6 後,可以安全地移除這些多餘的 AsQueryable() 呼叫,以保持程式碼簡潔。
異動歷程
- 初版文件建立。