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