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