Как получить сгенерированные сервером значения идентификаторов при использовании SqlBulkCopy

Я знаю, что могу выполнить массовую вставку в свою таблицу с помощью столбца идентификаторов, не указав SqlBulkCopyOptions.KeepIdentity, как указано здесь.

Что я хотел бы сделать, так это получить значения идентичности, которые генерирует сервер, и поместить их в свою таблицу данных или даже в список. Я видел это сообщение , но я хочу, чтобы мой код был общим, и у меня не может быть столбца версии во всех моих таблицах. Любые предложения очень ценятся. Вот мой код:

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);
    }
}

person dseiple    schedule 10.02.2014    source источник
comment
Я сделал это, добавив еще один столбец в целевую таблицу с именем OriginalKey, а затем после того, как SqlBulkCopy завершил выбор OriginalKey, NewIdentityId и удалил столбец. Я считаю, что вы также можете SqlBulkCopy для временной таблицы и INSERT с предложением OUTPUT, но теперь вы делаете два шага вместо одного. Или пропустите SqlBulkCopy и используйте TVP в инструкции INSERT ... OUTPUT.   -  person ta.speot.is    schedule 11.02.2014
comment
Рассматривали ли вы создание триггера INSERT в своей таблице и копирование значений идентификаторов во временную таблицу, которую ваш код C # может запрашивать?   -  person Rick S    schedule 11.02.2014
comment
@ ta.speot.is Если у меня большая таблица (несколько миллионов записей), как добавление и удаление столбца повлияет на производительность? Будет ли это работать с одновременной вставкой процессов в одну и ту же таблицу?   -  person dseiple    schedule 11.02.2014
comment
Блокировка схемы для таблицы, которая будет в основном сериализовать доступ к таблице для всех вставляемых процессов.   -  person ta.speot.is    schedule 11.02.2014


Ответы (2)


AFAIK, вы не можете.

Единственный способ (о котором я знаю) получить значения в поле идентификатора - это использовать либо SCOPE_IDENTITY(), когда вы вставляете строку за строкой; или используя подход OUTPUT при вставке всего набора.

«Самый простой» подход, вероятно, будет заключаться в том, что вы должны SqlBulkCopy записи в таблице, а затем снова получить их позже. Проблема может заключаться в том, что может быть трудно (и быстро) снова получить эти строки с сервера. (например, было бы довольно некрасиво (и медленно) иметь предложение WHERE с IN (guid1, guid2, .., guid999998, guid999999) =)

Я предполагаю, что производительность здесь является проблемой, поскольку вы уже используете SqlBulkCopy, поэтому я бы предложил использовать подход OUTPUT, и в этом случае вам сначала понадобится промежуточная таблица для SqlBulkCopy ваших записей. Затем указанная таблица должна включать какой-то идентификатор партии (GUID?), позволяющий нескольким ступеням работать бок о бок. Вам понадобится хранимая процедура, чтобы INSERT <table> OUTPUT inserted.* SELECT данные из промежуточной таблицы в действительную целевую таблицу, а также снова очистить промежуточную таблицу. Восстановленный набор записей из указанной процедуры затем будет соответствовать 1: 1 исходному набору данных, отвечающему за заполнение промежуточной таблицы, но, конечно, вы НЕ должны полагаться на его порядок. Другими словами: ваша следующая задача - сопоставить возвращенные поля Identity обратно с исходными записями в вашем приложении.

Поразмыслив, я бы сказал, что во всех случаях, за исключением подхода «строка за строкой» и SCOPY_IDENTITY (), который будет медленным, вам понадобится (или добавить) ключ 'к вашим данным, чтобы связать сгенерированный идентификатор обратно с исходными данными = /

person deroby    schedule 10.02.2014

Вы можете использовать аналогичный подход, описанный выше, с помощью deroby, но вместо того, чтобы получать их обратно с помощью WHERE IN (guid1, etc..., вы сопоставляете их с строками, вставленными в память, в зависимости от их порядка.

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

  • Создайте новый Guid и установите это значение для всех строк в сопоставлении массового копирования с новым столбцом.

  • Запустите WritToServer метод объекта BulkCopy

  • Получить все строки с таким же ключом

  • Просмотрите этот список, который будет в том порядке, в котором они были добавлены, они будут в том же порядке, что и коллекция строк в памяти, чтобы вы затем знали сгенерированный идентификатор для каждого элемента.

Это даст вам лучшую производительность, чем присвоение каждой отдельной строке уникального ключа. Итак, после того, как вы массово вставите таблицу данных, вы можете сделать что-то вроде этого (в моем примере у меня будет список объектов, из которых я буду создавать таблицу данных, а затем отображать сгенерированные идентификаторы обратно в них)

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;
 }
person user2945722    schedule 10.11.2015
comment
Хотя это, вероятно, работает в 99% случаев, мне интересно, гарантирует ли SqlBulkCopy исходный порядок при загрузке и / или при отправке данных в СУБД. Но даже в этом случае ваша выборка должна убедиться, что она извлекает значения в порядке полей идентификаторов. Поскольку ваш текущий запрос не требует какого-либо порядка, вы просто запрашиваете набор строк в любом заданном порядке. В зависимости от макета таблицы вы можете (часто) вернуть данные обратно в порядке поля идентификатора, но нет никакой гарантии! Как только многопоточность заработает, вы будете укушены! - person deroby; 03.12.2015