При интеграции с внешними системами через веб-сервисы (SOAP) часто возникает задача создания и заполнения сложных XDTO-объектов. В процессе можно столкнуться с ошибкой «Поле объекта недоступно для записи», даже если, на первый взгляд, код выглядит корректно. Разберем на примере, почему возникает эта ошибка и как правильно работать с такими объектами.
Автор столкнулся с проблемой при попытке заполнить свойство MATERIAL у созданного XDTO-объекта. Код завершался ошибкой на строке ОбъектXDTO.MATERIAL = MATERIAL;. Проанализируем ситуацию и найдем правильное решение.
Ключ к пониманию ошибки лежит в структуре данных, которая описывается XSD-схемой веб-сервиса. В схеме для каждого элемента можно указать атрибуты minOccurs (минимальное количество вхождений) и maxOccurs (максимальное количество вхождений).
Если для элемента, такого как MATERIAL, атрибут maxOccurs установлен в значение больше 1 (например, maxOccurs="100" или maxOccurs="unbounded"), платформа 1С интерпретирует это свойство не как одиночный объект, а как список или коллекцию. В терминах 1С это будет объект типа СписокXDTO (XDTOList).
Именно здесь и кроется главная особенность:
Свойство, содержащее коллекцию (СписокXDTO), является свойством только для чтения.
Это означает, что вы не можете напрямую присвоить ему новое значение (новый объект или массив). Платформа автоматически создает пустой объект СписокXDTO в этом свойстве в момент создания родительского объекта. Ваша задача — не заменить этот список, а добавить элементы в него.
Таким образом, ошибка «Поле недоступно для записи» возникает из-за попытки перезаписать системно созданную коллекцию, что запрещено платформой.
Рассмотрим, как исправить код, чтобы он работал корректно. Разберем процесс по шагам, от получения типов до добавления данных в коллекцию.
Сначала, как и в исходном коде, мы подключаемся к веб-сервису и получаем фабрику XDTO.
// Подключение к веб-сервису
мОпределения = Новый WSОпределения(Узел.URL + "?wsdl", Узел.Пользователь, Узел.Пароль);
Прокси = Новый WSПрокси(мОпределения, "urn:sap-com:document:sap:soap:functions:mc-style", "MaterialMasterService", "MaterialMasterPort");
Прокси.Пользователь = Узел.Пользователь;
Прокси.Пароль = Узел.Пароль;
// Получаем фабрику XDTO из прокси
Фабрика = Прокси.ФабрикаXDTO;
Далее нужно правильно получить типы для родительского объекта и для элемента, который мы будем добавлять в список. Автор в одном из сообщений пришел к верному способу получения типа для вложенного элемента:
DT_MaterialList).MATERIAL).Это надежный способ, так как он не зависит от ручного указания полного имени типа с пространством имен.
// 1. Получаем тип родительского объекта (например, тип параметра веб-метода)
// Имя типа нужно смотреть в WSDL или в отладчике
НоменклатураТип = Фабрика.Тип("urn:sap-com:document:sap:soap:functions:mc-style", "DT_MaterialList");
// 2. Получаем тип элемента, который будет находиться в списке MATERIAL
// Обращаемся к свойству родительского типа и берем его тип
МатериалТип = НоменклатураТип.Свойства.Получить("MATERIAL").Тип;
Важно: НоменклатураТип.Свойства.Получить("MATERIAL").Тип вернет тип элемента коллекции, а не тип самой коллекции. Это именно то, что нам нужно для создания нового материала.
Теперь создаем экземпляры родительского и дочернего объектов с помощью метода Фабрика.Создать().
// Создаем родительский объект XDTO
ОбъектXDTO = Фабрика.Создать(НоменклатураТип);
// Создаем объект для добавления в список
МатериалXDTO = Фабрика.Создать(МатериалТип);
Заполняем свойства созданного объекта МатериалXDTO, как и планировалось.
// Заполняем поля простого типа в родительском объекте, если они есть
ОбъектXDTO.INTCODE = 13;
// Заполняем поля дочернего объекта
МатериалXDTO.MATNR = Выборка.Артикул + "NF";
МатериалXDTO.DELIND = 0;
МатериалXDTO.MATTXT = Выборка.Наименование;
МатериалXDTO.BASEUOM = Выборка.БазоваяЕдиницаИзмерения;
МатериалXDTO.ARTIC = Выборка.Артикул + "NF";
МатериалXDTO.PACMASS = Выборка.Вес;
МатериалXDTO.NESTAM = Выборка.ЕдиницаИзмеренияМестКоэффициент;
МатериалXDTO.SHLIFE = Выборка.СрокГодности;
Это ключевой момент. Вместо неверной операции присваивания ОбъектXDTO.MATERIAL = МатериалXDTO; используем правильный подход: обращаемся к свойству-коллекции MATERIAL и вызываем у него метод Добавить().
// Правильный способ: добавляем созданный и заполненный объект в коллекцию
ОбъектXDTO.MATERIAL.Добавить(МатериалXDTO);
Если по логике нужно передать несколько материалов, можно организовать цикл и добавлять в него столько объектов, сколько требуется. Каждый раз нужно будет создавать новый экземпляр МатериалXDTO.
Теперь объект ОбъектXDTO полностью сформирован и готов для передачи в качестве параметра метода веб-сервиса.
В процессе работы не всегда очевидно, какая структура у объекта получилась в итоге. Чтобы проверить себя, можно использовать мощный инструмент — сериализацию XDTO-объекта в XML-строку. Это позволит увидеть финальный XML, который будет отправлен на сервер.
Для этого используется объект ЗаписьXML.
// Создаем объект для записи XML в строку
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку("UTF-8"); // Указываем кодировку
// Устанавливаем красивый вывод с отступами для удобства чтения
ЗаписьXML.ЗаписатьОбъявлениеXML();
ЗаписьXML.Форматировать = Истина;
// Сериализуем наш XDTO-объект в XML
Фабрика.ЗаписатьXML(ЗаписьXML, ОбъектXDTO, "ИмяКорневогоЭлемента"); // Имя корневого элемента можно посмотреть в схеме
// Получаем результат в виде строки
XMLСтрока = ЗаписьXML.Закрыть();
// Теперь можно посмотреть переменную XMLСтрока в отладчике или вывести в Сообщения
Сообщить(XMLСтрока);
Этот метод отладки сразу покажет, является ли свойство списком (в XML будут повторяющиеся теги), корректно ли заполнены все поля и соблюдена ли иерархия, которую ожидает веб-сервис.