使用Database同步DataTable自动增量列

时间:2011-12-07 22:31:40

标签: c# mysql concurrency datatable auto-increment

尝试在我的数据库上使用Update()方法时发生DBConcurrencyException问题。我在数据库中有一个表,它有一个自动增加的ID列,以及我的c#程序中的一个DataTable,它从该表中获取信息(包括当我使用MissingSchemaAction = MissingSchemaAction.AddWithKey时的自动增量部分)。

如果我创建行并将它们添加到数据表中,则数据表会自动填充自动增量ID列(从数据库表停止的位置开始),这很好。但是,如果我删除刚刚添加的行(没有先使用Update())并添加新的行,则数据表自动增量列将根据DATATABLE的位置填充,而不是数据库的位置,这就是我得到的并发错误。

例如:

数据库中的表具有以下记录:

1 Apple
2 Orange
3 Pear

将其复制到数据表中,因此当我添加名为“grape”的新行时,我得到:

1 Apple
2 Orange
3 Pear
4 Grape

哪个好,但是如果不运行Update()方法,我删除葡萄行并添加一行新的“Melon”我得到:

1 Apple
2 Orange
3 Pear
5 Melon

当我尝试运行Update()时,数据库期望4是下一个自动增量值,而是得到5.所以我得到了错误。当用户单击“保存”按钮时会发生Update(),所以理想情况下我希望它们能够在最终保存之前进行大量更改,如上所示,但这是保持并发使用Update的唯一方法( )添加/删除每一行后?

2 个答案:

答案 0 :(得分:0)

预期值为5 - 每次执行某些操作时,数据库尝试填充列中的漏洞都是非常低效的。一旦使用了auto_increment,它就会永远消失。

因此,请始终确保您的专栏足够大,保留所有记录。例如,如果您使用TINYINT,则表中只能有127条记录。

自动增量存储在表级别,Mysql永远不会回头查看它是否可以更低。您可以通过执行以下操作手动更改它:

ALTER TABLE tablename AUTO_INCREMENT=2;

但是如果你这样做并且在路上发生了碰撞 - 糟糕的事情将会发生。

或者你可以检查它是什么

SHOW CREATE TABLE tablename;
CREATE TABLE `tablename` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`cat_id` int(10) unsigned NOT NULL,
`status` int(10) unsigned NOT NULL,
`date_added` datetime DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `categories_list_INX` (`cat_id`,`status`),
KEY `cat_list_INX` (`date_added`,`cat_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=latin1

你会发现最后一个是什么。

SELECT LAST_INSERT_ID();
+------------------+
| LAST_INSERT_ID() |
+------------------+
|                2 |
+------------------+
1 row in set (0.00 sec)

答案 1 :(得分:0)

我首先想到的是你应该只处理删除行并首先更新它的情况,然后删除它以保持自动增量ID同步。

但是,我遇到了同样的情况,它似乎是由我的DataGridView托管的第三方控件引起的。具体来说,当用户将焦点放在DataGridView的“new”行中时会出现问题,交换机到另一个应用程序,然后单击回DataGridView。此时,将删除新行的原始DataRow实例,并使用递增的ID值创建新的实例。在实际删除行之前,我无法找到处理删除行的方法,也无法弄清楚第三方控件正在做什么来触发它。

因此,目前我正以非常严厉的方式处理此问题,通过从数据库查询正确的自动增量值,必要时更正新的DataRows。如果一切都失败了,这个解决方案似乎有效。 (注意我使用的是SqlCe而不是MySQL)

void OnLoad()
{
    base.OnLoad(e);
    ...
    _dataTable.TableNewRow += HandleTableNewRow;
}

void HandleTableNewRow(object sender, DataTableNewRowEventArgs e)
{
    SetAutoIncrementValues(e.Row);
}

void SetAutoIncrementValues(DataRow row)
{
    foreach (DataColumn dataColumn in _dataTable.Columns
        .OfType<DataColumn>()
        .Where(column => column.AutoIncrement))
    {
        using (SqlCeCommand sqlcmd = new SqlCeCommand(
            "SELECT AUTOINC_NEXT FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = '" +
            Name + "' AND COLUMN_NAME = '" + dataColumn.ColumnName + "'", _connection))
        using (SqlCeResultSet queryResult =
            sqlcmd.ExecuteResultSet(ResultSetOptions.Scrollable))
        {
            if (queryResult.ReadFirst())
            {
                var nextValue = Convert.ChangeType(queryResult.GetValue(0), dataColumn.DataType);

                if (!nextValue.Equals(row[dataColumn.Ordinal]))
                {
                    // Since an auto-increment column is going to be read-only, apply
                    // the new auto-increment value via a separate array variable.
                    object[] rowData = row.ItemArray;
                    rowData[dataColumn.Ordinal] = nextValue;
                    row.ItemArray = rowData;
                }
            }
        }
    }
}