【问题标题】:How to populate a generated Column in a DataGridView efficiently?如何有效地在 DataGridView 中填充生成的列?
【发布时间】:2020-04-29 14:13:05
【问题描述】:

我的 Winforms 程序中有一个 DataGridView 设置,其中包含从数据库填充的信息。

我使用Select * FROM [Data] 使用以下代码填充了DataGridView:

private void GetData(string selectCommand)
{
    try
    {
        // Create a new data adapter based on the specified query.
        dataAdapter = new OleDbDataAdapter(selectCommand, strConn);

        // Create a command builder to generate SQL update, insert, and
        // delete commands based on selectCommand.
        OleDbCommandBuilder commandBuilder = new OleDbCommandBuilder(dataAdapter);

        // Populate a new data table and bind it to the BindingSource.
        DataTable table = new DataTable
        {
            Locale = CultureInfo.InvariantCulture
        };
        dataAdapter.Fill(table);
        bindingSource1.DataSource = table;

        // Resize the DataGridView columns to fit the newly loaded content.
        LogDataGridView.AutoResizeColumns(
            DataGridViewAutoSizeColumnsMode.AllCellsExceptHeader);
    }
    catch (OleDbException) { }
}

【问题讨论】:

  • 我们对您的数据源了解不够。可以离开加入状态信息吗?
  • @LarsTech 我编辑了帖子并添加了更多代码。我认为 left join 会起作用,我该怎么做?
  • 如果可能的话,您可以直接在同一个查询中交叉连接两个数据库并生成一个已经包含状态的数据表。意思是,如果存储状态信息的数据库没有被其他进程独立更新。否则,您必须对其进行轮询。如果一定要轮询,只需要更新DataTable的一个Field,作为DGV的DataSoure。它将立即更新 UI。
  • @Jimi 我认为这正是我在查询中需要的,我的第二个访问数据库有 SO nr 和另一个包含状态的列。你能给我一个例子,说明我如何能够根据两个表之间共享的“SO nr”交叉连接这两个表吗?

标签: c# winforms datagridview oledb


【解决方案1】:

选项 1

如果存储状态信息的数据库没有被其他进程不断更新,您可以简单地指定要连接的列是外部数据库中表的一部分。

如果它从其他来源接收更新,则必须使用 Timer 轮询它(或使用 FileSystemWatcher 类在数据库文件更改时接收通知)。不过查询是一样的。

在 JOIN 子句后添加 [;database=Second database Path].[Source Table] AS alias,然后照常进行。

例如,设置 DataGridView.Datasource 指定第二个数据库路径。

LogDataGridView.DataSource = GetCrossJoinedTable([Second database path]);

连接字符串的Data Source= 包含第一个数据库的路径。
然后,使用公共键交叉连接两个数据库中的两个表,并返回包含第二个数据库中表的状态信息的列([Order Status] 列,如果我正确阅读了更新的代码)。

private DataTable GetCrossJoinedTable(string secondDataBasePath)
{
    var dt = new DataTable("JoinedTable");
    string sql = "SELECT [Data].*, status.[Order Status] " +
                $"FROM [Data] LEFT JOIN [;database={secondDataBasePath}].[Output] AS status " +
                 "ON [Data].[SO nr] = status.[Source No]";
    using (var conn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=firstDatabase.accdb;Persist Security Info=false;"))
    using (var cmd = new OleDbCommand(sql, conn)) {
        conn.Open();
        using (var reader = cmd.ExecuteReader()) {
            dt.Load(reader);
            return dt;
        }
    }
}

选项 2
查询第一个数据库,添加一个状态列,从第二个数据库更新状态信息,然后使用计时器轮询新的状态信息。

您可以拨打LogDataGridView.DataSource = GetMainTableData()设置数据源。它还将从第二个数据库加载状态信息。

然后计时器将仅轮询第二个数据库,以检查 DataTable 中匹配的[SO nr] 列的[Order Status] 是否已更改。
当 DataTable(这里是一个名为 mainDT 的字段)更新时,您的 DataGraidView 将立即反映更改。

  • 如果可以编辑数据,如果 IsCurrentCellInEditMode 返回 true(阅读那里关于 CheckBoxColumns 的注释),或在单元格处于编辑模式时暂停(CellBeginEdit 事件)和当 Cell 退出编辑模式时恢复(CellEndEdit 事件)。
  • 记得在表单关闭时停止并处理计时器(Form.FormClosing 事件)。

如果您不需要计时器来轮询状态信息,只需在需要更新 DGV 时致电UpdateStatusInfo()

System.Windows.Forms.Timer sqlTimer = null;
DataTable mainDT = new DataTable("MainTable");

private DataTable GetMainTableData()
{
    string sql = "SELECT * FROM Data";
    using (var conn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=firstDatabase.accdb;Persist Security Info=false;"))
    using (var cmd = new OleDbCommand(sql, conn)) {
        conn.Open();
        var reader = cmd.ExecuteReader();
        mainDT.Load(reader);
        mainDT.Columns.Add(new DataColumn() {
            Caption = "Status", ColumnName = "fStatus", DataType = typeof(string), ReadOnly = true
        });
    }
    sqlTimer = new System.Windows.Forms.Timer() { Interval = 5000 };
    sqlTimer.Tick += (s, ev) => { UpdateStatusInfo(mainDT, false); };
    sqlTimer.Start();
    return UpdateStatusInfo(mainDT, true);
}

private DataTable UpdateStatusInfo(DataTable dt, bool returnTable)
{
    string sql = "SELECT Output.[Order Status], Output.[Source No] FROM Output";
    using (var conn = new OleDbConnection("Provider=Microsoft.ACE.OLEDB.12.0;Data Source=secondDatabase.accdb;Persist Security Info=false;"))
    using (var cmd = new OleDbCommand(sql, conn)) {
        conn.Open();
        var reader = cmd.ExecuteReader();
        dt.Columns["fStatus"].ReadOnly = false;
        while (reader.Read()) {
            dt.Select($"fNumber = {reader["Source No"]}").FirstOrDefault()?
              .SetField("fStatus", reader["Order Status"].ToString());
        }
        dt.Columns["fStatus"].ReadOnly = true;
        return returnTable ? dt : null;
    }
}

【讨论】:

  • 非常感谢您的回答!第一种方法似乎有效,但需要一些时间才能运行。选项 1:如果我想检查状态的新更新,我将如何刷新整个表?选项 2:这似乎不起作用,只显示一个空数据表。这就是为什么我得到一个 System.Data.OleDb.OleDbException: 'IErrorInfo.GetDescription failed with E_FAIL(0x80004005)'。当调用 UpdateStatusInfo
  • 1) 两种方法都经过测试并且有效。 2)3你需要适应你的实际连接和提供者类型(这里,我使用ACE.OLEDB.12,但你可能需要改变它))3)第一个很慢:如果数据库在不同的地点;更具体地说,如果其中一个在远程或可移动驱动器上。当然,记录的数量很重要。我的测试数据库位于同一文件夹中,包含 ~1k 记录:查询需要 ~4-6ms 才能完成。
  • 4) 第二种方法只是将字符串值从一个字段复制到另一个字段。如果你有一个例外,那么你做错了什么。例如,字段名称不一样,数据类型不一样等等。但是,由于例外是0x80004005,你可能使用了我在这里使用的相同的ACE.OLEDB.12提供程序,而你实际上并没有拥有它或它的位数与您的应用程序不同。不要使用我在这里使用的连接字符串,使用你已经拥有的。如果您仍然有某种问题,请发布修改后的代码。我无法调试稀薄的空气。
  • 我的数据库都在网络驱动器上。我正在为我的数据库使用 .mdb 文件,该文件使用 Provider=Microsoft.Jet.OLEDB.4.0;并使用 LogDataGridView.DataSource = GetMainTableData(); 完全遵循您的代码设置数据源。我得到的错误是在 var reader = command.ExecuteReader() 上。相同的连接字符串似乎在选项 1 上工作正常
  • 如果您在网络驱动器上有 2 个 mdb 文件(使用古代历史格式和古代历史提供程序,Jet.OLEDB.4.0),您不能指望它会很快。如果您在使用第二个示例代码时遇到异常,那么您在某处犯了错误。如前所述,如果我看不到您实现了哪些代码以及如何实现(包括连接字符串),我无法帮助您。
【解决方案2】:

你可以做什么:

1/ 您从 OUTPUT 表创建一个 DataTable 变量(例如 OutputDataTable)

2/ 在您的方法 private void GetData(string selectCommand) 中,在设置数据源 (bindingSource1.DataSource = table;) 之前,您订阅了一个事件 RowsAdded (dataGridView1.RowsAdded += this.DataGridView1_RowsAdded;)。 如果您执行以下操作:

    private void DataGridView1_RowsAdded(object sender, DataGridViewRowsAddedEventArgs e)
    {
        string valueToFind = dataGridView1[dataGridView1.Columns["SO nr"].Index, e.RowIndex].Value.ToString();
        var row = OutputDataTable.Select($"Source No" = '{valueToFind}'").First();
        string status = row["Order Status"].ToString();
        dataGridView1[dataGridView1[dataGridView1.Columns["Status"].Index, e.RowIndex].Value = status;
    }

【讨论】:

    猜你喜欢
    • 2019-09-29
    • 1970-01-01
    • 2013-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多