Как получить старое значение реквизита из справочника в управляемой форме перед записью изменений?

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

При работе в системе 1С:Предприятие, особенно в управляемом приложении, мы часто сталкиваемся с задачами, требующими взаимодействия с данными до или после их сохранения. Одним из таких сценариев является необходимость получить текущее (старое) значение реквизита элемента справочника перед тем, как пользователь внесет и сохранит новые изменения через форму. Разберем эту ситуацию подробнее, выясним причины возникающих сложностей и предложим оптимальные решения.

Представим себе такую задачу: у нас есть справочник "Житель" с реквизитом IDжителя. Если пользователь изменяет значение этого реквизита в форме элемента справочника, нам необходимо сначала отправить старое значение IDжителя в стороннее программное обеспечение (СТОРОННЕЕ ПО) для удаления, а затем уже записать новое значение в нашу базу 1С. В такой ситуации возникает ряд вопросов, касающихся взаимодействия между формой, объектом и данными в базе данных.

Понимание ключевых концепций: Ссылка и Объект в 1С

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

  1. Ссылка (например, СправочникСсылка.Житель, ДокументСсылка.РеализацияТоваровУслуг и т.д.):

    • Что это? Ссылка — это уникальный идентификатор элемента в базе данных. Она подобна указателю или адресу, который точно указывает на местоположение элемента, но не содержит самих данных.

    • Назначение: Ссылки используются для установления связей между различными объектами системы (например, элемент справочника "Контрагенты" ссылается на элемент справочника "ВидыДеятельности"). Они также применяются для быстрого поиска и идентификации элементов.

    • Производительность: Работа со ссылками является более производительной, поскольку при этом загружается только сам идентификатор, а не весь объект со всеми его реквизитами, табличными частями и сопутствующими данными. Это особенно важно при формировании отчетов, проведении выборки большого количества данных или при передаче информации по сети.

    • Чтение данных по ссылке: Технически, мы можем обратиться к реквизитам объекта через ссылку, используя точечную нотацию (например, СсылкаНаЭлемент.Наименование или СсылкаНаЭлемент.Идентификатор). Однако, важно понимать, что такое обращение через точку к реквизитам от ссылки фактически приводит к неявному чтению объекта из базы данных целиком. Платформа 1С загружает весь объект в память на сервере, чтобы получить значение нужного реквизита. Это может быть неоптимально, если нам нужно получить всего один-два реквизита из большого объекта.

  2. Объект (например, СправочникОбъект.Житель, ДокументОбъект.РеализацияТоваровУслуг и т.д.):

    • Что это? Объект — это непосредственные данные элемента, включая все его реквизиты, табличные части и другую информацию. Объект представляет собой экземпляр прикладного объекта, загруженный в оперативную память.

    • Назначение: Основное назначение объекта — это возможность изменять его данные и записывать эти изменения в базу данных, а также удалять элемент. Мы можем программно устанавливать значения реквизитов, добавлять строки в табличные части и выполнять другие манипуляции.

    • Получение объекта по ссылке: Для того чтобы изменить или удалить существующий элемент, необходимо сначала получить его объект по ссылке. Для этого используется метод ПолучитьОбъект(). Например, ЭлементСправочника = СсылкаНаЭлементСправочника.ПолучитьОбъект();. После получения объекта мы можем изменять его свойства и затем вызывать метод Записать() для сохранения изменений в базе данных.

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

Таким образом, основной принцип прост: ссылка — для идентификации и связей, объект — для чтения, изменения и записи данных.

Реквизит "Объект" формы и клиент-серверная архитектура управляемого приложения

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

Почему так происходит? Причина кроется в клиент-серверной архитектуре управляемого приложения:

  1. Разделение клиента и сервера: В управляемом приложении клиент (интерфейс пользователя, работающий, например, в браузере или тонком клиенте) и сервер (где хранятся данные и выполняется основная бизнес-логика) работают как отдельные сущности. Они обмениваются информацией, но не являются одним целым.

  2. Оптимизация трафика: Передача всего объекта справочника со всеми его реквизитами, табличными частями и метаданными при каждом запросе от клиента на сервер и обратно была бы крайне неэффективной с точки зрения производительности и сетевого трафика. Поэтому на клиент передается только необходимая для отображения на форме информация.

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

Именно из-за этой архитектуры возникает ваша "глупость" – необходимость повторного "поиска" или чтения данных из справочника, даже если вы уже находитесь в форме его элемента. Реквизит Объект формы содержит уже измененные данные (или те, что были загружены при открытии), но не дает прямого доступа к "оригиналу" в базе данных, чтобы получить его "старые" значения до момента записи.

Оптимальный подход: Получение старого значения реквизита перед записью

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

Почему именно ПередЗаписью в модуле объекта?

  1. Контекст сервера: Событие ПередЗаписью (и ПриЗаписи, ПослеЗаписи) всегда выполняется на сервере. Это позволяет нам напрямую работать с данными базы данных без дополнительных серверных вызовов из формы.

  2. Внутри транзакции: Этот обработчик выполняется внутри транзакции записи. Это означает, что если в процессе записи произойдет ошибка (например, из-за сбоя при отправке данных в СТОРОННЕЕ ПО), вся транзакция будет отменена, и данные в базе 1С останутся неизменными. Это гарантирует целостность данных.

  3. Обработка всех сценариев записи: Код в модуле объекта сработает не только при интерактивной записи через форму, но и при программной записи объекта из любого места системы. Это обеспечивает унифицированность логики.

В обработчике ПередЗаписью мы имеем доступ к двум ключевым элементам:

Давайте рассмотрим два основных варианта получения старого значения IDжителя.

Вариант 1: Получение старого значения через Ссылку

Самый простой способ получить старое значение — это обратиться к реквизиту через ссылку объекта, который находится в процессе записи. Платформа 1С в данном контексте понимает, что ЭтотОбъект.Ссылка указывает на текущее состояние элемента в базе данных до применения изменений из ЭтотОбъект.

Однако, как мы уже говорили, обращение через точку к реквизитам от ссылки (ЭтотОбъект.Ссылка.IDжителя) приводит к загрузке объекта целиком из базы данных. Для одного реквизита это обычно не критично, но следует об этом помнить.

Вот пример кода в модуле объекта справочника "Житель":


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

    // 1. Получаем новое значение реквизита из объекта, который будет записан
    //    Это значение, которое пользователь ввел в форме или которое было установлено программно.
    НовоеЗначениеIDжителя = ЭтотОбъект.IDжителя;

    // 2. Получаем старое значение реквизита из базы данных по ссылке.
    //    ЭтотОбъект.Ссылка представляет собой ссылку на элемент до того, как изменения будут зафиксированы.
    //    Важно: это обращение приведет к неявной загрузке всего объекта из базы данных.
    СтароеЗначениеIDжителя = Неопределено;
    Если ЗначениеЗаполнено(ЭтотОбъект.Ссылка) Тогда // Проверяем, что это не создание нового элемента
        СтароеЗначениеIDжителя = ЭтотОбъект.Ссылка.IDжителя;
    КонецЕсли;

    // 3. Сравниваем значения и выполняем необходимые действия со сторонним ПО
    //    Если значения отличаются, значит IDжителя был изменен.
    Если ЗначениеЗаполнено(СтароеЗначениеIDжителя) И НовоеЗначениеIDжителя <> СтароеЗначениеIDжителя Тогда
        // Отправляем старое значение в стороннее ПО для удаления/обновления
        // Предполагаем, что _ФИС.УдалитьИдентификатор - это функция для взаимодействия со сторонним ПО
        // И КлючСессииДляИС доступен в этом контексте
        Сообщить("Отправляем в СТОРОННЕЕ ПО команду на удаление старого идентификатора: " + Строка(СтароеЗначениеIDжителя));
        // _ФИС.УдалитьИдентификатор(КлючСессииДляИС, СтароеЗначениеIDжителя); 
        // Если _ФИС.УдалитьИдентификатор возвращает флаг успешности
        // Если Не _ФИС.УдалитьИдентификатор(КлючСессииДляИС, СтароеЗначениеIDжителя) Тогда
        //     Отказ = Истина; // Отменяем запись в 1С, если не удалось удалить в СТОРОННЕЕ ПО
        //     Сообщить("Не удалось удалить идентификатор в стороннем ПО. Запись отменена.");
        // КонецЕсли;
    КонецЕсли;

КонецПроцедуры

Обратите внимание, что мы проверяем ЗначениеЗаполнено(ЭтотОбъект.Ссылка). Это важно, потому что при создании нового элемента справочника ЭтотОбъект.Ссылка еще не имеет значения (пустая ссылка), и попытка обращения к ее реквизитам вызовет ошибку. В этом случае старого значения просто не существует.

Вариант 2: Получение старого значения с помощью Запроса

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

Вот как это будет выглядеть с использованием запроса:


Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

    // 1. Получаем новое значение реквизита из объекта, который будет записан
    НовоеЗначениеIDжителя = ЭтотОбъект.IDжителя;

    // 2. Получаем старое значение реквизита из базы данных с помощью запроса
    СтароеЗначениеIDжителя = Неопределено;

    // Выполняем запрос только если это не новый элемент и ссылка заполнена
    Если ЗначениеЗаполнено(ЭтотОбъект.Ссылка) Тогда
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |    СправочникЖители.IDжителя КАК СтароеЗначениеIDжителя
            |ИЗ
            |    Справочник.Жители КАК СправочникЖители
            |ГДЕ
            |    СправочникЖители.Ссылка = &Ссылка"; // Использование параметра для ссылки

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

    // 3. Сравниваем значения и выполняем необходимые действия со сторонним ПО
    Если ЗначениеЗаполнено(СтароеЗначениеIDжителя) И НовоеЗначениеIDжителя <> СтароеЗначениеIDжителя Тогда
        Сообщить("Отправляем в СТОРОННЕЕ ПО команду на удаление старого идентификатора (через запрос): " + Строка(СтароеЗначениеIDжителя));
        // _ФИС.УдалитьИдентификатор(КлючСессииДляИС, СтароеЗначениеIDжителя);
        // Дополнительная логика обработки ошибок, как в предыдущем примере.
    КонецЕсли;

КонецПроцедуры

Этот вариант с запросом является более предпочтительным, если требуется оптимизация производительности или если вы работаете с несколькими реквизитами, но не со всем объектом.

Логика взаимодействия со сторонним ПО

В обоих случаях, после получения старого и нового значений, мы должны сравнить их. Если НовоеЗначениеIDжителя отличается от СтароеЗначениеIDжителя, это означает, что реквизит был изменен, и мы можем выполнить необходимые действия:

  1. Передать старое значение в СТОРОННЕЕ ПО: Вызываем функцию или процедуру, которая отправляет команду в ваше стороннее приложение, используя СтароеЗначениеIDжителя в качестве параметра. Например, _ФИС.УдалитьИдентификатор(КлючСессииДляИС, СтароеЗначениеIDжителя);.

  2. Обработка ошибок: Крайне важно предусмотреть обработку возможных ошибок при взаимодействии со СТОРОННИМ ПО. Если стороннее приложение не смогло обработать команду, мы, возможно, захотим отменить запись в 1С. Для этого в обработчике ПередЗаписью устанавливаем параметр Отказ = Истина. Это прервет транзакцию и не сохранит изменения в базе 1С, предотвращая расхождение данных между 1С и СТОРОННИМ ПО.


// Пример полной логики с учетом обработки ошибок
Процедура ПередЗаписью(Отказ, РежимЗаписи, РежимПроведения)

    НовоеЗначениеIDжителя = ЭтотОбъект.IDжителя;
    СтароеЗначениеIDжителя = Неопределено;

    Если ЗначениеЗаполнено(ЭтотОбъект.Ссылка) Тогда
        // Используем запрос для получения старого значения
        Запрос = Новый Запрос;
        Запрос.Текст = 
            "ВЫБРАТЬ
            |    СправочникЖители.IDжителя КАК СтароеЗначениеIDжителя
            |ИЗ
            |    Справочник.Жители КАК СправочникЖители
            |ГДЕ
            |    СправочникЖители.Ссылка = &Ссылка";
        Запрос.УстановитьПараметр("Ссылка", ЭтотОбъект.Ссылка);
        РезультатЗапроса = Запрос.Выполнить();
        Выборка = РезультатЗапроса.Выбрать();
        Если Выборка.Следующий() Тогда
            СтароеЗначениеIDжителя = Выборка.СтароеЗначениеIDжителя;
        КонецЕсли;
    КонецЕсли;

    // Если идентификатор изменился и это не новый элемент (т.е. старое значение существовало)
    Если ЗначениеЗаполнено(СтароеЗначениеIDжителя) И НовоеЗначениеIDжителя <> СтароеЗначениеIDжителя Тогда
        Попытка
            // Вызов функции стороннего ПО
            УспешноУдалено = _ФИС.УдалитьИдентификатор(КлючСессииДляИС, СтароеЗначениеIDжителя);
            Если Не УспешноУдалено Тогда
                Отказ = Истина; // Отменяем запись, если удаление в СТОРОННЕЕ ПО не удалось
                Сообщить("Ошибка: не удалось удалить старый идентификатор '" + Строка(СтароеЗначениеIDжителя) + 
                         "' в стороннем ПО. Запись элемента отменена.", СтатусСообщения.Важное);
            КонецЕсли;
        Исключение
            Отказ = Истина; // Отменяем запись при возникновении исключения
            Сообщить("Критическая ошибка при взаимодействии со сторонним ПО: " + ОписаниеОшибки() + 
                     ". Запись элемента отменена.", СтатусСообщения.Важное);
        КонецПопытки;
    КонецЕсли;

КонецПроцедуры

В этом примере _ФИС.УдалитьИдентификатор должна быть функцией, которая возвращает Истина в случае успеха и Ложь в случае неудачи. Также убедитесь, что переменные вроде _ФИС и КлючСессииДляИС доступны в контексте модуля объекта (например, определены как экспортные переменные общего модуля или параметры вызова).

Важные аспекты и рекомендации

  1. Новый элемент или существующий? Всегда учитывайте, работаем ли мы с новым элементом или с уже существующим. У нового элемента нет "старого" значения, поскольку он еще не записан в базу данных. В наших примерах мы используем проверку ЗначениеЗаполнено(ЭтотОбъект.Ссылка), чтобы избежать ошибок при работе с новыми объектами.

  2. Отмена записи: В случае, если пользователь отменил запись (например, закрыл форму без сохранения), действия в ПередЗаписью не будут иметь значения, поскольку сам объект не будет записан. Если бы мы отправляли данные в СТОРОННЕЕ ПО в событии ПриЗаписиНаСервере формы или ПослеЗаписи объекта, нам пришлось бы реализовать более сложную логику отката в СТОРОННЕМ ПО.

  3. Производительность: Для небольших справочников и редких изменений, обращение ЭтотОбъект.Ссылка.Реквизит вполне допустимо. Для высоконагруженных систем или больших объектов всегда предпочтительнее использовать запросы для получения выборочных данных.

  4. Место размещения кода: Мы настоятельно рекомендуем размещать подобную логику именно в модуле объекта справочника. Это обеспечивает централизацию бизнес-логики, ее исполнение на сервере и корректную работу независимо от того, как элемент был изменен — через форму, программно или с помощью обмена данными.

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

← На главную