2011-06-21 70 views
2

我試圖將使用ADO.NET的DataTable逐字的內容插入到SQL Server數據庫中。 (注意:目前我正在使用SQL Server CE,但我想要一種適用於常規或CE的解決方案。)將DataTable的所有行插入到SQL Server表中,包括Id值

使用以下代碼會導致不包含Id列的命令:

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

這將導致Id是自動生成的,這不是我想要的。我需要按照出現在DataTable中的每個Id進行復制。

當使用GetInsertCommand()時,有什麼辦法可以強制SqlCeCommandBuilder在插入命令中包含Id字段嗎?如果沒有,是否有另一種解決方案?我願意使用ADO.NET或EntityFramework。

請注意,Id列是AutoIncrement = true的主鍵。

編輯

我想出了什麼,我認爲是一個解決方案,但最終只是創造了另一個問題(見我的回答如下)。所以,我仍然在尋找一個很好的答案。

編輯2

我剛編輯我的答案。我想我找到了一個解決方法。仍然笨重和數據庫特定。希望有一個適當的ADO.NET或EntityFramework解決方案。

回答

2

我花了很多時間搞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列。這似乎是訣竅。

0

儘管您將手動準備插入語句,直到表架構(AutoIncrement = false)已被更改,您將無法插入Id。

+0

嗨ibram,我有點麻煩理解。你是說我需要手動編寫插入語句,還*設置'AutoIncrement = false'? – devuxer

+0

對不起我的法語不好:)我的意思是這是不可能的,因爲AutoIncrement = true。只有當你改變爲AutoIncrement = false時,它才能工作。 – ibram

+0

@ibtram,我實際上已經嘗試過,但插入命令仍然缺少「Id」。 – devuxer

相關問題