Автоинкрементный ключ Entity Framework

1

Я столкнулся с проблемой дублирования инкрементного поля в сценарии параллелизма.

Я использую EF как инструмент ORM, пытаясь вставить объект с полем, которое действует как инкрементное поле INT. В основном это поле называется "SequenceNumber", где каждая новая запись перед вставкой будет читать базу данных с использованием MAX, чтобы получить последний SequenceNumber, добавить +1 к ней и сохранить изменения.

Между получением последнего SequenceNumber и сохранением, где происходит параллелизм.

Я не использую ID для SequenceNumber поскольку он не является уникальным ограничением и может перезагружаться при определенных условиях, таких как ежемесячно, ежегодно и т.д.

InvoiceNumber       | SequenceNumber | DateCreated
INV00001_08_14      | 1              | 25/08/2014
INV00001_08_14      | 1              | 25/08/2014 <= (concurrency is creating two SeqNo 1)
INV00002_08_14      | 2              | 25/08/2014
INV00003_08_14      | 3              | 26/08/2014
INV00004_08_14      | 4              | 27/08/2014
INV00005_08_14      | 5              | 29/08/2014
INV00001_09_14      | 1              | 01/09/2014 <= (sequence number reset)

Номер счета отформатирован на основе SequenceNumber.

После некоторого исследования я закончил с этими возможными решениями, но хочу знать наилучшую практику

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

  2. Создавайте хранимую процедуру исключительно для этой цели, выбираете и вставляете в один оператор, поскольку такой параллелизм минимален (предпочтет подход на основе EF, если это возможно)

==============================

РЕДАКТИРОВАТЬ

Другое решение, о котором я думал, состоит в том, чтобы сделать InvoiceNumber уникальным ограничением, так что в случае повторяющихся записей он будет бросать ошибки Unique Constraint Violation, ловить его и снова пытаться получить новый SequenceNumber и InvoiceNumber для повторного ввода в БД.

  • 0
    Это должно быть ключ таблицы или что-то? О блокировке таблицы до завершения транзакции. Да, вы можете сделать это, просто установив уровень изоляции на Serializable. Но это может снизить производительность вашего приложения (и, вероятно, это не очень хорошая идея).
  • 0
    Вы можете вызвать SP, используя EF ... (о 2-й точке).
Показать ещё 4 комментария
Теги:
entity-framework
concurrency

3 ответа

1
Лучший ответ

Для всех, кто может столкнуться с тем же вызовом, что и у меня, то, что я завершил, было сделать ограничение InvoiceNumber и обработать исключительное исключение UniqueConstraint и повторить попытку получить InvoiceNumber.

Чтобы избежать бесконечных циклов (если это когда-либо случается), добавлен счетчик "повторных попыток".

        int retry = 5;
        try
        {
            // Get new purchase order number from service

            Invoice.InvoiceNumber = GetNewPurchaseOrderNumber();

            // Explicit set state as Added
            Invoice.ObjectState = ObjectState.Added;

            _uow.Repository<Invoice>().InsertGraph(Invoice);
            _uow.Save();
        }
        catch (DbUpdateException ex)
        {
            retry--;
            if (null == ex.InnerException) throw;

            var innerException = ex.InnerException.InnerException
                                   as System.Data.SqlClient.SqlException;
            if (innerException != null &&
                   (
                       innerException.Number == 2627 ||
                       innerException.Number == 2601)
                       && retry > 0
                   )
            {
                // Get new Invoice Number from service
                Invoice.InvoiceNumber = GetNewPurchaseOrderNumber();

                _uow.Save();
            }
            else
            {
                throw;
            }
        }
4

используйте поддельную таблицу/объект с ключом идентификации.

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

Для сброса: обрезаем поддельную таблицу.

Предсказуемая проблема: у вас может быть отверстие в последовательности, в зависимости от ошибок вставки.

знать CREATE SEQUENCE с SQL Server 2012. BTW, если вы считаете, что идея хорошая, также перейдите на datavoice

=====

последовательность sql может использоваться как значение по умолчанию через ограничение:

ALTER TABLE Invoice
ADD CONSTRAINT DefSequence DEFAULT (NEXT VALUE FOR InvoiceSeq) 
    FOR SequenceNumber;
GO

установив SequenceNumber как DatabaseGeneratedOption.Computed это может быть идеально.

  • 0
    Голосование за ссылку на последовательность
  • 0
    @ tschmit007 спасибо за предложения. Я полагаю, что наличие поддельной таблицы / сущности является чрезмерным перебором в моем сценарии, поскольку я не указал, что в этой таблице более 30 полей, а также 5 других таких таблиц, которые должны обрабатывать SequenceNumber.
Показать ещё 1 комментарий
1

Оптимистичный параллелизм означает, что блокировки не установлены. Изменения в БД проверяются в конце операции. Если nothign еще не изменил что-то, что повлияло на операцию, значит, все пошло нормально. Если кто-то еще изменил что-то, что повлияло на операцию, оно выкинет исключение. (Имя исходит из того факта, что вы оптимистичны в надежде, что ничто иное не изменит данные, пока вы их угадываете).

Хранимая процедура является хорошим решением. Почему бы не использовать его? - Помните, что вы должны заключить код процедуры внутри транзакции. Если нет, вы не защищены от проблем с параллелизмом.

Вы можете изменять данные внутри транзакции непосредственно из EF.

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

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

РЕДАКТИРОВАНИЕ Проблемы с производительностью создаются долгосрочными транзакциями, то есть транзакциями, которые занимают много времени для выполнения, например, потому что они читают или записывают много данных (например, обновляя поле, содержащее aggreate дочерней таблицы со многими связанными строками, или поиск данных в огромной таблице с миллионами строк без индекса availabe). Если вы сохраняете последовательность в одной таблице с одной строкой, чтение ее происходит быстро и, надеюсь, записывая строку в другую таблицу, также очень и очень быстро, особенно если кластеризованный индекс (по умолчанию первичный ключ кластерный индекс) позволяет записать строку в конце таблицы, как и в переполненном столе. Для обеспечения того, чтобы данные были обрезаны в конце таблицы, каждая новая строка должна быть последней в кластерном индексе.

  • 0
    Извиняюсь, я имел в виду пессимистичный параллелизм, спасибо за исправление

Ещё вопросы

Сообщество Overcoder
Наверх
Меню