Каждый разработчик на платформе «1С:Предприятие 8» рано или поздно сталкивается с ситуациями, которые на первый взгляд кажутся необъяснимой мистикой или багом самой платформы. Один из таких классических «полтергейстов» — когда код по изменению реквизитов или строк табличной части объекта успешно отрабатывает в конфигураторе, отладчик показывает измененные значения вплоть до закрытия формы, но после повторного открытия объекта или при выполнении запроса к базе данных выясняется, что новые данные не сохранились.
В этой статье мы подробно проанализируем эту проблему на примере реального кейса из конфигурации «1С:Документооборот» (справочник «Проекты», табличная часть «Дополнительные реквизиты»), выясним физические причины такого поведения платформы и разберем правильные шаблоны разработки, которые навсегда избавят вас от подобных ошибок.
Рассмотрим стандартный сценарий, с которым столкнулся автор темы. В модуле объекта (например, справочника или документа) в предопределенной процедуре ПриЗаписи разработчик пишет код, который программно заполняет или корректирует строки табличной части этого же объекта. Алгоритм выглядит абсолютно рабочим:
ПриЗаписи в модуле объекта.ПриЗаписи и даже далее — в модуле формы в процедуре ПриЗакрытии. Отладчик послушно показывает, что в переменной ЭтотОбъект (или в реквизите формы Объект) все данные заполнены корректно.Почему так происходит? Для ответа на этот вопрос нам необходимо детально изучить архитектуру СУБД под управлением платформы «1С:Предприятие» и жизненный цикл транзакции записи.
Главный секрет этого «полтергейста» кроется в том, как именно платформа взаимодействует с базой данных (MS SQL, PostgreSQL и др.) в процессе выполнения транзакции записи. Давайте разберем этот процесс по шагам.
Когда для объекта вызывается метод Записать(), платформа запускает строго регламентированную последовательность событий:
Отказ = Истина, транзакция полностью откатится, вернув базу данных к исходному состоянию.ПередЗаписью (в модуле объекта). На этом этапе данные объекта находятся исключительно в оперативной памяти сервера приложений 1С. Физического обращения к таблицам БД с командами записи еще не происходило. Любые изменения реквизитов, модификация табличных частей или добавление новых строк, сделанные в этом обработчике, будут успешно учтены платформой при формировании SQL-запроса.INSERT или UPDATE) и отправляет их на SQL-сервер. С этого момента данные физически записаны во временные таблицы СУБД в рамках открытой транзакции. Важно: для самой платформы процесс сериализации и сохранения состояния данного конкретного объекта на этом завершен.ПриЗаписи (в модуле объекта). Этот обработчик вызывается уже после того, как SQL-запрос на запись объекта был отправлен и выполнен СУБД. Транзакция все еще открыта, но сам объект ЭтотОбъект в базе данных уже сохранен.ПриЗаписи. Выполняется код внешних модулей, подписанных на это событие.Теперь посмотрим, что происходит, когда мы пытаемся изменить табличную часть в процедуре ПриЗаписи. Изменения вносятся в переменную ЭтотОбъект, которая находится в оперативной памяти сервера приложений. Эти изменения действительно живут в памяти до тех пор, пока объект не будет уничтожен (например, при закрытии формы), поэтому отладчик показывает их наличие.
Однако платформа 1С не будет повторно отправлять SQL-запросы для записи этого объекта, так как этап физической записи (Шаг 3) уже пройден! Платформа считает, что объект уже успешно сохранен. Повторная автоматическая отправка данных привела бы к бесконечным рекурсивным циклам записи и колоссальным просадкам производительности СУБД. В результате транзакция фиксируется с теми данными, которые были отправлены на Шаге 3, а ваши изменения в памяти просто бесследно уничтожаются при очистке кэша сервера.
Именно поэтому стандарты разработки 1С (требования 1С:ИТС) категорически запрещают изменять реквизиты и табличные части самого записываемого объекта в обработчике ПриЗаписи.
У автора темы возник вполне резонный вопрос: «Почему наименование и другие табличные части перезаписались нормально, а одна конкретная табличная часть — нет?». Существует три логичных объяснения этой иллюзии:
1. Модификация данных на уровне формы. Если наименование или другие реквизиты заполнялись не в модуле объекта, а в модуле управляемой формы (например, в обработчиках ПередЗаписьюНаСервере или ПриЗаписиНаСервере), то эти изменения передавались в объект *до* того, как платформа инициировала транзакцию записи объекта в БД. Такие изменения всегда сохраняются корректно.
2. Каскадная или повторная запись объекта. Иногда в конфигурациях (особенно нетиповых или сильно кастомизированных) в подписках на события или в сопутствующих бизнес-процессах программно вызывается повторный метод Объект.Записать(). Если в первой транзакции в процедуре ПриЗаписи вы изменили реквизит в памяти, а затем какая-то подсистема принудительно вызвала запись этого же объекта еще раз в новой транзакции, то измененные данные запишутся. Но это является грубейшей архитектурной ошибкой, приводящей к избыточной нагрузке на СУБД и взаимоблокировкам (Deadlocks).
3. Путаница между табличными частями и регистрами сведений. Разработчики часто путают изменение табличной части объекта (которая физически хранится в таблице самого справочника/документа) и запись связанных данных в независимые или подчиненные регистры сведений. Запись в регистры сведений (например, запись движений документа или дополнительных сведений) в процедуре ПриЗаписи работает отлично и является стандартной практикой, так как это запись *других* объектов БД.
Ситуация усугубляется, когда речь идет о типовых конфигурациях на базе БСП (Библиотеки стандартных подсистем), таких как «1С:Документооборот», «1С:ERP» или «1С:Управление торговлей». В рассматриваемом кейсе фигурирует табличная часть ДополнительныеРеквизиты справочника «Проекты».
Подсистема «Свойства» из состава БСП накладывает жесткие правила на работу с этой табличной частью:
ПередЗаписью и ПриЗаписи).ДополнительныеРеквизиты.Для корректной работы с дополнительными реквизитами на платформе БСП следует использовать штатные методы общего модуля УправлениеСвойствами:
// Пример правильного программного изменения дополнительных реквизитов объекта через API БСП
ЗначенияСвойств = Новый Структура;
ЗначенияСвойств.Вставить(СвойствоСсылка, НовоеЗначение);
УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(СсылкаНаОбъект, ЗначенияСвойств);
Рассмотрим подробнее, как нужно изменить код, чтобы данные вашей табличной части гарантированно сохранялись в базе данных.
Единственным верным и архитектурно правильным решением для модификации собственных реквизитов и табличных частей объекта является использование обработчика ПередЗаписью модуля объекта. Перенесем наш алгоритм туда.
Посмотрим на пример правильной реализации заполнения реквизитов табличной части:
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
// Обязательно проверяем, не выполняется ли запись в режиме обмена данными
Если ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Разберем по шагам заполнение нашей табличной части
Для Каждого СтрокаТЧ Из ЭтотОбъект.ДополнительныеРеквизиты Цикл
// Проанализируем условие, при котором нужно изменить значение
Если СтрокаТЧ.Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("ПроектныйДепартамент") Тогда
// Устанавливаем новое значение реквизита строки ТЧ
СтрокаТЧ.Значение = Справочники.СтруктураПредприятия.НайтиПоНаименованию("Департамент Разработки");
КонецЕсли;
КонецЦикла;
КонецПроцедуры
В данном случае, так как код выполняется в ПередЗаписью, измененные данные строки табличной части попадут в итоговый SQL-запрос обновления объекта и успешно запишутся в СУБД.
Если вам требуется реализовать заполнение табличной части не в самом модуле объекта (чтобы не снимать типовую конфигурацию с поддержки), используйте общие модули и механизм подписок на события. Создайте подписку на событие ПередЗаписью для нужного типа объекта:
// Процедура-обработчик подписки на событие "ПередЗаписью"
Процедура ИмяПодпискиПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
// Источник — это и есть наш записываемый объект (ЭтотОбъект)
Для Каждого СтрокаТЧ Из Источник.ДополнительныеРеквизиты Цикл
// Реализация логики заполнения...
КонецЦикла;
КонецПроцедуры
Чтобы окончательно закрепить материал, систематизируем правила использования ключевых обработчиков событий модуля объекта в виде структурированной таблицы рекомендаций:
| Обработчик события | Что МОЖНО делать | Что НЕЛЬЗЯ делать |
|---|---|---|
| ПередЗаписью (BeforeWrite) |
|
|
| ПриЗаписи (OnWrite) |
|
|
Описанный на форуме «полтергейст» с незаписывающимися строками табличной части — это классическое проявление нарушения стандартов разработки платформы 1С. Понимание разницы между состояниями объекта в оперативной памяти и физическими записями в таблицах СУБД позволяет легко избегать подобных архитектурных ошибок.
Всегда помните золотое правило разработчика 1С: любые изменения собственных реквизитов и табличных частей объекта должны завершаться до окончания работы процедуры ПередЗаписью модуля объекта. Соблюдение этого правила гарантирует корректность, стабильность работы вашей системы и убережет от долгих часов безрезультатной отладки.