【问题标题】:Increamental loading in grid using DataTable as DataSource使用 DataTable 作为 DataSource 在网格中增量加载
【发布时间】:2015-10-10 22:39:56
【问题描述】:

在我的窗口应用程序中,有许多带有网格的屏幕。
我使用 DataTable 作为网格的 DataSource,DataTable 有一些非常大的数据集 (> 50,000),如果我们在加载 UI 时一次加载所有数据,则需要很长时间才能在屏幕上加载数据。响应直到所有数据都没有被加载,
所以我已经使用Background Worker在该网格中实现了增量加载。
这是代码:

// DoWork Event of the background Wroker.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            while (bgstop)
            {                   
                e.Result = addNewRecord(); 
                if (Convert.ToBoolean(e.Result) == false)
                {
                    e.Cancel = true;                       
                    bgstop = false;
                    killBGWorker();
                    break;
                }
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
    }


    // to add/merge the records in the DataTable
    private bool addNewRecord()
    {
        int flag = 0;
        try
        {
            Thread.Sleep(500); //optional
            DataTable tableAdd = getTableData();
            if (tableAdd.Rows.Count > 0)
            {
                dtRecords.Merge(tableAdd);  // dtRecords is the DataTable which attached to grid
                flag++;
            }
            else
                backgroundWorker1.WorkerSupportsCancellation = true;
        }
        catch (Exception ex)
        {
            MessageBox.Show(ex.Message);
        }
        if (flag > 0)
            return true;
        else
            return false;
    }


    // To get the next slot of Records from the DataBase
    private DataTable getTableData()
    {
        DataTable dt = new DataTable();
        start = nextRows * noOfRows;
        stop = start + noOfRows;
        dt = SQLHelper.getAllRecords(totalRows,noOfRows, start + 1, stop);
        nextRows++;
        return dt;
    }

    // kill the backgroudworker after the all data/records get loaded from  database to grid/DataTable 
    private void killBGWorker()
    {
        backgroundWorker1.WorkerSupportsCancellation = true;
        backgroundWorker1.CancelAsync();
    }

上面的代码获取第一个定义的记录数(比如 200 条),然后在后台工作人员启动并开始在插槽中获取数据并将其与网格 DataSource 合并,直到所有数据(比如 >50,000 条记录)都加载到网格。
但是 UI 交互仍然存在一些问题,直到数据库中的所有记录都加载到网格中,UI 才会多次挂起 2-3 秒。

我经历了this,但在该示例中使用了 DataModel,但在我的情况下,他们只是从 DataBase 中的 DataTable 中获取了没有 DataModel,现在我们无法移动到 DataModel。

还有其他方法吗以良好的 UI 交互来实现增量加载?
或者
在当前场景中,有什么方法可以实现 IBindingList 吗?

【问题讨论】:

  • 让 UI 保持响应式是否可以接受,或者增量检索行是否重要?是否允许用户与部分数据集进行交互?
  • 为了获得良好的用户体验,我需要逐步检索行。是的,用户可以与部分数据集进行交互。
  • 用户是否同时查看了所有网格?
  • @NamBình:可能!我正在使用 MDI 表单,其中存在多个屏幕/表单,用户可以打开多个表单。

标签: c# winforms datagridview datatable lazy-loading


【解决方案1】:

我的解决方案是像这样使用BindingSource

    // To take data in silent
    BackgroundWorker m_oWorker;

    // To hold my data. tblDuToanPhanBo is my data type
    List<tblDuToanPhanBo> lst2 = new List<tblDuToanPhanBo>();

    BindingSource bs = new BindingSource();

    // replace 50000 with your total data count
    int totalData = 500000;

    // No of rows to load a time by BackgroundWorker
    int RowsToTake = 2000;

    // No of rows loaded
    int RowsTaken = 0;

获取第一部分数据,让BackgroundWorker 完成剩下的工作:

    private void UserControl1_Load(object sender, EventArgs e)
    {
        m_oWorker = new BackgroundWorker();
        m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
        m_oWorker.ProgressChanged += new ProgressChangedEventHandler (m_oWorker_ProgressChanged);
        m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler (m_oWorker_RunWorkerCompleted);
        // QLQT is my DataContext
        using (QLQT db = new QLQT())
        {
            lst2.AddRange(db.tblDuToanPhanBos.Skip(RowsTaken).Take(RowsToTake).ToList());
        }
        RowsTaken = lst2.Count;
        bs.DataSource = lst2;
        dataGridView1.DataSource = bs;
        m_oWorker.RunWorkerAsync();
    }

BackgroundWorker取一部分数据:

    void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
    {
        // Load data
        using (QLQT db = new QLQT())
        {
            lst2.AddRange(db.tblDuToanPhanBos.Skip(RowsTaken).Take(RowsToTake).ToList());
        }
        // Update number of rows loaded
        RowsTaken = lst2.Count;
        if (((BackgroundWorker)sender).CancellationPending)
        {
            e.Cancel = true;
            return;
        }
    }

BackgroundWorker完成后,更新BindingSource,运行BackgroundWorker直到所有数据加载完毕:

    void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
            MessageBox.Show("Loading Cancelled.");
        }
        else if (e.Error != null)
        {
            MessageBox.Show("Error while performing background operation.");
        }
        else
        {
            if (lst2.Count < totalData)
            {
                bs.ResetBindings(false);
                m_oWorker.RunWorkerAsync();
            }
            else
            {
                bs.ResetBindings(false);
            }
        }
    }

希望有帮助:)

【讨论】:

  • 感谢您提供漂亮的代码,但我已经提到我的应用程序没有 DataModel,我使用 DataTable 作为 DataSource。第二件事是我也曾考虑从RunWorkerCompleted 再次调用DoWork,但在通过this 链接后,我改变了主意。
  • 在一个DoWork 完成而不是重复使用之后创建新的BackgroundWorker 可以吗? BindingSourceDataSource可以设置为DataTable而不是List,这里我使用BindingSource的原因是不需要我重新设置DataGrid/DataGridViewDataSource时间。我不擅长使用DataTable,所以我不怎么使用它(除了报告)。希望你能尽快找到另一个解决方案 :) Ps:我没有用DataTable 测试过,但我认为它有效。也许我有时间的时候。 List 大约有 100,000 条记录的结果是可以接受的
【解决方案2】:

您可以通过将 DataGridView 从 BindingMode 更改为 VirtualMode 来实现。

以下更改将尽可能多地重用您已有的内容,您将看到 DataGridView 以增量方式加载。我不知道您一次提取了多少记录,但您可以将这个数字保持在较低水平。

将属性VirtualMode 设置为true。从属性DataSource 中删除所有值。向 DataGridView 添加与 DataGrid 中的列一样多的 Unbounded 列(如果需要,可以自动完成)。

CellValueNeeded 添加一个事件处理程序。

将以下代码添加到该处理程序:

private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
    e.Value = dtRecords.Rows[e.RowIndex][e.ColumnIndex];
}

在你的 backgroundworker1 上将属性 WorkerReportsProgress 设置为 True
ProgressChanged. 添加一个事件处理程序到您的后台工作人员。使用以下代码:

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    this.dataGridView1.RowCount = (int) e.UserState;
}

在你的方法addNewRecord下面这一行添加:

dtRecords.Merge(tableAdd);  // dtRecords is the DataTable which attached to grid
// added to bring the number of records to the UI thread
backgroundWorker1.ReportProgress(42, dtRecords.Rows.Count);

这样,您的 datagridview 现在应该以增量方式加载其数据。真正的诀窍是设置RowCount 属性。如果它可以显示一条记录并且它会调整它的滚动条,那么它会向数据网格发出信号。

【讨论】:

  • +1 以获得很好的解释。但是如果我不使用 DataGridView 怎么办?我使用的是第三方(syncfusion、telrik 等)网格,而不是 DGV。我想要这样的解决方案,它不依赖于 UI 控制。
  • 我明白了。使用 VirtualMode,它变成了一个拉模型,DGV 决定何时请求物品。如果您使用 BindingSource,它将成为推送模型,并且当集合更改时,控件需要再次检查完整列表以找出它需要做什么。这不仅需要时间,而且可能也在 UI 线程上完成,这解释了您看到的滞后。您可以尝试在调用 Merge 时将绑定源上的 RaiseListChangeEvents 设置为 false 有帮助。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-26
  • 1970-01-01
  • 2012-02-26
  • 1970-01-01
  • 1970-01-01
  • 2020-11-25
相关资源
最近更新 更多