При переходе на управляемые формы или при доработке типовых конфигураций программисты часто сталкиваются с ситуацией, когда код, успешно работающий в толстом клиенте, вызывает фатальную ошибку в тонком клиенте. Типичный сценарий: вы добавили колонки для расчета веса и объема в табличную часть «Товары», написали код расчета в модуле формы, и при открытии документа система выдает ошибку: не обнаружено поле объекта ВесЧислитель. Давайте разберем по шагам, почему это происходит и как правильно реализовать такие вычисления.
Проанализируем ситуацию. В толстом клиенте (особенно в режиме обычного приложения) платформа работает непосредственно с объектами базы данных. Когда вы обращаетесь к реквизиту через точку, например Строка.Номенклатура.ВесЧислитель, система автоматически подгружает объект справочника из базы, чтобы достать нужное значение. Это удобно, но крайне неэффективно с точки зрения производительности сети.
В тонком клиенте всё иначе. На стороне клиента мы имеем дело не с объектами, а с ДаннымиФормы. Когда мы видим в строке табличной части колонку Номенклатура, для тонкого клиента это всего лишь Ссылка (уникальный идентификатор). В этой ссылке нет данных о весе, объеме или других реквизитах. Платформа 1С:Предприятие 8.3 специально запрещает неявные обращения к серверу через точку от ссылки на стороне клиента. Это сделано для того, чтобы интерфейс не «зависал» при каждом обращении к базе данных. Именно поэтому мы получаем ошибку: поле объекта не найдено, так как у Ссылкы нет таких свойств.
Рассмотрим самый очевидный способ исправления. Если нам нужно получить данные реквизитов справочника, мы обязаны сделать это на сервере. Посмотрим на пример кода, который вызывал ошибку:
Для Каждого Строка Из Объект.Товары Цикл
// ОШИБКА: Обращение к реквизиту через точку от ссылки на клиенте
Объем1 = Объем1 + Строка.Количество * Строка.Номенклатура.ОбъемЧислитель;
Вес1 = Вес1 + Строка.Количество * Строка.Номенклатура.ВесЧислитель;
КонецЦикла;
Чтобы это заработало, нам необходимо создать серверную функцию. Разберем, как это реализовать правильно:
&НаКлиенте
Процедура РасчитатьИтогиНаКлиенте()
// Вызываем серверный метод для получения данных
РасчитатьИтогиНаСервере();
КонецПроцедуры
&НаСервере
Процедура РасчитатьИтогиНаСервере()
ИтогоВес = 0;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
// На сервере обращение через точку допустимо (хотя и не всегда оптимально)
СтрокаТЧ.ВесТовара = СтрокаТЧ.Количество * СтрокаТЧ.Номенклатура.ВесЧислитель;
ИтогоВес = ИтогоВес + СтрокаТЧ.ВесТовара;
КонецЦикла;
КонецПроцедуры
Предыдущий метод прост, но имеет скрытую ловушку. Если в документе 100 или 200 строк, обращение СтрокаТЧ.Номенклатура.ВесЧислитель в цикле приведет к тому, что система выполнит 100–200 отдельных микро-запросов к базе данных. Это называется проблемой N+1 запроса. Проанализируем ситуацию: гораздо эффективнее получить все данные одним запросом.
Рассмотрим алгоритм оптимального расчета:
Пример реализации:
&НаСервере
Процедура ОптимизированныйРасчетНаСервере()
МассивНоменклатуры = Объект.Товары.ВыгрузитьКолонку("Номенклатура");
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Номенклатура.Ссылка КАК Ссылка,
| Номенклатура.ВесЧислитель КАК ВесЧислитель,
| Номенклатура.ОбъемЧислитель КАК ОбъемЧислитель
|ИЗ
| Справочник.Номенклатура КАК Номенклатура
|ГДЕ
| Номенклатура.Ссылка В(&МассивНоменклатуры)";
Запрос.УстановитьПараметр("МассивНоменклатуры", МассивНоменклатуры);
РезультатЗапроса = Запрос.Выполнить().Выгрузить();
// Используем соответствие для быстрого поиска данных в памяти
СоответствиеДанных = Новый Соответствие;
Для Каждого СтрДанные Из РезультатЗапроса Цикл
СоответствиеДанных.Вставить(СтрДанные.Ссылка, СтрДанные);
КонецЦикла;
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
ДанныеНоменклатуры = СоответствиеДанных.Получить(СтрокаТЧ.Номенклатура);
Если ДанныеНоменклатуры <> Неопределено Тогда
СтрокаТЧ.ВесТовара = СтрокаТЧ.Количество * ДанныеНоменклатуры.ВесЧислитель;
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Если вы работаете в современной конфигурации (УТ 11, ERP, КА 2), в вашем распоряжении есть мощный инструмент — общий модуль ОбщегоНазначения. Рассмотрим, как сократить код получения реквизитов до одной строки. Выясним, какие методы нам помогут:
Функция ОбщегоНазначения.ЗначенияРеквизитовОбъекта() позволяет получить структуру нужных полей, не заботясь о создании запросов вручную. Однако помните, что вызывать её в цикле — плохая практика. Лучше использовать ОбщегоНазначения.ЗначенияРеквизитовОбъектов() (во множественном числе) для получения данных сразу по всему списку ссылок.
Пример использования БСП:
// Получаем соответствие, где ключом будет ссылка, а значением - структура реквизитов
Реквизиты = "ВесЧислитель, ОбъемЧислитель";
ДанныеВсехТоваров = ОбщегоНазначения.ЗначенияРеквизитовОбъектов(МассивНоменклатуры, Реквизиты);
Для Каждого СтрокаТЧ Из Объект.Товары Цикл
ТекущиеДанные = ДанныеВсехТоваров[СтрокаТЧ.Номенклатура];
Если ТекущиеДанные <> Неопределено Тогда
СтрокаТЧ.ВесТовара = СтрокаТЧ.Количество * ТекущиеДанные.ВесЧислитель;
КонецЕсли;
КонецЦикла;
Проанализируем еще один важный момент. Если вес и объем критически важны для документа (например, для печати накладных или расчета логистики), правильнее хранить эти значения в реквизитах табличной части документа, а не вычислять их динамически «на лету» только в форме.
Преимущества такого подхода:
Подведем итог: ошибка в тонком клиенте — это не досадный баг, а следствие работы системы безопасности производительности. Всегда переносите получение данных ссылочных полей на сервер и старайтесь минимизировать количество серверных вызовов, используя запросы или кэшированные функции БСП.