Можно ли вернуть идентификаторы PrimaryKey после SQL BulkCopy?

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

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

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

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

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


person chobo2    schedule 31.05.2010    source источник
comment
Что связывает столы справа?   -  person Ralf de Kleine    schedule 31.05.2010


Ответы (7)


В этом случае я бы использовал SqlBulkCopy для вставки в промежуточную таблицу (т.е. ту, которая выглядит как данные, которые я хочу импортировать, но не является частью основных транзакционных таблиц), а затем в БД в 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
person Marc Gravell    schedule 31.05.2010
comment
Хм. Я работал над чем-то, как вы думаете, это сработает (см. Мое редактирование). Если нет, я думаю, я попробую промежуточный стол. - person chobo2; 31.05.2010
comment
@chobo2 - ну, за исключением нескольких сценариев, я бы все равно использовал промежуточную таблицу - так что: а: я не влияю на реальную таблицу во время сетевого ввода-вывода, и б: чтобы получить полные журналы транзакций. - person Marc Gravell; 31.05.2010
comment
Хорошо, я только что получил свое редактирование. Проходя, я думаю, что мне, возможно, придется сделать постановочный стол. Пока не уверен. У меня есть несколько вопросов по пути, хотя. Эти фиктивные таблицы созданы в файле или используются только для примера? 2-й, как вы делаете SQlbulkCopy в хранимой процедуре. 3-й, как работает этот толчок. Вы просто вставляете всю таблицу или что-то в этом роде? 4-й, как насчет одновременных подключений, где может быть пара пользователей? Все это попадет в промежуточную таблицу, поэтому должен быть какой-то способ узнать, какие данные добавить, а затем удалить? - person chobo2; 01.06.2010
comment
@ chobo2 - я бы поставил там промежуточную таблицу как постоянную. Однако вам может потребоваться дополнительная работа, если вам нужно параллельное использование. Ре СП; Я бы использовал SqlBulkCopy затем вызвать SP, но вы можете использовать массовую вставку (не SqlBulkCopy) из TSQL. 3: не понимаю вопроса, но вы пишете код, необходимый для вашего сценария... 4: в этом случае я бы добавил столбец в промежуточную таблицу для идентификации различных запросов (все строки для запроса A будут иметь одинаковое значение). В качестве альтернативы, принудительно (отдельно), чтобы одновременно происходило только одно (но параллельно лучше). - person Marc Gravell; 01.06.2010
comment
Итак, как взять мои результаты из оператора select, а затем выполнить вставку с ними? Я не уверен, как сохранить их в sp, а затем выполнить массовую вставку. - person chobo2; 01.06.2010
comment
@chobo2 - в примере выполняется выбор/вставка; есть что-то более конкретное? - person Marc Gravell; 01.06.2010
comment
Ах, может быть, я просто запутался в этой строке. ВЫВОД INSERTED.id, INSERTED.data INTO [Table2](id1,data) - person chobo2; 01.06.2010
comment
К вашему сведению: пример кода для использования промежуточной таблицы вместе с массовой вставкой из С# здесь: stackoverflow.com/a/41289532/361842 - person JohnLBevan; 22.12.2016

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

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

person Cosmin    schedule 30.04.2012

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

Решение:

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

SELECT TOP InsertedRowCount * 
FROM   MyTable 
ORDER BY ID DESC

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

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

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

person Vitaly Galinsky    schedule 14.12.2012
comment
Это хорошее решение, но ТОЛЬКО если вы можете гарантировать, что никакие записи не будут вставлены из другого потока после того, как вы вставите, но до того, как вы выберете элементы. - person nuzzolilo; 17.06.2013
comment
помимо действительной точки зрения, данной @nuzzolilo, этот ответ заслуживает большего количества голосов. - person Dirk Boer; 19.07.2019

Мой подход аналогичен тому, что описал 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;
        }
    }
person BeccaGirl    schedule 10.10.2018
comment
Я рассмотрел подход, подобный вашему, но я не думаю, что это решает проблему коллизий идентификаторов. Использование Transaction не мешает кому-либо еще использовать таблицу, вставлять строки и использовать идентификаторы до того, как вы завершите вставку. Любая единственная вставка до завершения, и это взрывается. Кроме того, sbc необходимо утилизировать. - person DonBoitnott; 19.06.2019
comment
Что мне не хватает? Согласно документам Microsoft, если используется System.Data.IsolationLevel.Serializable, то в DataSet устанавливается блокировка диапазона, не позволяющая другим пользователям обновлять или вставлять строки в набор данных до завершения транзакции. Я также тестировал, пытаясь вставить из SSMS, пока выполнялась моя массовая вставка, и моя вставка всегда происходила после завершения всей массовой вставки. - person BeccaGirl; 20.06.2019
comment
По-видимому, мое понимание сериализуемого было неполным. Теперь, когда я изучил его более внимательно, я понимаю, что вы имеете в виду. Похоже, он должен блокировать последующие записи до тех пор, пока не будет выполнена пакетная вставка. Моя ошибка. - person DonBoitnott; 20.06.2019
comment
Без проблем! Это было довольно прочно, я использовал его в производстве некоторое время без проблем. - person BeccaGirl; 20.06.2019

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

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

person mattmc3    schedule 19.06.2010

Я буду:

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

  2. Возьмите идентификатор последней строки таблицы

  3. Цикл из (int i = Id; i < datable.rows.count+1; i++)

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

  5. Запустите массовую вставку SQL с включенным сохранением удостоверения.

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

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

person RiceRiceBaby    schedule 26.02.2016

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

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

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

var bulk = new BulkOperation(connection);

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

bulk.BulkInsert(dt);
person Jonathan Magnan    schedule 02.03.2016
comment
Я недоумеваю, как люди могут продавать свои продукты на этом форуме. :-) Связанные со SklBulkCopy функции просто обернуты за такую ​​​​большую цену ... Невероятно. - person Usman; 14.09.2016
comment
Привет. Не могли бы вы рассказать, как вернуть вставленный идентификатор строки, с датой и временем, пожалуйста, я не могу получить строки - идентификаторы для тех, которые были успешно вставлены, когда я попробовал ваш код. - person Transformer; 29.05.2019
comment
Вот скрипт, который показывает, как вернуть значение: dotnetfiddle.net/g5pSS1, чтобы вернуть больше столбца, вы просто нужно сопоставить столбец с направлением Output - person Jonathan Magnan; 29.05.2019