DataGridView的VirtualMode,在大容量數據加載時特別有用


DataGridView的虛擬模式,允許我們在需要時再檢索數據。達到實時加載的目的。這避免了DataGridView由於大數據量而刷新延遲,也避免了數據量過大造成網絡超時,更可以節約本地內存。總之,它的好處多多,但是要實現這種方式,需要做的事情可不是那么簡單,所以需要權衡項目中是否有這樣的大數據。

基本思路就是:

1,本地設立數據的緩存。比如裝載100條數據。

2,如果用戶查閱的數據超過了這個數,就去取下100條數據,並可以考慮丟掉前面的一些緩存,以保證本地內存不會太大。

3,如何判斷是否應該去取數據,DataGridView已經提供了相應的一些事件,如:CellValueNeeded,一般以XXXNeeded的事件就是告訴你需要取數據了。當然,一個重要的前提是要開啟DataGridView的虛擬模式,這些事件才會有效。this.dataGridView1.VirtualMode = true;在這些事件里,我們可以對DataGridView的單元格賦值(就是直接取緩存的數據來顯示,緩存里如果沒有,會自動去取下100條數據)。

4,取下100條數據,可能要涉及到SQL的分頁。比如用top 100 等就可以簡單實現。

下面的例子就是一個緩存的例子,照着MSDN做的,可以做個參考。即使不用於DataGridView,這個緩存的思想也是非常值得借鑒的。

View Code
using System;
using System.Collections.Generic;
using System.Text;
using System.Data;
using System.Data.SqlClient;

namespace DataGridViewVirtualModeTest
{
    interface IDataPageRetriever
    {
        DataTable ApplyPageOfData(int lowPageBoundary, int rowsPerPage);
    }

    internal class DataRetriever : IDataPageRetriever
    {
        private const String Conn_Str =
             @"Data Source=SK-WZ\SQLEXPRESS;Initial Catalog=TestUse;Persist Security Info=True;User ID=wang;password=wang";
        private string tableName;
        private SqlCommand command;


        public DataRetriever(string tableName)
        {
            this.tableName = tableName;
            SqlConnection conn = new SqlConnection(Conn_Str);
            conn.Open();
            command = conn.CreateCommand();
        }

        private int rowCount = -1;
        public int RowCount
        {
            get
            {
                if (rowCount != -1)
                    return RowCount;

                command.CommandText = "select count(*) from " + tableName;
                rowCount = (int)command.ExecuteScalar();
                return rowCount;
            }
        }

        private DataColumnCollection columns;
        public DataColumnCollection Columns
        {
            get
            {
                if (columns != null)
                    return columns;

                command.CommandText = "select * from " + tableName;
                SqlDataAdapter adapter = new SqlDataAdapter();
                adapter.SelectCommand = command;
                DataTable table = new DataTable();
                table.Locale = System.Globalization.CultureInfo.CurrentCulture;
                adapter.FillSchema(table, SchemaType.Source);
                columns = table.Columns;
                return columns;
            }
        }

        private string commaSeparatedColumnNames;
        public string CommaSeparatedColumnNames
        {
            get
            {
                if (commaSeparatedColumnNames != null)
                    return commaSeparatedColumnNames;

                StringBuilder builder = new StringBuilder();
                bool isFirstColumn = true;
                foreach (DataColumn column in Columns)
                {
                    if (!isFirstColumn)
                    {
                        builder.Append(",");
                    }
                    isFirstColumn = false;
                    builder.Append(column.ColumnName);
                }
                commaSeparatedColumnNames = builder.ToString();
                return commaSeparatedColumnNames;
            }
        }

        #region IDataPageRetriver 
        private string keyColumnName;
        public DataTable ApplyPageOfData(int lowPageBoundary, int rowsPerPage)
        {
            keyColumnName = Columns[0].ColumnName;
            StringBuilder builder = new StringBuilder();
            builder.Append(" select top " + rowsPerPage + " " + CommaSeparatedColumnNames + " from ");
            builder.Append(tableName);
            builder.Append(" where " + keyColumnName + " not in (");
            builder.Append("   select top " + lowPageBoundary + " " + keyColumnName);
            builder.Append("   from " + tableName + " order by " + keyColumnName + ") ");
            builder.Append(" order by " + keyColumnName);

            command.CommandText = builder.ToString();
            SqlDataAdapter adapter = new SqlDataAdapter();
            adapter.SelectCommand = command;
            DataTable table = new DataTable();
            adapter.Fill(table);

            return table;
        }

        #endregion
    }

    internal class Cache
    {
        private struct DataPage
        {
            public DataTable table;
            private int lowIndex;
            private int highIndex;

            public DataPage(DataTable table, int rowIndex)
            {
                this.table = table;
                this.lowIndex = MapLowerBoundary(rowIndex);
                this.highIndex = MapUpperBoundary(rowIndex);
            }
            public int LowIndex
            {
                get { return this.lowIndex; }
            }
            public int HighIndex
            {
                get { return this.highIndex; }
            }

            public static int MapLowerBoundary(int rowIndex)
            {
                return (rowIndex / RowPerPage) * RowPerPage;
            }
            public static int MapUpperBoundary(int rowIndex)
            {
                return MapLowerBoundary(rowIndex) + RowPerPage - 1;
            }
        }
        IDataPageRetriever dataSupply;
        static int RowPerPage;
        DataPage[] catchPages = new DataPage[2];

        public Cache(IDataPageRetriever dataSupplier, int rowsPerPage)
        {
            this.dataSupply = dataSupplier;
            RowPerPage = rowsPerPage;
            PreLoadDataPages();
        }
        private void PreLoadDataPages()
        {
            catchPages[0] = new DataPage(dataSupply.ApplyPageOfData(0, RowPerPage), 0);
            catchPages[1] = new DataPage(dataSupply.ApplyPageOfData(RowPerPage, RowPerPage), RowPerPage);
        }
        public string RetrieveElement(int rowIndex, int colIndex)
        {
            string element = "";
            if (IfPageCatched_TheSetElement(rowIndex, colIndex, ref element))
            {
                return element;
            }
            else
            {
                element = RetrieveData_CatchIt_ReturnElement(rowIndex, colIndex);
            }
            return element;
        }

        private bool IfPageCatched_TheSetElement(int rowIndex, int colIndex, ref string element)
        {
            if (IsRowCatchedInPage(0, rowIndex))
            {
                element = catchPages[0].table.Rows[rowIndex % RowPerPage][colIndex].ToString();
                return true;
            }
            else if (IsRowCatchedInPage(1, rowIndex))
            {
                element = catchPages[1].table.Rows[rowIndex % RowPerPage][colIndex].ToString();
                return true;
            }
            return false;
        }

        private string RetrieveData_CatchIt_ReturnElement(int rowIndex, int colIndex)
        {

            DataPage newPage = new DataPage(dataSupply.ApplyPageOfData(DataPage.MapLowerBoundary(rowIndex), RowPerPage), rowIndex);

            //which old datapage should be replaced?
            catchPages[GetIndexOfReplacedPage(rowIndex)] = newPage;

            return RetrieveElement(rowIndex, colIndex);
        }

        private bool IsRowCatchedInPage(int pageNum, int rowIndex)
        {
            return catchPages[pageNum].LowIndex <= rowIndex &&
                catchPages[pageNum].HighIndex >= rowIndex;
        }

        private int GetIndexOfReplacedPage(int rowIndex)
        {
            if (catchPages[0].HighIndex < rowIndex && catchPages[1].HighIndex < rowIndex)
            {
                int offsetFromPage0 = rowIndex - catchPages[0].HighIndex;
                int offsetFromPage1 = rowIndex - catchPages[1].HighIndex;
                if (offsetFromPage0 < offsetFromPage1)
                    return 1;
                else
                    return 0;
            }
            else
            {
                int offsetFromPage0 = catchPages[0].LowIndex - rowIndex;
                int offsetFromPage1 = catchPages[1].LowIndex - rowIndex;
                if (offsetFromPage0 < offsetFromPage1)
                    return 1;
                return 0;
            }
        }
    }
}

DataGridView調用

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

namespace DataGridViewVirtualModeTest
{
    public partial class Form1 : Form
    {
        Cache memoryCache; 
        public Form1()
        {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            DataRetriever  dataSupply = new DataRetriever("t_temp");
            memoryCache = new Cache(dataSupply, 50);

            foreach (DataColumn column in dataSupply.Columns)
            {
                this.dataGridView1.Columns.Add(column.ColumnName, column.ColumnName);
            }
            this.dataGridView1.RowCount = dataSupply.RowCount;
            this.dataGridView1.VirtualMode = true;
            this.dataGridView1.ReadOnly = true;
            this.dataGridView1.AllowUserToAddRows = false;
            this.dataGridView1.AllowUserToOrderColumns = false;
            this.dataGridView1.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
            this.dataGridView1.CellValueNeeded += new DataGridViewCellValueEventHandler(dataGridView1_CellValueNeeded);
            
            this.dataGridView1.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.DisplayedCells;
            
        }

        void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
        {
            e.Value = memoryCache.RetrieveElement(e.RowIndex, e.ColumnIndex);
        }

    }
}

我測試的是100萬條數據,真的和直接加載一個100萬條數據的DataTable有天壤之別。附一個圖:


免責聲明!

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



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