使用 SqlBulkCopy 时如何检索服务器生成的标识值

问题描述 投票:0回答:3

我知道我可以通过不指定

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);
    }
}
c# sql-server identity sqlbulkcopy
3个回答
6
投票

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 链接回原始数据 =/


0
投票

您可以使用 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;
 }

0
投票

您的源数据表有一个指定的标识列,您可以在创建数据表时设置。然后添加源(标题)表以及要在其中将相关 HeaderId 标记为 DataSet 的详细表,并创建一个具有 Rule.Cascade 的 UpdateRuleForeignKeyConsraint

当您为头表的数据调用 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();
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.