В практике разработки на платформе 1С:Предприятие часто встречается ситуация, когда разработчик устанавливает, казалось бы, жесткие ограничения на заполнение полей в конфигураторе, но в базе данных все равно появляются "пустые" записи. Особенно часто это происходит с регистрами сведений, данные в которые пишутся не пользователями вручную, а регламентными заданиями, обработками или при проведении документов.
В этой статье мы подробно разберем, почему стандартные механизмы проверки могут "пропускать" незаполненные значения, в чем разница между свойствами метаданных и как гарантированно защитить целостность данных при программной записи.
Чтобы понять корень проблемы, нам необходимо четко разделить два свойства, которые новички (и иногда опытные программисты) часто путают или считают взаимозаменяемыми. Давайте проанализируем их техническую суть.
Рассмотрим подробнее каждый из них, так как понимание их различий является ключом к решению задачи.
Если мы говорим о свойстве Запрет незаполненных значений, то оно чаще всего применяется к измерениям регистра. Когда вы устанавливаете этот флаг в Истину, платформа на уровне СУБД создает ограничение NOT NULL.
Это "жесткая" проверка. Если попытаться записать в такое поле NULL, СУБД вернет ошибку, и транзакция будет отменена. Однако здесь есть нюанс: для СУБД "пустая ссылка" (например, Справочники.Номенклатура.ПустаяСсылка()) — это не NULL. Это конкретное значение (строка нулей в поле ID). Поэтому технически запись с пустой ссылкой может попасть в таблицу, если не включены дополнительные проверки платформы.
Важно: Для ресурсов регистра это свойство часто работает мягче или вообще не приводит к желаемому эффекту блокировки записи на уровне платформы, если не настроены другие параметры.
Именно с этим свойством чаще всего возникают проблемы, описанные в исходной задаче. Свойство Проверка заполнения (значение "Выдавать ошибку") предназначено для логической валидации данных.
Давайте выясним, как оно работает:
В рассматриваемой нами ситуации измерения регистра были заполнены (так как они образуют уникальный ключ записи), а вот ресурсы, помеченные как обязательные через Проверка заполнения, оставались пустыми. Поскольку запись производилась фоновым заданием программно, платформа игнорировала настройки проверки и молча сохраняла "битые" данные.
Когда мы пишем код вида:
Набор = РегистрыСведений.НашРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Измерение.Установить(Значение);
НоваяЗапись = Набор.Добавить();
// Забыли заполнить Ресурс!
Набор.Записать();
Метод Записать() выполняет физическую запись данных в таблицу. Он не инициирует вызов валидации данных, описанных в свойстве Проверка заполнения. Это сделано для оптимизации производительности: считается, что программист, пишущий алгоритм записи, сам контролирует целостность данных.
Существует несколько способов заставить платформу проверять заполнение полей при программной работе. Разберем их от простого к более надежному.
У набора записей регистра сведений (как и у объектов справочников и документов) есть метод ПроверитьЗаполнение(). Если вы пишете алгоритм заполнения регистра, правильным тоном будет явный вызов этого метода перед записью.
Посмотрим на исправленный пример кода:
Набор = РегистрыСведений.НашРегистр.СоздатьНаборЗаписей();
Набор.Отбор.Измерение.Установить(Значение);
НоваяЗапись = Набор.Добавить();
// ... заполнение полей ...
// Явный вызов проверки
Если НЕ Набор.ПроверитьЗаполнение() Тогда
// Здесь можно записать ошибку в журнал регистрации
ЗаписьЖурналаРегистрации("Ошибка заполнения регистра",
УровеньЖурналаРегистрации.Ошибка,,, "Не заполнены обязательные ресурсы");
// Прерываем выполнение, чтобы не записывать пустые данные
Возврат;
КонецЕсли;
Набор.Записать();
Этот метод задействует штатный механизм платформы: он проверит все поля, помеченные в конфигураторе как "Выдавать ошибку", и даже выполнит код в обработчике ОбработкаПроверкиЗаполнения в модуле набора записей, если он там есть.
Если записей регистра создается много в разных местах конфигурации (разные документы, обработки, регламентные задания), искать и править каждый вызов Записать() неэффективно. В этом случае идеальным решением будет использование Подписки на событие.
Нам понадобится подписка на событие ПередЗаписью для нужного регистра сведений. В обработчике подписки мы можем реализовать жесткую проверку.
Разберем пример реализации:
Процедура ПередЗаписьюРегистраСведений(Источник, Отказ, Замещение) Экспорт
// Если обмен данными - обычно проверки не делаем, но зависит от задачи
Если Источник.ОбменДанными.Загрузка Тогда
Возврат;
КонецЕсли;
Для Каждого Запись Из Источник Цикл
Если НЕ ЗначениеЗаполнено(Запись.ВажныйРесурс) Тогда
ТекстОшибки = "Попытка записи пустого ресурса в регистр!";
// Сообщаем пользователю или пишем в лог
ЗаписьЖурналаРегистрации("Контроль целостности",
УровеньЖурналаРегистрации.Ошибка,,, ТекстОшибки);
// Отменяем транзакцию записи
Отказ = Истина;
Возврат; // Прерываем цикл, так как ошибка уже найдена
КонецЕсли;
КонецЦикла;
КонецПроцедуры
Такой подход гарантирует, что независимо от того, откуда пришла запись (из формы, из регламентного задания или внешней обработки), данные будут проверены перед попаданием в базу.
В обсуждении проблемы также упоминался флаг ОбменДанными.Загрузка = Истина. Давайте уточним его роль.
Если при записи набора установлен этот флаг, платформа отключает большинство проверок, чтобы ускорить загрузку данных (например, при обмене между базами). В этом режиме:
Если вы столкнулись с "битыми" данными, обязательно проверьте код (поиск по конфигурации): не устанавливается ли где-то это свойство перед записью. Если да, то даже ПроверитьЗаполнение() может не сработать так, как вы ожидаете, а подписка на событие должна быть написана аккуратно (как в примере выше), чтобы понимать, нужно ли блокировать такую запись или нет.
Подводя итог, можно сформулировать следующие правила для обеспечения целостности данных:
ПроверитьЗаполнение() перед записью объекта.ПередЗаписью, где проверяйте заполнение ключевых ресурсов. Это последний рубеж обороны.