Почему не сохраняются реквизиты табличной части при записи объекта в 1С? Разбор «полтергейста»

Программист 1С v8.3 (Управляемые формы) 1С:Документооборот IT и автоматизация бизнеса
← На главную

Каждый разработчик на платформе «1С:Предприятие 8» рано или поздно сталкивается с ситуациями, которые на первый взгляд кажутся необъяснимой мистикой или багом самой платформы. Один из таких классических «полтергейстов» — когда код по изменению реквизитов или строк табличной части объекта успешно отрабатывает в конфигураторе, отладчик показывает измененные значения вплоть до закрытия формы, но после повторного открытия объекта или при выполнении запроса к базе данных выясняется, что новые данные не сохранились.

В этой статье мы подробно проанализируем эту проблему на примере реального кейса из конфигурации «1С:Документооборот» (справочник «Проекты», табличная часть «Дополнительные реквизиты»), выясним физические причины такого поведения платформы и разберем правильные шаблоны разработки, которые навсегда избавят вас от подобных ошибок.

Суть проблемы: когда отладчик «врет», а база данных пуста

Рассмотрим стандартный сценарий, с которым столкнулся автор темы. В модуле объекта (например, справочника или документа) в предопределенной процедуре ПриЗаписи разработчик пишет код, который программно заполняет или корректирует строки табличной части этого же объекта. Алгоритм выглядит абсолютно рабочим:

  1. Пользователь нажимает кнопку «Записать» в форме.
  2. Срабатывает обработчик ПриЗаписи в модуле объекта.
  3. Код обходит строки табличной части и устанавливает новые значения для определенных реквизитов.
  4. Разработчик ставит точку останова в конце процедуры ПриЗаписи и даже далее — в модуле формы в процедуре ПриЗакрытии. Отладчик послушно показывает, что в переменной ЭтотОбъект (или в реквизите формы Объект) все данные заполнены корректно.
  5. Форма закрывается. Разработчик открывает этот же элемент заново — табличная часть пуста или содержит старые данные. При попытке прочитать таблицу запросом результат аналогичный — изменений нет.

Почему так происходит? Для ответа на этот вопрос нам необходимо детально изучить архитектуру СУБД под управлением платформы «1С:Предприятие» и жизненный цикл транзакции записи.

Архитектурные основы: Жизненный цикл записи объекта в 1С

Главный секрет этого «полтергейста» кроется в том, как именно платформа взаимодействует с базой данных (MS SQL, PostgreSQL и др.) в процессе выполнения транзакции записи. Давайте разберем этот процесс по шагам.

Когда для объекта вызывается метод Записать(), платформа запускает строго регламентированную последовательность событий:

  1. Начало транзакции БД. Все последующие действия выполняются в рамках одной транзакции СУБД. Если на любом этапе произойдет ошибка или будет установлен флаг Отказ = Истина, транзакция полностью откатится, вернув базу данных к исходному состоянию.
  2. Обработчик ПередЗаписью (в модуле объекта). На этом этапе данные объекта находятся исключительно в оперативной памяти сервера приложений 1С. Физического обращения к таблицам БД с командами записи еще не происходило. Любые изменения реквизитов, модификация табличных частей или добавление новых строк, сделанные в этом обработчике, будут успешно учтены платформой при формировании SQL-запроса.
  3. Физическая запись в СУБД. Платформа генерирует низкоуровневые SQL-запросы (например, INSERT или UPDATE) и отправляет их на SQL-сервер. С этого момента данные физически записаны во временные таблицы СУБД в рамках открытой транзакции. Важно: для самой платформы процесс сериализации и сохранения состояния данного конкретного объекта на этом завершен.
  4. Обработчик ПриЗаписи (в модуле объекта). Этот обработчик вызывается уже после того, как SQL-запрос на запись объекта был отправлен и выполнен СУБД. Транзакция все еще открыта, но сам объект ЭтотОбъект в базе данных уже сохранен.
  5. Обработчики подписок на события ПриЗаписи. Выполняется код внешних модулей, подписанных на это событие.
  6. Фиксация (Commit) транзакции. Если не возникло ошибок, транзакция закрывается, и изменения становятся доступными для других пользователей.

Теперь посмотрим, что происходит, когда мы пытаемся изменить табличную часть в процедуре ПриЗаписи. Изменения вносятся в переменную ЭтотОбъект, которая находится в оперативной памяти сервера приложений. Эти изменения действительно живут в памяти до тех пор, пока объект не будет уничтожен (например, при закрытии формы), поэтому отладчик показывает их наличие.

Однако платформа 1С не будет повторно отправлять SQL-запросы для записи этого объекта, так как этап физической записи (Шаг 3) уже пройден! Платформа считает, что объект уже успешно сохранен. Повторная автоматическая отправка данных привела бы к бесконечным рекурсивным циклам записи и колоссальным просадкам производительности СУБД. В результате транзакция фиксируется с теми данными, которые были отправлены на Шаге 3, а ваши изменения в памяти просто бесследно уничтожаются при очистке кэша сервера.

Именно поэтому стандарты разработки 1С (требования 1С:ИТС) категорически запрещают изменять реквизиты и табличные части самого записываемого объекта в обработчике ПриЗаписи.

Почему другие реквизиты могли записываться? Развенчиваем иллюзии

У автора темы возник вполне резонный вопрос: «Почему наименование и другие табличные части перезаписались нормально, а одна конкретная табличная часть — нет?». Существует три логичных объяснения этой иллюзии:

1. Модификация данных на уровне формы. Если наименование или другие реквизиты заполнялись не в модуле объекта, а в модуле управляемой формы (например, в обработчиках ПередЗаписьюНаСервере или ПриЗаписиНаСервере), то эти изменения передавались в объект *до* того, как платформа инициировала транзакцию записи объекта в БД. Такие изменения всегда сохраняются корректно.

2. Каскадная или повторная запись объекта. Иногда в конфигурациях (особенно нетиповых или сильно кастомизированных) в подписках на события или в сопутствующих бизнес-процессах программно вызывается повторный метод Объект.Записать(). Если в первой транзакции в процедуре ПриЗаписи вы изменили реквизит в памяти, а затем какая-то подсистема принудительно вызвала запись этого же объекта еще раз в новой транзакции, то измененные данные запишутся. Но это является грубейшей архитектурной ошибкой, приводящей к избыточной нагрузке на СУБД и взаимоблокировкам (Deadlocks).

3. Путаница между табличными частями и регистрами сведений. Разработчики часто путают изменение табличной части объекта (которая физически хранится в таблице самого справочника/документа) и запись связанных данных в независимые или подчиненные регистры сведений. Запись в регистры сведений (например, запись движений документа или дополнительных сведений) в процедуре ПриЗаписи работает отлично и является стандартной практикой, так как это запись *других* объектов БД.

Специфика БСП и табличной части «Дополнительные реквизиты»

Ситуация усугубляется, когда речь идет о типовых конфигурациях на базе БСП (Библиотеки стандартных подсистем), таких как «1С:Документооборот», «1С:ERP» или «1С:Управление торговлей». В рассматриваемом кейсе фигурирует табличная часть ДополнительныеРеквизиты справочника «Проекты».

Подсистема «Свойства» из состава БСП накладывает жесткие правила на работу с этой табличной частью:

Для корректной работы с дополнительными реквизитами на платформе БСП следует использовать штатные методы общего модуля УправлениеСвойствами:


// Пример правильного программного изменения дополнительных реквизитов объекта через API БСП
ЗначенияСвойств = Новый Структура;
ЗначенияСвойств.Вставить(СвойствоСсылка, НовоеЗначение);

УправлениеСвойствами.ЗаписатьСвойстваУОбъекта(СсылкаНаОбъект, ЗначенияСвойств);

Как правильно решить проблему: пошаговое руководство

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

Шаг 1. Перенос кода в обработчик ПередЗаписью

Единственным верным и архитектурно правильным решением для модификации собственных реквизитов и табличных частей объекта является использование обработчика ПередЗаписью модуля объекта. Перенесем наш алгоритм туда.

Посмотрим на пример правильной реализации заполнения реквизитов табличной части:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)
	
	// Обязательно проверяем, не выполняется ли запись в режиме обмена данными
	Если ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	// Разберем по шагам заполнение нашей табличной части
	Для Каждого СтрокаТЧ Из ЭтотОбъект.ДополнительныеРеквизиты Цикл
		
		// Проанализируем условие, при котором нужно изменить значение
		Если СтрокаТЧ.Свойство = ПланыВидовХарактеристик.ДополнительныеРеквизитыИСведения.НайтиПоНаименованию("ПроектныйДепартамент") Тогда
			
			// Устанавливаем новое значение реквизита строки ТЧ
			СтрокаТЧ.Значение = Справочники.СтруктураПредприятия.НайтиПоНаименованию("Департамент Разработки");
			
		КонецЕсли;
		
	КонецЦикла;
	
КонецПроцедуры

В данном случае, так как код выполняется в ПередЗаписью, измененные данные строки табличной части попадут в итоговый SQL-запрос обновления объекта и успешно запишутся в СУБД.

Шаг 2. Использование подписок на события (при необходимости)

Если вам требуется реализовать заполнение табличной части не в самом модуле объекта (чтобы не снимать типовую конфигурацию с поддержки), используйте общие модули и механизм подписок на события. Создайте подписку на событие ПередЗаписью для нужного типа объекта:


// Процедура-обработчик подписки на событие "ПередЗаписью"
Процедура ИмяПодпискиПередЗаписью(Источник, Отказ, РежимЗаписи, РежимПроведения) Экспорт
	
	Если Источник.ОбменДанными.Загрузка Тогда
		Возврат;
	КонецЕсли;
	
	// Источник — это и есть наш записываемый объект (ЭтотОбъект)
	Для Каждого СтрокаТЧ Из Источник.ДополнительныеРеквизиты Цикл
		// Реализация логики заполнения...
	КонецЦикла;
	
КонецПроцедуры

Сравнительный анализ обработчиков событий объекта

Чтобы окончательно закрепить материал, систематизируем правила использования ключевых обработчиков событий модуля объекта в виде структурированной таблицы рекомендаций:

Обработчик события Что МОЖНО делать Что НЕЛЬЗЯ делать
ПередЗаписью (BeforeWrite)
  • Изменять любые реквизиты объекта (ЭтотОбъект.Реквизит = ...)
  • Добавлять, изменять и удалять строки табличных частей объекта
  • Проводить валидацию данных и прерывать запись (Отказ = Истина)
  • Записывать другие объекты, которые могут инициировать запись текущего (риск взаимоблокировок)
  • Вызывать транзакционные операции, требующие наличия ссылки нового объекта в БД
ПриЗаписи (OnWrite)
  • Записывать движения по регистрам (накопления, бухгалтерии, сведений)
  • Создавать и записывать подчиненные или связанные объекты (например, связанные задачи, историю изменений)
  • Регистрировать объект на узлах планов обмена
  • Категорически нельзя изменять реквизиты и табличные части текущего записываемого объекта (ЭтотОбъект)
  • Вызывать метод ЭтотОбъект.Записать()

Резюме

Описанный на форуме «полтергейст» с незаписывающимися строками табличной части — это классическое проявление нарушения стандартов разработки платформы 1С. Понимание разницы между состояниями объекта в оперативной памяти и физическими записями в таблицах СУБД позволяет легко избегать подобных архитектурных ошибок.

Всегда помните золотое правило разработчика 1С: любые изменения собственных реквизитов и табличных частей объекта должны завершаться до окончания работы процедуры ПередЗаписью модуля объекта. Соблюдение этого правила гарантирует корректность, стабильность работы вашей системы и убережет от долгих часов безрезультатной отладки.

← На главную