C# SqlDataAdapter.Update在第二次更新之前不会写入表

C# SqlDataAdapter.Update在第二次更新之前不会写入表,c#,datagridview,sqldataadapter,sqlcommandbuilder,C#,Datagridview,Sqldataadapter,Sqlcommandbuilder,我有一个在C#2010 Express中开发的程序,它的表单由DataGridView组成,下面有单独的单元格编辑字段。选择行(在其左端单击)时,该行中的值将复制到编辑字段中。进行更改,并单击“记忆事务”按钮。新值将显示在DataGridView中。如果随后关闭并重新打开表单(释放并重新加载),将显示原始值,而不是新值。显然,数据库记录没有更新 如果重复上述过程,除了在关闭表单之前再次选择记录并再次单击“记忆事务”按钮外,该值将存储在数据库中。关闭表单然后重新打开时,将显示新值 显然,期望的行为

我有一个在C#2010 Express中开发的程序,它的表单由DataGridView组成,下面有单独的单元格编辑字段。选择行(在其左端单击)时,该行中的值将复制到编辑字段中。进行更改,并单击“记忆事务”按钮。新值将显示在DataGridView中。如果随后关闭并重新打开表单(释放并重新加载),将显示原始值,而不是新值。显然,数据库记录没有更新

如果重复上述过程,除了在关闭表单之前再次选择记录并再次单击“记忆事务”按钮外,该值将存储在数据库中。关闭表单然后重新打开时,将显示新值

显然,期望的行为是在第一次单击Memory Transaction按钮时立即将值存储在数据库中

以下声明显示在form类的顶部:

    DataTable _dt;
    SqlDataAdapter _adapter;
load_DataGridView()方法如下所示。请注意,适配器的更新、删除和插入查询是通过调用SqlCommandBuilder的各自GetXXXCommand()方法生成的。调试期间的断点表示这些断点已正确填充

    private void load_dataGridView()
    {
        DataTable dt = new DataTable();
        _dt = dt;
        SqlConnection dbconn = new SqlConnection(dbq.connectionString);
        dbconn.Open();
        string qstr = @"SELECT transaction_id
    , 0 as pay
    , m.has_splits
    , m.Num
    , m.Type
    , m.last_date
    , m.next_date
    , m.Payee
    , m.Memo
    , m.Tax_Category
    , m.Amount
    , m.Clr
    , m.Initials
    , m.sales_tax_paid
    , m.from_institution_id as FromInstitutionID
    , m.from_institution as FromInstitution
    , m.from_account_id as FromAccountID
    , m.from_account as FromAccount
    , m.from_envelope_id as FromEnvelopeID
    , m.from_envelope as FromEnvelope
    , m.to_institution_id as ToInstitutionID
    , m.to_institution as ToInstitution
    , m.to_account_id as ToAccountID
    , m.to_account as ToAccount
    , m.to_envelope_id as ToEnvelopeID
    , m.to_envelope as ToEnvelope
    , m.frequency
    FROM memorized m
    ";
        SqlDataAdapter adapter = new SqlDataAdapter(qstr,dbconn);
        SqlCommandBuilder scb = new SqlCommandBuilder(adapter);
        adapter.UpdateCommand = new SqlCommandBuilder(adapter).GetUpdateCommand();
        adapter.DeleteCommand = new SqlCommandBuilder(adapter).GetDeleteCommand();
        adapter.InsertCommand = new SqlCommandBuilder(adapter).GetInsertCommand();
        _adapter = adapter;
        _adapter.Fill(_dt);
        build_dgv_columns(Properties.Settings.Default.currentInstitutionId,
        Properties.Settings.Default.currentAccountId);
        memorizedDataGridView.DataSource = _dt;
    }
单击“记忆事务”按钮时调用的代码如下:

    private void bnMemorizeTx_Click(object sender, EventArgs e)
    {
        if (_newTx)
        {
            _currentTxRow = _dt.Rows.Count;
            _dt.Rows.Add();
            _newTx = false;
        }
        else
        {
            _currentTxRow = memorizedDataGridView.SelectedRows[0].Index;
        }
        populateRowFromFields(memorizedDataGridView.Rows[_currentTxRow]);
        memorizedDataGridView.Refresh();
        memorizedDataGridView.ClearSelection();
        //grid shows new value with no rows selected at this point, row is "dirty"
        try
        {
            _adapter.Update(_dt);
        }
        catch (SqlException e1)
        {
            MessageBox.Show(e1.Message + e1.InnerException, "Error Thrown",MessageBoxButtons.OK);
        }
        reset_transaction_fields();
    }
不会抛出任何异常。我怀疑,将数据从DataGridView中拉入编辑字段,然后再将其写回的操作,在某种程度上与_adapter.Update(_dt)命令检测行已更改的能力有关,因此在调用它时,它不会更新任何内容。然后,在第二次尝试中,当重新选择行(显示新值)但未进行编辑更改时,它确实起作用。不知何故,即使新值出现在网格中,这就好像更改尚未提交,必须重新选择行才能发生

populateRowFromFields方法如下所示,通过使用单元格索引的列名引用将编辑字段中的值写入行中的单元格值:

            private void populateRowFromFields(DataGridViewRow dgvr)
    {
        dgvr.Cells["Num"].Value = tbRefNum.Text;
        dgvr.Cells["Type"].Value = cbType.GetItemText(cbType.SelectedItem);
        dgvr.Cells["last_date"].Value = dtpLastPaid.Value.ToShortDateString();
        dgvr.Cells["next_date"].Value = dtpNextPay.Value.ToShortDateString();
        dgvr.Cells["Payee"].Value = tbPayee.Text;
        dgvr.Cells["Amount"].Value = tbAmount.Text;
        dgvr.Cells["FromInstitutionID"].Value = cbFromInst.GetItemText(cbFromInst.SelectedValue); // fromInst
        dgvr.Cells["FromInstitution"].Value = cbFromInst.GetItemText(cbFromInst.SelectedItem); // fromAcct
        dgvr.Cells["FromAccountID"].Value = cbFromAcct.GetItemText(cbFromAcct.SelectedValue); // fromEnv
        dgvr.Cells["FromAccount"].Value = cbFromAcct.GetItemText(cbFromAcct.SelectedItem); // fromInst
        dgvr.Cells["FromEnvelopeID"].Value = cbFromEnv.GetItemText(cbFromEnv.SelectedValue); // fromAcct
        dgvr.Cells["FromEnvelope"].Value = cbFromEnv.GetItemText(cbFromEnv.SelectedItem); // fromEnv
        if ((cbType.GetItemText(cbType.SelectedItem) == "+iTx") || (cbType.GetItemText(cbType.SelectedItem) == "-iTx") || (cbType.GetItemText(cbType.SelectedItem) == "+eTx") || (cbType.GetItemText(cbType.SelectedItem) == "-eTx")) 
        {
            dgvr.Cells["ToInstitutionID"].Value = cbToInst.GetItemText(cbToInst.SelectedValue); // toInst
            dgvr.Cells["ToInstitution"].Value = cbToInst.GetItemText(cbToInst.SelectedItem); // toInst
            dgvr.Cells["ToAccountID"].Value = cbToAcct.GetItemText(cbToAcct.SelectedValue); // toAcct
            dgvr.Cells["ToAccount"].Value = cbToAcct.GetItemText(cbToAcct.SelectedItem); // toAcct
            dgvr.Cells["ToEnvelopeID"].Value = cbToEnv.GetItemText(cbToEnv.SelectedValue); // toEnv
            dgvr.Cells["ToEnvelope"].Value = cbToEnv.GetItemText(cbToEnv.SelectedItem); // toEnv
            hideTxToFields();
        }
        dgvr.Cells["Memo"].Value = tbMemo.Text;
        dgvr.Cells["Tax_Category"].Value = cbTaxCategory.GetItemText(cbTaxCategory.SelectedItem); // taxCategory
        dgvr.Cells["sales_tax_paid"].Value = cbSalesTaxPaid.GetItemText(cbSalesTaxPaid.SelectedItem); // salesTaxPaid
        dgvr.Cells["Clr"].Value = "U";
        dgvr.Cells["Initials"].Value = cbBy.GetItemText(cbBy.SelectedItem); // by
        dgvr.Cells["frequency"].Value = cbFrequency.GetItemText(cbFrequency.SelectedItem); // frequency
    }

救命啊!这简直快把我逼疯了

最简单的选择是通过
BindingSource
为网格和编辑控件使用数据绑定,例如

private SqlConnection connection;
private SqlDataAdapter adapter;
private SqlCommandBuilder builder;
private DataTable table;

private void Form1_Load(object sender, EventArgs e)
{
    connection = new SqlConnection("connection string here");
    adapter = new SqlDataAdapter("SELECT * FROM Person", connection);
    builder = new SqlCommandBuilder(adapter);
    table = new DataTable();

    adapter.Fill(table);
    bindingSource1.DataSource = table;
    dataGridView1.DataSource = bindingSource1;
    givenNameTextBox.DataBindings.Add("Text", bindingSource1, "GivenName");
    familyNameTextBox.DataBindings.Add("Text", bindingSource1, "FamilyName");
}

private void addButton_Click(object sender, EventArgs e)
{
    bindingSource1.AddNew();
}

private void deleteButton_Click(object sender, EventArgs e)
{
    bindingSource1.RemoveCurrent();
}

private void saveButton_Click(object sender, EventArgs e)
{
    bindingSource1.EndEdit();
    adapter.Update(table);
}

我建议你走错方向了。您应该填充一个
DataTable
,将其绑定到
BindingSource
,然后将其绑定到
DataGridView
和编辑字段。当用户在网格中选择一行时,其字段将自动显示在编辑字段中;不需要代码。提交更新时,编辑字段中所做的任何更改都将自动推回到网格中。当您导航到另一条记录时,这将自动发生,但您也可以在
BindingSource
上调用
EndEdit
,在保存之前强制执行它。这是有道理的——我完全赞成“无需代码”。但我不确定将编辑字段(全部24个)绑定到BindingSource的代码是什么样子。你能举个简单的例子吗?另外,当我说_adapter.Fill(_dt)时,我不是在填充数据表吗;和MemoredDataGridView.DataSource=_dt;?(您必须在代码窗口中向下滚动才能看到这些行)是的,
Fill
调用是填充
DataTable
的方式,但我想在我的描述中包括所有必需的步骤。我将发布一个代码示例作为答案。谢谢,j,给我一天时间尝试一下,然后我将发布结果并标记为解决方案。更清楚。再次感谢,j,一旦我对C#进行了必要的转换(谢天谢地,VB和C#在这种情况下非常接近,但可能会让新手感到困惑),它就工作得很好。我正在把这个烫金以备将来参考!:-)非常抱歉发布VB代码。我大部分时间都在回答VB问题,但忘了这次我不是。即使您已经为自己完成了,我也会替换代码,以防其他人遇到类似问题。