希格工作室

2012年4月11日 星期三

Linq時代的分頁使用關係 (C#)

       在早期,我們在完成一個網頁表單會使用DataGird這個物件來實現,而DataGird這個物件本身也就支援著分頁的功能了。不過,我們應該都知道DataGird實現分頁的方式就是將資料透過ViewState保留後,再從中篩選要顯示的資料給User來檢閱,也因此在大量資料下,除了會造成資料庫負擔外,更會因為ViewState過大的關係,使得網頁停止回應,導致整個掛掉。

      所以我們會利用資料篩選的方式,只透過回應少量且必要的資料來減低資料庫負擔並提高效能,而通常會透過一連串的SQL語法去達成,至今亦是如此。

      在SQL2000之前,我們可能會透過cursor的方式叫用sp_cursoropen及sp_cursorfetch來達成,而在SQL2005之後多了ROW_NUMBER()這個函數,讓我們方便替每筆資料列做編號,之後只要透過where語法去取用需要的編號範圍,即可達成分頁。

      例如我們有一語法取得資料表的幣別種類並替它編號
SELECT ROW_NUMBER() OVER (ORDER BY [Currency]) AS [ROW_NUMBER], [Currency], [CurrencyName] FROM [dbo].[Currency]
      結果大概長這樣


    如果我們打算將它分成兩頁並取第一頁前5筆時

select * from
( SELECT top 5 ROW_NUMBER() OVER (ORDER BY [Currency]) AS [ROW_NUMBER], [Currency], [ CurrencyName ] FROM [dbo].[Currency] )  AS   a
where [ROW_NUMBER] > 0
    結果自然長這樣
     而至於我為什麼要講那麼多SQL呢,是因為使用Linq有著更令人一目瞭然的寫法,如:
var data = (from i in db.Currency select i).Skip(0).Take(5).ToList();

    好了....解決了(註:Skip是略過幾筆,Take是取得幾筆),當你使用SQL Profiler時會發現,Linq會自動幫你轉成類似語法,來達到資料分頁,不過這其實不是啥了不起的事...,因為這已經好幾年前的舊聞了,我這裡會講這個,主要只是要表達使用Linq後,在背後的執行狀況。

    在VS2005還是VS2008時,DataGird被捨棄了,取而代之的是GridView,而GridView可以搭配著各種DataSource容器,只要指定好DataSourceID,並在畫面上拖拖拉拉後,再將AllowPaging打開,分頁輕輕鬆鬆便做好了!而且更重要的是,透過Profiler去追蹤,你會發現DataSource傳送的SQL語法就是剛才所使用的ROW_NUMBER,簡直棒透了不是?

    這當然也不是新聞。

   問題在於一點,GridView必需搭配DataSource容器才得以自動完成[資料分頁],但是在分散式專案的架構下,想單單靠著指定DataSourceID來完成表單似乎有點難度,故在這情況下只好又做回原來的GridView.DataSource&DataBind()來製作表單,但不知道是不是由於DataSourceID太好用的關係吧?又或著有些人認為GridView搭配Linq會聰明的自己作分頁..?之類的,你可能會看到如下程式碼:

public void Page_Load(object sender, EventArgs e)
  {
     GridView1.DataSource = getData();
     GridView1.DataBind();
  }
  public IEnumerable<TBX> getData()
  {
     var data = from i in DB.Table select i;
     return data;
  }
        然後在介面上設定AllowPaging=true


        這是一個不知道該說什麼好的一件事,似乎會以為這樣就會替你完成[資料分頁]?那當然是不可能的...,GirdView並沒有你想像的聰明,它只會將資料全數取回後送給GridView1,存放在GridView1的ViewState裡,緊接著在裡頭進行著分頁動作,就又回到N年前的DataGird問題裡(即使你用別人開發的控件亦同)。

        要記得一個大原則,只要透過DataSource&DataBind()就代表你放棄好用的工具,所以所有功能你都得自己寫!!

       是故,請好好利用Skip和Take進行分頁吧。

        另外在Google時發現一篇文章,文章裡問道,為何我用Skip&Take後,出來的語法和想像的不大一樣?

       其實這問題很簡單,因為你下的就是告訴它我要先Skip(跳過)再Take(取用),所以出來的資料截取方式當然是要先等資料讀過後,才知道要where和Top哪邊,如果要像此文章裡的想法下,只Top一定數再where範圍的話...。

       只要反過來先Take再Skip就好囉!分頁程式如下:

public List<TheTable> ddd(int PageSize, int PageCount)
{
     Conn db = new Conn();
     var data = from i in conn.TheTable  select i;
     var Take = (PageSize * PageCount);
     var Skip = ( (PageCount - 1) * PageSize);
     return data.Take(Take).Skip(Skip).ToList();
}
之後只要去取用就好囉

public void Button1_Click(object sender, EventArgs e)
{
 GridView1.DataSource = getAList(GridView1.PageSize, int.Parse(PageCount.Text));
 GridView1.DataBind();
}


不過這個方法沒有詳細Debug,防呆做好的話理論上應該不會有問題....吧?
(甚至你想先Take再Take說不定也行?)

2012/4/19:
關於Take Skip,查執行計劃,效率上似乎一樣。

沒有留言:

張貼留言