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