Я потратил много времени на манипулирование с 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.Похоже, это помогло.