Почему возникает ошибка «Обращение к процедуре как к функции» в 1С и как правильно работать с регистром сведений?

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

При разработке и отладке конфигураций на платформе 1С:Предприятие программисты часто сталкиваются с неочевидным поведением системы при работе с регистрами сведений. В этой статье мы подробно разберем, почему возникает ошибка «Обращение к процедуре как к функции», выясним, кто такие «убийцы регистров сведений», и научимся правильно изменять или удалять записи регистра, когда стандартный отбор по измерениям использовать невозможно.

Разбор ошибки «Обращение к процедуре как к функции»

Эта ошибка указывает на синтаксическое или логическое противоречие в коде: платформа ожидает получить возвращаемое значение (что свойственно только функциям), но вместо этого вызывается процедура, которая по определению ничего не возвращает.

Рассмотрим две основные причины возникновения этой ошибки:

  1. Ошибки непосредственного написания кода. Если разработчик пытается присвоить результат выполнения процедуры переменной или использовать ее внутри логического условия.

    Пример ошибочного кода:

    
    // Ошибка: метод Прочитать() является процедурой и не возвращает значения!
    Если МенеджерРегистра.Прочитать() Тогда 
        // Какой-то код
    КонецЕсли;
    

    Правильный вариант использования выглядит следующим образом:

    
    МенеджерРегистра.Прочитать(); // Просто вызываем процедуру
    Если МенеджерРегистра.Выбран() Тогда // Метод Выбран() — это функция, возвращающая Булево
        // Код обработки считанных данных
    КонецЕсли;
    
  2. Вычисление выражения в табло отладчика. Это именно та ситуация, с которой столкнулся автор темы. Когда мы находимся в режиме отладки на точке останова и выделяем строку МенеджерРегистра.Прочитать(), а затем нажимаем клавиши Shift + F9 (или добавляем выражение в «Табло»), платформа пытается вычислить значение этого выражения. Поскольку процедура не возвращает результат, конфигуратор выдает ошибку «Обращение к процедуре как к функции».

Решение: Для проверки того, прочиталась ли запись в режиме отладки, не нужно вычислять метод Прочитать() в табло. Сначала выполните этот шаг в коде (шагнуть по F8), а затем добавьте в табло сам объект МенеджерРегистра и анализируйте его свойства (заполнились ли ресурсы и реквизиты) либо вычислите функцию МенеджерРегистра.Выбран().

Проанализируем ситуацию, когда метод Прочитать() отрабатывает без ошибок, но менеджер записи остается пустым. Выясним, почему это происходит.

Правильная работа с Менеджером Записи

Объект РегистрСведенийМенеджерЗаписи предназначен для работы с одной конкретной записью регистра. Чтобы платформа могла однозначно идентифицировать и прочитать эту запись из базы данных, мы обязаны передать значения всех ключевых полей — измерений регистра (а также период, если регистр является периодическим).

Разберем типичную ошибку начинающих разработчиков на примере кода из обсуждения:


МенеджерРегистра = РегистрыСведений.НоменклатураПоставщика.СоздатьМенеджерЗаписи();
МенеджерРегистра.Поставщик = РезультатЗапроса.Поставщик;
МенеджерРегистра.Наименование = РезультатЗапроса.Наименование;
МенеджерРегистра.ХарактеристикаНоменклатуры = РезультатЗапроса.ХарактеристикаНоменклатуры;
МенеджерРегистра.КодНоменклатурыПоставщика = 2;
МенеджерРегистра.Прочитать();

В данном случае метод Прочитать() не заполнит менеджер данными, если поля Наименование или КодНоменклатурыПоставщика являются ресурсами или реквизитами, а не измерениями. Платформа просто проигнорирует их при поиске записи и попытается найти строку по незаполненным ключевым измерениям.

Правило: Перед вызовом метода Прочитать() у менеджера записи необходимо заполнять только измерения регистра. Ресурсы и реквизиты заполнятся автоматически после успешного чтения.

Опасность «Убийц Регистров Сведений» (Набор Записей)

В процессе обсуждения участники упомянули так называемых «убийц РС». Этот термин описывает критическую ошибку при использовании объекта РегистрСведенийНаборЗаписей, которая приводит к полной и безвозвратной очистке данных регистра.

Рассмотрим, как работает этот опасный механизм:


НаборЗаписей = РегистрыСведений.НоменклатураПоставщика.СоздатьНаборЗаписей();
// Разработчик забыл установить отбор!
НаборЗаписей.Записать(); 

При вызове метода Записать() без параметров платформа по умолчанию использует режим замещения (параметр Замещать равен Истина). Это означает следующее: база данных должна стереть все старые записи, соответствующие установленному отбору, и записать на их место новые записи из набора в памяти.

Если отбор не был установлен, область замещения охватывает весь регистр. А поскольку в созданном объекте НаборЗаписей нет ни одной строки (он пустой), платформа просто очистит весь регистр сведений в базе данных.

Чтобы избежать этой катастрофы, всегда придерживайтесь правил безопасной разработки:

  1. Всегда устанавливайте жесткий отбор перед записью набора.
  2. Если требуется очистить конкретные данные, явно пишите комментарии в коде, чтобы другие разработчики понимали ваши намерения.
  3. Используйте метод установки отбора Установить(), который гарантирует корректное назначение фильтра.

Пример безопасного удаления записей по конкретному измерению:


НаборЗаписей = РегистрыСведений.НоменклатураПоставщика.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Поставщик.Установить(ВыбранныйПоставщик);
// Набор пустой, но установлен отбор по поставщику. 
// Запись этого набора удалит из базы данных только записи по этому поставщику!
НаборЗаписей.Записать(Истина); 

Как изменить или удалить записи регистра по значению ресурса или реквизита

В практике часто возникает сложная задача. Например: нужно удалить все записи в независимом регистре сведений, у которых ресурс Цвет равен значению Фиолетовый (или заменить его на Баклажановый).

Сложность заключается в том, что объект Отбор у набора записей не поддерживает работу с ресурсами и реквизитами — фильтровать данные при записи можно исключительно по ключевым измерениям. Напрямую установить отбор по ресурсу Цвет платформа не позволит.

Разберем по шагам правильный алгоритм решения этой задачи.

Шаг 1: Получение уникальных ключей записей через запрос

Сначала нам нужно выбрать из базы данных все строки, которые соответствуют нашему условию по ресурсу, и получить значения их измерений. Нам понадобятся абсолютно все измерения, составляющие уникальный ключ записи.


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	НоменклатураПоставщика.Поставщик КАК Поставщик,
    |	НоменклатураПоставщика.Номенклатура КАК Номенклатура,
    |	НоменклатураПоставщика.Характеристика КАК Характеристика
    |ИЗ
    |	РегистрСведений.НоменклатураПоставщика КАК НоменклатураПоставщика
    |ГДЕ
    |	НоменклатураПоставщика.Цвет = &ЦветФиолетовый";

Запрос.УстановитьПараметр("ЦветФиолетовый", Справочники.Цвета.НайтиПоНаименованию("Фиолетовый"));
РезультатВыборки = Запрос.Выполнить().Выбрать();

Шаг 2: Обработка записей в цикле

В зависимости от объема данных и требований к производительности мы можем использовать два разных метода изменения/удаления.

Способ А: Через Менеджер Записи (подходит для штучных изменений)

Этот способ интуитивно понятен, но работает медленнее на больших объемах данных, так как производит запись каждой строки отдельной транзакцией.


Пока РезультатВыборки.Следующий() Цикл
    
    МенеджерЗаписи = РегистрыСведений.НоменклатураПоставщика.СоздатьМенеджерЗаписи();
    // Заполняем исключительно измерения для точного позиционирования
    ЗаполнитьЗначенияСвойств(МенеджерЗаписи, РезультатВыборки);
    
    МенеджерЗаписи.Прочитать();
    Если МенеджерЗаписи.Выбран() Тогда
        // Для изменения значения:
        МенеджерЗаписи.Цвет = Справочники.Цвета.НайтиПоНаименованию("Баклажановый");
        МенеджерЗаписи.Записать();
        
        // Либо для удаления записи:
        // МенеджерЗаписи.Удалить();
    КонецЕсли;
    
КонецЦикла;

Способ Б: Через Набор Записей (высокопроизводительный метод)

Этот метод предпочтительнее для оптимизации работы с базой данных. Если нам нужно именно удалить записи, мы можем сделать это мгновенно, не тратя время на предварительное чтение записи из базы данных в память.

Рассмотрим пример быстрого удаления записей через пустой набор с отбором по всем измерениям:


Пока РезультатВыборки.Следующий() Цикл
    
    НаборЗаписей = РегистрыСведений.НоменклатураПоставщика.СоздатьНаборЗаписей();
    
    // Устанавливаем точечный отбор по всем измерениям для конкретной записи
    НаборЗаписей.Отбор.Поставщик.Установить(РезультатВыборки.Поставщик);
    НаборЗаписей.Отбор.Номенклатура.Установить(РезультатВыборки.Номенклатура);
    НаборЗаписей.Отбор.Характеристика.Установить(РезультатВыборки.Характеристика);
    
    // Записываем пустой набор с замещением — запись удаляется мгновенно!
    НаборЗаписей.Записать(Истина);
    
КонецЦикла;

Если же стоит задача изменить ресурс (например, перезаписать цвет), то код модифицируется так:


Пока РезультатВыборки.Следующий() Цикл
    
    НаборЗаписей = РегистрыСведений.НоменклатураПоставщика.СоздатьНаборЗаписей();
    НаборЗаписей.Отбор.Поставщик.Установить(РезультатВыборки.Поставщик);
    НаборЗаписей.Отбор.Номенклатура.Установить(РезультатВыборки.Номенклатура);
    НаборЗаписей.Отбор.Характеристика.Установить(РезультатВыборки.Характеристика);
    
    НаборЗаписей.Прочитать();
    Если НаборЗаписей.Количество() > 0 Тогда
        // Изменяем ресурс в прочитанной строке
        НаборЗаписей[0].Цвет = Справочники.Цвета.НайтиПоНаименованию("Баклажановый");
        НаборЗаписей.Записать(Истина);
    КонецЕсли;
    
КонецЦикла;

Таким образом, мы рассмотрели все тонкости работы с транзакциями, отборами и объектами метаданных при операциях чтения, записи и удаления в регистрах сведений 1С. Понимание этих механизмов убережет ваши базы данных от потери информации и оптимизирует скорость работы конфигурации.

← На главную