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