DataGridView分頁與數據庫分頁查詢


     最近一個項目中,需要從數據庫查詢大量數據(上萬條)並在Winform里面用Datagridview展示,故而查找了相關資料並進行研究。

     在.NET中有兩種思路實現這種分頁式datagridview: 一種是通過純客戶端進行數據分頁篩取展示來實現; 另一種是通過結合數據庫分頁查詢來實現.

 

1. 客戶端分頁篩選式DataGridView

    在Web程序中,Asp.NET提供了Web的分頁式DataGridView控件,顯然web數據傳輸限制了通信數據量,html和js限制了如winform中的許多高級特性,這恐怕也是其不得不提供分頁的原因。然而在Form程序中並沒有這種實現,相反卻提供了許多讓人愛不釋手的特性,可唯獨缺乏了這種分頁機制。

    為什么要分頁。可能有人會覺得數據量增大時,通信的代價遠大於較短的窗體構建過程,然而事實於此相反,當每行記錄字段較為復雜時(比如包括圖片,bool,字符串)時,datagridview構建的窗體cell將包含圖片、復選框、單選框、文本框,筆者實測上千條記錄就能導致3-5s以上的窗體構建過程,這段時間窗體主線程將die在這了。退一步講,就算記錄字段較為簡單,都是文本集合,然而上萬條記錄仍將導致數秒中的響應時間。可見大數量數據構建分頁的必要性。

    [1]中展示了一個簡潔易行的datagridview分頁方案。其效果如下圖所示,其思路大概如下:通過對DataTable的行Rows以設定的每頁大小進行篩選來構成新的某頁數據的DataTable,並把其綁定到Bindingsource上來控制BindingNavigate上面的導航條和DataGridView的展現。其實現了上頁,下頁,首頁,尾頁的功能,當然也可以實現跳轉到某頁的功能(這並不復雜)。最后為了代碼的移植性和易用性,可以把兩個控件合一起做成一個控件。具體結果及代碼張貼如下:

 

mxcpdemo

 1 // 1、定義幾個所需的公有成員:

      int pageSize = 0;     //每頁顯示行數
      int nMax = 0;         //總記錄數
      int pageCount = 0;    //頁數=總記錄數/每頁顯示行數
      int pageCurrent = 0;   //當前頁號
      int nCurrent = 0;      //當前記錄行
      DataSet ds = new DataSet();
      DataTable dtInfo = new DataTable();

//2、在窗體載入事件中,從數據源讀取記錄到DataTable中:

      string strConn = "SERVER=127.0.0.1;DATABASE=NORTHWIND;UID=SA;PWD=ULTRATEL";   //數據庫連接字符串
      SqlConnection conn = new SqlConnection(strConn);
      conn.Open();
      string strSql = "SELECT * FROM CUSTOMERS";
      SqlDataAdapter sda = new SqlDataAdapter(strSql,conn);
      sda.Fill(ds,"ds");
      conn.Close();
      dtInfo = ds.Tables[0];
      InitDataSet();
  
//3、用當前頁面數據填充DataGridView

      private void InitDataSet()
      {
          pageSize = 20;      //設置頁面行數
          nMax = dtInfo.Rows.Count;
          pageCount=(nMax/pageSize);    //計算出總頁數
          if ((nMax % pageSize) > 0) pageCount++;
          pageCurrent = 1;    //當前頁數從1開始
          nCurrent = 0;       //當前記錄數從0開始
          LoadData();
       }

      private void LoadData()
      {
          int nStartPos = 0;   //當前頁面開始記錄行
          int nEndPos = 0;     //當前頁面結束記錄行
          DataTable dtTemp = dtInfo.Clone();   //克隆DataTable結構框架

          if (pageCurrent == pageCount)
          {
              nEndPos = nMax;
          }
          else
          {
               nEndPos = pageSize * pageCurrent;
          }

          nStartPos = nCurrent;
          lblPageCount.Text = pageCount.ToString();
          txtCurrentPage.Text = Convert.ToString(pageCurrent);
  

          //從元數據源復制記錄行
          for (int i = nStartPos; i < nEndPos; i++)
          {
              dtTemp.ImportRow(dtInfo.Rows[i]);
              nCurrent++;
          }
          bdsInfo.DataSource = dtTemp;
          bdnInfo.BindingSource = bdsInfo;
          dgvInfo.DataSource = bdsInfo;
     }

    //   4、菜單響應事件:
 
    private void bdnInfo_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
    {
         if (e.ClickedItem.Text == "關閉")
          {
             this.Close();
          }
         if (e.ClickedItem.Text == "上一頁")
          {
              pageCurrent--;
              if (pageCurrent <= 0)
              {
                 MessageBox.Show("已經是第一頁,請點擊“下一頁”查看!");
                 return;
              }
              else
              {
                 nCurrent = pageSize * (pageCurrent - 1);
              }
              LoadData();
           }
          if (e.ClickedItem.Text == "下一頁")
          {
              pageCurrent++;
              if (pageCurrent > pageCount)
              {
                  MessageBox.Show("已經是最后一頁,請點擊“上一頁”查看!");
                  return;
               }
               else
              { 
                  nCurrent=pageSize*(pageCurrent-1);
              }
              LoadData();
           }
     }

 

2. 數據庫分頁查詢式Datagridview

        解決了大量數據客戶端Datagridview顯示后,大大縮小了反應的時間。然而隨着數據量的增大,到上百萬條,通信數據量將大大提高,造成的數據讀取延時較為明顯。當然這還是要通過分頁解決,畢竟用戶根本不需要那么多數據同時展現,他也看不過來,那么僅有限的數據量是有必要的,太大的數據量相信大多數人會通過專門的查找選項(畢竟有用的數據有限)。這時,顯然仍通過客戶端篩取的方法是不可行的,數據庫分頁查詢勢在必行,需要將上下頁及跳轉的導航結合數據庫的查詢。

2.1      首先來看數據庫的分頁查詢

        (1)  [1]文中介紹了幾種適用的SQL Server分頁查詢的方法,思路主要有兩種。一種是通過降序和升序結合來查詢某段的數據;另一種是通過ROW_NUMER()函數來生成排序號,通過該排序號進行篩選數據。這兩種思想分別體現在文中第4種和第5種方案。正如作者推薦,第5種方案更可取,當數據字段沒有明顯排序規律,或者想進一步操作時,合適和排序號row_number機制更佳。針對該函數,可參考[4]中SQL2005四個排名函數(row_number、rank、dense_rank和ntile)的比較。需要注意的是,正如[4]中所言,row_number依賴於over子句的排序,當存在排序字段相同的記錄時,記錄的順序不定,所以多次分頁查詢,相同排序字段的記錄順序不定。

         (2) [2]文中介紹了Winform中datagridview大數量查詢分頁顯示,微軟的解決辦法。代碼張貼如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using System.Data.SqlClient;

//Winform datagridview 大數量查詢分頁顯示 微軟的解決辦法
namespace WindowsApplication1
{
    public partial class Form1 : Form
    {
        // WinForm上的控件
        Button prevBtn = new Button();
        Button nextBtn = new Button();
        Button firstBtn = new Button();
        Button lastBtn = new Button();
        static DataGrid myGrid = new DataGrid();
        static Label pageLbl = new Label();
        // 分頁的變量
        static int pageSize = 4; // 每頁顯示多少
        static int leftpageSiz; // 分頁余數
        static int totalPages = 0; // 總共頁數
        static int currentPage = 0; // 當前頁數.
        static string firstVisibleCustomer = ""; // First customer on page to determine location for move previous.
        static string lastVisibleCustomer = ""; // Last customer on page to determine location for move next.
        // DataSet to bind to DataGrid.
        static DataTable custTable;
        // Initialize connection to database and DataAdapter.
        static SqlConnection nwindConn = new SqlConnection("Data Source=localhost;Integrated Security=SSPI;Initial Catalog=northwind");
        static SqlDataAdapter custDA = new SqlDataAdapter("", nwindConn);
        static SqlCommand selCmd = custDA.SelectCommand;

        public Form1()
        {
            InitializeComponent();

            // Initialize controls and add to form.
            this.ClientSize = new Size(360, 274);
            this.Text = "NorthWind Data";
            myGrid.Size = new System.Drawing.Size(729, 240);
            myGrid.Dock = System.Windows.Forms.DockStyle.Top;
            myGrid.AllowSorting = true;
            myGrid.CaptionText = "NorthWind Customers";
            myGrid.ReadOnly = true;
            myGrid.AllowNavigation = false;
            myGrid.PreferredColumnWidth = 150;

            firstBtn.Text = "First";
            firstBtn.Size = new Size(48, 24);
            firstBtn.Location = new Point(22, 240);
            firstBtn.Click += new EventHandler(First_OnClick);

            prevBtn.Text = "Prev";
            prevBtn.Size = new Size(48, 24);
            prevBtn.Location = new Point(92, 240);
            prevBtn.Click += new EventHandler(Prev_OnClick);

            nextBtn.Text = "Next";
            nextBtn.Size = new Size(48, 24);
            nextBtn.Location = new Point(160, 240);
            nextBtn.Click += new EventHandler(Next_OnClick);

            lastBtn.Text = "Last";
            lastBtn.Size = new Size(48, 24);
            lastBtn.Location = new Point(230, 240);
            lastBtn.Click += new EventHandler(Last_OnClick);


            pageLbl.Text = "沒有記錄";
            pageLbl.Size = new Size(130, 16);
            pageLbl.Location = new Point(300, 244);
            this.Controls.Add(myGrid);
            this.Controls.Add(prevBtn);
            this.Controls.Add(firstBtn);
            this.Controls.Add(nextBtn);
            this.Controls.Add(lastBtn);
            this.Controls.Add(pageLbl);

            // 獲取第一頁數據
            GetData("Default");
            DataView custDV = new DataView(custTable, "", "ID", DataViewRowState.CurrentRows);
            myGrid.SetDataBinding(custDV, "");

        }
        public static void First_OnClick(object sender, EventArgs args)
        {
            GetData("First");
        }
        public static void Prev_OnClick(object sender, EventArgs args)
        {
            GetData("Previous");
        }
        public static void Next_OnClick(object sender, EventArgs args)
        {
            GetData("Next");
        }
        public static void Last_OnClick(object sender, EventArgs args)
        {
            GetData("Last");
        }
        private void Form1_Load(object sender, EventArgs e)
        {

        }
        public static void GetData(string direction)
        {
            // Create SQL statement to return a page of records.
            selCmd.Parameters.Clear();
            switch (direction)
            {
                case "First":
                    selCmd.CommandText = "SELECT TOP " + pageSize + " * FROM Customers ";
                    break;

                case "Next":
                    selCmd.CommandText = "SELECT TOP " + pageSize + " * FROM Customers " +
                    "WHERE ID > @ID ORDER BY ID";
                    selCmd.Parameters.Add("@ID", SqlDbType.VarChar, 5).Value = lastVisibleCustomer;
                    break;

                case "Previous":
                    selCmd.CommandText = "SELECT TOP " + pageSize + " * FROM Customers " +
                    "WHERE ID < @ID ORDER BY ID DESC";
                    selCmd.Parameters.Add("@ID", SqlDbType.VarChar, 5).Value = firstVisibleCustomer;
                    break;

                case "Last":
                    selCmd.CommandText = "SELECT TOP " + leftpageSiz + " * FROM Customers ORDER BY ID DESC";
                    break;

                default:
                    selCmd.CommandText = "SELECT TOP " + pageSize + " * FROM Customers ORDER BY ID";

                    // Determine total pages. 
                    SqlCommand totCMD = new SqlCommand("SELECT Count(*) FROM Customers", nwindConn);
                    nwindConn.Open();
                    int totalRecords = (int)totCMD.ExecuteScalar();
                    nwindConn.Close();
                    totalPages = (int)Math.Ceiling((double)totalRecords / pageSize);
                    if ((totalRecords % pageSize) == 0)
                    {
                        leftpageSiz = pageSize;
                    }
                    else
                    {
                        leftpageSiz = totalRecords % pageSize;
                    }
                    break;
            }
            // Fill a temporary table with query results. 
            DataTable tmpTable = new DataTable("Customers");
            int recordsAffected = custDA.Fill(tmpTable);
            // If table does not exist, create it. 
            if (custTable == null)
                custTable = tmpTable.Clone();
            // Refresh table if at least one record returned. 
            if (recordsAffected > 0)
            {
                switch (direction)
                {
                    case "First":
                        currentPage = 1;
                        break;
                    case "Next":
                        currentPage++;
                        break;
                    case "Previous":
                        currentPage--;
                        break;
                    case "Last":
                        currentPage = totalPages;
                        break;
                    default:
                        currentPage = 1;
                        break;
                }
                pageLbl.Text = "Page " + currentPage + " of " + totalPages;
                // Clear rows and add new results. 
                custTable.Rows.Clear();
                foreach (DataRow myRow in tmpTable.Rows)
                    custTable.ImportRow(myRow);
                // Preserve first and last primary key values. 
                DataRow[] ordRows = custTable.Select("", "ID ASC");
                firstVisibleCustomer = ordRows[0][0].ToString();
                lastVisibleCustomer = ordRows[custTable.Rows.Count - 1][0].ToString();
            }
        } 

    }
}

        顯而易見,這種方法效率極高,因為只對一個字段進行排序篩選。但同時,這也存在極大的缺點:

  • 只能依據一個排序字段,如果有多個字段需要排序則無能為力
  • 無法提供跳轉的功能,因為僅靠通過上次起始ID來進行上頁和下頁的查詢,但無法指定查詢哪一頁。

K(6JM6D5K)Z[Q%9M40N5NCG

        (3) 我喜歡的兩種: 正如(1)所言, [2]中的4方案和5方案是極佳選擇

2.2 DataGridView的實現

     由前文可知,2.1中(2)的方法提供了一種簡單高效的分頁查詢方法, 但其缺點也顯而易見,不適宜更高級的查詢需求。同時將顯示指定SQL查詢字符串,無法構造通用的DataGridView控件,使得控件的可移植性受限。下文將介紹一種可行的方案(未完待續)。

 

參考文獻:

[1]  DataGridView分頁功能的實現, http://www.cnblogs.com/kevin-top/archive/2010/01/05/1639448.html

[2]  高效的SQLSERVER分頁查詢, http://www.jb51.net/article/35213.htm

[3] Winform datagridview 大數量查詢分頁顯示 微軟的解決辦法,http://bbs.csdn.net/topics/320194542

[4]SQL2005四個排名函數(row_number、rank、dense_rank和ntile)的比較,   ,http://www.cnblogs.com/xhyang110/archive/2009/10/27/1590448.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM