Show / Hide Table of Contents

Реализация виртуального режима с JIT-загрузкой данных для элемента управления DataGridView в Windows Forms

Одна из причин для реализации виртуальный режим в DataGridView элемент управления является получение данных только по мере необходимости. Это называется загрузка данных just-in-time.

Если вы работаете с очень большими таблицами в удаленной базе данных, например, можно избежать задержки при запуске, извлечение только данные, необходимые для отображения и получения дополнительных данных только в том случае, когда пользователь прокручивает новых строк в представление. Если клиентские компьютеры под управлением приложения имеют ограниченный объем памяти для хранения данных, можно также удалить неиспользуемые данные, при получении новых значений из базы данных.

В следующих разделах рассматривается использование DataGridView элемента управления с кэшем just-in-time.

Чтобы скопировать код из этого раздела единым блоком, см. раздел Практическое руководство. Реализация виртуального режима с JIT в загрузкой данных в Windows Forms элемента управления DataGridView.

Формы

В следующем примере кода определяется форму, содержащую только для чтения DataGridView элемента управления, который взаимодействует с Cache объекта через CellValueNeeded обработчик событий. Cache Объекта управляет значениями, хранящимися локально и использует DataRetriever объект для извлечения значения из таблицы Orders учебной базы данных "Борей". DataRetriever Объект, который реализует IDataPageRetriever интерфейс, необходимый Cache класса, также используется для инициализации DataGridView управления строками и столбцами.

IDataPageRetriever, DataRetriever, И Cache типы описаны далее в этом разделе.

Note

Хранение конфиденциальных сведений (например, пароля) в строке подключения может повлиять на безопасность приложения. Использование проверки подлинности Windows (также называемой встроенными средствами безопасности) — более безопасный способ управления доступом к базе данных. Дополнительные сведения см. в разделе Защита сведений о подключении.

using System;
using System.Data;
using System.Data.SqlClient;
using System.Drawing;
using System.Windows.Forms;

public class VirtualJustInTimeDemo : System.Windows.Forms.Form
{
    private DataGridView dataGridView1 = new DataGridView();
    private Cache memoryCache;

    // Specify a connection string. Replace the given value with a 
    // valid connection string for a Northwind SQL Server sample
    // database accessible to your system.
    private string connectionString =
        "Initial Catalog=NorthWind;Data Source=localhost;" +
        "Integrated Security=SSPI;Persist Security Info=False";
    private string table = "Orders";

    protected override void OnLoad(EventArgs e)
    {
        // Initialize the form.
        this.AutoSize = true;
        this.Controls.Add(this.dataGridView1);
        this.Text = "DataGridView virtual-mode just-in-time demo";

        // Complete the initialization of the DataGridView.
        this.dataGridView1.Size = new Size(800, 250);
        this.dataGridView1.Dock = DockStyle.Fill;
        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);

        // Create a DataRetriever and use it to create a Cache object
        // and to initialize the DataGridView columns and rows.
        try
        {
            DataRetriever retriever =
                new DataRetriever(connectionString, table);
            memoryCache = new Cache(retriever, 16);
            foreach (DataColumn column in retriever.Columns)
            {
                dataGridView1.Columns.Add(
                    column.ColumnName, column.ColumnName);
            }
            this.dataGridView1.RowCount = retriever.RowCount;
        }
        catch (SqlException)
        {
            MessageBox.Show("Connection could not be established. " +
                "Verify that the connection string is valid.");
            Application.Exit();
        }

        // Adjust the column widths based on the displayed values.
        this.dataGridView1.AutoResizeColumns(
            DataGridViewAutoSizeColumnsMode.DisplayedCells);

        base.OnLoad(e);
    }

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

    [STAThreadAttribute()]
    public static void Main()
    {
        Application.Run(new VirtualJustInTimeDemo());
    }

}

Интерфейс IDataPageRetriever

В следующем примере кода определяется IDataPageRetriever интерфейс, который реализуется DataRetriever класса. — Единственный метод, объявленный в этом интерфейсе SupplyPageOfData метод, который требуется индекс начальной строки и число строк на одной странице данных. Эти значения используются средством реализации для извлечения подмножества данных из источника данных.

Объект Cache использует реализацию этого интерфейса во время построения для загрузки двух начальных страниц данных. Каждый раз, когда требуется значение без кэширования, отменяет одну из этих страниц и запрашивает новую страницу с значением из кэша IDataPageRetriever.

public interface IDataPageRetriever
{
    DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage);
}

Класс DataRetriever

В следующем примере кода определяется DataRetriever класса, который реализует IDataPageRetriever интерфейс для извлечения страниц данных с сервера. DataRetriever Класс также предоставляет Columns и RowCount свойства, который DataGridView элемент управления использует для создания необходимых столбцов и добавьте соответствующее количество пустых строк Rows коллекции. Добавление пустых строк необходим, чтобы элемент управления будет действовать как если бы, если он содержит все данные в таблице. Это означает, что бегунка полосы прокрутки будет иметь соответствующий размер, что пользователь сможет получить доступ к любой строки в таблице. Строки заполняются CellValueNeeded обработчик событий только в том случае, если они оказались в представление.

public class DataRetriever : IDataPageRetriever
{
    private string tableName;
    private SqlCommand command;

    public DataRetriever(string connectionString, string tableName)
    {
        SqlConnection connection = new SqlConnection(connectionString);
        connection.Open();
        command = connection.CreateCommand();
        this.tableName = tableName;
    }

    private int rowCountValue = -1;

    public int RowCount
    {
        get
        {
            // Return the existing value if it has already been determined.
            if (rowCountValue != -1)
            {
                return rowCountValue;
            }

            // Retrieve the row count from the database.
            command.CommandText = "SELECT COUNT(*) FROM " + tableName;
            rowCountValue = (int)command.ExecuteScalar();
            return rowCountValue;
        }
    }

    private DataColumnCollection columnsValue;

    public DataColumnCollection Columns
    {
        get
        {
            // Return the existing value if it has already been determined.
            if (columnsValue != null)
            {
                return columnsValue;
            }

            // Retrieve the column information from the database.
            command.CommandText = "SELECT * FROM " + tableName;
            SqlDataAdapter adapter = new SqlDataAdapter();
            adapter.SelectCommand = command;
            DataTable table = new DataTable();
            table.Locale = System.Globalization.CultureInfo.InvariantCulture;
            adapter.FillSchema(table, SchemaType.Source);
            columnsValue = table.Columns;
            return columnsValue;
        }
    }

    private string commaSeparatedListOfColumnNamesValue = null;

    private string CommaSeparatedListOfColumnNames
    {
        get
        {
            // Return the existing value if it has already been determined.
            if (commaSeparatedListOfColumnNamesValue != null)
            {
                return commaSeparatedListOfColumnNamesValue;
            }

            // Store a list of column names for use in the
            // SupplyPageOfData method.
            System.Text.StringBuilder commaSeparatedColumnNames =
                new System.Text.StringBuilder();
            bool firstColumn = true;
            foreach (DataColumn column in Columns)
            {
                if (!firstColumn)
                {
                    commaSeparatedColumnNames.Append(", ");
                }
                commaSeparatedColumnNames.Append(column.ColumnName);
                firstColumn = false;
            }

            commaSeparatedListOfColumnNamesValue =
                commaSeparatedColumnNames.ToString();
            return commaSeparatedListOfColumnNamesValue;
        }
    }

    // Declare variables to be reused by the SupplyPageOfData method.
    private string columnToSortBy;
    private SqlDataAdapter adapter = new SqlDataAdapter();

    public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
    {
        // Store the name of the ID column. This column must contain unique 
        // values so the SQL below will work properly.
        if (columnToSortBy == null)
        {
            columnToSortBy = this.Columns[0].ColumnName;
        }

        if (!this.Columns[columnToSortBy].Unique)
        {
            throw new InvalidOperationException(String.Format(
                "Column {0} must contain unique values.", columnToSortBy));
        }

        // Retrieve the specified number of rows from the database, starting
        // with the row specified by the lowerPageBoundary parameter.
        command.CommandText = "Select Top " + rowsPerPage + " " +
            CommaSeparatedListOfColumnNames + " From " + tableName +
            " WHERE " + columnToSortBy + " NOT IN (SELECT TOP " +
            lowerPageBoundary + " " + columnToSortBy + " From " +
            tableName + " Order By " + columnToSortBy +
            ") Order By " + columnToSortBy;
        adapter.SelectCommand = command;

        DataTable table = new DataTable();
        table.Locale = System.Globalization.CultureInfo.InvariantCulture;
        adapter.Fill(table);
        return table;
    }

}

Класс кэша

В следующем примере кода определяется Cache класс, который управляет двумя страницами данных, которое заполняется с помощью IDataPageRetriever реализации. Cache Класс определяет внутреннее DataPage структуру, которая содержит DataTable для хранения значений в одном кэше страницы и которая вычисляет индексы строк, представляющих верхнюю и нижнюю границы страницы.

Cache Класс загружает две страницы данных во время построения. Каждый раз, когда CellValueNeeded событий запрашивает значение, Cache объект определяет, если значение доступно в одной из двух его страниц и если да, то возвращает его. Если значение не доступен локально, Cache определяет, какой из двух страниц дальше всего от текущей строки и заменяет страницу с запрошенным значением, которое затем возвращается новый.

Предположим, что количество строк на странице данных является таким же, как количество строк, которые могут отображаться на экране за один раз, эта модель позволяет пользователям, разбиение по страницам в таблице, эффективно вернуться на последнюю просмотренную страницу.

public class Cache
{
    private static int RowsPerPage;

    // Represents one page of data.  
    public struct DataPage
    {
        public DataTable table;
        private int lowestIndexValue;
        private int highestIndexValue;

        public DataPage(DataTable table, int rowIndex)
        {
            this.table = table;
            lowestIndexValue = MapToLowerBoundary(rowIndex);
            highestIndexValue = MapToUpperBoundary(rowIndex);
            System.Diagnostics.Debug.Assert(lowestIndexValue >= 0);
            System.Diagnostics.Debug.Assert(highestIndexValue >= 0);
        }

        public int LowestIndex
        {
            get
            {
                return lowestIndexValue;
            }
        }

        public int HighestIndex
        {
            get
            {
                return highestIndexValue;
            }
        }

        public static int MapToLowerBoundary(int rowIndex)
        {
            // Return the lowest index of a page containing the given index.
            return (rowIndex / RowsPerPage) * RowsPerPage;
        }

        private static int MapToUpperBoundary(int rowIndex)
        {
            // Return the highest index of a page containing the given index.
            return MapToLowerBoundary(rowIndex) + RowsPerPage - 1;
        }
    }

    private DataPage[] cachePages;
    private IDataPageRetriever dataSupply;

    public Cache(IDataPageRetriever dataSupplier, int rowsPerPage)
    {
        dataSupply = dataSupplier;
        Cache.RowsPerPage = rowsPerPage;
        LoadFirstTwoPages();
    }

    // Sets the value of the element parameter if the value is in the cache.
    private bool IfPageCached_ThenSetElement(int rowIndex,
        int columnIndex, ref string element)
    {
        if (IsRowCachedInPage(0, rowIndex))
        {
            element = cachePages[0].table
                .Rows[rowIndex % RowsPerPage][columnIndex].ToString();
            return true;
        }
        else if (IsRowCachedInPage(1, rowIndex))
        {
            element = cachePages[1].table
                .Rows[rowIndex % RowsPerPage][columnIndex].ToString();
            return true;
        }

        return false;
    }

    public string RetrieveElement(int rowIndex, int columnIndex)
    {
        string element = null;

        if (IfPageCached_ThenSetElement(rowIndex, columnIndex, ref element))
        {
            return element;
        }
        else
        {
            return RetrieveData_CacheIt_ThenReturnElement(
                rowIndex, columnIndex);
        }
    }

    private void LoadFirstTwoPages()
    {
        cachePages = new DataPage[]{
            new DataPage(dataSupply.SupplyPageOfData(
                DataPage.MapToLowerBoundary(0), RowsPerPage), 0), 
            new DataPage(dataSupply.SupplyPageOfData(
                DataPage.MapToLowerBoundary(RowsPerPage), 
                RowsPerPage), RowsPerPage)};
    }

    private string RetrieveData_CacheIt_ThenReturnElement(
        int rowIndex, int columnIndex)
    {
        // Retrieve a page worth of data containing the requested value.
        DataTable table = dataSupply.SupplyPageOfData(
            DataPage.MapToLowerBoundary(rowIndex), RowsPerPage);

        // Replace the cached page furthest from the requested cell
        // with a new page containing the newly retrieved data.
        cachePages[GetIndexToUnusedPage(rowIndex)] = new DataPage(table, rowIndex);

        return RetrieveElement(rowIndex, columnIndex);
    }

    // Returns the index of the cached page most distant from the given index
    // and therefore least likely to be reused.
    private int GetIndexToUnusedPage(int rowIndex)
    {
        if (rowIndex > cachePages[0].HighestIndex &&
            rowIndex > cachePages[1].HighestIndex)
        {
            int offsetFromPage0 = rowIndex - cachePages[0].HighestIndex;
            int offsetFromPage1 = rowIndex - cachePages[1].HighestIndex;
            if (offsetFromPage0 < offsetFromPage1)
            {
                return 1;
            }
            return 0;
        }
        else
        {
            int offsetFromPage0 = cachePages[0].LowestIndex - rowIndex;
            int offsetFromPage1 = cachePages[1].LowestIndex - rowIndex;
            if (offsetFromPage0 < offsetFromPage1)
            {
                return 1;
            }
            return 0;
        }

    }

    // Returns a value indicating whether the given row index is contained
    // in the given DataPage. 
    private bool IsRowCachedInPage(int pageNumber, int rowIndex)
    {
        return rowIndex <= cachePages[pageNumber].HighestIndex &&
            rowIndex >= cachePages[pageNumber].LowestIndex;
    }

}

Дополнительные сведения

Приведенные выше примеры кода предоставляются в качестве демонстрации загрузки данных just-in-time. Необходимо будет изменить код для ваших нужд для достижения максимальной эффективности. Как минимум необходимо будет выбрать соответствующее значение для числа строк на одной странице данных в кэше. Это значение передается в Cache конструктор. Количество строк на странице должно быть не меньше, чем количество строк, которые могут отображаться одновременно в вашей DataGridView элемента управления.

Для получения наилучших результатов необходимо провести тестирование производительности для определения требований системы и пользователей. Несколько факторов, которые необходимо принять во внимание включают в себя объем памяти в клиентских компьютеров под управлением приложения, доступная пропускная способность сетевого подключения и задержки сервера, используемого. Значения пропускной способности и задержки должно определяться во время активного использования.

Для повышения быстродействия приложения, можно увеличить объем данных, хранящихся локально. Для сокращения времени запуска, однако следует загружать слишком много данных изначально. Может потребоваться изменить Cache класса, чтобы увеличить число страниц данных, оно может хранить. С помощью нескольких страниц данных повышает эффективность прокрутки, но вам потребуется определить оптимальное количество строк на странице данных, в зависимости от доступной пропускной способности и задержки сервера. С помощью страницы меньшего размера сервер будет осуществляться чаще, но займет меньше времени для возврата запрошенных данных. Если задержки более критичны, чем пропускная способность, можно использовать страниц данных большего размера.

См. также

  • DataGridView
  • VirtualMode
  • Оптимизация производительности элемента управления DataGridView в Windows Forms
  • Масштабирование элемента управления DataGridView в Windows Forms
  • Виртуальный режим элемента управления DataGridView в Windows Forms
  • Пошаговое руководство. Реализация виртуального режима для элемента управления DataGridView в Windows Forms
  • Практическое руководство. Реализация виртуального режима с JIT-загрузкой данных для элемента управления DataGridView в Windows Forms
Back to top Неофициальная документация по .NET на русском языке. Лицензия: CC-BY 4.0. Основано на документации по .NET с Microsoft Docs
Generated by DocFX