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