26

我正在使用 C# 并使用 SqlBulkCopy。我有一个问题。我需要在一个表中进行批量插入,然后将另一个批量插入到另一个表中。

这2个有PK/FK关系。

Table A
Field1 -PK auto incrementing (easy to do SqlBulkCopy as straight forward)

Table B
Field1 -PK/FK - This field makes the relationship and is also the PK of this table. It is not auto incrementing and needs to have the same id as to the row in Table A.

所以这些表具有一对一的关系,但我不确定如何取回批量插入所做的所有 PK Id,因为我需要它们用于表 B。

编辑

我可以做这样的事情吗?

SELECT * 
FROM Product
WHERE NOT EXISTS (SELECT * FROM ProductReview WHERE Product.ProductId = ProductReview.ProductId AND Product.Qty = NULL AND Product.ProductName != 'Ipad')

这应该找到刚刚插入 sql 批量复制的所有行。我不确定如何从中获取结果,然后从 SP 对它们进行批量插入。

我能看到的唯一问题是,如果用户一次只做一个记录,并且这条语句同时运行,它可能会尝试在“产品评论表”中插入一行两次。

所以说我喜欢一个用户使用手动方式,而另一个用户大约同时使用大众方式。

手动方式。 1. 用户提交数据 2. Linq to sql 制作产品对象并用数据填充并提交。3. 此对象现在包含 ProductId 4. 为 Product review 表创建另一个 linq to sql 对象并插入(同时发送步骤 3 中的 Product Id)。

大众方式。 1. 用户从共享数据的用户那里获取数据。2. 抓取分享用户的所有产品行。3. 发生在产品行上的 SQL 批量复制插入。4. 我的 SP 选择了仅存在于 Product 表中并满足其他一些条件的所有行 5. 对这些行进行批量插入。

那么如果步骤 3(手动方式)与步骤 4(大众方式)同时发生会发生什么。我认为它会尝试两次插入同一行,从而导致主要约束执行。

4

7 回答 7

18

在那种情况下,我会使用SqlBulkCopy插入到临时表(即看起来像我要导入的数据,但不是主要事务表的一部分),然后在数据库中将数据移动到INSERT/SELECT进入第一张真正的桌子。

现在我有两种选择,具体取决于服务器版本;我可以对第二个真实表执行第二个INSERT/ ,或者我可以使用/子句执行第二个 insert ,使用表中的标识行。SELECTINSERTOUTPUT

例如:

     -- dummy schema
     CREATE TABLE TMP (data varchar(max))
     CREATE TABLE [Table1] (id int not null identity(1,1), data varchar(max))
     CREATE TABLE [Table2] (id int not null identity(1,1), id1 int not null, data varchar(max))

     -- imagine this is the SqlBulkCopy
     INSERT TMP VALUES('abc')
     INSERT TMP VALUES('def')
     INSERT TMP VALUES('ghi')

     -- now push into the real tables
     INSERT [Table1]
     OUTPUT INSERTED.id, INSERTED.data INTO [Table2](id1,data)
     SELECT data FROM TMP
于 2010-05-31T19:34:46.820 回答
10

如果您的应用程序允许,您可以添加另一列来存储批量插入的标识符(例如 guid)。您将明确设置此 ID。

然后在批量插入之后,您只需选择具有该标识符的行。

于 2012-04-30T12:30:52.160 回答
6

我遇到了同样的问题,我必须取回使用 SqlBulkCopy 插入的行的 ID。我的 ID 列是一个身份列。

解决方案:

我已经插入了 500 多行的大容量复制,然后使用以下查询将它们选择回来:

SELECT TOP InsertedRowCount * 
FROM   MyTable 
ORDER BY ID DESC

此查询返回我刚刚插入的行及其 ID。就我而言,我有另一个独特的专栏。所以我选择了该列和 ID。然后用 IDictionary 映射它们,如下所示:

 IDictionary<string, int> mymap = new Dictionary<string, int>()
 mymap[Name] = ID

希望这可以帮助。

于 2012-12-14T21:26:56.983 回答
3

我的方法与 RiceRiceBaby 所描述的类似,除了要添加的重要一点是,检索 Max(Id) 的调用需要成为事务的一部分,以及对 SqlBulkCopy.WriteToServer 的调用。否则,其他人可能会在您的交易过程中插入,这会使您的 ID 不正确。这是我的代码:

public static void BulkInsert<T>(List<ColumnInfo> columnInfo, List<T> data, string 
destinationTableName, SqlConnection conn = null, string idColumn = "Id")
    {
        NLogger logger = new NLogger();

        var closeConn = false;


        if (conn == null)
        {
            closeConn = true;
            conn = new SqlConnection(_connectionString);
            conn.Open();
        }

        SqlTransaction tran = 
    conn.BeginTransaction(System.Data.IsolationLevel.Serializable);

        try
        {
            var options = SqlBulkCopyOptions.KeepIdentity;
            var sbc = new SqlBulkCopy(conn, options, tran);

            var command = new SqlCommand(
                    $"SELECT Max({idColumn}) from {destinationTableName};", conn, 
           tran);
            var id = command.ExecuteScalar();

            int maxId = 0;

            if (id != null && id != DBNull.Value)
            {
                maxId = Convert.ToInt32(id);
            }

            data.ForEach(d =>
                {
                    maxId++;
                    d.GetType().GetProperty(idColumn).SetValue(d, maxId);
                });

            var dt = ConvertToDataTable(columnInfo, data);

            sbc.DestinationTableName = destinationTableName;

            foreach (System.Data.DataColumn dc in dt.Columns)
            {
                sbc.ColumnMappings.Add(dc.ColumnName, dc.ColumnName);
            }

            sbc.WriteToServer(dt);

            tran.Commit();

            if(closeConn)
            {
                conn.Close();
                conn = null;
            }
        }
        catch (Exception ex)
        {
            tran.Rollback();
            logger.Write(LogLevel.Error, $@"An error occurred while performing a bulk 
insert into table {destinationTableName}. The entire
                                           transaction has been rolled back. 

{ex.ToString()}");
            throw ex;
        }
    }
于 2018-10-10T17:37:49.117 回答
0

根据您的需求以及您对表的控制程度,您可能需要考虑使用 UNIQUEIDENTIFIER (Guids) 而不是 IDENTITY 主键。这会将密钥管理移到数据库之外并进入您的应用程序。这种方法有一些严重的权衡,因此它可能无法满足您的需求。但这可能值得考虑。如果您确定您将通过批量插入将大量数据注入您的表中,那么在您的对象模型中管理这些键而不是您的应用程序依赖数据库来回馈您通常非常方便数据。

您还可以像之前建议的那样对临时表采取混合方法。使用关系的 GUID 将数据放入这些表中,然后通过 SQL 语句,您可以按顺序获取整数外键并将数据泵入您的生产表中。

于 2010-06-19T22:07:57.243 回答
0

我会:

  1. 在桌子上打开身份插入

  2. 抓取表格最后一行的id

  3. 循环从(int i = Id; i < datable.rows.count+1; i++)

  4. 在循环中,将数据表的 Id 属性分配给i+1.

  5. 在保持标识打开的情况下运行 SQL 批量插入。

  6. 关闭身份插入

我认为这是在 SQL 批量插入中获取 id 的最安全方法,因为它可以防止可能由应用程序在另一个线程上执行导致的不匹配 id。

于 2016-02-26T18:22:46.763 回答
-1

免责声明:我是C# Bulk Operations项目的所有者

该库克服了 SqlBulkCopy 的限制并添加了灵活的功能,例如输出插入的标识值。

在代码后面,它与公认的答案完全一样,但更易于使用。

var bulk = new BulkOperation(connection);

// Output Identity
bulk.ColumnMappings.Add("ProductID", ColumnMappingDirectionType.Output);
// ... Column Mappings...

bulk.BulkInsert(dt);
于 2016-03-02T22:24:38.537 回答