Можно ли вернуть идентификаторы PrimaryKey после SQL BulkCopy? - PullRequest
18 голосов
/ 31 мая 2010

Я использую 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, которые сделал массовый вкладыш, так как они мне нужны для таблицы 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.

Единственная проблема, с которой я могу столкнуться, заключается в том, что, если пользователь делает записи по одной за раз, а оператор this выполняется одновременно, он может попытаться дважды вставить строку в «Таблицу проверки продукта». 1017 *

Скажем, я получил, как один пользователь, использующий ручной способ, и другой пользователь, выполняющий массовый способ примерно в одно и то же время.

ручной способ. 1. Пользователь отправляет данные 2. Linq to sql Объект Product сделан и заполнен данными и передан. 3. этот объект теперь содержит ProductId 4. Еще один объект linq to sql создан для таблицы просмотра Продукта и вставлен (Идентификатор продукта из шага 3 передается вместе).

Массовый путь. 1. Пользователь получает данные от пользователя, который делится данными. 2. Захвачены все строки продукта от общего пользователя. 3. Произойдет массовая вставка SQL в строки Product. 4. Мой SP выбирает все строки, которые существуют только в таблице Product, и удовлетворяет некоторым другим условиям. 5. Массовая вставка происходит с этими рядами.

Так что же происходит, если шаг 3 (ручной путь) происходит одновременно с шагом 4 (массовый путь). Я думаю, что он попытается вставить одну и ту же строку дважды, что вызовет выполнение основного ограничения.

Ответы [ 7 ]

12 голосов
/ 31 мая 2010

В этом сценарии я бы использовал SqlBulkCopy для вставки в промежуточную таблицу (т. Е. Такую, которая выглядит как данные, которые я хочу импортировать, но не является частью основных транзакционных таблиц), и затем в DB к INSERT / SELECT, чтобы переместить данные в первую реальную таблицу.

Теперь у меня есть два варианта в зависимости от версии сервера; Я мог бы сделать вторую INSERT / SELECT для второй реальной таблицы, или я мог бы использовать предложение INSERT / OUTPUT для второй вставки, используя строки идентификаторов из таблицы.

Например:

     -- 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
6 голосов
/ 30 апреля 2012

Если ваше приложение позволяет, вы можете добавить еще один столбец, в котором вы храните идентификатор массовой вставки (например, guid). Вы бы установить этот идентификатор явно.

Затем после массовой вставки вы просто выбираете строки с этим идентификатором.

1 голос
/ 10 октября 2018

Мой подход аналогичен описанному в RiceRiceBaby, за исключением того, что нужно добавить одну важную вещь: вызов для извлечения Max (Id) должен быть частью транзакции вместе с вызовом SqlBulkCopy.WriteToServer. В противном случае, кто-то другой может вставить во время вашей транзакции, и это сделает ваш идентификатор неверным. Вот мой код:

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;
        }
    }
1 голос
/ 15 декабря 2012

У меня была та же проблема, когда мне приходилось возвращать идентификаторы строк, вставленных с SqlBulkCopy. Мой столбец идентификаторов был столбцом идентификаторов.

Решение:

Я вставил 500+ строк с массовым копированием, а затем выбрал их обратно с помощью следующего запроса:

SELECT TOP InsertedRowCount * 
FROM   MyTable 
ORDER BY ID DESC

Этот запрос возвращает только что вставленные строки с их идентификаторами. В моем случае у меня была еще одна уникальная колонка. Поэтому я выбрал этот столбец и идентификатор. Затем сопоставьте их с IDictionary следующим образом:

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

Надеюсь, это поможет.

0 голосов
/ 03 марта 2016

Отказ от ответственности : я являюсь владельцем проекта Массовые операции C #

Библиотека преодолевает ограничения SqlBulkCopy и добавляет гибкие функции, такие как выходное значение вставленной идентификации.

Позади кода, он точно такой же, как принятый ответ, но проще в использовании.

var bulk = new BulkOperation(connection);

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

bulk.BulkInsert(dt);
0 голосов
/ 26 февраля 2016

Я бы:

  1. Включить идентификационную вставку на стол

  2. Получить идентификатор последней строки таблицы

  3. Петля от (int i = Id; i < datable.rows.count+1; i++)

  4. В цикле присвойте свойству Id объекта данных значение i+1.

  5. Запустите массовую вставку SQL с включенной идентификационной информацией.

  6. Отключить идентификационную вставку обратно

Я думаю, что это самый безопасный способ получить свои идентификаторы на массовой вставке SQL, потому что это предотвратит несовпадение идентификаторов, которые могут быть вызваны приложением, в другом потоке.

0 голосов
/ 20 июня 2010

В зависимости от ваших потребностей и степени контроля над таблицами, вы можете рассмотреть возможность использования UNIQUEIDENTIFIER (Guids) вместо ваших первичных ключей IDENTITY. Это перемещает управление ключами за пределы базы данных и в ваше приложение. У этого подхода есть некоторые серьезные компромиссы, поэтому он может не соответствовать вашим потребностям. Но это может стоить задуматься. Если вы точно знаете, что вы будете загружать много данных в свои таблицы с помощью массовой вставки, часто очень удобно иметь управление этими ключами в вашей объектной модели, а не в том, что ваше приложение полагается на базу данных, чтобы вернуть данные.

Вы также можете использовать гибридный подход с промежуточными таблицами, как предлагалось ранее. Получите данные в эти таблицы, используя идентификаторы GUID для отношений, а затем с помощью операторов SQL вы можете получить целочисленные внешние ключи в порядке и перекачать данные в рабочие таблицы.

...