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

效能優化總結(六):預載入與聚合 SQL 實戰案例

     編輯:關於.NET

案例背景:GIX4 專案

領域模型關係:

領域模型關係

重點區域:Project(建設專案)→ ProjectPBS(專案 PBS 清單)→ PBSPropertyValue(PBS 屬性值,對應範本定義的屬性與可選值)。

UI 介面:左側專案清單,點選後右側顯示該專案所有 PBS 及屬性值。

UI 介面

聚合 SQL 應用:一次撈取完整樹狀資料

避免 Lazy Load 造成 N+1 遠端呼叫,使用單一 SQL 聯結三張表:

private static readonly string SQL_GET_PBS_BY_PBSTYPE_WITH_PROPERTIES = string.Format(@"
select
  {0}, -- PBS 欄位
  {1}, -- PBSProperty 欄位 (別名 p)
  {2}  -- PBSPropertyOptionalValue 欄位 (別名 v)
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"));

資料層 DataPortal_Fetch 執行 SQL,將 DataTable 透過 ReadFromTable 還原為物件圖(PBS.GetChild_With_Properties)。

預載入策略:非同步背景載入 + 介面即時回應

問題:聚合 SQL 資料量大,同步等待會凍結 UI。

解法:

  1. 使用者點選專案 → 立即以 Lazy Load 顯示 PBS 清單(無屬性值)。
  2. 背景啟動 ForeAsyncLoader 執行聚合 SQL 載入所有屬性值。
  3. 載入完成前,使用者操作某 PBS → 走原本 Lazy Load 遠端撈取該 PBS 屬性(資料量小可忽略)。
  4. 載入完成後,將完整屬性值合併至已綁定 UI 的物件,後續切換 PBS 無需再遠端呼叫。

觸發預載入

EventHandler projectPBSView_DataChanged = (sender, e) =>
{
    var project = view.CurrentObject as Project;
    if (project != null) project.PBSPropertyValuesLoader.BeginLoading();
};
projectPBSView.DataChanged += projectPBSView_DataChanged;

ForeAsyncLoader 定義於 Project 部分類別

[NonSerialized, NotUndoable]
private ForeAsyncLoader _PBSPropertyValuesLoader;

public ForeAsyncLoader PBSPropertyValuesLoader
{
    get
    {
        if (_PBSPropertyValuesLoader == null)
            _PBSPropertyValuesLoader = new ForeAsyncLoader(this.LoadPBSPropertyValues);
        return _PBSPropertyValuesLoader;
    }
}

PBS 原本 Lazy Load 屬性


private static PropertyInfo<PBSPropertys> PBSPropertysProperty =
    RegisterProperty(new PropertyInfo<PBSPropertys>("PBSPropertys"));

[Association]
public PBSPropertys PBSPropertys
{
    get { return this.GetLazyChildren(PBSPropertysProperty, PBSPropertys.NewChild, PBSPropertys.Get); }
}

新挑戰:資料合併與衝突處理

因無「唯一實體」機制,背景載入的物件與 UI 綁定物件為不同實例。若使用者在載入完成前已編輯某 PBS 屬性,單純覆蓋會導致資料遺失。

解法:載入每筆 PBS 屬性前,先判斷該 PBS 是否已有本地編輯(PropertyValuesLoaded()),若有則合併(LoadPropertyValues),否則直接替換。


private bool _PBSPropertyValuesLoaded;

private void LoadPBSPropertyValues()
{
    if (this._PBSPropertyValuesLoaded) return;
    lock (this)
    {
        bool pbssLoaded = this.FieldManager.FieldExists(ProjectPBSsProperty);
        ProjectPBS[] projectPBSCache = null;
        if (pbssLoaded)
        {
            projectPBSCache = this.ProjectPBSs.ToArray();
            this._PBSPropertyValuesLoaded = projectPBSCache.All(pp => pp.PropertyValuesLoaded());
            if (this._PBSPropertyValuesLoaded) return;
        }

        var oldProjectPBSs = ProjectPBSs.GetListByProject_With_PropertyValues(this);

        if (pbssLoaded)
        {
            foreach (var pbs in projectPBSCache)
                pbs.LoadPropertyValues(oldProjectPBSs); // 合併邏輯
        }
        else
        {
            this.LoadProperty(ProjectPBSsProperty, oldProjectPBSs);
        }
        this._PBSPropertyValuesLoaded = true;
    }
}

lock(this) 防止多重非同步載入競爭。

成果與後續

實施後使用者反饋:「軟體快多了,早這樣做就好。」

下一步重構目標:簡化聚合載入 API,減少手寫 SQL、方法定義樣板碼,但不想重造 ORM 輪子。

本系列至此告一段落。

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