LinQ實作簡單的存取優化

以往接到專案時基本沒去處理內部的某些不是很優的寫法,如今稍有空暇整理一下,並提出分享,以產生兩層 Navigation Bar的程式碼為例子,並提供比較。

程式的優化很大一塊都是在於I/O讀取上的優化,分散的多次讀取會造成運作效率的低落,當資料龐大或系統壅塞時更可見其效益,今天以一個簡單的例子說明一下,主要使用的語法是SQL和LinQ。

// 用此物件來讀取 Menu + Item
// Num:編號(PK) Title:名稱 Url:連結(Menu層為空) Root:層次 0代表Menu 數字代表為 item Ran:排序
class MenuItem
{
    public MenuItem()
    {

    }

    public MenuItem(int Num_, string Ti_, string Url_, int Root_, int Ran_)
    {
        Num = Num_;
        Title = Ti_;
        Url = Url_;
        Root = Root_;
        Ran = Ran_;
    }

    public int Num { get; set; }
    public string Title { get; set; }
    public string Url { get; set; }
    public int Root { get; set; }
    public int Ran { get; set; }
}

資料表說明:

--選單選項
CREATE Table Menus( 
	Num identity(1,1) int, 
	Title Nvarchar(30) not null,
	URL Nvarchar(180) null,
	Root int not null,
	Range int not null
	)

-- 權限群組
CREATE Table Group(
	(PK)g_name Nvarchar(5) not null, 
	ViewItems Nvarchar(Max) not null, -- 可檢視頁面 存檔資料為 int的文字串 例如: "3,5,9,125,13,17,26,235,152"
	EditItems Nvarchar(Max) not null  -- 有編輯權限頁面 存檔資料為 int的文字串 例如: "3,5,9,125,13,17,26,235,152"
)

-- 使用者
CREATE Table Users(
	(PK)u_id Nvarchar(30) not null, 
	[Power] Nvarchar(5) not null
)

原本的寫法是兩個迴圈,大圈包小圈做SQL的資料讀取,大圈讀Menu 小圈讀Item,每次只讀取一行,這方法實在是不優,每次要開啟SQL Profile來除錯時,總是看到一大堆沒意義的查詢造成除錯非常麻煩,既然不是好方法,就不攤開展示是什麼了。

// 將結果封裝於 子程序中 以 uid (使用者名稱) 讀取出該權限可以進入的群組清單內 用子查詢的方式把 Menus + Items 一次全部取出到物件
publc void BuildNavBar(string uid)
{
        List<MenuItem> menuItems = new List<MenuItem>();
        string Query = "SELECT Num, Title, Url, Root, Range FROM Menus i WHERE Num in (SELECT value FROM STRING_SPLIT((SELECT ViewItems FROM Group g WHERE g.g_name= (select [Power] FROM Users a WHERE u_id=@uid)),',')) ORDER BY Root ,Range;";
        string Link="", NavBar = "";
        using(SqlConnection Conn = new SqlConnection(db))
        {
            try
            {
                SqlCommand sqa = new SqlCommand(Query, Conn);
                sqa.Parameters.Add(new SqlParameter("@uid", SqlDbType.NVarChar) { Value = uid });
                SqlDataReader Sr = sqa.ExecuteReader();
                while (Sr.Read())
                {
                    // 用此方法排除 Null資料的問題
                    Link = DBNull.Value.Equals(Sr["url"]) ? "" : Sra.GetString(Sr.GetOrdinal("url"));

                    menuItems.Add(
                        new MenuItem(
                            Sr.GetInt32(Sr.GetOrdinal("num")),
                            Sr.GetString(Sr.GetOrdinal("title")), Link,
                            Sr.GetInt32(Sr.GetOrdinal("root")),
                            Sr.GetInt32(Sr.GetOrdinal("range"))));
                }
            }
            catch(Exception ex)
            {
                Literal1.Text = ex.ToString();
                return;
            }
        }
        
        
		// 讀取 Menu 層 (Root = 0)
        var Menu = from m in menuItems where m.Root == 0 orderby m.Ran select m;
        foreach (MenuItem it in Menu)
        {
            NavBar += @"<li class=""nav-item dropdown"">
                        <a class=""nav-link dropdown-toggle"" href=""#"" id=""navbarDropdownMenuLink" + it.Num.ToString() + @""" role=""button"" data-toggle=""dropdown"" aria-haspopup=""true"" aria-expanded=""false"">" + it.Title + @"</a>
                        <div class=""dropdown-menu"" aria-labelledby=""navbarDropdownMenuLink"">

            ";
            // 讀取 Items層 (Root = Menu層的Num )
            var Items = from i in menuItems where i.Root == it.Num orderby i.Ran select i;
            foreach (MenuItem item in Items)
            {
                NavBar += "<a class=\"dropdown-item\" href=\"" + ResolveUrl(item.Url) + "\">" + item.Title + @"</a>
                ";
            }
            NavBar += @"</div></li>

            ";
        }
        // 轉換成Bootstrap的 NavBar 寫入到Literal物件中,溶入原始碼,忠實呈現所有的Tag內容。
        Literal1.Text = NavBar;
}

最後,實測的平均載入時間由995毫秒變成520毫秒,本例的Menu+Items一共有86個 15組,舊方法是讀取Menu+Items一共86次,當然也有讀取16次的方法,也算得上是優化,以本文所提出新的寫法只需要讀取一次,然後用物件化查詢方式把後續的切割與分段都全數搞定,馬上就節省了85次IO動作,如果同一時間有300個用戶在線上的話,其節省IO的作法的效益更巨大。

iT邦幫忙 個人帳號:Kw6732