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