UI 層直接存取 DataContext 時,var query = ... select new { ... } 匿名型別配合智能感應極便利。但引入服務層 (WCF/遠端) 後:
| 方案 | 做法 | 結果 | 失敗原因 |
|---|---|---|---|
| 1. 擴充實體類別 | partial class 加欄位 | 查詢結果欄位為 null | L2S 只映射 [Column] 屬性,忽略擴充屬性 |
| 2. Translate + 擴充實體 | ExecuteQuery + Translate | 新增欄位仍為 null | Translate 邏輯同 (1),僅映射 [Column] |
| 3. 手寫 T-SQL + ExecuteQuery | 原生 SQL 查詢 | 新增欄位仍為 null | 同 (2),映射規則不變 |
| 4. 繼承實體類別 | class CustomerExt : Customer | InvalidOperationException | L2S 繼承映射需 Discriminator,且基類欄位映射衝突 |
public class CustomerOrderDetail
{
public string CustomerID { get; set; }
public string CompanyName { get; set; }
public string ContactName { get; set; }
public string Address { get; set; }
public DateTime? OrderDate { get; set; }
public string ProductName { get; set; }
}
// 服務層
public List<CustomerOrderDetail> GetOrderInfo(string customerID)
{
using (var ctx = new NorthWindDataContext())
{
var query = from C in ctx.Customers
join O in ctx.Orders on C.CustomerID equals O.CustomerID
join OD in ctx.Order_Details on O.OrderID equals OD.OrderID
join P in ctx.Products on OD.ProductID equals P.ProductID
where C.CustomerID == customerID
select new
{
C.CustomerID, C.CompanyName, C.ContactName, C.Address,
O.OrderDate, P.ProductName
};
return ctx.ExecuteQuery<CustomerOrderDetail>(query).ToList();
}
}
服務層回傳 IQueryable<匿名型別>,呼叫端用 ConvertTo<DTO>() 依**屬性名稱同名複製**轉換。
// DTO 繼承 L2S 實體 (復用基礎屬性定義)
public class CustomerExt : Customer
{
public DateTime? OrderDate { get; set; }
public string ProductName { get; set; }
}
// 服務層
public List<CustomerExt> GetOrderInfo(string customerID)
{
var query = from C in ctx.Customers
join O in ctx.Orders on C.CustomerID equals O.CustomerID
join OD in ctx.Order_Details on O.OrderID equals OD.OrderID
join P in ctx.Products on OD.ProductID equals P.ProductID
where C.CustomerID == customerID
select new { C.CustomerID, C.CompanyName, C.ContactName, C.Address, O.OrderDate, P.ProductName };
return query.ConvertTo<CustomerExt>(); // 擴展方法
}
public static List<TResult> ConvertTo<TResult>(this IEnumerable source) where TResult : new()
{
if (source == null) return null;
if (source is IEnumerable<TResult>) return source.Cast<TResult>().ToList();
var result = new List<TResult>();
IEnumerable<CommonProperty> commonProps = null;
bool first = true;
foreach (var s in source)
{
if (first)
{
if (s is TResult) return source.Cast<TResult>().ToList();
commonProps = GetCommonProperties(s.GetType(), typeof(TResult)); // 快取
first = false;
}
var t = new TResult();
foreach (var cp in commonProps)
cp.TargetProperty.SetValue(t, cp.SourceProperty.GetValue(s, null), null);
result.Add(t);
}
return result;
}
// 靜態快取優化:PropertyCache 雙重檢查鎖定
// Key: "SourceTypeFullName+TargetTypeFullName"
// Value: CommonProperty[] (同名屬性對)
| 優點 | 缺點 |
|---|---|
| 零手寫映射程式碼 | 效能:反射 + 逐項複製 (大量資料較慢) |
| DTO 可繼承 L2S 實體,復用定義 | 回傳「黑盒子」:呼叫端不知哪些屬性有值、哪些為 null |
| 支援任意匿名型別 → 任意 DTO | 語義風險:繼承關係被濫用 (CustomerOrderDetail 不是 Customer) |
EF 概念模型支援 **多表映射單一實體** (Table Splitting / Entity Splitting),完美解決此問題。L2S 僅支援 1:1 表對實體映射,這是架構層面的侷限。
Select(c => new DTO { ... }))。LINQ to SQL 在 N 層架構下回傳自訂實體,本質是**查詢投影與跨邊界型別映射**的矛盾。顯式 DTO 最穩健,ObjectConverter 最靈活,EF Core 則是現代終極解。理解各方案權衡,依專案性質抉擇。