Почему ХранилищеЗначения.Получить() возвращает Неопределено при работе с реквизитами документов в 1С:Предприятие 8?

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

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

Начнем с анализа типичной ситуации, описанной в нашем случае. Разработчик создает реквизит документа с типом ХранилищеЗначения и пытается записать в него данные, используя обработчик события ПриЗаписи() в модуле объекта документа, а затем получить эти данные из модуля формы.


// В модуле документа, обработчик ПриЗаписи()
// НЕПРАВИЛЬНО
Процедура ПриЗаписи(Отказ)
    Тест = Новый ХранилищеЗначения(Новый ТаблицаЗначений);
КонецПроцедуры

// В модуле формы, процедура &НаСервере
// НЕПРАВИЛЬНО
Процедура ПолучитьЗначениеИзХранилищаНаСервере()
    обб = РеквизитФормыВЗначение("объект");
    ИзХран = обб.Тест.Получить(); // Возвращает Неопределено
КонецПроцедуры

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

Причина 1: Неправильный обработчик записи — ПриЗаписи() вместо ПередЗаписьюНаСервере

Одной из основных причин, по которой ХранилищеЗначения.Получить() может возвращать Неопределено, является некорректный выбор обработчика события при записи документа. Давайте рассмотрим различия между обработчиками ПриЗаписи() и ПередЗаписью()/ПередЗаписьюНаСервере().

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

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

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

Решение: Перенесите логику присвоения значения реквизиту ХранилищеЗначения в обработчик ПередЗаписью() в модуле объекта документа или ПередЗаписьюНаСервере() в модуле формы.


// В модуле объекта документа
// ПРАВИЛЬНО
Процедура ПередЗаписью(Отказ)
    Тест = Новый ХранилищеЗначения(Новый ТаблицаЗначений);
    // Или, если требуется сохранить конкретное значение:
    // Тест = Новый ХранилищеЗначения("Некое строковое значение");
КонецПроцедуры

Используя ПередЗаписью(), мы гарантируем, что наше ХранилищеЗначения будет установлено непосредственно перед тем, как система зафиксирует документ в базе данных.

Причина 2: Запись из модуля формы — использование РеквизитФормыВЗначение("Объект") вместо ТекущийОбъект

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

Рассмотрим следующий ошибочный код, который демонстрирует эту проблему:


// В модуле формы, процедура &НаСервере, например, при нажатии кнопки
// НЕПРАВИЛЬНО
Процедура ЗаписатьХранилищеИзФормыНекорректноНаСервере()
    обб = РеквизитФормыВЗначение("объект");
    Хран = Новый ХранилищеЗначения("ааа");
    обб.Тест = Хран; // Это изменение в копии объекта, оно не сохранится
    // Далее может быть вызов Записать() у обб, но это не изменит реквизит основного объекта формы
КонецПроцедуры

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

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

Решение: Используйте параметр ТекущийОбъект в серверных обработчиках формы для записи данных в реквизит ХранилищеЗначения.


// В модуле формы, обработчик ПередЗаписьюНаСервере
// ПРАВИЛЬНО
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект)
    // Здесь ТекущийОбъект - это тот объект, который будет записан в базу данных.
    // Мы можем модифицировать его реквизиты.
    Если ТекущийОбъект.Тест.Получить() = Неопределено Тогда
        // Пример: если ХранилищеЗначения еще не заполнено, заполним его
        ТекущийОбъект.Тест = Новый ХранилищеЗначения("Записано из формы!");
    КонецЕсли;
    
    // Допустим, у нас есть реквизит формы "ДанныеДляХранилища"
    // и мы хотим сохранить его содержимое
    Если Не ЗначениеЗаполнено(ЭтотОбъект.ДанныеДляХранилища) Тогда
        ТекущийОбъект.Тест = Новый ХранилищеЗначения("Пустое значение из реквизита формы");
    Иначе
        ТекущийОбъект.Тест = Новый ХранилищеЗначения(ЭтотОбъект.ДанныеДляХранилища);
    КонецЕсли;
КонецПроцедуры

В этом примере мы обращаемся непосредственно к ТекущийОбъект.Тест и присваиваем ему новое ХранилищеЗначения. Эти изменения будут сохранены при сохранении документа.

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

Рекомендации по чтению ХранилищаЗначения из модуля формы

После того как мы успешно записали данные в ХранилищеЗначения, нам, естественно, потребуется их прочитать и, возможно, отобразить на форме. Здесь также есть свои нюансы и лучшие практики.

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

Правильный подход: Для чтения данных из ХранилищаЗначения, которое является реквизитом документа, наиболее подходящим обработчиком в модуле формы является ПриЧтенииНаСервере(ТекущийОбъект).


// В модуле формы, обработчик ПриЧтенииНаСервере
// ПРАВИЛЬНО
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
    // ТекущийОбъект здесь - это уже прочитанный объект документа
    Если ТекущийОбъект.Тест.Получить() <> Неопределено Тогда
        ПеременнаяНаФорме = ТекущийОбъект.Тест.Получить();
        // Если Тест хранит ТаблицуЗначений
        // ЭтаФорма.Элементы.ПолеТаблицы.Значение = ПеременнаяНаФорме;
        // Если Тест хранит строку
        // ЭтаФорма.РеквизитФормыСтрока = ПеременнаяНаФорме;
    КонецЕсли;
КонецПроцедуры

В обработчике ПриЧтенииНаСервере() мы получаем доступ к уже прочитанному объекту документа через параметр ТекущийОбъект. Это позволяет нам безопасно извлечь данные из ХранилищаЗначения и, при необходимости, присвоить их реквизитам формы для отображения пользователю.

Мы используем ПриЧтенииНаСервере() потому, что он выполняется после того, как объект документа был полностью загружен из базы данных и готов к обработке. Это гарантирует, что мы работаем с актуальными данными. Например, если ХранилищеЗначения содержит двоичные данные, такие как изображение, мы можем получить их здесь, временно сохранить во временном хранилище и затем использовать для отображения на форме.

Дополнительные нюансы: пустые значения в ХранилищеЗначения

Важный момент, который также иногда вызывает недопонимание, связан с хранением "пустых" значений. Некоторые ошибочно полагают, что ХранилищеЗначения не может хранить пустые объекты, например, пустую ТаблицуЗначений. Это не совсем так. ХранилищеЗначения способно хранить любой сериализуемый тип данных, включая:

Если мы передаем в конструктор Новый ХранилищеЗначения(...) пустую ТаблицуЗначений или даже просто строковое значение, но не сохраняем объект документа в базу данных, то при последующей попытке получить значение, мы ожидаемо получим Неопределено. Это происходит не потому, что "пустое значение не хранится", а потому что само ХранилищеЗначения как реквизит документа не было сохранено с каким-либо содержимым.

Мы можем проверить, хранится ли что-либо в ХранилищеЗначения, после того как мы его получили, следующим образом:


Если ОбъектДокумента.Тест.Получить() = Неопределено Тогда
    Сообщить("ХранилищеЗначения пусто или не было сохранено.");
Иначе
    Сообщить("В ХранилищеЗначения есть данные!");
КонецЕсли;

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

Важно отметить, что если ХранилищеЗначения было создано без параметров (Новый ХранилищеЗначения()), оно изначально не содержит никакого значения и его Получить() вернет Неопределено. Это может быть полезно для инициализации "пустого" хранилища.

Резюме и основные выводы

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

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

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

  3. Организуйте чтение данных корректно: Для извлечения данных из ХранилищаЗначения при открытии или обновлении формы используйте обработчик ПриЧтенииНаСервере(ТекущийОбъект). Это гарантирует, что данные объекта уже загружены из базы данных и доступны для использования.

  4. Помните о сериализации и сжатии: ХранилищеЗначения предназначено для хранения сериализуемых данных в сжатом виде. Это мощный инструмент для работы с произвольными данными, но требует внимательного подхода к моменту записи и чтения.

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

← На главную