Разработка в среде 1С:Предприятие 8 на базе управляемого приложения накладывает строгие правила на работу с контекстом формы и серверными вызовами. Одна из самых распространенных ошибок начинающих программистов — появление предупреждения «Данные были изменены или удалены другим пользователем» при попытке повторно записать документ или продолжить работу с формой после ее программного сохранения. В этой статье мы подробно разберем, почему возникает эта ошибка, как устроен механизм версионирования в 1С, и рассмотрим несколько надежных способов синхронизации данных между базой данных и открытой формой.
Для того чтобы понять природу проблемы, давайте проанализируем, как платформа 1С осуществляет контроль ссылочной целостности и предотвращает перезапись данных. В платформе реализован механизм так называемой оптимистической блокировки. Каждая таблица СУБД для объектных сущностей (документов, справочников) содержит скрытое системное поле ВерсияДанных. Этот реквизит представляет собой уникальную метку (хэш-код или метку времени), которая изменяется автоматически при каждой транзакции записи объекта в базу данных.
Когда пользователь открывает форму документа, клиентское приложение кэширует текущие данные объекта, включая его текущую ВерсиюДанных. Дальнейшие события развиваются по следующему сценарию:
ДанныеФормыВЗначение или напрямую получает объект из базы данных по ссылке, вносит изменения и вызывает метод Записать().Давайте выясним, как правильно обойти это поведение платформы и какие паттерны проектирования рекомендует использовать фирма 1С.
В большинстве случаев усложнение кода и написание собственных процедур записи на сервере избыточно. Если перед выводом документа на печать вам необходимо просто зафиксировать набранные пользователем данные (например, проставленные в табличной части галочки), самым логичным и безопасным решением будет вызов стандартного метода управляемой формы Записать() прямо на клиенте.
Давайте посмотрим на пример реализации такого подхода:
&НаКлиенте
Процедура Подключаемый_ВыполнитьКомандуПечати(Команда)
// Пытаемся записать форму стандартными средствами платформы
ПараметрыЗаписи = Новый Структура;
Если ЭтаФорма.Записать(ПараметрыЗаписи) Тогда
// Если запись прошла успешно, данные в БД и на форме синхронизированы
УправлениеПечатьюКлиент.ВыполнитьПодключаемуюКомандуПечати(Команда, ЭтаФорма, Объект);
Иначе
Сообщить("Не удалось записать документ. Проверьте заполнение реквизитов!");
КонецЕсли;
КонецПроцедуры
Преимущества данного метода:
Если специфика вашей бизнес-логики требует обязательного программного изменения реквизитов непосредственно в коде сервера перед записью, мы должны использовать специализированные методы взаимного преобразования данных формы и прикладных объектов.
Стандарты разработки 1С предписывают использовать связку методов управляемой формы РеквизитФормыВЗначение() и ЗначениеВРеквизитФормы(). Рассмотрим подробнее правильный алгоритм работы на сервере:
&НаСервере
Функция ЗаписатьДокументНаСервере()
Попытка
// 1. Преобразуем данные формы (реквизит "Объект") в полноценный прикладной объект ДокументОбъект
ОбъектДокумент = РеквизитФормыВЗначение("Объект");
// 2. Вносим необходимые изменения в прикладной объект
// Например, программно проставляем флаг печати в шапке или строках таблицы
ОбъектДокумент.ФлагДокументРаспечатан = Истина;
// 3. Записываем объект в базу данных. В этот момент в СУБД обновляется версия данных
ОбъектДокумент.Записать();
// 4. КРИТИЧЕСКИ ВАЖНЫЙ ШАГ: загружаем измененный объект обратно в реквизит формы.
// Этот метод не только обновит отображение данных на экране пользователя,
// но и передаст актуальную "ВерсиюДанных" из СУБД в контекст управляемой формы!
ЗначениеВРеквизитФормы(ОбъектДокумент, "Объект");
Возврат Истина;
Исключение
ЗаписьЖурналаРегистрации("Ошибка записи документа при печати",
УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Ложь;
КонецПопытки;
КонецФункции
Использование этого паттерна гарантирует, что после завершения серверного вызова клиентская форма будет обладать точно такой же версией данных, какая зафиксирована в базе данных. Пользователь сможет беспрепятственно продолжить редактирование документа без перезапуска формы.
Еще одним способом быстрого обновления данных в форме является вызов метода Прочитать() расширения управляемой формы. Этот метод принудительно обращается к базе данных, перечитывает текущее состояние объекта по ссылке и обновляет реквизиты формы.
На клиенте вызов выглядит следующим образом:
&НаКлиенте
Процедура Подключаемый_ВыполнитьКомандуПечати(Команда)
Если ЗаписатьДокументНаСервере() Тогда
УправлениеПечатьюКлиент.ВыполнитьПодключаемуюКомандуПечати(Команда, ЭтаФорма, Объект);
// Перечитываем данные формы напрямую из БД
ЭтаФорма.Прочитать();
Иначе
Сообщить("Ошибка при сохранении данных перед печатью!");
ЕслиКонец;
КонецПроцедуры
Важное предостережение: Метод Прочитать() работает «жестко». Если перед нажатием кнопки печати пользователь изменил какие-либо текстовые поля (например, написал комментарий к документу), но не сохранил документ, вызов метода Прочитать() безвозвратно сотрет все несохраненные изменения пользователя, заменив их версией из базы данных. Используйте данный метод только в тех случаях, когда вы абсолютно уверены, что на форме нет несохраненного ввода, либо если форма открыта исключительно в режиме просмотра.
Давайте проанализируем саму задачу автора темы. Ему требуется отмечать галочками определенные строки табличной части непосредственно в момент печати. Здесь возникает важный архитектурный вопрос: а нужно ли вообще сохранять эти галочки в базе данных на постоянной основе?
Если эти отметки носят сугубо оперативный характер (нужны только в момент формирования печатной формы и их история не важна для бизнеса), то хранить их в реквизитах табличной части документа в СУБД — грубая архитектурная ошибка. Избыточные транзакции записи приводят к износу оборудования, росту объема базы данных и блокировкам таблиц.
Правильное проектирование в таких случаях:
Булево непосредственно в самом Реквизите Формы (внутри дерева реквизитов формы), добавив ее в подчинение табличной части формы, но не добавляя в метаданные самого документа в конфигураторе.Мы рассмотрели ключевые аспекты работы с версионированием данных в управляемых формах 1С. При проектировании механизмов программного изменения документов всегда оценивайте необходимость сохранения данных в БД. Если сохранение неизбежно, отдавайте приоритет стандартному клиентскому методу ЭтаФорма.Записать(). Если логика требует серверных манипуляций, используйте безопасный контекстный паттерн РеквизитФормыВЗначение - ЗначениеВРеквизитФормы.