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