Контекст в представлениях

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

Загрузка строки сущности

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

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

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

Скрипт "Перед загрузкой по ID"

Если есть необходимость подменить загруженные данные после их загрузки но перед тем как вернуть их представлению, то можно воспользоваться скриптом "перед загрузкой по ID". Здесь можно скорректировать табличную переменную с данными. Например на скриншоте ниже поля "Дата с" и "Дата по" всегда подменяются на текущую дату. Можно даже заменить целую строку или добавить в нее запись, если например запись отсутствует в базе.

Сохранение сущности

Когда пользователь нажимает кнопку Сохранить, представление передает контексту отредактированные данные в виде xml и поручает ему сохранить все что изменилось обратно в базу и сделать все необходимые записи в логе. И все это естественно в одной транзакции.

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

set arithabort on
set xact_abort on
declare @transactionCount int
declare @docHandle int
begin try
    exec SY_Try

/* Подготовка временных таблиц */
...

/* Заполнение временных таблиц */
...

    exec sp_xml_preparedocument @docHandle output,  @xml, ''
    exec sp_xml_removedocument @docHandle
    set @docHandle = 0
    set @transactionCount = @@trancount
    if (@transactionCount = 0)
        begin transaction

/* Insert Update Delete */
...

    if (@transactionCount = 0)
        commit transaction
    exec SY_Catch 80
end try
begin catch
    if coalesce(@docHandle, 0) <> 0
        exec sp_xml_removedocument @docHandle
    declare
			@errorMessage nvarchar(4000),
			@errorSeverity int,
			@errorState int
    select 
        @errorMessage = error_message(),
        @errorSeverity = error_severity(),
        @errorState = error_state()
    if @errorState = 0
        set @errorState = 1
    if (@transactionCount = 0 and @@trancount > 0)
        rollback transaction
    raiserror (@errorMessage, @errorSeverity, @errorState)
end catch

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

А во-вторых тут есть несколько точек для "встраивания". Управляя контекстом мы можем вклиниться в этот скрипт, в разные его точки. До открытия транзакции, после, перед закрытием и т.д.

В скрипт сохранения подается xml с внесенными изменениями - добавление, редактирование и удаление. Автогенерируемый скрипт разбирает этот xml в три табличные переменные

  • @added...

  • @modified...

  • @deleted...

Добавленные строки вставляется в таблицу, измененные соответственно, обновляется, а удаленные удаляется. Все просто и как вы уже успели заметить - пакетно. Это значит, что если мы написали какую-то логику и встроили свой код в скрипт сохранения, то эта же логика может быть использована не только для редактирования одной сущности, но и для импорта/обновления из Excel, а также через Web API.

Для встраивания своего sql кода в скрипт сохранения необходимо добавить в контекст метаданные скрипта соответствующего типа.

Наиболее часто используемые

  • Перед открытием транзакции.

  • После открытия транзакции.

  • Перед закрытием транзакции.

  • Скрипт перед загрузкой по ID. Для заполнения виртуальных полей или какой-то модификации уже загруженных данных перед их отображением.

Здесь также есть скрипты "Выражения вставки/обновления/удаления" - эти скрипты полностью подменяют выражения insert/update/delete, которые генерируются автоматом. Это следует делать только в самых крайних случаях. Лучше встроиться в другое место и модифицировать таблицу @added..., @modified... перед их автоматической заливкой в базу, чем подменить этот автоматизм своим ручным скриптом и постоянно следить за его актуальностью при каждом добавлении/удалении столбца в таблицу.

Эти partial-скрипты, если так можно выразиться, имеют свои имена. Имена присваиваются автоматом, например при добавлении скрипта "Перед открытием транзакции" ему по умолчанию будет присвоено имя Begin. Но следует помнить, что механизм Наследование метаданных переопределяет элементы коллекций с одинаковыми именами. Это значит что если мы добавим скрипт в базовый контекст и оставим ему имя по умолчанию, затем добавим скрипт того же типа в дочерний контекст и тоже оставим ему такое же имя, то в рантайме скрипт базового контекста затрется (переопределится) скриптом с таким же именем в дочернем контексте (хотя возможно этого и нужно было достигнуть). В любом случае лучше придавать скриптам осмысленные названия, например:

  • BeginStatus - судя по префиксу Begin это скрипт перед открытием транзакции, а судя по Status - тут какая-то логика со статусом

  • CommitWatcher - судя по префиксу Commit это скрипт перед закрытием транзакции, Watcher - какие-то подписчики

Типичный пример скрипта "после открытия транзакции" выгляди так. Эта часть отработает до открытия транзакции.

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

Last updated