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