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