Почему не срабатывает запрет незаполненных значений в регистре сведений при программной записи?

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

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

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

Два механизма контроля: в чем путаница?

Чтобы понять корень проблемы, нам необходимо четко разделить два свойства, которые новички (и иногда опытные программисты) часто путают или считают взаимозаменяемыми. Давайте проанализируем их техническую суть.

  1. Запрет незаполненных значений (DenyIncompleteValues). Это свойство влияет на структуру базы данных (уровень SQL).
  2. Проверка заполнения (FillChecking). Это свойство влияет на логику работы формы и валидацию объекта (уровень встроенного языка 1С).

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

Механизм 1: Запрет незаполненных значений

Если мы говорим о свойстве Запрет незаполненных значений, то оно чаще всего применяется к измерениям регистра. Когда вы устанавливаете этот флаг в Истину, платформа на уровне СУБД создает ограничение NOT NULL.

Это "жесткая" проверка. Если попытаться записать в такое поле NULL, СУБД вернет ошибку, и транзакция будет отменена. Однако здесь есть нюанс: для СУБД "пустая ссылка" (например, Справочники.Номенклатура.ПустаяСсылка()) — это не NULL. Это конкретное значение (строка нулей в поле ID). Поэтому технически запись с пустой ссылкой может попасть в таблицу, если не включены дополнительные проверки платформы.

Важно: Для ресурсов регистра это свойство часто работает мягче или вообще не приводит к желаемому эффекту блокировки записи на уровне платформы, если не настроены другие параметры.

Механизм 2: Проверка заполнения

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

Давайте выясним, как оно работает:

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

Почему программная запись игнорирует проверки?

Когда мы пишем код вида:


Набор = РегистрыСведений.НашРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Измерение.Установить(Значение);
НоваяЗапись = Набор.Добавить();
// Забыли заполнить Ресурс!
Набор.Записать();

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

Решение проблемы

Существует несколько способов заставить платформу проверять заполнение полей при программной работе. Разберем их от простого к более надежному.

Способ 1: Явный вызов метода ПроверитьЗаполнение()

У набора записей регистра сведений (как и у объектов справочников и документов) есть метод ПроверитьЗаполнение(). Если вы пишете алгоритм заполнения регистра, правильным тоном будет явный вызов этого метода перед записью.

Посмотрим на исправленный пример кода:


Набор = РегистрыСведений.НашРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Измерение.Установить(Значение);
НоваяЗапись = Набор.Добавить();
// ... заполнение полей ...

// Явный вызов проверки
Если НЕ Набор.ПроверитьЗаполнение() Тогда
    // Здесь можно записать ошибку в журнал регистрации
    ЗаписьЖурналаРегистрации("Ошибка заполнения регистра", 
        УровеньЖурналаРегистрации.Ошибка,,, "Не заполнены обязательные ресурсы");
    // Прерываем выполнение, чтобы не записывать пустые данные
    Возврат; 
КонецЕсли;

Набор.Записать();

Этот метод задействует штатный механизм платформы: он проверит все поля, помеченные в конфигураторе как "Выдавать ошибку", и даже выполнит код в обработчике ОбработкаПроверкиЗаполнения в модуле набора записей, если он там есть.

Способ 2: Использование Подписки на событие

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

Нам понадобится подписка на событие ПередЗаписью для нужного регистра сведений. В обработчике подписки мы можем реализовать жесткую проверку.

Разберем пример реализации:


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

КонецПроцедуры

Такой подход гарантирует, что независимо от того, откуда пришла запись (из формы, из регламентного задания или внешней обработки), данные будут проверены перед попаданием в базу.

Влияние Обмена Данными

В обсуждении проблемы также упоминался флаг ОбменДанными.Загрузка = Истина. Давайте уточним его роль.

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

  1. Не выполняются проверки заполнения.
  2. Игнорируются многие обработчики событий или в них срабатывает условие выхода при загрузке.

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

Выводы и рекомендации

Подводя итог, можно сформулировать следующие правила для обеспечения целостности данных:

  1. Не полагайтесь только на галочки в конфигураторе. Свойство "Проверка заполнения" гарантированно работает только при интерактивном вводе данных пользователем.
  2. Используйте явную проверку. В программном коде, особенно в регламентных заданиях, вызывайте метод ПроверитьЗаполнение() перед записью объекта.
  3. Страхуйте подписками. Для критически важных регистров создавайте подписку на событие ПередЗаписью, где проверяйте заполнение ключевых ресурсов. Это последний рубеж обороны.
  4. Анализируйте источник. Если пустые записи уже появляются, используйте Журнал регистрации с включенным стеком вызовов (в новых версиях платформы) или добавьте свою запись в лог в подписку на событие, чтобы отследить, какой именно алгоритм создает некорректные данные.
← На главную