Вставка всех строк DataTable в таблицу SQL Server, включая значения Id - PullRequest
2 голосов
/ 21 июня 2011

Я пытаюсь вставить содержимое DataTable дословно в базу данных SQL Server с помощью ADO.NET. (Примечание: на данный момент я использую 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 в команду вставки при использовании GetInsertCommand()? Если нет, есть ли другое решение? Я открыт для использования ADO.NET или EntityFramework.

Обратите внимание, что столбец Id является первичным ключом с AutoIncrement = true.

Редактировать

Я придумал то, что мне показалось решением этой проблемы, но в итоге я создал еще одну проблему (см. Мой ответ ниже). Итак, я все еще ищу хороший ответ на этот вопрос.

Редактировать 2

Я только что отредактировал свой ответ. Я думаю, что нашел обходной путь. Все еще неуклюжий и специфичный для базы данных. Предпочли бы правильное решение ADO.NET или EntityFramework.

Ответы [ 2 ]

2 голосов
/ 05 августа 2011

Я потратил много времени на манипулирование с 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:

Дублирующее значение не может быть вставлено в уникальный индекс

Я предполагаю, что SQL Server CE не просто использует MAX(Id) + 1 при определении того, каким должен быть следующий Id.Я полагаю, что он пытается отследить его на основе прошлых вставок, но когда вставки выполняются с включенным IDENTITY_INSERT, это отслеживание не происходит.В любом случае, это моя догадка.

Итак, на данный момент это действительно жизнеспособное решение, только если база данных доступна только для чтения или вы всегда генерируете свои собственные идентификаторы.


Edit 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.Похоже, это помогло.

0 голосов
/ 22 июня 2011

Вы не сможете вставить идентификатор, хотя вы будете готовить оператор вставки вручную, пока схема таблицы (AutoIncrement = false) не будет изменена.

...