使用 Entity Framework (EF) 查詢時,為了效能常只選取所需欄位,透過匿名型別或自訂 DTO 投影。但若直接回傳 IQueryable<匿名型別> 給上層(如 Web API、WCF、跨專案呼叫),會因匿名型別編譯器產生的內部型別不可跨組件存取,導致序列化失敗或執行時異常。
// 實體類別
public class Category {
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
}
// 查詢僅需 Id、Name
var data = from c in context.Categories
select new { cId = c.Id, cName = c.Name }; // 匿名型別
// 直接回傳 IQueryable<匿名型別> 給跨域呼叫者 → 爆炸
建立對應的 DTO 類別,查詢時投影至具名型別:
public class CategoryView {
public int Id { get; set; }
public string Name { get; set; }
}
// 查詢
var data = from c in context.Categories
select new CategoryView { Id = c.Id, Name = c.Name };
// 回傳型別可為 IQueryable<CategoryView> 或 List<CategoryView>
return data.ToList();
優點:型別公開、可序列化、跨組件安全、支援 Swagger/OpenAPI 文件產生。
缺點:需維護額外類別檔案。
ExpandoObject(.NET 4.0+)var data = from c in context.Categories
select new { c.Id, c.Name };
var list = new List<dynamic>();
foreach (var item in data) {
dynamic dto = new ExpandoObject();
dto.Id = item.Id;
dto.Name = item.Name;
list.Add(dto);
}
return list;
優點:無需預先定義類別。
缺點:動態型別效能稍差、無靜態型別檢查、文件生成困難。
var data = from c in context.Categories
select new { c.Id, c.Name };
var list = data.AsEnumerable()
.Select(x => (Id: x.Id, Name: x.Name))
.ToList();
return list;
優點:語法簡潔、具名元素、可序列化。
缺點:舊版 .NET 不支援、跨語言互通性較弱。
Dictionary<string, object>var list = data.AsEnumerable()
.Select(x => new Dictionary<string, object> {
["Id"] = x.Id,
["Name"] = x.Name
})
.ToList();
適用場景:極度動態欄位需求,但序列化體積較大。
ViewModels 或 Dtos 資料夾集中管理,利於維護與團隊協作。ProjectTo<CategoryView>() 直接在 SQL 層面投影,避免載入完整實體再映射。IHttpActionResult / ActionResult<T>:配合具名 DTO 可自動產生 OpenAPI 規格。IQueryable<Entity>,投影邏輯下沉至 Service/Controller 層。EF 查詢匿名物件雖便利,但跨組件/跨域序列化會失敗。定義具名 DTO 是最穩健、可維護的解法,配合 AutoMapper ProjectTo 可兼顧效能與整潔架構。