Как эффективно использовать НайтиСтроки() с ДанныеФормыКоллекция на сервере и редактировать найденные строки на клиенте?

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

При работе с управляемыми формами в 1С:Предприятии 8.3 мы часто сталкиваемся с необходимостью поиска и обработки данных в табличных частях формы, представленных объектами типа ДанныеФормыКоллекция. Платформа 1С рекомендует использовать метод НайтиСтроки() на сервере для повышения производительности, особенно при работе с большими объемами данных. Однако возникает логичный вопрос: как получить *сами найденные строки* для их дальнейшего редактирования на клиенте, если прямая передача ссылок на элементы формы между клиентом и сервером не предусмотрена?

В этой статье мы подробно разберем эту проблему, рассмотрим различные подходы и найдем наиболее эффективное и производительное решение, учитывая особенности клиент-серверного взаимодействия — в этом поможет ассистент разработчика 1С на базе искусственного интеллекта для быстрого написания кода.

Понимание основной проблемы: ДанныеФормыКоллекция и клиент-серверное взаимодействие

Прежде чем перейти к решениям, давайте глубже проанализируем, почему возникает эта проблема. Платформа 1С:Предприятие 8.3 спроектирована таким образом, чтобы минимизировать объем данных, передаваемых между клиентом и сервером. Это особенно актуально для объектов типа ДанныеФормыКоллекция, которые часто представляют собой табличные части на форме.

  1. Рекомендации 1С по НайтиСтроки(): Методу НайтиСтроки(), используемому для поиска строк в коллекциях, рекомендуется выполняться на сервере. Почему? Потому что выполнение этого метода на клиенте для больших коллекций (более 20 строк) может привести к многочисленным неявным обращениям к серверу. Данные формы загружаются порциями, и каждый раз, когда клиент обращается к строкам, которые еще не загружены, происходит неявный серверный вызов для подгрузки очередной порции данных. Это существенно замедляет работу.
  2. Особенности передачи ДанныеФормыКоллекция:
    • Объект ДанныеФормыКоллекция (например, Объект.Товары или реквизит формы типа "ДинамическийСписок") может быть передан между клиентом и сервером по значению. Это означает, что вы можете передать всю коллекцию на сервер, выполнить там поиск и обработку.
    • Однако, отдельные элементы этой коллекции, то есть ДанныеФормыЭлементКоллекции (сами строки, возвращаемые НайтиСтроки()), не предусмотрены для прямой передачи между клиентом и сервером с сохранением контекста изменения. Вы не можете просто получить на сервере ссылку на строку, вернуть её на клиент и ожидать, что изменения, внесенные в эту "ссылку" на клиенте, будут автоматически применены к исходной коллекции на форме. Это связано с архитектурой управляемых форм и механизмом работы с данными формы. Метод НайтиСтроки() возвращает массив ссылок на строки в памяти сервера, и эти ссылки не могут быть "десериализованы" на клиенте как прямые объекты для редактирования.
  3. Что возвращает НайтиСтроки(): Как мы уже выяснили, метод НайтиСтроки() возвращает массив ссылок на строки коллекции. Эти ссылки позволяют на сервере обращаться к найденным строкам и, при необходимости, изменять их. Однако, если мы возвращаем булево значение (как в примере 1С: Объект.Товары.НайтиСтроки(...).Количество() > 0), то это работает отлично, так как передается простое значение. Но если нам нужны *сами строки* для дальнейшей работы на клиенте, мы сталкиваемся с ограничением.

Таким образом, наша задача заключается в том, чтобы найти способ эффективно выполнять поиск на сервере и иметь возможность работать с найденными строками на клиенте, сохраняя возможность их редактирования в исходной коллекции формы.

Варианты решения и их анализ

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

1. Неявный серверный вызов при использовании НайтиСтроки() на клиенте

Мы выяснили, что 1С не рекомендует использовать НайтиСтроки() на клиенте для больших коллекций. Разберем, почему это происходит и к чему это приводит.

Если мы попытаемся использовать НайтиСтроки() непосредственно на клиенте (быстро сгенерировать каркас кода поможет консоль кода с ИИ-помощником — см. набор инструментов разработчика 1С с консолью кода), как в примере, который мы часто видим в старых решениях или интуитивно пытаемся применить:


&НаКлиенте
Процедура ПроверитьНаличиеСтрокСНезаполненнымКоличествомКлиент()
    // Этот код НЕ рекомендуется для больших коллекций!
    Если Объект.Товары.НайтиСтроки(Новый Структура("Количество", 0)).Количество > 0 Тогда
        Предупреждение(НСтр("ru = 'Есть строки с нулевым количеством'"));
    КонецЕсли;
КонецПроцедуры

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

2. Обход ДанныеФормыКоллекция циклом на клиенте

Альтернативный подход – это перебор строк коллекции прямо на клиенте с помощью цикла. На первый взгляд, это кажется неоптимальным, но, как показывают тесты некоторых разработчиков, для определенных сценариев это может быть быстрее, чем многократные неявные серверные вызовы.


&НаКлиенте
Процедура ПроверитьНаличиеСтрокСНезаполненнымКоличествомЦиклом()
    ЕстьНулевыеСтроки = Ложь;
    Для Каждого СтрокаТовара Из Объект.Товары Цикл
        Если СтрокаТовара.Количество = 0 Тогда
            ЕстьНулевыеСтроки = Истина;
            Прервать;
        КонецЕсли;
    КонецЦикла;

    Если ЕстьНулевыеСтроки Тогда
        Предупреждение(НСтр("ru = 'Есть строки с нулевым количеством'"));
    КонецЕсли;
КонецПроцедуры

Анализ:

  1. Производительность: Сообщение 2 форума указывает, что "быстрее всего будет использовать обход ДанныеФормыКоллекция циклом". Это может быть верно для случаев, когда количество строк не слишком велико (например, до 200) и весь необходимый для проверки диапазон строк уже загружен на клиент, или же он загружается последовательно. Последовательный доступ к элементам коллекции может быть более оптимизирован платформой, чем произвольный доступ, который может быть вызван НайтиСтроки().
  2. Ограничения: Для очень больших коллекций (сотни и тысячи строк), даже последовательный обход на клиенте все равно будет вызывать порционную загрузку данных с сервера, что может быть медленно. Кроме того, сам клиентский процессор будет тратить время на перебор.
  3. Совет по сравнению: В сообщении 2 также упомянуто, что при сравнении строк НЕ следует пользоваться СтрСравнить(), а именно проверять равенством. Это очень важный момент производительности, так как функция СтрСравнить() может быть значительно медленнее, чем прямое сравнение значений (Строка1 = Строка2) или сравнение примитивных типов (Число = 0).

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

3. Упаковка значений в массив структур для передачи на клиент

Если нам нужны только *данные* найденных строк (например, для отображения информации, но без дальнейшего редактирования исходной коллекции на форме), мы можем использовать подход с упаковкой значений. В этом случае мы выполняем поиск на сервере, затем из каждой найденной строки извлекаем нужные значения и упаковываем их в массив структур или таблицу значений, которую уже возвращаем на клиент.


&НаСервере
Функция ПолучитьДанныеСтрокСНулевымКоличеством(Знач ДанныеФормыКоллекцияТоваров)

    МассивНайденныхСтрок = ДанныеФормыКоллекцияТоваров.НайтиСтроки(Новый Структура("Количество", 0));
    Результат = Новый Массив;

    Для Каждого Строка Из МассивНайденныхСтрок Цикл
        НоваяСтруктура = Новый Структура;
        НоваяСтруктура.Вставить("Номенклатура", Строка.Номенклатура);
        НоваяСтруктура.Вставить("Количество", Строка.Количество);
        // Добавьте все необходимые поля
        Результат.Добавить(НоваяСтруктура);
    КонецЦикла;

    Возврат Результат;

КонецФункции

&НаКлиенте
Процедура ОбработатьНайденныеДанные()
    МассивДанныхСтрок = ПолучитьДанныеСтрокСНулевымКоличеством(Объект.Товары);

    Для Каждого ДанныеСтроки Из МассивДанныхСтрок Цикл
        Сообщить("Найдена номенклатура: " + ДанныеСтроки.Номенклатура + ", Количество: " + ДанныеСтроки.Количество);
        // Здесь мы работаем только со значениями, редактировать исходную строку формы нельзя
    КонецЦикла;
КонецПроцедуры

Анализ:

  1. Преимущества: Этот подход эффективен, если нам действительно нужны только значения. Все операции поиска выполняются на сервере, а на клиент передается уже готовый, компактный массив данных. Это минимизирует количество клиент-серверных взаимодействий.
  2. Недостатки: Мы получаем "слепок" данных, а не ссылки на *оригинальные, редактируемые* строки формы. Если задача подразумевает изменение найденных строк в табличной части формы, этот метод не подходит. Как верно подмечено в сообщении 4: "Если бы нужны были только данные, то я давно использовал РеквизитФормыВЗначение".

4. Передача идентификаторов строк: Наиболее гибкое и производительное решение

Это наиболее рекомендуемый и элегантный способ работы с найденными строками ДанныеФормыКоллекция, если нам необходимо их редактировать. Метод основан на использовании уникальных идентификаторов строк коллекции.

Каждая строка в ДанныеФормыКоллекция имеет уникальный идентификатор. Этот идентификатор генерируется платформой и остается неизменным для данной строки, даже если ее положение в коллекции меняется. Главное, он позволяет однозначно найти эту строку.

Разберем процесс по шагам:

  1. На сервере выполняем поиск: Мы вызываем серверную функцию, которая получает всю ДанныеФормыКоллекция (или работает с Объект.<ТабЧасть>). На сервере выполняем НайтиСтроки(), как нам и рекомендовано.
  2. Получаем идентификаторы: Для каждой найденной строки мы используем метод ПолучитьИдентификатор(). Этот метод возвращает уникальный строковый идентификатор конкретной строки ДанныеФормыЭлементКоллекции.
  3. Возвращаем массив идентификаторов на клиент: Серверная функция возвращает на клиент массив этих идентификаторов. Поскольку идентификаторы являются простыми строковыми значениями, их передача между клиентом и сервером не вызывает проблем.
  4. На клиенте находим строки по идентификаторам: Получив массив идентификаторов, мы перебираем его. Для каждого идентификатора используем метод НайтиПоИдентификатору(), вызванный для той же ДанныеФормыКоллекция на клиенте. Этот метод находит *ту самую* строку коллекции формы.
  5. Редактируем найденные строки на клиенте: Теперь у нас есть прямая ссылка на редактируемую строку коллекции формы. Мы можем изменять её поля, и эти изменения будут автоматически отражены в данных формы.

Рассмотрим пример кода, представленный в сообщении 12, с нашими пояснениями:


// Это серверная функция, которая выполняет поиск и возвращает идентификаторы
&НаСервереБезКонтекста // Или &НаСервере, если нужен контекст формы
Функция ПолучитьМассивИдентификаторовНайденныхСтрок(Знач ДанныеФормыКоллекцияДляПоиска)

    // Выполняем поиск на сервере по заданным критериям.
    // Например, ищем строки, где "Реквизит1" пустой.
    // Если нужно передать параметры поиска, их также передаем в Структуру
    МассивНайденныхСтрокНаСервере = ДанныеФормыКоллекцияДляПоиска.НайтиСтроки(Новый Структура("Реквизит1", ""));
    
    Результат = Новый Массив; // Создаем массив для хранения идентификаторов

    // Перебираем найденные строки на сервере
    Для Каждого Стр Из МассивНайденныхСтрокНаСервере Цикл
        // Для каждой строки получаем ее уникальный идентификатор
        Результат.Добавить(Стр.ПолучитьИдентификатор());
    КонецЦикла;

    // Возвращаем массив идентификаторов на клиент
    Возврат Результат;

КонецФункции

// Это клиентская процедура, которая вызывает серверную функцию
// и затем работает с найденными строками на клиенте
&НаКлиенте
Процедура ОбработатьСтрокиПоИдентификаторам(Команда) // Например, при нажатии кнопки

    // Предположим, 'Таблица' - это реквизит формы типа ДанныеФормыКоллекция,
    // например, Объект.Товары или Элементы.ТаблицаТоваров.ТекущиеДанные
    // В данном примере передается Таблица, что подразумевает доступ к реквизиту формы
    // в клиентском контексте, который, в свою очередь, является ДанныеФормыКоллекция.
    МассивИдентификаторов = ПолучитьМассивИдентификаторовНайденныхСтрок(Таблица);

    Для Каждого ИдентификаторСтроки Из МассивИдентификаторов Цикл
        // Находим реальную строку коллекции на клиенте по идентификатору
        СтрокаТаблицы = Таблица.НайтиПоИдентификатору(ИдентификаторСтроки);
        
        Если СтрокаТаблицы <> Неопределено Тогда
            // Теперь у нас есть прямая ссылка на редактируемую строку
            // Мы можем изменять ее поля. Изменения автоматически отразятся на форме.
            СтрокаТаблицы.Реквизит1 = "Новое значение";
            СтрокаТаблицы.Количество = 10;
            Сообщить("Строка с идентификатором " + ИдентификаторСтроки + " обновлена.");
        Иначе
            Сообщить("Строка с идентификатором " + ИдентификаторСтроки + " не найдена на клиенте (возможно, была удалена).");
        КонецЕсли;
    КонецЦикла;

КонецПроцедуры

Важные замечания по идентификаторам:

  1. Уникальность: Идентификаторы уникальны в пределах одной коллекции. Даже если строка удаляется и затем добавляется снова, она получит новый идентификатор.
  2. Сохранение: Идентификаторы сохраняются при записи и чтении данных формы. Это означает, что если вы получили идентификаторы, закрыли форму и открыли ее снова, эти идентификаторы могут быть использованы для поиска тех же строк, если строки остались в коллекции.
  3. Получение коллекции: В примере ПолучитьМассивИдентификаторовНайденныхСтрок(Знач ДанныеФормыКоллекцияДляПоиска) передается коллекция по значению. Если функция серверная и имеет контекст формы (&НаСервере), то можно обращаться к Объект.<ИмяТабличнойЧасти> напрямую без передачи параметром. Если функция &НаСервереБезКонтекста, то передача коллекции параметром обязательна.

Преимущества этого подхода:

Оптимизация производительности и дополнительные нюансы

Помимо выбора правильного метода работы, существуют общие принципы и нюансы (а также помощник отладки — см. инструмент отладки и анализа кода 1С), которые помогут нам добиться максимальной производительности.

  1. Минимизация клиент-серверных вызовов:

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

    1. Клиент: найти строки1 -> Сервер: вернуть ИД1
    2. Клиент: обработать ИД1
    3. Клиент: найти строки2 -> Сервер: вернуть ИД2
    4. Клиент: обработать ИД2

    Лучше сделать один вызов:

    1. Клиент: вызвать комплексную обработку -> Сервер: (найти строки1, обработать их, найти строки2, обработать их) -> Сервер: вернуть финальный результат/статус

    Этот подход значительно сокращает сетевые задержки и накладные расходы на каждый вызов.

  2. Использование замеров производительности:

    Для диагностики медленных операций и выявления неявных серверных вызовов, которые могут возникать даже при, казалось бы, "клиентском" коде, крайне рекомендуется использовать инструмент "Замер производительности" в конфигураторе 1С. Этот инструмент позволяет точно определить, какие участки кода занимают больше всего времени и сколько клиент-серверных вызовов они провоцируют.

  3. Негарантированный порядок строк при НайтиСтроки():

    Важно помнить, что метод НайтиСтроки() для таблиц значений (и, по аналогии, для ДанныеФормыКоллекция) не гарантирует, что порядок возвращаемых строк будет таким же, как в исходной таблице. Если порядок строк имеет значение для вашей логики, вам потребуется либо использовать дополнительные механизмы (например, добавлять колонки с индексами и сортировать по ним), либо применять другие методы обхода.

  4. Индексирование для таблиц значений:

    Хотя ДанныеФормыКоллекция не является напрямую таблицей значений, этот принцип полезен. Для оптимизации поиска в обычных таблицах значений, если вы используете НайтиСтроки(), убедитесь, что список полей индекса таблицы точно совпадает со структурой поиска (порядок полей не важен). В противном случае индекс не будет задействован, и поиск будет выполняться перебором всех строк, что является ресурсоемкой операцией.

Итак, мы рассмотрели различные подходы к решению задачи эффективного использования НайтиСтроки() с ДанныеФормыКоллекция, уделяя особое внимание клиент-серверному взаимодействию и производительности. Передача идентификаторов строк является наиболее гибким и рекомендованным решением, позволяющим выполнить поиск на сервере и получить возможность редактирования найденных строк на клиенте.

← На главную