В процессе разработки в системе «1С:Предприятие 8» одним из наиболее важных и при этом вызывающих вопросы механизмов является управление движениями документов. Часто начинающие (и даже опытные) разработчики сталкиваются с дилеммой: когда нужно вызывать метод Очистить(), а когда Записать()? Как эти методы взаимодействуют с настройками документа и как влияют на производительность системы? В этой статье мы подробно проанализируем механику работы с наборами записей регистров, разберем влияние транзакций и выясним, как правильно организовывать проведение документа, чтобы избежать дублей и ошибок контроля остатков.
Для начала разберем природу этих двух методов. Несмотря на то, что оба они кажутся направленными на одно и то же — изменение состава записей, — они работают на разных уровнях архитектуры системы.
Метод Очистить() — это метод коллекции. Представьте его как команду «Очистить» у обычной Табличной части или Массива. Вызывая Движения.ИмяРегистра.Очистить(), мы работаем исключительно с оперативной памятью сервера приложений. Мы приказываем коллекции в памяти забыть все строки, которые в ней находились. При этом никаких запросов к СУБД (SQL) в этот момент не отправляется. Если мы выполним Очистить() и завершим процедуру проведения, не вызывая запись, в базе данных фактически ничего не изменится (если только не сработают автоматические механизмы платформы).
Метод Записать() — это уже команда управления данными в СУБД. Когда вызывается этот метод, система инициирует транзакционную операцию записи. По умолчанию метод Записать() вызывается с параметром Замещать = Истина. Это означает, что платформа сначала найдет в базе данных все старые записи по данному регистратору, удалит их и запишет на их место то состояние коллекции, которое сейчас находится в памяти. Если коллекция в памяти была пустой, то старые записи в БД просто удалятся.
Рассмотрим, как ведет себя платформа в зависимости от настроек документа. Проанализируем свойство «Удалять движения», которое может принимать значения «Удалять автоматически при отмене проведения», «Удалять автоматически» или «Не удалять автоматически».
ОбработкаПроведения сама очищает движения документа в базе данных. Это гарантирует, что к моменту начала работы вашего кода в СУБД по этому регистратору записей нет. В этом случае метод Очистить() в коде чаще всего излишен, так как набор в памяти изначально пуст.Рассмотрим типичный пример кода, который может привести к ошибкам, если не понимать механику платформы:
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Если мы не очистим набор, а он был прочитан ранее (например, в форме),
// то новые записи просто добавятся к старым в памяти.
Для Каждого Стр Из Товары Цикл
Движение = Движения.Остатки.Добавить();
Движение.Номенклатура = Стр.Номенклатура;
Движение.Количество = Стр.Количество;
КонецЦикла;
// В конце процедуры платформа сама запишет движения,
// если установлено свойство Записывать = Истина.
КонецПроцедуры
Выясним причину появления дублей. Дубли возникают, если набор записей в памяти уже содержал какие-то строки до того, как вы начали заполнять его в цикле. Это случается, если:
Прочитать().Чтобы гарантировать корректность, рассмотрим правильную последовательность действий перед циклом формирования движений:
Движения.Остатки.Записывать = Истина;
// Очищаем коллекцию в памяти от возможных старых данных
Движения.Остатки.Очистить();
Для Каждого Стр Из СписокНоменклатуры Цикл
Движение = Движения.Остатки.Добавить();
// Заполнение полей...
КонецЦикла;
Здесь Очистить() гарантирует, что в коллекции «Остатки» находятся только те записи, которые мы создали в текущей итерации проведения.
Рассмотрим подробнее использование метода Записать() внутри процедуры ОбработкаПроведения. По стандартам разработки «1С», лишние вызовы Записать() не приветствуются, так как это лишняя нагрузка на сервер базы данных. Однако существует так называемая «новая методика проведения».
В чем ее суть? Мы сначала записываем движения документа в базу данных (пусть даже они еще не проверены на избыток), а затем выполняем запрос к регистру для проверки отрицательных остатков. В этом случае явный вызов Записать() обязателен до выполнения запроса:
Движения.Остатки.Записывать = Истина;
Движения.Остатки.Очистить();
// Формируем движения...
Для Каждого Стр Из Товары Цикл
Движение = Движения.Остатки.ДобавитьРасход();
// ...
КонецЦикла;
// Явно записываем в базу, чтобы запрос к остаткам «увидел» эти данные
Движения.Остатки.Записать();
// Теперь делаем запрос к виртуальным таблицам остатков для контроля
// Если обнаружим минус — устанавливаем Отказ = Истина
Важный нюанс: Если вы используете метод Движения.Записать() (без указания конкретного регистра), платформа запишет только те наборы, у которых флаг Записывать установлен в Истина. После записи этот флаг автоматически сбрасывается в Ложь, чтобы предотвратить повторную запись в конце процедуры (это оптимизация платформы).
Любая процедура ОбработкаПроведения выполняется внутри одной большой транзакции. Что это означает для методов Записать() и Очистить()?
Записать(), данные физически отправились в базу данных, но они еще не «зафиксированы» для других пользователей (в зависимости от уровня изоляции).Отказ = Истина, транзакция откатывается. Все, что вы «записали» методом Записать(), будет удалено из БД, и состояние регистров вернется к тому, каким оно было до начала проведения.Очистить() в рамках транзакции просто обнуляет текущую коллекцию в памяти, помогая вам подготовить «чистый лист» для формирования новых данных.Подведем итог нашему анализу и сформулируем правила хорошего тона при работе с регистрами:
Очистить() перед началом цикла формирования движений, если вы не уверены на 100%, что набор в памяти пустой. Это защитит вас от дублей.Записать() для каждого конкретного регистра без необходимости. Если ваш алгоритм проведения классический («сначала считаем остатки, потом пишем»), доверьте запись платформе — она сделает это сама максимально эффективно в конце транзакции.Записать() для набора или всей коллекции Движения только в том случае, если вам необходимо зафиксировать данные в БД для последующего анализа в этой же транзакции (контроль остатков по новой методике).Движения.ИмяРегистра.Записать() без параметров эквивалентно Записать(Истина), что означает полное замещение старого набора новым. Если вам нужно просто добавить записи к существующим (что бывает крайне редко в проведении), используйте Записать(Ложь).Записывать = Истина для тех регистров, по которым планируете делать движения, иначе ваши изменения в коллекции так и останутся в памяти и не попадут в базу.Следуя этим правилам, вы обеспечите стабильную и быструю работу системы, а ваши алгоритмы проведения будут прозрачными и предсказуемыми.