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

时间:2011-06-21 02:42:19

标签: c# .net sql-server ado.net sql-server-ce

我正在尝试使用ADO.NET将DataTable verbatim 的内容插入到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进行复制。

使用SqlCeCommandBuilder时,有没有办法强制Id在insert命令中包含GetInsertCommand()字段?如果没有,还有其他解决方案吗?我愿意使用ADO.NET或EntityFramework。

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

修改

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

修改2

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

2 个答案:

答案 0 :(得分:2)

我花了很多时间搞乱CommandBuilder,包括手动将“Id”插入CommandText并向参数添加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列。这似乎可以解决问题。

答案 1 :(得分:0)

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