这是 DataTable API 中的错误吗? 改变以“错误的顺序”存储/执行。

发布于 2024-07-25 17:14:17 字数 3734 浏览 8 评论 0原文

编辑:无论您是否认为这是一个 .NET bug,任何评论都将不胜感激。

我有一个错误,我已设法将其简化为以下情况:

我有一个 DataTable,其中主键必须保持连续,例如,如果您在其他行之间插入一行,则必须首先将后续行的 ID 增加到腾出空间,然后插入该行。

如果删除一行,则必须递减任何后续行的 ID,以填充表中该行留下的空白。

正确运行的测试用例

从表中的 3 行开始,ID 为 1、2 和 3。

然后删除 ID=2,并在 ID=3 处设置 ID=2(以填补空白); 这工作正常。 dataTable.GetChanges() 包含删除的行,然后包含修改的行; 当您运行 dataAdapter.Update(table) 时,它执行得很好。

测试用例不起作用

但是,如果您从 2 行(ID 1 和 2)开始,然后设置 ID=3(其中 ID=2),并插入 ID=2,然后提交(或接受)变化。 现在这应该与第一次测试处于相同的状态。

然后执行与之前相同的步骤,即删除 ID=2 并在 ID=3 处设置 ID=2,但现在 dataTable.GetChanges() 的顺序错误。 第一行是修改的行,第二行是删除的行。 然后,如果您尝试 dataAdapter.Update(table) ,它将给出主键冲突 - 它尝试在删除之前将行修改为已存在的行。

解决方法

我可以想到解决该问题的方法,即强制执行,以便首先提交删除的行,然后修改行,然后添加行。 但为什么会出现这种情况呢? 还有其他解决方案吗?

我想我之前在字典中见过类似的“问题”,如果您添加一些项目,然后删除,重新插入它们,那么它们将不会与您添加的顺序相同(当您枚举字典时)。

这里有两个 NUnit 测试显示了问题:

[Test]
public void GetChanges_Working()
{
    // Setup ID table with three rows, ID=1, ID=2, ID=3
    DataTable idTable = new DataTable();
    idTable.Columns.Add("ID", typeof(int));

    idTable.PrimaryKey = new DataColumn[] { idTable.Columns["ID"] };

    idTable.Rows.Add(1);
    idTable.Rows.Add(2);
    idTable.Rows.Add(3);

    idTable.AcceptChanges();

    // Delete ID=2, and move old ID=3 to ID=2
    idTable.Select("ID = 2")[0].Delete();
    idTable.Select("ID = 3")[0]["ID"] = 2;

    // Debug GetChanges
    foreach (DataRow row in idTable.GetChanges().Rows)
    {
        if (row.RowState == DataRowState.Deleted)
            Console.WriteLine("Deleted: {0}", row["ID", DataRowVersion.Original]);
        else
            Console.WriteLine("Modified: {0} = {1}", row["ID", DataRowVersion.Original], row["ID", DataRowVersion.Current]);
    }

    // Check GetChanges
    Assert.AreEqual(DataRowState.Deleted, idTable.GetChanges().Rows[0].RowState, "1st row in GetChanges should be deleted row");
    Assert.AreEqual(DataRowState.Modified, idTable.GetChanges().Rows[1].RowState, "2nd row in GetChanges should be modified row");
}

输出:

Deleted: 2
Modified: 3 = 2

1 passed, 0 failed, 0 skipped, took 4.27 seconds (NUnit 2.4).

下一个测试:

[Test]
public void GetChanges_NotWorking()
{
    // Setup ID table with two rows, ID=1, ID=2
    DataTable idTable = new DataTable();
    idTable.Columns.Add("ID", typeof(int));

    idTable.PrimaryKey = new DataColumn[] { idTable.Columns["ID"] };

    idTable.Rows.Add(1);
    idTable.Rows.Add(2);

    idTable.AcceptChanges();

    // Move old ID=2 to ID=3, and add ID=2
    idTable.Select("ID = 2")[0]["ID"] = 3;
    idTable.Rows.Add(2);

    idTable.AcceptChanges();

    // Delete ID=2, and move old ID=3 to ID=2
    idTable.Select("ID = 2")[0].Delete();
    idTable.Select("ID = 3")[0]["ID"] = 2;

    // Debug GetChanges
    foreach (DataRow row in idTable.GetChanges().Rows)
    {
        if (row.RowState == DataRowState.Deleted)
            Console.WriteLine("Deleted: {0}", row["ID", DataRowVersion.Original]);
        else
            Console.WriteLine("Modified: {0} = {1}", row["ID", DataRowVersion.Original], row["ID", DataRowVersion.Current]);
    }

    // Check GetChanges
    Assert.AreEqual(DataRowState.Deleted, idTable.GetChanges().Rows[0].RowState, "1st row in GetChanges should be deleted row");
    Assert.AreEqual(DataRowState.Modified, idTable.GetChanges().Rows[1].RowState, "2nd row in GetChanges should be modified row");
}

输出:

Modified: 3 = 2
Deleted: 2
TestCase 'GetChanges_NotWorking'
failed: 
  1st row in GetChanges should be deleted row
  Expected: Deleted
  But was:  Modified

Edit: any comments whether you think this is a .NET bug or not would be appreciated.

I have a bug which I've managed to simplify to the following scenario:

I have a DataTable where the primary keys must be kept consecutive, e.g. if you insert a row between other rows, you must first increment the ID of the succeeding rows to make space, and then insert the row.

And if you delete a row, you must decrement the ID of any succeeding rows to fill the gap left by the row in the table.

Test case that works correctly

Start with 3 rows in the table, with IDs 1, 2 and 3.

Then delete ID=2, and set ID=2 where ID=3 (to fill the gap); this works correctly. The dataTable.GetChanges() contains the deleted row, and then the modified row; when you run dataAdapter.Update(table) it executes fine.

Test case that does not work

However, if you start with 2 rows (IDs 1 and 2), then set ID=3 where ID=2, and insert ID=2, then commit (or accept) changes. This should be now be the same state as the first test.

Then you do the same steps as before, i.e. delete ID=2 and set ID=2 where ID=3, but now the dataTable.GetChanges() are in the wrong order. The first row is a modified row, and the second row is the deleted row. Then if you try dataAdapter.Update(table) it will give a primary key violation - it tried to modify a row to an already existing row before it deletes.

Workaround

I can think of a workaround to the problem, i.e. force it so that deleted rows are committed first, and then modified rows, and then added rows. But why is this happening? Is there another solution?

I think I have seen a similar "problem" before with dictionaries, that if you add some items, delete then, re-insert them, then they will not be in the same sequence that you added them (when you enumerate the dictionary).

Here are two NUnit tests which show the problem:

[Test]
public void GetChanges_Working()
{
    // Setup ID table with three rows, ID=1, ID=2, ID=3
    DataTable idTable = new DataTable();
    idTable.Columns.Add("ID", typeof(int));

    idTable.PrimaryKey = new DataColumn[] { idTable.Columns["ID"] };

    idTable.Rows.Add(1);
    idTable.Rows.Add(2);
    idTable.Rows.Add(3);

    idTable.AcceptChanges();

    // Delete ID=2, and move old ID=3 to ID=2
    idTable.Select("ID = 2")[0].Delete();
    idTable.Select("ID = 3")[0]["ID"] = 2;

    // Debug GetChanges
    foreach (DataRow row in idTable.GetChanges().Rows)
    {
        if (row.RowState == DataRowState.Deleted)
            Console.WriteLine("Deleted: {0}", row["ID", DataRowVersion.Original]);
        else
            Console.WriteLine("Modified: {0} = {1}", row["ID", DataRowVersion.Original], row["ID", DataRowVersion.Current]);
    }

    // Check GetChanges
    Assert.AreEqual(DataRowState.Deleted, idTable.GetChanges().Rows[0].RowState, "1st row in GetChanges should be deleted row");
    Assert.AreEqual(DataRowState.Modified, idTable.GetChanges().Rows[1].RowState, "2nd row in GetChanges should be modified row");
}

Output:

Deleted: 2
Modified: 3 = 2

1 passed, 0 failed, 0 skipped, took 4.27 seconds (NUnit 2.4).

Next test:

[Test]
public void GetChanges_NotWorking()
{
    // Setup ID table with two rows, ID=1, ID=2
    DataTable idTable = new DataTable();
    idTable.Columns.Add("ID", typeof(int));

    idTable.PrimaryKey = new DataColumn[] { idTable.Columns["ID"] };

    idTable.Rows.Add(1);
    idTable.Rows.Add(2);

    idTable.AcceptChanges();

    // Move old ID=2 to ID=3, and add ID=2
    idTable.Select("ID = 2")[0]["ID"] = 3;
    idTable.Rows.Add(2);

    idTable.AcceptChanges();

    // Delete ID=2, and move old ID=3 to ID=2
    idTable.Select("ID = 2")[0].Delete();
    idTable.Select("ID = 3")[0]["ID"] = 2;

    // Debug GetChanges
    foreach (DataRow row in idTable.GetChanges().Rows)
    {
        if (row.RowState == DataRowState.Deleted)
            Console.WriteLine("Deleted: {0}", row["ID", DataRowVersion.Original]);
        else
            Console.WriteLine("Modified: {0} = {1}", row["ID", DataRowVersion.Original], row["ID", DataRowVersion.Current]);
    }

    // Check GetChanges
    Assert.AreEqual(DataRowState.Deleted, idTable.GetChanges().Rows[0].RowState, "1st row in GetChanges should be deleted row");
    Assert.AreEqual(DataRowState.Modified, idTable.GetChanges().Rows[1].RowState, "2nd row in GetChanges should be modified row");
}

Output:

Modified: 3 = 2
Deleted: 2
TestCase 'GetChanges_NotWorking'
failed: 
  1st row in GetChanges should be deleted row
  Expected: Deleted
  But was:  Modified

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

归途 2024-08-01 17:14:17

这不是一个错误,关键是你正在以一种(非常)非标准的方式使用 ID。 两个答案:

1)使用 DataTable.GetChanges(DataRowState.Modified) 按顺序处理更新(我认为它将被删除、修改、插入)。 这也是您必须对主/详细信息关系执行的操作(在 .net 3.0 之前)

2) 重新考虑您的设计,一般来说 ID 应该是不可变的并允许间隙等。这将使您的所有数据库操作更加可靠和高效更轻松。 您可以使用另一列来维护顺序编号以呈现给用户。

It's not a bug, the point is that you are using ID's in a (very) nonstandard way. Two answers:

1) Use DataTable.GetChanges(DataRowState.Modified) to process your updates in order (I think it would be deleted, modified, inserted). This is what you have to do with Master/Detail relations as well (before .net 3.0)

2) Rethink your design, in general ID's should be immutable and allow for gaps etc. This will make all your database operations much more reliable and much easier. You can use another column to maintain a sequential numbering to present to the user.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文