Каждый начинающий разработчик в среде «1С:Предприятие» рано или поздно сталкивается с загадочным поведением платформы: один и тот же код прекрасно функционирует в режиме толстого клиента, но вызывает фатальную ошибку при запуске в тонком клиенте. Обычно это происходит при попытке модификации существующих форм документов — например, при добавлении расчета веса и объема в табличную часть документа Заказ клиента.
В этой статье мы подробно разберем, почему возникает ошибка «Поле объекта не обнаружено» при переходе на тонкий клиент, проанализируем архитектурные различия между клиентами и рассмотрим несколько правильных и производительных способов решения этой задачи согласно стандартам разработки фирмы «1С».
Давайте проанализируем ситуацию, описанную автором темы. Разработчик добавил в табличную часть Товары документа колонки ВесТовара и ОбъемТовара, а также решил рассчитать их общие итоги в подвале формы при ее открытии. Для этого был написан следующий код на клиенте:
Для Каждого Строка Из Объект.Товары Цикл
Объем1 = Объем1 + Строка.Количество * Строка.Номенклатура.ОбъемЧислитель;
Вес1 = Вес1 + Строка.Количество * Строка.Номенклатура.ВесЧислитель;
КонецЦикла;
В режиме толстого клиента этот код успешно отрабатывает. Дело в том, что толстый клиент работает в едином адресном пространстве с базой данных (или ее локальным кэшем). Когда платформа видит конструкцию Строка.Номенклатура.ВесЧислитель, она выполняет неявное («ленивое») чтение объекта из базы данных. Платформа самостоятельно обращается к СУБД, считывает все реквизиты справочника Номенклатура и подставляет нужное значение. Это работает крайне неэффективно и медленно, но не вызывает ошибок у пользователя.
В режиме тонкого клиента ситуация кардинально меняется. Тонкий клиент оптимизирован для работы по слабым каналам связи (включая веб-соединения). Данные на клиент передаются в усеченном виде. Ссылка на номенклатуру (СправочникСсылка.Номенклатура) на клиенте — это всего лишь легковесный идентификатор (UUID). Он не содержит внутри себя никаких реквизитов, кроме ссылки и представления. Попытка обратиться к реквизиту ссылки «через точку» на клиенте вызывает мгновенный отказ платформы, так как неявные сетевые запросы к базе данных из клиентского контекста строго запрещены.
В ходе обсуждения участники форума предложили автору создать вспомогательную функцию на сервере, которая извлекает нужные реквизиты с помощью библиотеки стандартных подсистем (БСП):
&НаСервереБезКонтекста
Функция ПолучитьСведенияНоменклатуры(Номенклатура)
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Номенклатура, "ОбъемЧислитель, ВесЧислитель");
КонецФункции
А затем вызывать её в процедуре ПриОткрытии на клиенте внутри цикла:
Для Каждого Строка Из Объект.Товары Цикл
СведенияНоменклатуры = ПолучитьСведенияНоменклатуры(Строка.Номенклатура);
Объем1 = Объем1 + Строка.Количество * СведенияНоменклатуры.ОбъемЧислитель;
// ... дальнейший расчет
КонецЦикла;
Внимание! Хотя этот код решает проблему с ошибкой открытия формы в тонком клиенте, он является грубейшим антипаттерном разработки (ошибкой производительности). Такой подход называют «серверным вызовом в цикле».
Давайте посчитаем: если в табличной части заказа клиента будет 100 строк, программа совершит 100 последовательных клиент-серверных вызовов. В клиент-серверном варианте работы (особенно при подключении через интернет или VPN) каждый такой вызов создает сетевую задержку (ping). В итоге открытие простого документа может занять от 5 до 30 секунд, что вызовет сильное раздражение пользователей и перегрузит сервер.
Рассмотрим по шагам, как реализовать эту задачу грамотно, быстро и в соответствии со стандартами разработки фирмы «1С».
Если нам требуется рассчитать вес и объем товаров исключительно в момент открытия документа, нет абсолютно никакой необходимости выполнять этот цикл на стороне клиента. Гораздо правильнее перенести всю логику в серверный обработчик ПриСозданииНаСервере. На сервере мы имеем прямой доступ к данным, и весь цикл выполнится мгновенно за одну транзакцию без каких-либо сетевых задержек.
Давайте посмотрим на пример правильной реализации в модуле формы документа:
&НаСервере
Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
// Выполняем расчет на сервере — это быстро и безопасно
ИтоговыйВес = 0;
ИтоговыйОбъем = 0;
Для Каждого Строка Из Объект.Товары Цикл
// На сервере мы можем безопасно использовать функцию БСП
Сведения = ОбщегоНазначения.ЗначенияРеквизитовОбъекта(Строка.Номенклатура, "ВесЧислитель, ОбъемЧислитель");
// Заполняем колонки веса и объема для текущей строки
Строка.ВесТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ВесЧислитель), Сведения.ВесЧислитель, 0);
Строка.ОбъемТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ОбъемЧислитель), Сведения.ОбъемЧислитель, 0);
// Накапливаем итоги
ИтоговыйВес = ИтоговыйВес + Строка.ВесТовара;
ИтоговыйОбъем = ИтоговыйОбъем + Строка.ОбъемТовара;
КонецЦикла;
// Записываем полученные итоги в реквизиты формы, связанные с подвалом таблицы
ИтогоВес = ИтоговыйВес;
ИтогоОбъем = ИтоговыйОбъем;
КонецПроцедуры
Что делать, если расчет веса и объема должен происходить динамически прямо на клиенте (например, когда пользователь интерактивно редактирует количество или добавляет новые строки)? В этом случае серверный вызов неизбежен, но мы обязаны свести количество запросов к серверу к минимуму — в идеале, совершить ровно один вызов.
Для этого мы применим метод групповой выборки реквизитов БСП. Сначала мы соберем все уникальные ссылки на номенклатуру из табличной части в массив, передадим этот массив на сервер, получим данные по всем позициям одним пакетом, а затем обработаем их локально на клиенте.
Давайте разберем этот алгоритм на примере кода:
&НаКлиенте
Процедура ПриОткрытии(Отказ)
// 1. Собираем уникальные ссылки на номенклатуру в массив
МассивНоменклатуры = Новый Массив;
Для Каждого Строка Из Объект.Товары Цикл
Если ЗначениеЗаполнено(Строка.Номенклатура)
И МассивНоменклатуры.Найти(Строка.Номенклатура) = Неопределено Тогда
МассивНоменклатуры.Добавить(Строка.Номенклатура);
КонецЕсли;
КонецЦикла;
Если МассивНоменклатуры.Количество() = 0 Тогда
Возврат;
КонецЕсли;
// 2. Делаем только ОДИН вызов сервера для получения данных по всему массиву
СведенияВсехТоваров = ПолучитьСведенияМассиваНоменклатуры(МассивНоменклатуры);
// 3. Выполняем расчет на клиенте без повторных обращений к серверу
СуммаВеса = 0;
СуммаОбъема = 0;
Для Каждого Строка Из Объект.Товары Цикл
// Извлекаем структуру показателей из возвращенного соответствия по ключу-ссылке
Сведения = СведенияВсехТоваров[Строка.Номенклатура];
Если Сведения <> Неопределено Тогда
Строка.ВесТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ВесЧислитель), Сведения.ВесЧислитель, 0);
Строка.ОбъемТовара = Строка.Количество * ?(ЗначениеЗаполнено(Сведения.ОбъемЧислитель), Сведения.ОбъемЧислитель, 0);
СуммаВеса = СуммаВеса + Строка.ВесТовара;
СуммаОбъема = СуммаОбъема + Строка.ОбъемТовара;
КонецЕсли;
КонецЦикла;
// Выводим итоги на форму
ИтогоВес = СуммаВеса;
ИтогоОбъем = СуммаОбъема;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ПолучитьСведенияМассиваНоменклатуры(МассивНоменклатуры)
// Функция БСП ЗначенияРеквизитовОбъектов возвращает Соответствие, где ключ — ссылка, а значение — Структура реквизитов
Возврат ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивНоменклатуры, "ВесЧислитель, ОбъемЧислитель");
КонецФункции
Если колонки ВесТовара и ОбъемТовара добавлены как полноценные реквизиты табличной части документа, то платформа «1С» позволяет полностью отказаться от ручного суммирования в коде. Мы можем задействовать встроенные механизмы расчета итогов:
Товары установите флаг Отображать подвал в значение Истина.ВесТовара, найдите свойство ПутьКДаннымПодвала и укажите значение Объект.Товары.ИтогВесТовара.ОбъемТовара, указав путь к данным подвала как Объект.Товары.ИтогОбъемТовара.При таком подходе платформа будет рассчитывать и отображать итоги автоматически в подвале таблицы прямо «на лету» силами самого клиентского приложения, не требуя от разработчика написания лишних строк кода и серверных вызовов.
Если же значение автоматического итога все-таки понадобится получить в кодовой логике на клиенте, достаточно обратиться к встроенному методу коллекции: Объект.Товары.Итог("ВесТовара").
Применяя данные подходы, вы гарантируете стабильную и быструю работу конфигурации как в тонком, так и в веб-клиенте, избегая зависаний интерфейса и ошибок доступа к данным.