【问题标题】:Updating a DataTables contents in an async method doesn't refresh bound DataGridView properly在异步方法中更新 DataTables 内容不会正确刷新绑定的 DataGridView
【发布时间】:2021-04-20 14:13:49
【问题描述】:

我的程序中有三个DataViews,每个都有一个DataTable 连接,我从SqlAdapter 完整和更新(通过合并和删除一些行)。更新是在异步方法中完成的,需要的地方包含在我自己的 Task.Run(...) Actions 中。更新后DataGridViews 不会显示更新的数据,尽管DataGridViewDataViewDataTable 的组合通常会处理这一点。

这似乎破坏了绑定到DataViewsDataGridViews 的自动刷新。观察DataGridViewsDataBindingComplete 事件表明,当我在异步方法中更新我的底层DataTables 时,它没有被正确触发。在更新所有三个DataTables 时,我只观察到在更新过程中为DataGridViews 之一触发的事件,其中DataTable... 在异步方法完成更新后只有一行还有更多的行永远不会显示。我的DataGridViews 会在我更改列的排序后立即显示正确的更新数据。

在触发刷新的按钮事件中我做了另一个测试:

private async void AddSomethingToolStripButton_Click(object sender, EventArgs e) {
    if (sender is ToolStripButton button) {
        int test1 = DataGridView1.RowCount; // 4
        int test2 = ((DataGridView1.DataSource as BindingSource).DataSource as DataView).Count; // 4

        /* Calls async method to write new value to DB and calls more async methods to refresh
         * all the DataTables with the new data which also effects the one in this example */
        await controller.AddSomethingToSqlDbAndRefreshDataTableAsync();

        int test3 = DataGridView1.RowCount; // Still 4
        int test4 = ((DataGridView1.DataSource as BindingSource).DataSource as DataView).Count; // 5
    }
}

这些值永远不会像应有的那样同步。在尝试通过在DataGridView 上调用Refresh 或在Binding 上调用ReadValues 手动刷新后,我设法通过在BindingSource 上调用ResetBindings 来使其正常工作,这感觉不太对。

我有什么遗漏吗?我不应该将这些数据结构和机制与async/await 模式结合使用吗?

编辑 - 刷新方法示例:

private async Task RefreshDataViewAsync(string query, DataView view) {
    await Task.Run(() => {
        using var con = new SqlConnection(sqlConnectionString);
        using var adapter = new SqlDataAdapter();

        adapter.SelectCommand = new SqlCommand(query, con);

        var newtable = new DataTable();
        adapter.FillSchema(newtable, SchemaType.Source);

        foreach (DataColumn col in newtable.Columns) {
            if (ColumnValueTypeMapping.TryGetValue(col.ColumnName, out Type coltype)) {
                col.DataType = coltype;
            }
        }

        adapter.Fill(newtable);

        view.Table.Merge(newtable, false);

        /* Remove rows that are not present anymore */
        var rows2del = new List<DataRow>();
        string[] primarykeys = view.Table.PrimaryKey.Select(keycol => keycol.ColumnName).ToArray();
        foreach (DataRow row in view.Table.Rows) {
            if (!newtable.Rows.Contains(primarykeys.Select(key => row.Field<object>(key)).ToArray())) {
                rows2del.Add(row);
            }
        }
        rows2del.ForEach(row => row.Delete());

        view.Table.AcceptChanges();
    });
}

【问题讨论】:

  • 如果需要访问BindingSource,存储为Field对象直接使用即可。 -- 包裹在我自己的 Task.Run(...) Actions 部分中的位置在哪里?如果你使用异步方法,Task.Run() 是干什么用的?您是否正在重置绑定的 DataTable?如果这样做,绑定就会丢失。
  • 我只为我的测试访问了这样的绑定源,如果将来使用它们是我选择的解决方案,我会让它更漂亮:DI 添加了一个我从不清除的刷新方法示例/删除 DataView 或附加到它的 DataTable。使用 Task.Run 是因为没有异步的 SqlAdapter 方法版本。当我更改列的排序时,绑定仍然有效。
  • 更新数据表后,DGV 不会自动重新绘制。诀窍是设置为空。 DataGridView1.DataSource = null; DataGridView1.DataSource = newtable;
  • Task.Run() 在 ThreadPool 线程中执行。你有SqlConnection.OpenAsync()SqlCommand.ExcuteReadAsync(),你可以await,然后[DataTable].Load([IDataReader]),所以你可以删除Task.Run()。 -- 不确定 Merge 和 DataRow 篡改的用途。在合并具有相同架构的两个表时,您不应该使用指定如何处理 PrimaryKey 的 Merge 方法重载吗?传递 DataTable 而不是 DataView,然后将其作为 DataTable 访问。更新通知必须到达BindingSource,否则不知道。
  • @Jimi 这些是删除我不知道的 Task.Run() 的方法。我可以调查一下,但我无法想象这会改变问题。行回火是删除本地副本中不再存在于远程的行。当然我可以传递表格而不是视图,但是更改事件的链仍然必须是 DataGridView->Binding->DataView->DataTable,不是吗?

标签: c# .net winforms async-await datagridview


【解决方案1】:

您应该将您的 UI 代码与非 UI 代码分开。

private async void AddSomethingToolStripButton_Click(object sender, EventArgs e)
{
    DataGridView1.DataSource = await GetDataAsync();
}

您的GetDataAsync 方法可以使用Task.Run。事实上,鉴于它是 Windows 窗体,它可能应该这样做。

【讨论】:

  • 这正是我实现的模式。如果方法私有保护级别让您感到困惑,它是由我的 Controller/ViewModel 的另一个公共方法调用的,而该方法又由我的 Views Code Behind 中的异步事件处理程序调用。无论如何,这对于我的更新事件没有到达 DataGridView 的问题没有解决方案。
  • 我在您的代码中看不到 DataSource 的设置位置。您不能依赖来自另一个线程的数据绑定。
  • 我不在另一个线程中进行绑定,而是在 Form 的 Loaded 事件中进行。最初数据显示正确,因此绑定有效。刷新后它是错误的,但是一旦我通过 UI 更改列的顺序/排序,新值就会再次正确显示,因此绑定也在那里工作。
  • 鉴于它是 Windows 窗体,它可能应该:注意解释这个陈述(在答案中)?
  • 尽快并尽可能长时间地远离 UI 线程。
猜你喜欢
  • 2017-01-09
  • 1970-01-01
  • 2018-07-21
  • 1970-01-01
  • 2015-12-26
  • 2017-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多