You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用SqlBulkCopy批量插入数据时如何获取自增主键(Identity列)的新插入ID

如何批量插入数据到SQL Server并获取自增Identity主键

我明白你的痛点——SqlBulkCopy虽然高效,但默认不会把生成的自增ID回写到你的DataTable或实体对象里。要解决这个问题,我们需要换个思路:先把数据批量写入临时载体(临时表或表值参数),再通过INSERT...OUTPUT语句将数据插入正式表并捕获生成的ID。

下面给你两种针对.NET Core 6和SQL Server的可行实现方案:

方案一:临时表 + SqlBulkCopy + INSERT...OUTPUT

这种方法用临时表作为中间过渡,先批量导入数据,再插入正式表时获取自增ID,还能通过临时标识列关联原数据和生成的ID。

步骤1:修改DataTable,添加临时标识列

给DataTable新增一个临时唯一标识列,用来后续匹配原实体和插入后的ID:

private static DataTable CreateDataTable(IEnumerable<ExamResultDb> examResults)
{
    var table = new DataTable("ExamResult_Temp");
    table.Columns.AddRange(new[] {
        new DataColumn("TempId", typeof(Guid)), // 临时唯一ID,用于关联原对象
        new DataColumn("Caption", typeof(string)),
        new DataColumn("SortOrder", typeof(int))
    });

    foreach (var examResult in examResults)
    {
        var row = table.NewRow();
        row["TempId"] = Guid.NewGuid();
        row["Caption"] = examResult.Caption;
        row["SortOrder"] = examResult.SortOrder;
        // 把TempId存到原实体,方便后续关联
        examResult.TempId = (Guid)row["TempId"];
        table.Rows.Add(row);
    }
    return table;
}

步骤2:批量插入临时表,再写入正式表并获取ID

修改批量插入逻辑,完成临时表导入、正式表写入和ID回写:

private static void BulkCopyAndGetIds(IEnumerable<ExamResultDb> examResults, SqlConnection connection)
{
    // 1. 创建临时表
    using (var createTempTableCmd = new SqlCommand(@"
        CREATE TABLE #TempExamResult (
            TempId UNIQUEIDENTIFIER NOT NULL,
            Caption NVARCHAR(1024) NULL,
            SortOrder INT NOT NULL
        )", connection))
    {
        createTempTableCmd.ExecuteNonQuery();
    }

    // 2. 批量插入数据到临时表
    using (var bulkCopy = new SqlBulkCopy(connection, SqlBulkCopyOptions.Default, null))
    {
        var table = CreateDataTable(examResults);
        bulkCopy.DestinationTableName = "#TempExamResult";
        bulkCopy.EnableStreaming = true;
        foreach (var item in table.Columns.Cast<DataColumn>())
        {
            bulkCopy.ColumnMappings.Add(item.ColumnName, item.ColumnName);
        }
        using (var reader = new DataTableReader(table))
        {
            bulkCopy.WriteToServer(reader);
        }
    }

    // 3. 插入正式表并捕获生成的ID
    using (var insertCmd = new SqlCommand(@"
        INSERT INTO dbo.ExamResult (Caption, SortOrder)
        OUTPUT inserted.Id, source.TempId
        SELECT Caption, SortOrder FROM #TempExamResult source", connection))
    {
        using (var reader = insertCmd.ExecuteReader())
        {
            while (reader.Read())
            {
                var generatedId = reader.GetInt64(0);
                var tempId = reader.GetGuid(1);
                // 把ID赋值回对应的原实体
                var matchingResult = examResults.First(r => r.TempId == tempId);
                matchingResult.Id = generatedId;
            }
        }
    }

    // 临时表会在连接关闭后自动删除,这里手动清理也可以
    using (var dropTempTableCmd = new SqlCommand("DROP TABLE #TempExamResult", connection))
    {
        dropTempTableCmd.ExecuteNonQuery();
    }
}

补充:实体类新增TempId属性

记得给ExamResultDb添加临时标识属性:

public class ExamResultDb
{
    public long Id { get; set; }
    public string Caption { get; set; }
    public int SortOrder { get; set; }
    public Guid TempId { get; set; } // 新增临时关联字段
}

方案二:表值参数(TVP) + 存储过程

如果希望逻辑更集中在数据库端,或者场景更复杂,可以用表值参数结合存储过程实现。

步骤1:创建SQL Server用户定义表类型

CREATE TYPE dbo.ExamResultType AS TABLE (
    TempId UNIQUEIDENTIFIER NOT NULL,
    Caption NVARCHAR(1024) NULL,
    SortOrder INT NOT NULL
)
GO

步骤2:创建存储过程,插入数据并返回ID

CREATE PROCEDURE dbo.InsertExamResults
    @Results dbo.ExamResultType READONLY
AS
BEGIN
    SET NOCOUNT ON;

    INSERT INTO dbo.ExamResult (Caption, SortOrder)
    OUTPUT inserted.Id, source.TempId
    SELECT Caption, SortOrder FROM @Results source;
END
GO

步骤3:C#代码调用存储过程

private static void BulkInsertWithTVP(IEnumerable<ExamResultDb> examResults, SqlConnection connection)
{
    // 创建带TempId的DataTable(和方案一的CreateDataTable逻辑一致)
    var table = CreateDataTable(examResults);

    using (var cmd = new SqlCommand("dbo.InsertExamResults", connection))
    {
        cmd.CommandType = CommandType.StoredProcedure;

        // 添加表值参数
        var tvpParam = new SqlParameter("@Results", SqlDbType.Structured)
        {
            TypeName = "dbo.ExamResultType",
            Value = table
        };
        cmd.Parameters.Add(tvpParam);

        // 读取返回的ID并赋值回原实体
        using (var reader = cmd.ExecuteReader())
        {
            while (reader.Read())
            {
                var generatedId = reader.GetInt64(0);
                var tempId = reader.GetGuid(1);
                var matchingResult = examResults.First(r => r.TempId == tempId);
                matchingResult.Id = generatedId;
            }
        }
    }
}

为什么原代码无法获取ID?

SqlBulkCopy的核心设计是高效批量写入,它只负责把数据推送到数据库,没有内置机制将数据库生成的自增ID回传给客户端的DataTable。因此必须通过OUTPUT子句主动捕获这些ID,再关联回你的实体对象。


内容的提问来源于stack exchange,提问作者Vitaliy

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 19:57:28