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