Entity Framework не обновляет значение, которое изменяется с помощью триггера

Моя таблица Sections (SQL Server) имеет ID в качестве первичного ключа (int, identity) и SortIndex column (int) для целей сортировки.

В базе данных есть триггер, который устанавливает SortIndex := ID в каждом INSERT. Очевидно, что я хочу изменить индекс сортировки позже, заменив значения для двух строк.

Я получаю доступ к данным с помощью Entity Framework, все с веб-приложением MVC3.

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

Я попытался изменить StoreGeneratedPattern для этого столбца в EDMX. Это кажется отличным и элегантным, но не решает проблему.

Если я установлен в Identity, это заставляет EF правильно обновлять значение, но оно становится только для чтения (исключение возникает при попытке изменить). Установка его в Computed аналогична, но вместо генерируемого исключения значения просто не записываются в БД.

Я могу воссоздать объект EF каждый раз, если мне нужно использовать его после вставки объекта, просто выполнив:

DatabaseEntities db = new DatabaseEntities()

Но это кажется уродливым обходным путем для меня.

Какое решение этой проблемы?

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

Ответ 1

Короче StoreGeneratedPattern означает: значение обрабатывается хранилищем, и ваше приложение никогда не будет изменять его. В таком случае вы получите автоматически генерируемое хранилище после вызова SaveChanges.

Если вы не используете StoreGeneratedPattern, вы не получите значение, и вам придется принудительно выполнить другое выполнение запроса для обновления вашего объекта. Например, вы можете:

objectContext.Refresh(RefreshMode.StoreWins, yourSection);

Обычно ситуации, когда вам нужно обновлять значения в обеих базе данных через триггеры и приложение, не очень хорошо играют с EF (и, возможно, также с другими инструментами ORM).

Ответ 2

Я нашел ответ от "Ladislav Mrnka" точным и отметил его как принятый. Вот и другие способы обхода, которые я нашел, пытаясь найти какое-то решение. Однако решение, которое я искал, вообще невозможно.

Одна из возможностей - установить StoreGeneratedPattern = Computed, чтобы знать EF, это значение вычисляется. И затем сделайте хранимую процедуру, чтобы фактически изменить значение SortIndex. Как правило, это изменение значений в две строки (своп их), чтобы изменить порядок сортировки. Эта процедура вместе с триггером при INSERT дает гарантию, что данные остаются согласованными в БД. Невозможно создать новую строку без правильного значения, установленного в SortIndex, невозможно, чтобы два объекта имели одно и то же значение (если хранимая процедура не имеет ошибки), и невозможно вручную каким-то образом сломать значение, поскольку это невозможно для редактирования через EF. Похоже на отличное решение.

Легко можно хранить хранимые процедуры, сопоставленные с функциями в EF.

Проблема заключается в том, что теперь можно ввести новую строку, а EF правильно обновляет данные в своем кеше, но кеш не обновляется после вызова хранимой процедуры. Тем не менее необходима ручная обновленная или обновленная функция. В противном случае следующий вызов для получения объектов, отсортированных по SortIndex, даст неправильные результаты.

Кроме этого, можно установить MergeOption = MergeOption.OverwriteChanges для нескольких объектов, что заставляет EF лучше обновлять данные из БД. После этого можно перечитать объект после его вставки или вызова хранимой процедуры, и он будет обновлен. Однако чтение коллекции объектов с помощью db.Section.OrderBy(o => o.SortIndex) будет по-прежнему возвращать кешированные результаты с неправильным порядком сортировки.

Если кому-то интересно, возможно сделать MergeOption по умолчанию чем-то другим, добавив частичный класс EF, а затем частичный метод OnContextCreated, как здесь:

public partial class DatabaseEntities
{
    partial void OnContextCreated()
    {
        Subsection.MergeOption = MergeOption.OverwriteChanges;
        Section.MergeOption = MergeOption.OverwriteChanges;
        Function.MergeOption = MergeOption.OverwriteChanges;
    }
}

Ответ 3

Знаете ли вы, что вы снова будете работать с этим столбцом в том же запросе?

Я бы использовал сценарий контекста для каждого запроса, который обычно выдает из многих проблем, потому что с каждым запросом создается новый EF-контекст, поэтому у вас есть свежие данные один раз за запрос.

С долговечным контекстом, как вы описали, могут возникнуть несоответствия.

В любом случае StoreGeneratedPattern, установленный для вычисления, должен быть прав. Но он обновляется только тогда, когда вы храните фактический объект. Он не обновляется, вставляя или обновляя любой другой объект.

из http://msdn.microsoft.com/en-us/library/dd296755(v=vs.90).aspx

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

Мы используем параметр вычисляемого значения для SQL GUID с секвенцией, и он работает нормально.