将 DataTable 的所有行插入到 SQL Server 表中,包括 Id 值

Posted

技术标签:

【中文标题】将 DataTable 的所有行插入到 SQL Server 表中,包括 Id 值【英文标题】:Inserting all rows of a DataTable into a SQL Server table, including Id values 【发布时间】:2011-06-21 02:42:19 【问题描述】:

我正在尝试使用 ADO.NET 将DataTable 逐字 的内容插入到 SQL Server 数据库中。 (注意:我目前使用的是 SQL Server CE,但我想要一个适用于常规或 CE 的解决方案。)

使用以下代码会导致命令不包含Id 列:

var builder = new SqlCeCommandBuilder();
builder.DataAdapter = adapter;
builder.SetAllValues = true;
var command = builder.GetInsertCommand(true);

这会导致Id 自动生成,这不是我想要的。我需要将每个Id 完全复制到DataTable 中。

在使用GetInsertCommand() 时,有什么方法可以强制SqlCeCommandBuilder 在插入命令中包含Id 字段?如果没有,是否有其他解决方案?我愿意使用 ADO.NET 或 EntityFramework。

请注意,Id 列是带有AutoIncrement = true 的主键。

编辑

我想出了一个我认为是解决这个问题的方法,但这最终只是产生了另一个问题(请参阅下面的答案)。所以,我仍在寻找一个好的答案。

编辑 2

我刚刚编辑了我的答案。我想我找到了解决方法。仍然笨重且特定于数据库。更喜欢合适的 ADO.NET 或 EntityFramework 解决方案。

【问题讨论】:

【参考方案1】:

我花了很多时间弄乱CommandBuilder,包括在CommandText中手动插入“Id”并在参数中添加一个Id,但我就是无法让它工作。

所以我决定暂时放弃它,自己滚动插入查询。下面的代码似乎适用于我扔给它的几个示例场景,但我可能会错过ItemToSqlFormattedValue() 中的一些转换。

非常感谢您的评论/更正,如果您有更好的解决方案,请将其作为答案发布。

public virtual void FillDatabaseTable(DataTable table)

    var connection = _dbContextLocator.Current.Database.Connection;
    connection.Open();
    SetIdentityInsert(table, connection, true);
    FillDatabaseRecords(table, connection);
    SetIdentityInsert(table, connection, false);
    connection.Close();


private void FillDatabaseRecords(DataTable table, DbConnection connection)

    var command = GetNewCommand();
    command.Connection = connection;
    var rawCommandText = string.Format("insert into 0 values (1)", table.TableName, "0");
    foreach (var row in table.AsEnumerable())
        FillDatabaseRecord(row, command, rawCommandText);


private void FillDatabaseRecord(DataRow row, DbCommand command, string rawCommandText)

    var values = row.ItemArray.Select(i => ItemToSqlFormattedValue(i));
    var valueList = string.Join(", ", values);
    command.CommandText = string.Format(rawCommandText, valueList);
    command.ExecuteNonQuery();


private string ItemToSqlFormattedValue(object item)

    if (item is System.DBNull)
        return "NULL";
    else if (item is bool)
        return (bool)item ? "1" : "0";
    else if (item is string)
        return string.Format("'0'", ((string)item).Replace("'", "''"));
    else if (item is DateTime)
        return string.Format("'0'", ((DateTime)item).ToString("yyyy-MM-dd HH:mm:ss"));
    else
        return item.ToString();


private void SetIdentityInsert(DataTable table, DbConnection connection, bool enable)

    var command = GetNewCommand();
    command.Connection = connection;
    command.CommandText = string.Format(
        "set IDENTITY_INSERT 0 1",
        table.TableName,
        enable ? "on" : "off");
    command.ExecuteNonQuery();


编辑

我发现了这个问题。尽管关闭了 IDENTITY_INSERT,但 SQL Server CE 似乎无法确定下一个 ID 应该是什么,因此如果您尝试在未指定 Id 值的情况下执行插入操作,它会失败并出现以下错误:

重复值不能插入唯一索引

我猜 SQL Server CE 在确定下一个 Id 应该是什么时不只是使用MAX(Id) + 1。我相信它会尝试根据过去的插入来跟踪它,但是当在 IDENTITY_INSERT 打开的情况下执行插入时,不会发生这种跟踪。反正这是我的预感。

所以,目前,如果数据库是只读的,或者您总是生成自己的 Id,这实际上只是一个可行的解决方案。


编辑 2

我认为/希望这可以解决它:

public virtual void FillDatabaseTable(DataTable table)

    var connection = _dbContextLocator.Current.Database.Connection;
    connection.Open();
    ReseedIdColumn(table, connection);
    SetIdentityInsert(table, connection, true);
    FillDatabaseRecords(table, connection);
    SetIdentityInsert(table, connection, false);
    connection.Close();


private void ReseedIdColumn(DataTable table, DbConnection connection)

    if (table.Rows.Count == 0) return;
    var command = GetNewCommand();
    command.Connection = connection;
    command.CommandText = string.Format(
        "alter table 0 alter column Id IDENTITY (1, 1)",
        table.TableName,
        table.AsEnumerable().Max(t => t.Field<int>("Id")) + 1);
    command.ExecuteNonQuery();


// note: for SQL Server, may need to replace `alter table` statement with this:
// "dbcc checkident(0.Id, reseed, 1)"

private void FillDatabaseRecords(DataTable table, DbConnection connection)

    var command = GetNewCommand();
    command.Connection = connection; //todo combine this sequence
    var rawCommandText = string.Format("insert into 0 values (1)", table.TableName, "0");
    foreach (var row in table.AsEnumerable())
        FillDatabaseRecord(row, command, rawCommandText);


private void FillDatabaseRecord(DataRow row, DbCommand command, string rawCommandText)

    var values = row.ItemArray.Select(i => ItemToSqlFormattedValue(i));
    var valueList = string.Join(", ", values);
    command.CommandText = string.Format(rawCommandText, valueList);
    command.ExecuteNonQuery();


private string ItemToSqlFormattedValue(object item)

    const string quotedItem = "'0'";
    if (item is System.DBNull)
        return "NULL";
    else if (item is bool)
        return (bool)item ? "1" : "0";
    else if (item is Guid)
        return string.Format(quotedItem, item.ToString());
    else if (item is string)
        return string.Format(quotedItem, ((string)item).Replace("'", "''"));
    else if (item is DateTime)
        return string.Format(quotedItem, ((DateTime)item).ToString("yyyy-MM-dd HH:mm:ss"));
    else
        return item.ToString();


private void SetIdentityInsert(DataTable table, DbConnection connection, bool enable)

    var command = GetNewCommand();
    command.Connection = connection;
    command.CommandText = string.Format(
        "set IDENTITY_INSERT 0 1",
        table.TableName,
        enable ? "on" : "off");
    command.ExecuteNonQuery();

主要区别在于我现在正在重新设置 Id 列。这似乎起到了作用。

【讨论】:

【参考方案2】:

您将无法插入 Id,尽管您将手动准备插入语句,直到更改表架构 (AutoIncrement=false)。

【讨论】:

嗨,ibram,我在理解上有点困难。你是说我需要手动编写插入语句并设置AutoIncrement = false 抱歉我的法语不好 :) 我的意思是这是不可能的,因为 AutoIncrement=true。仅当您更改为 AutoIncrement=false 时它才应该起作用。 @ibtram,我确实试过了,但插入命令中仍然缺少Id

以上是关于将 DataTable 的所有行插入到 SQL Server 表中,包括 Id 值的主要内容,如果未能解决你的问题,请参考以下文章

一次将整个 DataTable 插入数据库而不是逐行插入?

DataTable RejectChanges 不会重置所有更改

SQL/C#:DataTable 到存储过程(从用户定义的表类型插入)- 转换错误

如何将数据表插入到 SQL Server 数据库表中?

使用 SqlBulkCopy 将 DataTable 中的列映射到 SQL 表

将小计插入动态生成的 DataTable