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