Ensure в цикле вместо EnsureBatch
критично по производительности
Каждый вызов Ensure делает как
минимум один запрос к БД (поиск по наименованию),
а при создании — ещё один (запись). При 200 позициях в списке это 200–400 запросов.
На большой базе это десятки секунд или таймаут.
// 200 позиций = 200–400 запросов! Для Каждого Стр Из ДанныеExcel Цикл Ном = A1sCatalogs.Ensure( "Номенклатура", Стр.Наименование, A1sDS.Of("Родитель", Гр)); A1sDocs.AddRow(Dok, "Продукция", A1sDS.Of("Номенклатура", Ном, ...)); КонецЦикла;
// Все имена одним батчем ДО цикла Имена = Новый Массив; Для Каждого Стр Из ДанныеExcel Цикл Имена.Добавить(Стр.Наименование); КонецЦикла; A1sCatalogs.EnsureBatch( "Номенклатура", A1sAR.RemoveDuplicates(Имена), A1sDS.Of("Родитель", Гр)); // Теперь LoadRows найдёт их в базе A1sDocs.LoadRows(Dok, "Продукция", ДанныеExcel);
EnsureBatch, PickByName), потом цикл. Внутри цикла — только работа с
уже полученными ссылками. Это снижает число запросов к БД с N до 1.GetObject для чтения вместо Details
лишние запросы и память
GetObject()
открывает весь объект документа или справочника — загружает все реквизиты и все ТЧ.
Если нужно просто прочитать несколько полей — это избыточно.
Details()
делает один лёгкий запрос только за нужными полями.
ДокОбъект = ДокСсылка.GetObject(); Сообщить(ДокОбъект.Номер); Сообщить(ДокОбъект.Дата); Сообщить(ДокОбъект.Комментарий); // Загружены ВСЕ ТЧ документа — зря
Инфо = A1sDocs.Details( ДокСсылка, "Комментарий"); Сообщить(Инфо.Number); Сообщить(Инфо.Date); Сообщить(Инфо.Комментарий); // Один лёгкий запрос, нет ТЧ
Details для документов и Details для справочников. Для изменения —
GetObject + изменить + Записать или Update.
Поиск / ByName внутри цикла загрузки
критично по производительности
Если внутри цикла по строкам документа вызывать ByName, ByAttr или
Ensure
для каждой строки — получаем N запросов. Если ещё и список групп ищется внутри — N×M.
Решение: собрать все нужные ссылки до цикла через batch-функции.
Для Каждого Стр Из ДанныеExcel Цикл // Запрос к БД на КАЖДУЮ строку! ЕдИзм = A1sCatalogs.ByName( "КлассификаторЕдиницИзмерения", Стр.Единица); Группа = A1sCatalogs.ByName( "НоменклатурныеГруппы", Стр.Группа); // ещё 2 запроса на строку... КонецЦикла;
// 2 запроса на весь список — до цикла МапЕд = A1sCatalogs.PickByName( "КлассификаторЕдиницИзмерения", СписокЕдиниц); // Array→Map МапГрупп = A1sCatalogs.PickByName( "НоменклатурныеГруппы", СписокГрупп); Для Каждого Стр Из ДанныеExcel Цикл ЕдИзм = МапЕд[Стр.Единица]; // O(1) Группа = МапГрупп[Стр.Группа]; // O(1) КонецЦикла;
Игнорирование Failed из PostAll / UpdateAll
скрытая потеря данных
PostAll, UnpostAll, UpdateAll и MoveBatch возвращают
структуру
{Success, Failed}.
Если не проверять Failed — часть операций молча провалится, документы останутся непроведёнными,
а вы узнаете об этом только при следующей выгрузке или жалобе пользователя.
// PostAll вернул результат — мы его выбросили A1sDocs.PostAll(МассивДоков); Сообщить("Готово"); // Сколько провалилось? Неизвестно
Итог = A1sDocs.PostAll(МассивДоков); Сообщить("✓ " + Итог.Success.Количество()); Если Итог.Failed.Количество() > 0 Тогда Сообщить("✗ Ошибки: " + Итог.Failed.Количество()); КонецЕсли;
{Success, Failed},
всегда добавляйте проверку: Если Итог.Failed.Количество() > 0 Тогда. Даже если просто пишете
в лог — это лучше, чем молчание.AddRow со строками вместо AddRowAuto
ошибка типов при записи
AddRow ожидает готовые ссылки. Если передать строку в поле типа
СправочникСсылка.Номенклатура — платформа выбросит исключение при записи документа
или молча запишет пустую ссылку. Для строковых данных используйте AddRowAuto.
// "Квартира 101" — строка, не ссылка // При записи → ошибка "Несоответствие типов" A1sDocs.AddRow(Dok, "Продукция", A1sDS.Of( "Номенклатура", "Квартира 101", "Количество", 1));
// AddRowAuto: видит что "Номенклатура" — ссылочное поле // вызывает EnsureByType → получает ссылку A1sDocs.AddRowAuto(Dok, "Продукция", A1sDS.Of( "Номенклатура", "Квартира 101", "Количество", 1));
AddRowAuto или LoadRows. Данные уже в виде ссылок (из запроса, из справочника) —
используйте AddRow или AddRows.
Ensure вместо EnsureUpdate при регулярной синхронизации
данные не обновляются
Ensure — «найди или создай». Нашёл → вернул ссылку и ничего не изменил.
Если сегодня прайс изменился — Ensure не обновит цену у существующего элемента.
Для регулярной синхронизации нужен EnsureUpdate — он всегда обновляет реквизиты.
// Квартира 101 уже есть в базе с ценой 3 500 000 // EnsureUpdate нужен, но используем Ensure Для Каждого Поз Из НовыйПрайс Цикл A1sCatalogs.Ensure( "Номенклатура", Поз.Наименование, A1sDS.Of("Цена", Поз.Цена)); КонецЦикла; // Цена не обновилась! Ensure нашёл элемент // и вернул ссылку без изменений.
Для Каждого Поз Из НовыйПрайс Цикл A1sCatalogs.EnsureUpdate( "Номенклатура", Поз.Наименование, A1sDS.Of("Цена", Поз.Цена)); КонецЦикла; // Нашёл → обновит Цену. // Не нашёл → создаст с Ценой.
Хардкод имени справочника вместо EnsureByType
хрупкий код — ломается при смене модели
Если в универсальном загрузчике прибить строку "Номенклатура" — он перестанет работать
как только вы захотите использовать его с другим документом или ТЧ.
EnsureByType берёт имя справочника из метаданных реквизита — код остаётся универсальным.
// Работает только с "Номенклатура" // Для другого документа — переписывать Для Каждого Стр Из ДанныеJSON Цикл Ном = A1sCatalogs.Ensure( "Номенклатура", Стр.item); A1sDocs.AddRow(Dok, "Продукция", A1sDS.Of("Номенклатура", Ном)); КонецЦикла;
// Работает с любым документом и ТЧ // Имя справочника — из метаданных ТипНом = A1sDocs.TabAttrType( ИмяДокумента, ИмяТЧ, "Номенклатура"); Для Каждого Стр Из ДанныеJSON Цикл Ном = A1sCatalogs.EnsureByType( ТипНом, Стр.item); A1sDocs.AddRow(Dok, ИмяТЧ, A1sDS.Of("Номенклатура", Ном)); КонецЦикла;
Fluent на клиенте или в &НаКлиентеНаСервере
ошибка выполнения
Все четыре Fluent-обёртки создают объект обработки через Обработки.A1sDP_Docs.Создать().
Обработки видны только на сервере. На клиенте или в &НаКлиентеНаСервере
этот вызов упадёт с ошибкой «Обработка не найдена».
// &НаКлиенте или &НаКлиентеНаСервере &НаКлиентеНаСервере Процедура ОбработатьФорму() // ОШИБКА: Обработки недоступны Ссылка = A1sDocs.On("ВыпускПродукцииУслуг", ...) .Post(); КонецПроцедуры
// Обернуть в серверный вызов &НаСервере Функция СоздатьВыпускНаСервере(Данные) Возврат A1sDocs.On( "ВыпускПродукцииУслуг", Данные) .Post(); КонецФункции // Или — обычные функции без Fluent Dok = A1sDocs.Of(...); // тоже только &НаСервере!
A1sDS.Of с вложенной структурой вместо пар
лишний код, теряет смысл Of
Иногда создают структуру заранее, а потом передают её в Of как единственный аргумент.
Это работает, но теряет весь смысл Of — инлайн-создание из пар.
Если структура уже есть — передавайте её напрямую. Если нужны пары — используйте Of.
// Создаём структуру... чтобы передать в Of? ДанныеШапки = Новый Структура; ДанныеШапки.Вставить("Дата", '20260201'); ДанныеШапки.Вставить("Организация", Орг); Dok = A1sDocs.Of( "ВыпускПродукцииУслуг", A1sDS.Of(ДанныеШапки)); // A1sDS.Of с одним аргументом — лишнее
// Если структура уже есть — просто передать Dok = A1sDocs.Of( "ВыпускПродукцииУслуг", ДанныеШапки); // уже структура // Если нет — инлайн через Of Dok = A1sDocs.Of( "ВыпускПродукцииУслуг", A1sDS.Of( "Дата", '20260201', "Организация", Орг));
SelfTest — проверить что модули работают
Каждый модуль содержит SelfTest() — набор
встроенных тестов.
Они проверяют базовые операции без обращения к реальным данным базы.
Запускать при первой установке, после обновления библиотеки или при загадочных ошибках.
// ── Запустить SelfTest всех модулей ────────────────────────────────────── // Поместить в обработку-проверку или запустить из консоли запросов Модули = A1sDS.Of( "A1sDS", A1sDS.SelfTest(), "A1sAR", A1sAR.SelfTest(), "A1sCatalogs", A1sCatalogs.SelfTest(), "A1sDocs", A1sDocs.SelfTest()); Сообщить("════ SelfTest результаты ════"); Для Каждого КЗ Из Модули Цикл Сообщить((КЗ.Значение ? "✓ " : "✗ ") + КЗ.Ключ); КонецЦикла; // Ожидаемый вывод: // ✓ A1sDS // ✓ A1sAR // ✓ A1sCatalogs // ✓ A1sDocs // ── Один модуль отдельно ────────────────────────────────────────────────── Ок = A1sDocs.SelfTest(); Если НЕ Ок Тогда ВызватьИсключение "A1sDocs.SelfTest: проверка не пройдена"; КонецЕсли; // ── Что проверяют SelfTest ──────────────────────────────────────────────── // A1sDS: Of, OfKeys, HasKey, IsEmpty, Equals, Merge, Defaults... // A1sAR: Of, OfN, Contains, IsEmpty, RemoveDuplicates, Sort... // A1sCatalogs: IsCatalog, IsRef, GetObject, GetRef... // A1sDocs: IsDocument, IsRef, IsObject, GetObject, GetRef, MetaCount...
// ════════════════════════════════════════════════════════════════════════════
// ЧЕК-ЛИСТ при загадочной ошибке
// ════════════════════════════════════════════════════════════════════════════
// 1. Проверить что модули работают (SelfTest выше)
// 2. Проверить что ТЧ не пустая перед Post
Если A1sDocs.RowCount(Dok, "Продукция") = 0 Тогда
Сообщить("ВНИМАНИЕ: пустая ТЧ");
КонецЕсли;
// 3. Проверить что обязательные реквизиты шапки заполнены
Инфо = A1sDocs.Details(Dok, "Организация, Склад");
Если НЕ ЗначениеЗаполнено(Инфо.Организация) Тогда
Сообщить("ВНИМАНИЕ: не заполнена Организация");
КонецЕсли;
// 4. Проверить существование справочников до загрузки строк
ТестСсылка = A1sCatalogs.ByName("Номенклатура", "Квартира 101");
Если НЕ ЗначениеЗаполнено(ТестСсылка) Тогда
Сообщить("'Квартира 101' не найдена в справочнике");
КонецЕсли;
// 5. Проверить что код работает на сервере
// (добавить &НаСервере к процедуре если нет)
// 6. Проверить IsPosted у существующих документов
Если НЕ A1sDocs.IsPosted(ДокСсылка) Тогда
Сообщить("Документ не проведён: " + ДокСсылка);
КонецЕсли;
| # | Антипаттерн | Эффект | Исправление | Критичность |
|---|---|---|---|---|
| АП-1 | Ensure в цикле | N запросов к БД вместо 1 | EnsureBatch до цикла | Высокая |
| АП-2 | GetObject для чтения | Лишний тяжёлый запрос | Details(ExtraAttrs) | Средняя |
| АП-3 | Поиск внутри цикла | N×M запросов | PickByName батчем до цикла | Высокая |
| АП-4 | Игнор Failed | Скрытые непроведённые | Проверять .Failed.Количество() | Высокая |
| АП-5 | AddRow со строками | Ошибка типов при записи | AddRowAuto / LoadRows | Высокая |
| АП-6 | Ensure при синхронизации | Данные не обновляются | EnsureUpdate | Высокая |
| АП-7 | Хардкод справочника | Хрупкий код | EnsureByType + TabAttrType | Средняя |
| АП-8 | Fluent на клиенте | Ошибка «Обработка не найдена» | Только &НаСервере | Высокая |
| АП-9 | Of с промежуточной структурой | Лишний код без смысла | Передать структуру напрямую | Низкая |