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

效能優化實戰(三):GIX4 專案中的聚合 SQL 應用詳解

     編輯:關於.NET

本篇將實際展示 GIX4 系統如何應用聚合 SQL 方案改善效能,並與既有架構整合。內容涵蓋 SQL 生成、資料映射讀取方案、工廠方法命名慣例與完整實例程式碼。

SQL 生成策略

GIX4 採用 CSLA 作為分散式框架,ORM 使用輕量級開源專案 LiteORM。領域模型類別定義包含資料表與欄位的中繼資料(Attribute 形式),為 SQL 自動生成奠定基礎。雖然高效能模組較少,核心查詢 SQL 仍以人工撰寫為主,但列名生成必須自動化以避免多表聯結時的欄位名稱衝突,並支援結構變更不影響手寫 SQL。

我們在基類 GEntity<T> 中封裝列名生成 API:

[Serializable]
public abstract class GEntity<T> : Entity<T> where T : GEntity<T>
{
    /// 
    /// 從 DataRow 直接讀取資料。欄位約定為 "表名_欄位名",如 "PBS_Name"。
    /// 
    public static T ReadDataDirectly(DataRow rowData) { ... }

    /// 
    /// 獲取可用於 ReadDirectly 讀取的列名 SQL,格式:PBS.Id as PBS_Id, PBS.Name as PBS_Name...
    /// 
    public static string GetReadableColumnsSql() { ... }

    /// 
    /// 支援表別名的列名 SQL 生成。
    /// 
    public static string GetReadableColumnsSql(string tableAlias) { ... }

    public static string GetReadableColumnSql(string columnName) { ... }
    public static ITable GetTableInfo() { ... }
}

聚合 SQL 範例

private static readonly string SQL_GET_PBS_BY_PBSTYPE_WITH_PROPERTIES = 
    string.Format(@"
select
    {0}, {1}, {2}
from PBS pbs
left outer join PBSProperty p on pbs.Id = p.PBSId
left outer join PBSPropertyOptionalValue v on p.Id = v.PBSPropertyId
where pbs.PBSTypeId = '{{0}}'
order by pbs.Id, p.Id",
    PBS.GetReadableColumnsSql(),
    PBSProperty.GetReadableColumnsSql("p"),
    PBSPropertyOptionalValue.GetReadableColumnsSql("v"));

生成的 SQL 列名格式統一為 表名_欄位名(或 別名_欄位名),儲存於靜態唯讀欄位,避免重複生成開銷。

映射與資料讀取方案

聚合查詢返回的扁平化資料表結構如下:

TableA  TableB  TableC  TableD...
a1      b1      c1      d1
a1      b1      c2      NULL
a1      b2      c3      NULL
a1      b3      NULL    NULL
a2      b4      c5      NULL
...

對應的物件層級結構為樹狀:每個 TableA 行對應一個子表,子表再對應更細粒度的子表。我們定義 IGTable 介面與兩個實作類別:

public interface IGTable
{
    int Count { get; }
    DataRow this[int rowIndex] { get; }
}

public class GTable : IGTable { /* 適配 DataTable */ }

public class SubTable : IGTable 
{ 
    /* 表示父表中指定範圍的行集合,構造函數傳入父 IGTable、起始行、結束行 */ 
}

核心讀取邏輯封裝於 EntityListHelper.ReadFromTable:

public static void ReadFromTable<TCollection, TEntity>(
    TCollection list, IGTable table, Func<TEntity, IGTable, TEntity> relationLoader)
    where TCollection : GBusinessListBase<TCollection, TEntity>
    where TEntity : GEntity<TEntity>
{
    // 演算法:遍歷 table,依據 TEntity.Id 變化切分子表,
    // 為每個 TEntity 建立 SubTable 傳給 relationLoader 遞迴載入子物件
    ...
}

relationLoader 由呼叫端定義,負責將子表資料映射為導航屬性集合,實現靈活的聚合物件構建。

工廠方法命名慣例

為保持資料存取層介面語意清晰,不暴露 ORM 細節,我們採用命名慣例而非表達式樹:

  • GetArticles_With_User
  • GetPBSTypes_With_PBSTree

註解說明該方法查詢出的物件所攜帶的關聯物件。此設計簡單實用,適合後期以 SQL/預存程序優化替換 ORM 的場景。

完整實例:PBS 模組聚合載入

資料存取層 (服務端執行)

// 客戶端呼叫入口
public static PBSs GetListByPBSTypeId_With_Properties(Guid pbsTypeId)
{
    return DataPortal.Fetch<PBSs>(new GetListCriteria_With_Properties(pbsTypeId));
}

// 服務端 DataPortal_Fetch
private void DataPortal_Fetch(GetListCriteria_With_Properties criteria)
{
    var sql = string.Format(SQL_GET_PBS_BY_PBSTYPE_WITH_PROPERTIES, criteria.PBSTypeId);
    using (var db = Helper.CreateDb())
    {
        IGTable table = db.QueryTable(sql);
        this.ReadFromTable(table, PBS.GetChild_With_Properties);
    }
}

// PBS 類別的 relationLoader
public class PBS : GTreeEntity<PBS>, IDisplayModeL
{
    public static PBS GetChild_With_Properties(PBS pbs, IGTable subTable)
    {
        pbs = GetChild(pbs);
        var properties = PBSPropertys.GetChild_WithValues(pbs, subTable);
        pbs.LoadProperty(PBSPropertysProperty, properties);
        return pbs;
    }
}

// PBSPropertys 類別的 relationLoader
public class PBSPropertys : GEntityTreeList<PBSPropertys, PBSProperty>
{
    public static PBSPropertys GetChild_WithValues(PBS pbs, IGTable table)
    {
        var properties = GetChild();
        properties.ReadFromTable(table, PBSProperty.GetChild_With_Values);
        foreach (var p in properties) p.PBS = pbs;
        return GetChild().AddRange(properties.OrderBy(p => p.OrderNo));
    }
}

// PBSProperty 類別的 relationLoader
public class PBSProperty : GEntity<PBSProperty>, ITreeNode, IOrderedObject
{
    public static PBSProperty GetChild_With_Values(PBSProperty property, IGTable table)
    {
        var model = GetChild(property);
        var values = PBSPropertyOptionalValues.GetChild(model, table);
        model.LoadProperty(PBSPropertyOptionalValuesProperty, values);
        return model;
    }
}

// 葉子節點 PBSPropertyOptionalValues
public partial class PBSPropertyOptionalValues : GEntityList<PBSPropertyOptionalValues, PBSPropertyOptionalValue>
{
    internal static PBSPropertyOptionalValues GetChild(PBSProperty parent, IGTable table)
    {
        var values = GetChild();
        values.ReadFromTable(table);
        foreach (var v in values) v.PBSProperty = parent;
        return values;
    }
}

調用關係圖

客戶端程式碼

var pbsList = PBSs.GetListByPBSTypeId_With_Properties(id);
foreach (var pbs in pbsList)
{
    foreach (var property in pbs.PBSPropertys)
    {
        foreach (var optionalValue in property.PBSPropertyOptionalValues)
        {
            // 直接使用,無額外資料庫往返
        }
    }
}

雖然客戶端使用多層迴圈,但實際僅發生一次遠端資料存取。

適用場景與小結

聚合 SQL 模式常用於專案重構/優化階段:初期開發時無法預知上層需求,僅當業務流程完成分析後,才能針對性編寫聚合 SQL。本文展示的完整方案——從 SQL 生成、列名約定、階層式資料映射到工廠方法命名——在 GIX4 專案中有效解決了 N+1 查詢問題,大幅降低資料庫往返次數。下一篇將介紹預載入技術與 .NET 4.0 平行運算庫在系統中的應用。

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