Как да извлечете стойности на идентичност, генерирани от сървъра, когато използвате 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 Trigger на вашата таблица и да копирате стойностите за идентичност във временна таблица, към която вашият 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 с оригиналния набор от данни, отговорен за попълването на таблицата за етапи, но разбира се, НЕ трябва да разчитате на неговия ред. С други думи: следващото ви предизвикателство ще бъде съпоставянето на върнатите полета за самоличност с оригиналните записи във вашето приложение.

Обмисляйки нещата, бих казал, че във всички случаи -- с изключение на подхода ред по ред и 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 гарантира, че запазва оригиналния ред при зареждане и/или при изпращане на данните към RDBMS. Но дори когато го направи, вашето извличане трябва да гарантира, че извлича стойностите в реда на полетата за самоличност. Тъй като текущата ви заявка не налага никакъв ред, вие просто питате за набора от редове във всеки даден ред. В зависимост от оформлението на таблицата може (често) да получите данните обратно в реда на полето за самоличност, но няма гаранция! След като многонишковостта влезе в действие, ще бъдете ухапани!! - person deroby; 03.12.2015