Entity Framework 6 и последовательности SQL Server

Я использую EF6 с первым проектом базы данных. У нас есть требование использовать последовательности, которые были добавлены в SQL Server 2012 (я считаю).

В таблице столбец идентификатора имеет значение по умолчанию, используя:

(NEXT VALUE FOR [ExhibitIdentity])

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

Моя проблема заключается в использовании этой функции в рамках Entity Framework, у меня есть googled, но я не могу найти много информации относительно того, поддерживает ли EF6 их. Я попытался установить StoreGeneratedPatttern в EFdesigner в Identity, но при сохранении это жалуется, что были затронуты нулевые строки, поскольку он использует scope_identity, чтобы увидеть, была ли вставка успешной, но поскольку мы используем последовательности, это возвращается как null.

Устанавливая его для вычисления, выдается сообщение об ошибке, указывающее, что я должен установить его идентификатор и не устанавливать его в none, чтобы он не вставлял 0 в качестве значения id и не выполнялся.

Нужно ли мне вызвать функцию/процедуру, чтобы получить следующую последовательность, а затем назначить ее значению id перед сохранением записи?

Любая помощь очень ценится.

Ответ 1

Ясно, что вы не можете убежать от этого catch-22, играя с DatabaseGeneratedOption s.

Лучшим вариантом, как вы предположили, является установка DatabaseGeneratedOption.None и получение следующего значения из последовательности (например, как в этот вопрос) перед сохранением новый рекорд. Затем назначьте его значению Id и сохраните. Это concurrency -безопасно, потому что вы будете единственным рисунком, которое имеет конкретное значение из последовательности (пусть никто не сбрасывает последовательность).

Однако есть возможный взлом...

Плохой, и я должен остановиться здесь...

EF 6 представил API-интерфейс перехватчика команд. Он позволяет вам манипулировать командами EF SQL и их результатами до и после выполнения команд. Конечно, мы не должны вмешиваться в эти команды, не так ли?

Ну... если мы посмотрим на команду insert, которая выполняется при установке DatabaseGeneratedOption.Identity, мы видим примерно следующее:

INSERT [dbo].[Person]([Name]) VALUES (@0)
SELECT [Id]
FROM [dbo].[Person]
WHERE @@ROWCOUNT > 0 AND [Id] = scope_identity()

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

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

SELECT current_value FROM sys.sequences WHERE name = 'PersonSequence'

Если бы мы могли заставить EF выполнить эту команду после вставки вместо scope_identity()!

Ну, мы можем.

Сначала мы должны создать класс, реализующий IDbCommandInterceptor, или наследуемый от реализации по умолчанию DbCommandInterceptor:

using System.Data.Entity.Infrastructure.Interception;

class SequenceReadCommandInterceptor : DbCommandInterceptor
{
    public override void ReaderExecuting(DbCommand command
           , DbCommandInterceptionContext<DbDataReader> interceptionContext)
    {
    }
}

Мы добавляем этот класс в контекст перехвата командой

DbInterception.Add(new SequenceReadCommandInterceptor());

Команда ReaderExecuting запускается непосредственно перед выполнением command. Если это команда INSERT с столбцом идентификации, ее текст выглядит как команда выше. Теперь мы можем заменить часть scope_identity() на запрос, получая текущее значение последовательности:

command.CommandText = command.CommandText
                             .Replace("scope_identity()",
                             "(SELECT current_value FROM sys.sequences
                               WHERE name = 'PersonSequence')");

Теперь команда будет выглядеть как

INSERT [dbo].[Person]([Name]) VALUES (@0)
SELECT [Id]
FROM [dbo].[Person]
WHERE @@ROWCOUNT > 0 AND [Id] = 
    (SELECT current_value FROM sys.sequences
     WHERE name = 'PersonSequence')

И если мы запустим это, самое смешное: оно работает. Сразу после команды SaveChanges новый объект получил свое постоянное значение Id.

Я действительно не думаю, что это готово к производству. Вам нужно будет изменить команду, когда она будет вставлять команду, выбрать правильную последовательность, основанную на вставленном объекте, все путем грязной манипуляции строк в довольно неясном месте. И я не знаю, получится ли с тяжелым concurrency правильное значение последовательности. Но кто знает, может быть, следующая версия EF поддержит это из коробки.