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