如何在RowChanging事件处理程序中测试DataTable更新

本文关键字:测试 DataTable 更新 程序 事件处理 RowChanging | 更新日期: 2023-09-27 18:18:54

我有一个WPF DataGrid绑定到一个数据表。我从数据库中的任意表填充底层数据集。我已经附加到DataTable RowChangingRowChanged事件。当用户更改一行时,这些事件将触发并允许我验证行。

为了从DataGrid获得最佳行为,对我来说似乎很明显,应该设置e.Row.RowError消息,并从RowChanging事件处理程序抛出异常。我有一些xaml,在行标头中将行标记为错误,因此它是可见的,并且我得到一个带有错误消息的漂亮工具提示。所谓最优,我的意思是当验证按照所描述的方式处理时,这些网格所期望的转义序列按照预期工作。尝试从RowChanged事件执行相同的验证会导致一些不正常的行为,无法正确回滚编辑。

我遇到的问题是,我需要更新底层数据集,以便应用所有DB验证规则,并且可以在RowChanging处理程序中检测到与其他用户更改的冲突。如果操作失败,我可以按照描述标记验证。但是,e.Row.RowState是不变的,如果我把它包含的数据集传递给我的DB更新方法,它的DataAdapter.Update(myDataTable)方法不会看到行被改变,因此,什么也不做。这种行为与我在RowChanged处理程序中执行相同操作时发生的情况形成对比。此时,记录(当前/原始/建议)值被更新,记录标记为已修改。

此时的DataAdapter更新会导致数据库活动。但是,在失败的情况下,我在事件序列中的错误点。我可以标记错误,但是网格的回滚行为不能正常工作(通常导致更改的单元格不能回滚)。

我的问题是,如何在Modified状态下获得记录(或记录的副本??)以便更新数据库?我通常使用类型化数据集,但这次我要使用任意表,因此,我使用数据集。

如何在RowChanging事件处理程序中测试DataTable更新

好吧,这有点有趣,但我最终解决了。关键在于如何处理add &修改RowChanging处理程序中的事件和删除RowDeleted处理程序中的事件。我将展示足够多的代码,以节省下一个人几个小时的挠头。

在下面的代码中,_dataSet是一个通过DataAdapter填充的DataSet。_dataTable为_dataSet.Tables[0].DefaultView。_dataTable作为ItemsSource绑定到XAML中的DataGrid。这段代码在我的ViewModel中,但它也可以在模型代码中。我把它删减了一点,所以它可能需要调整才能在代码中为你工作。

private void AttachDataTableEvents()
{
    _dataTable.RowChanging += new DataRowChangeEventHandler(DataTable_RowChanging);
    _dataTable.RowChanged += new DataRowChangeEventHandler(DataTable_RowChanged);
    _dataTable.RowDeleting += new DataRowChangeEventHandler(DataTable_RowDeleting);
    _dataTable.RowDeleted += new DataRowChangeEventHandler(DataTable_RowDeleted);
}
private void DataTable_RowChanging(object sender, DataRowChangeEventArgs e)
{
    Trace.WriteLine(string.Format("DataTable_RowChanging(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
    if (e.Action == DataRowAction.Add)
    {
        e.Row.ClearErrors();
        DataTable updateDataTable = CreateUpdateDataTableForRowAdd(_dataSet, 0, e.Row);
        int rowsAffected;
        string errorMessage;
        if (!UpdateTableData(updateDataTable, out rowsAffected, out errorMessage))
        {
            e.Row.RowError = errorMessage;
            throw new ArgumentException(errorMessage);
        }
    }
    else if (e.Action == DataRowAction.Change)
    {
        e.Row.ClearErrors();
        DataTable updateDataTable = CreateUpdateDataTableForRowChange(_dataSet, 0, e.Row);
        int rowsAffected;
        string errorMessage;
        if (!UpdateTableData(updateDataTable, out rowsAffected, out errorMessage))
        {
            e.Row.RowError = errorMessage;
            throw new ArgumentException(errorMessage);
        }
    }
}
private void DataTable_RowChanged(object sender, DataRowChangeEventArgs e)
{
    Trace.WriteLine(string.Format("DataTable_RowChanged(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
    if (e.Action == DataRowAction.Add)
    {
        e.Row.AcceptChanges();
    }
    else if (e.Action == DataRowAction.Change)
    {
        e.Row.AcceptChanges();
    }
}
private void DataTable_RowDeleting(object sender, DataRowChangeEventArgs e)
{
    Trace.WriteLine(string.Format("DataTable_RowDeleting(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
    // can't stop the operation here
}
private void DataTable_RowDeleted(object sender, DataRowChangeEventArgs e)
{
    Trace.WriteLine(string.Format("DataTable_RowDeleted(): Action {0}, RowState {1}", e.Action, e.Row.RowState));
    DataTable updateDataTable = CreateUpdateDataTableForRowDelete(_dataSet, 0, e.Row);
    int rowsAffected;
    string errorMessage;
    if (!UpdateTableData(updateDataTable, out rowsAffected, out errorMessage))
    {
        e.Row.RejectChanges();
        Mediator mediator = _iUnityContainer.Resolve<Mediator>();
        mediator.NotifyColleagues<string>(MediatorMessages.NotifyViaModalDialog, errorMessage);
    }
    else
    {
        e.Row.AcceptChanges();
    }
}

关键是用要更新的记录创建一个新的DataTable。然后将这个数据表传递给DataAdapter.Update(DataTable)方法。对于添加/更改/删除事件,创建DataSet模式的克隆,然后将一条记录以正确的状态添加到DataTable中。下面显示的三个帮助函数返回一个DataTable,其中记录处于适当的状态,并且在Current/Original/Proposed成员中具有正确的列信息。

        private static DataTable CreateUpdateDataTableForRowAdd(DataSet originalDataSet, int originalDataTableIndex, DataRow addedDataRow)
    {
        DataSet updateDataSet = originalDataSet.Clone();
        DataTable updateDataTable = updateDataSet.Tables[originalDataTableIndex];
        DataRow dataRow = updateDataTable.NewRow();
        int columnCount = updateDataTable.Columns.Count;
        for (int i = 0; i < columnCount; ++i)
        {
            dataRow[i] = addedDataRow[i, DataRowVersion.Proposed];
        }
        updateDataTable.Rows.Add(dataRow);
        // dataRow state is *Added*
        return updateDataTable;
    }
    private static DataTable CreateUpdateDataTableForRowChange(DataSet originalDataSet, int originalDataTableIndex, DataRow changedDataRow)
    {
        DataSet updateDataSet = originalDataSet.Clone();
        DataTable updateDataTable = updateDataSet.Tables[originalDataTableIndex];
        DataRow dataRow = updateDataTable.NewRow();
        int columnCount = updateDataTable.Columns.Count;
        for (int i = 0; i < columnCount; ++i)
        {
            dataRow[i] = changedDataRow[i, DataRowVersion.Original];
        }
        updateDataTable.Rows.Add(dataRow);
        dataRow.AcceptChanges();
        dataRow.BeginEdit();
        for (int i = 0; i < columnCount; ++i)
        {
            dataRow[i] = changedDataRow[i, DataRowVersion.Proposed];
        }
        dataRow.EndEdit();
        // dataRow state is *Modified*
        return updateDataTable;
    }
    private static DataTable CreateUpdateDataTableForRowDelete(DataSet originalDataSet, int originalDataTableIndex, DataRow deletedDataRow)
    {
        DataSet updateDataSet = originalDataSet.Clone();
        DataTable updateDataTable = updateDataSet.Tables[originalDataTableIndex];
        DataRow dataRow = updateDataTable.NewRow();
        int columnCount = updateDataTable.Columns.Count;
        for (int i = 0; i < columnCount; ++i)
        {
            dataRow[i] = deletedDataRow[i, DataRowVersion.Original];
        }
        updateDataTable.Rows.Add(dataRow);
        dataRow.AcceptChanges();
        dataRow.Delete();
        // dataRow state is *Deleted*
        return updateDataTable;
    }

如果上面的代码实现了,行为几乎是正确的。看到的问题是当您离开记录时验证失败。它第一次工作时,即错误标记显示在行标题上。但是,如果像编辑一样移动到记录中,但没有更改任何值,然后再次移动,则错误指示符将消失。但是,在移回该行并取消编辑之前,您仍然无法移动到网格中的另一个单元格。

为了获得正确的行为,您需要为网格添加一个验证规则:

        <DataGrid Grid.Column="1" Grid.Row="1" AutoGenerateColumns="True" ItemsSource="{Binding TableDataView}" Name="_gridTableGrid" CanUserDeleteRows="True" CanUserAddRows="True" RowHeaderWidth="25" CanUserResizeRows="False">
        <DataGrid.RowValidationRules>
            <local:DataGridRowValidationRule ValidationStep="CommittedValue" />
        </DataGrid.RowValidationRules>
    </DataGrid>

然后,在代码后面,添加以下内容:

    public class DataGridRowValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = (BindingGroup)value;
        if (bindingGroup.Items.Count > 0)
        {
            System.Data.DataRowView dataRowView = bindingGroup.Items[0] as System.Data.DataRowView;
            if (dataRowView.Row.HasErrors)
            {
                string errorMessage = string.IsNullOrWhiteSpace(dataRowView.Row.RowError) ? "There is an unspecified error in the row" : dataRowView.Row.RowError;
                return new ValidationResult(false, errorMessage);
            }
            else
            {
                return ValidationResult.ValidResult;
            }
        }
        else
        {
            return ValidationResult.ValidResult;
        }
    }
}

现在错误指示可以健壮地工作了。

需要处理的最后一个问题与自动生成的索引值有关。如果存在带有自动生成索引的表,则可以在该字段以及其他字段中输入不同的值,并提交记录(将其关闭或返回)。如果网格视图被刷新,我们将看到其他字段已经更改,但键保留了其初始值。我必须弄清楚如何检索/重新显示该记录,而不必检索/刷新所有其他行(任意且可能很大的数字)。

这一努力的结果是保留了通过标准转义序列所期望的取消编辑行为。例如,如果记录验证失败,第一个将取消当前的单元格编辑;第二个命令取消行编辑。

分享和享受!

编辑:我添加了XAML和代码隐藏中使用的验证规则,以获得可靠的错误指示。抱歉,这么长的回答。如果我一开始就弄清楚了这一切,我就会选择一个更合适的论坛来展示这个方法。

如果您只需要将RowState更改为modified,则需要调用DataRow.SetModified()方法