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

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

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

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

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

Форма

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

Далее в этой статье описываются типа IDataPageRetriever, DataRetriever и Cache.

Примечание.

Хранение конфиденциальных сведений (например, пароля) в строке подключения может повлиять на безопасность приложения. Использование проверки подлинности 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());
    }
}
Imports System.Data
Imports System.Data.SqlClient
Imports System.Drawing
Imports System.Windows.Forms

Public Class VirtualJustInTimeDemo
    Inherits System.Windows.Forms.Form

    Private WithEvents dataGridView1 As New DataGridView()
    Private memoryCache As Cache

    ' 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 connectionString As String = _
        "Initial Catalog=NorthWind;Data Source=localhost;" & _
        "Integrated Security=SSPI;Persist Security Info=False"
    Private table As String = "Orders"

    Private Sub VirtualJustInTimeDemo_Load( _
        ByVal sender As Object, ByVal e As EventArgs) _
        Handles Me.Load

        ' Initialize the form.
        With Me
            .AutoSize = True
            .Controls.Add(Me.dataGridView1)
            .Text = "DataGridView virtual-mode just-in-time demo"
        End With

        ' Complete the initialization of the DataGridView.
        With Me.dataGridView1
            .Size = New Size(800, 250)
            .Dock = DockStyle.Fill
            .VirtualMode = True
            .ReadOnly = True
            .AllowUserToAddRows = False
            .AllowUserToOrderColumns = False
            .SelectionMode = DataGridViewSelectionMode.FullRowSelect
        End With

        ' Create a DataRetriever and use it to create a Cache object
        ' and to initialize the DataGridView columns and rows.
        Try
            Dim retriever As New DataRetriever(connectionString, table)
            memoryCache = New Cache(retriever, 16)
            For Each column As DataColumn In retriever.Columns
                dataGridView1.Columns.Add( _
                    column.ColumnName, column.ColumnName)
            Next
            Me.dataGridView1.RowCount = retriever.RowCount
        Catch ex As SqlException
            MessageBox.Show("Connection could not be established. " & _
                "Verify that the connection string is valid.")
            Application.Exit()
        End Try

        ' Adjust the column widths based on the displayed values.
        Me.dataGridView1.AutoResizeColumns( _
            DataGridViewAutoSizeColumnsMode.DisplayedCells)

    End Sub

    Private Sub dataGridView1_CellValueNeeded( _
        ByVal sender As Object, ByVal e As DataGridViewCellValueEventArgs) _
        Handles dataGridView1.CellValueNeeded

        e.Value = memoryCache.RetrieveElement(e.RowIndex, e.ColumnIndex)

    End Sub

    <STAThreadAttribute()> _
    Public Shared Sub Main()
        Application.Run(New VirtualJustInTimeDemo())
    End Sub

End Class

Интерфейс IDataPageRetriever

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

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

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

    Function SupplyPageOfData( _
        ByVal lowerPageBoundary As Integer, ByVal rowsPerPage As Integer) _
        As DataTable

End Interface

Класс 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.
        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;
    }
}
Public Class DataRetriever
    Implements IDataPageRetriever

    Private tableName As String
    Private command As SqlCommand

    Public Sub New( _
        ByVal connectionString As String, ByVal tableName As String)

        Dim connection As New SqlConnection(connectionString)
        connection.Open()
        command = connection.CreateCommand()
        Me.tableName = tableName

    End Sub

    Private rowCountValue As Integer = -1

    Public ReadOnly Property RowCount() As Integer
        Get
            ' Return the existing value if it has already been determined.
            If Not rowCountValue = -1 Then
                Return rowCountValue
            End If

            ' Retrieve the row count from the database.
            command.CommandText = "SELECT COUNT(*) FROM " & tableName
            rowCountValue = CInt(command.ExecuteScalar())
            Return rowCountValue
        End Get
    End Property

    Private columnsValue As DataColumnCollection

    Public ReadOnly Property Columns() As DataColumnCollection
        Get
            ' Return the existing value if it has already been determined.
            If columnsValue IsNot Nothing Then
                Return columnsValue
            End If

            ' Retrieve the column information from the database.
            command.CommandText = "SELECT * FROM " & tableName
            Dim adapter As New SqlDataAdapter()
            adapter.SelectCommand = command
            Dim table As New DataTable()
            table.Locale = System.Globalization.CultureInfo.InvariantCulture
            adapter.FillSchema(table, SchemaType.Source)
            columnsValue = table.Columns
            Return columnsValue
        End Get
    End Property

    Private commaSeparatedListOfColumnNamesValue As String = Nothing

    Private ReadOnly Property CommaSeparatedListOfColumnNames() As String
        Get
            ' Return the existing value if it has already been determined.
            If commaSeparatedListOfColumnNamesValue IsNot Nothing Then
                Return commaSeparatedListOfColumnNamesValue
            End If

            ' Store a list of column names for use in the
            ' SupplyPageOfData method.
            Dim commaSeparatedColumnNames As New System.Text.StringBuilder()
            Dim firstColumn As Boolean = True
            For Each column As DataColumn In Columns
                If Not firstColumn Then
                    commaSeparatedColumnNames.Append(", ")
                End If
                commaSeparatedColumnNames.Append(column.ColumnName)
                firstColumn = False
            Next

            commaSeparatedListOfColumnNamesValue = _
                commaSeparatedColumnNames.ToString()
            Return commaSeparatedListOfColumnNamesValue
        End Get
    End Property

    ' Declare variables to be reused by the SupplyPageOfData method.
    Private columnToSortBy As String
    Private adapter As New SqlDataAdapter()

    Public Function SupplyPageOfData( _
        ByVal lowerPageBoundary As Integer, ByVal rowsPerPage As Integer) _
        As DataTable Implements IDataPageRetriever.SupplyPageOfData

        ' Store the name of the ID column. This column must contain unique 
        ' values so the SQL below will work properly.
        If columnToSortBy Is Nothing Then
            columnToSortBy = Me.Columns(0).ColumnName
        End If

        If Not Me.Columns(columnToSortBy).Unique Then
            Throw New InvalidOperationException(String.Format( _
                "Column {0} must contain unique values.", columnToSortBy))
        End If

        ' 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

        Dim table As New DataTable()
        table.Locale = System.Globalization.CultureInfo.InvariantCulture
        adapter.Fill(table)
        Return table

    End Function

End Class

Класс Cache

В следующем примере кода определяется класс 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;
    }
}
Public Class Cache

    Private Shared RowsPerPage As Integer

    ' Represents one page of data.  
    Public Structure DataPage

        Public table As DataTable
        Private lowestIndexValue As Integer
        Private highestIndexValue As Integer

        Public Sub New(ByVal table As DataTable, ByVal rowIndex As Integer)

            Me.table = table
            lowestIndexValue = MapToLowerBoundary(rowIndex)
            highestIndexValue = MapToUpperBoundary(rowIndex)
            System.Diagnostics.Debug.Assert(lowestIndexValue >= 0)
            System.Diagnostics.Debug.Assert(highestIndexValue >= 0)

        End Sub

        Public ReadOnly Property LowestIndex() As Integer
            Get
                Return lowestIndexValue
            End Get
        End Property

        Public ReadOnly Property HighestIndex() As Integer
            Get
                Return highestIndexValue
            End Get
        End Property

        Public Shared Function MapToLowerBoundary( _
            ByVal rowIndex As Integer) As Integer

            ' Return the lowest index of a page containing the given index.
            Return (rowIndex \ RowsPerPage) * RowsPerPage

        End Function

        Private Shared Function MapToUpperBoundary( _
            ByVal rowIndex As Integer) As Integer

            ' Return the highest index of a page containing the given index.
            Return MapToLowerBoundary(rowIndex) + RowsPerPage - 1

        End Function

    End Structure

    Private cachePages As DataPage()
    Private dataSupply As IDataPageRetriever

    Public Sub New(ByVal dataSupplier As IDataPageRetriever, _
        ByVal rowsPerPage As Integer)

        dataSupply = dataSupplier
        Cache.RowsPerPage = rowsPerPage
        LoadFirstTwoPages()

    End Sub

    ' Sets the value of the element parameter if the value is in the cache.
    Private Function IfPageCached_ThenSetElement(ByVal rowIndex As Integer, _
        ByVal columnIndex As Integer, ByRef element As String) As Boolean

        If IsRowCachedInPage(0, rowIndex) Then
            element = cachePages(0).table.Rows(rowIndex Mod RowsPerPage) _
                .Item(columnIndex).ToString()
            Return True
        ElseIf IsRowCachedInPage(1, rowIndex) Then
            element = cachePages(1).table.Rows(rowIndex Mod RowsPerPage) _
                .Item(columnIndex).ToString()
            Return True
        End If

        Return False

    End Function

    Public Function RetrieveElement(ByVal rowIndex As Integer, _
        ByVal columnIndex As Integer) As String

        Dim element As String = Nothing
        If IfPageCached_ThenSetElement(rowIndex, columnIndex, element) Then
            Return element
        Else
            Return RetrieveData_CacheIt_ThenReturnElement( _
                rowIndex, columnIndex)
        End If

    End Function

    Private Sub LoadFirstTwoPages()

        cachePages = New DataPage() { _
            New DataPage(dataSupply.SupplyPageOfData( _
                DataPage.MapToLowerBoundary(0), RowsPerPage), 0), _
            New DataPage(dataSupply.SupplyPageOfData( _
                DataPage.MapToLowerBoundary(RowsPerPage), _
                RowsPerPage), RowsPerPage) _
        }

    End Sub

    Private Function RetrieveData_CacheIt_ThenReturnElement( _
        ByVal rowIndex As Integer, ByVal columnIndex As Integer) As String

        ' Retrieve a page worth of data containing the requested value.
        Dim table As DataTable = 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)

    End Function

    ' Returns the index of the cached page most distant from the given index
    ' and therefore least likely to be reused.
    Private Function GetIndexToUnusedPage(ByVal rowIndex As Integer) _
        As Integer

        If rowIndex > cachePages(0).HighestIndex AndAlso _
            rowIndex > cachePages(1).HighestIndex Then

            Dim offsetFromPage0 As Integer = _
                rowIndex - cachePages(0).HighestIndex
            Dim offsetFromPage1 As Integer = _
                rowIndex - cachePages(1).HighestIndex
            If offsetFromPage0 < offsetFromPage1 Then
                Return 1
            End If
            Return 0
        Else
            Dim offsetFromPage0 As Integer = _
                cachePages(0).LowestIndex - rowIndex
            Dim offsetFromPage1 As Integer = _
                cachePages(1).LowestIndex - rowIndex
            If offsetFromPage0 < offsetFromPage1 Then
                Return 1
            End If
            Return 0
        End If

    End Function

    ' Returns a value indicating whether the given row index is contained
    ' in the given DataPage. 
    Private Function IsRowCachedInPage( _
        ByVal pageNumber As Integer, ByVal rowIndex As Integer) As Boolean

        Return rowIndex <= cachePages(pageNumber).HighestIndex AndAlso _
            rowIndex >= cachePages(pageNumber).LowestIndex

    End Function

End Class

Дополнительные рекомендации

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

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

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

См. также