我知道我可以通过不指定
SqlBulkCopyOptions.KeepIdentity
来使用标识列批量插入到我的表中here.
我想做的是获取服务器生成的标识值并将它们放入我的数据表,甚至列表中。我看到了 this 帖子,但我希望我的代码是通用的,而且我不能在所有表中都有版本列。非常感谢任何建议。这是我的代码:
public void BulkInsert(DataTable dataTable, string DestinationTbl, int batchSize)
{
// Get the DataTable
DataTable dtInsertRows = dataTable;
using (SqlBulkCopy sbc = new SqlBulkCopy(sConnectStr))
{
sbc.DestinationTableName = DestinationTbl;
// Number of records to be processed in one go
sbc.BatchSize = batchSize;
// Add your column mappings here
foreach (DataColumn dCol in dtInsertRows.Columns)
{
sbc.ColumnMappings.Add(dCol.ColumnName, dCol.ColumnName);
}
// Finally write to server
sbc.WriteToServer(dtInsertRows);
}
}
AFAIK,你不能。
获取身份字段值的唯一方法(据我所知)是在逐行插入时使用
SCOPE_IDENTITY()
;或者在插入整个集合时使用 OUTPUT
方法。
“最简单”的方法可能是您将 SqlBulkCopy 表中的记录,然后稍后再取回它们。问题可能是很难再次从服务器正确(快速)获取这些行。 (例如,如果有一个带有
WHERE
= 的 IN (guid1, guid2, .., guid999998, guid999999)
子句会很丑陋(而且很慢)
我假设这里的性能是一个问题,因为您已经在使用 SqlBulkCopy,所以我建议采用
OUTPUT
方法,在这种情况下,您首先需要一个暂存表来将您的记录写入 SqlBulkCopy。所述表应该然后包括某种批处理标识符(GUID?)以允许多个胎面并排运行。您将需要一个存储过程来将登台表中的数据INSERT <table> OUTPUT inserted.* SELECT
到实际目标表中,并再次清理登台表。来自所述过程的返回记录集然后将 1:1 匹配到负责填充登台表的原始数据集,但当然你不应该依赖它的顺序。换句话说:您的下一个挑战是将返回的身份字段与应用程序中的原始 records 匹配。
仔细想想,我会说在所有情况下——除了逐行和 SCOPY_IDENTITY() 方法,它会很慢——你需要有(或添加)一个 ' key' 到您的数据以将生成的 ID 链接回原始数据 =/
您可以使用 deroby 上面描述的类似方法,但不是通过
WHERE IN (guid1, etc...
检索它们,而是根据它们的顺序将它们匹配回插入内存中的行。
所以我建议在表中添加一列以将行与 SqlBulkCopy 事务匹配,然后执行以下操作以将生成的 ID 匹配回您刚刚插入的行的内存集合。
创建一个新的 Guid 并在大容量复制映射到新列的所有行上设置此值
运行BulkCopy对象的
WriteToServer
方法
检索具有相同键的所有行
遍历这个列表,这将按照它们被添加的顺序进行,这些将与内存中的行集合的顺序相同,因此您将知道每个项目的生成 ID。
与给每一行一个唯一的键相比,这会给你更好的性能。因此,在批量插入数据表之后,您可以执行类似的操作(在我的示例中,我将有一个对象列表,我将从中创建数据表,然后将生成的 ID 映射回它们)
List<myObject> myCollection = new List<myObject>
Guid identifierKey = Guid.NewGuid();
//Do your bulk insert where all the rows inserted have the identifierKey
//set on the new column. In this example you would create a data table based
//off the myCollection object.
//Identifier is a column specifically for matching a group of rows to a sql
//bulk copy command
var myAddedRows = myDbContext.DatastoreRows.AsNoTracking()
.Where(d => d.Identifier == identiferKey)
.ToList();
for (int i = 0; i < myAddedRows.Count ; i++)
{
var savedRow = myAddedRows[i];
var inMemoryRow = myCollection[i];
int generatedId = savedRow.Id;
//Now you know the generatedId for the in memory object you could set a
// a property on it to store the value
inMemoryRow.GeneratedId = generatedId;
}
您的源数据表有一个指定的标识列,您可以在创建数据表时设置。然后添加源(标题)表以及要在其中将相关 HeaderId 标记为 DataSet 的详细表,并创建一个具有 Rule.Cascade 的 UpdateRule 的ForeignKeyConsraint。
当您为头表的数据调用 WriteToServer 时,如果 UpdateRule 设置为级联,则 ForeignKeyConstraint 指定的相关列会自动更新。
以下是一些片段,可以让您了解什么对我很有效:
internal static class ClaimsDataSet
{
static DataSet _claimsDataSet = new DataSet();
static int _currentHeaderId = 0;
static ClaimsDataSet()
{
_claimsDataSet.Tables.Add("Header");
_claimsDataSet.Tables["Header"].Columns.Add("Id", typeof(int)).AutoIncrement = true;
_claimsDataSet.Tables["Header"].Columns["Id"].AutoIncrementSeed = 1;
_claimsDataSet.Tables["Header"].Columns["Id"].AutoIncrementStep = 1;
_claimsDataSet.Tables["Header"].Columns["Id"].Unique = true;
_claimsDataSet.Tables["Header"].Columns.Add("TreatmentDate", typeof(System.DateTime));
// Code omitted for brevity. Refer to sample app on github for all source code
_claimsDataSet.Tables.Add("Detail");
_claimsDataSet.Tables["Detail"].Columns.Add("Id", typeof(int)).AutoIncrement = true;
_claimsDataSet.Tables["Detail"].Columns["Id"].AutoIncrementSeed = 1;
_claimsDataSet.Tables["Detail"].Columns["Id"].AutoIncrementStep = 1;
_claimsDataSet.Tables["Detail"].Columns["Id"].Unique = true;
_claimsDataSet.Tables["Detail"].Columns.Add("HeaderId", typeof(int));
_claimsDataSet.Tables["Detail"].Columns.Add("TreatmentDate", typeof(System.DateTime));
// Code omitted for brevity. Refer to sample app on github for all source code
ForeignKeyConstraint foreignKeyConstraint = new ForeignKeyConstraint("HeaderIdConstraint", _claimsDataSet.Tables["Header"].Columns["Id"], _claimsDataSet.Tables["Detail"].Columns["HeaderId"]);
foreignKeyConstraint.UpdateRule = Rule.Cascade;
_claimsDataSet.Tables["Detail"].Constraints.Add(foreignKeyConstraint);
}
internal static int AddHeaderRow(string rowContent)
{
string errorMessage;
DateTime workingDate;
decimal workingAmount;
string[] commaSeparatedValues = ParseCSVLine(rowContent);
DataRow row = _claimsDataSet.Tables["Header"].NewRow();
if (DateTime.TryParse(commaSeparatedValues[0], out workingDate))
{
row["TreatmentDate"] = workingDate;
}
else
{
errorMessage = String.Format("Error converting string content to date value in {0}, Column: {1}", "Treatment Header", "TreatmentDate");
Console.WriteLine(errorMessage);
throw new FormatException(errorMessage);
}
row["Beneficiary"] = commaSeparatedValues[1];
row["ServiceProvider"] = commaSeparatedValues[2];
// Code omitted for brevity. Refer to sample app on github for all source code
_claimsDataSet.Tables["Header"].Rows.Add(row);
_currentHeaderId = Int32.Parse(row["Id"].ToString());
return _currentHeaderId;
}
internal static void AddDetailRow(string rowContent)
{
string errorMessage = "";
DateTime workingDate;
Decimal workingAmount;
string[] commaSeparatedValues = ParseCSVLine(rowContent);
DataRow row = _claimsDataSet.Tables["Detail"].NewRow();
row["HeaderId"] = _currentHeaderId;
if (DateTime.TryParse(commaSeparatedValues[0], out workingDate))
{
row["TreatmentDate"] = workingDate;
}
else
{
errorMessage = String.Format("Error converting string content to date value in {0}, Column: {1}", "Treatment Detail", "TreatmentDate");
Console.WriteLine(errorMessage);
throw new FormatException(errorMessage);
}
row["TariffCode"] = commaSeparatedValues[1];
row["TariffDescription"] = commaSeparatedValues[2];
// Code omitted for brevity. Refer to sample app on github for all source code
_claimsDataSet.Tables["Detail"].Rows.Add(row);
}
internal static void WriteToTargetDatabase()
{
try
{
string connectionString = ConfigurationManager.ConnectionStrings["claimAdminConnectionString"].ConnectionString;
using (SqlConnection destinationConnection = new SqlConnection(connectionString))
{
destinationConnection.Open();
ConnectionState destState = destinationConnection.State;
SqlBulkCopy bulkCopy = new SqlBulkCopy(destinationConnection);
bulkCopy.ColumnMappings.Add("TreatmentDate", "TreatmentDate");
// Code omitted for brevity. Refer to sample app on github for all source code
bulkCopy.DestinationTableName = "dbo.ClaimHeader";
bulkCopy.WriteToServer(_claimsDataSet.Tables["Header"]);
bulkCopy.ColumnMappings.Clear();
bulkCopy.ColumnMappings.Add("HeaderId", "HeaderId");
bulkCopy.ColumnMappings.Add("TreatmentDate", "ClaimDate");
bulkCopy.ColumnMappings.Add("TariffCode", "TariffCode");
// Code omitted for brevity. Refer to sample app on github for all source code
bulkCopy.DestinationTableName = "dbo.ClaimDetail";
bulkCopy.WriteToServer(_claimsDataSet.Tables["Detail"]);
destinationConnection.Close();
}
}
catch (Exception ex)
{
Console.WriteLine("Problem with bulk copy operation...");
Console.WriteLine(ex.Message);
}
}
internal static string[] ParseCSVLine(string inputString)
{
// Code omitted for brevity. Refer to sample app on github for all source code
//takes inputString and splits it into array of strings split at commas
// Convert list to array and return.
return finalValue.ToArray();
}
}