首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> 關於.NET

LINQ to SQL N 層架構查詢 (上):自訂實體回傳與資料傳輸載體設計

     編輯:關於.NET

分層架構下的核心衝突

UI 層直接存取 DataContext 時,var query = ... select new { ... } 匿名型別配合智能感應極便利。但引入服務層 (WCF/遠端) 後:

  • 匿名型別無法跨組件邊界 (非 public、編譯器內部型別)。
  • 服務層需定義 **資料傳輸物件 (DTO)** 作為契約。
  • 如何在服務層用 LINQ to SQL 查詢並高效映射到 DTO?

嘗試與失敗的方案回顧

方案做法結果失敗原因
1. 擴充實體類別partial class 加欄位查詢結果欄位為 nullL2S 只映射 [Column] 屬性,忽略擴充屬性
2. Translate + 擴充實體ExecuteQuery + Translate新增欄位仍為 nullTranslate 邏輯同 (1),僅映射 [Column]
3. 手寫 T-SQL + ExecuteQuery原生 SQL 查詢新增欄位仍為 null同 (2),映射規則不變
4. 繼承實體類別class CustomerExt : CustomerInvalidOperationExceptionL2S 繼承映射需 Discriminator,且基類欄位映射衝突

可行方案一:顯式定義 DTO 類別 (最穩健)

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();
    }
}
  • 優點:型別安全、效能好 (單次查詢)、無映射驚喜。
  • 缺點:每個查詢場景需手寫 DTO,重複屬性定義。

可行方案二:視圖 / 預存程序 / 函數映射

  • 在 DB 建立 View / SP,L2S 設計器拖入 → 自動產生實體類別。
  • 缺點:場景變化需改 DB 物件,爆炸式增長或效能差 (大 View Join 所有表)。

可行方案三:通用物件轉換器 (ObjectConverter) - 靈活折中

核心思想

服務層回傳 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>(); // 擴展方法
}

ConvertTo 實現精要

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[] (同名屬性對)

ObjectConverter 優缺點分析

優點缺點
零手寫映射程式碼效能:反射 + 逐項複製 (大量資料較慢)
DTO 可繼承 L2S 實體,復用定義回傳「黑盒子」:呼叫端不知哪些屬性有值、哪些為 null
支援任意匿名型別 → 任意 DTO語義風險:繼承關係被濫用 (CustomerOrderDetail 不是 Customer)

Entity Framework 的優勢啟示

EF 概念模型支援 **多表映射單一實體** (Table Splitting / Entity Splitting),完美解決此問題。L2S 僅支援 1:1 表對實體映射,這是架構層面的侷限。

方案選擇建議

  1. 嚴格契約、高效能、長期維護 → 方案一:顯式 DTO + ExecuteQuery。
  2. 快速原型、內部工具、場景多變 → 方案三:ObjectConverter (含快取)。
  3. 新專案 → 直接採用 **Entity Framework Core**,原生支援複雜映射與 DTO 投影 (Select(c => new DTO { ... }))。

總結

LINQ to SQL 在 N 層架構下回傳自訂實體,本質是**查詢投影與跨邊界型別映射**的矛盾。顯式 DTO 最穩健,ObjectConverter 最靈活,EF Core 則是現代終極解。理解各方案權衡,依專案性質抉擇。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved