1С запись в регистр сведений из кода выполняется двумя объектами: менеджером записи (одна строка по ключу) и набором записей (группа строк по отбору). Ниже — рабочие примеры для платформы 8.3: создать, изменить и удалить запись, заменить данные по отбору, дописать строки без удаления существующих, записать движения документа, загрузить тысячи строк и изменить значение в транзакции с блокировкой.
Имена регистров в примерах условные, подставьте метаданные своей конфигурации:
КурсыВалют — независимый, периодический (День): измерение Валюта, ресурсы Курс и Кратность;НастройкиИнтеграции — независимый, непериодический: измерения Организация и ИмяНастройки, ресурс Значение;ЦеныНоменклатуры — периодический, подчинён регистратору (документ УстановкаЦен): измерение Номенклатура, ресурс Цена;НомераЗаявок — независимый, непериодический: измерение Префикс, ресурс ПоследнийНомер.Весь код серверный. В тонком и веб-клиенте РегистрыСведений недоступны, поэтому запись размещают в общем модуле с флагом «Сервер», в модуле объекта или в процедуре формы с директивой &НаСервере или &НаСервереБезКонтекста. Если общий модуль вызывается с клиента, ему нужен ещё флаг «Вызов сервера». Как создать регистр и выбрать периодичность, описано в статье о регистре сведений.
Ключ записи — период (у периодического регистра) плюс все измерения. По одному ключу хранится одна запись, поэтому «записать» почти всегда значит «создать или заменить». Способ выбирают по задаче:
| Задача | Способ |
|---|---|
| Создать или заменить одну запись | МенеджерЗаписи.Записать() |
| Прочитать запись и изменить ресурс | Прочитать(), Выбран(), Записать() |
| Удалить одну запись | МенеджерЗаписи.Удалить() |
| Заменить все записи по измерению | Набор с Отбор и Записать() |
| Удалить записи по отбору | Пустой набор с отбором и Записать() |
| Дописать строки, не трогая остальные | Отбор по всем измерениям или Записать(Ложь) |
| Записать данные документа | Движения в ОбработкаПроведения |
| Загрузить тысячи строк | Набор записей порциями |
| Прочитать и изменить при параллельной работе | Транзакция и БлокировкаДанных |
Менеджер записи есть только у независимого регистра. Регистр в режиме «Подчинение регистратору» меняют набором записей с отбором по регистратору, обычно через движения документа.
Чтобы записать строку, достаточно заполнить ключ и ресурсы и вызвать Записать(). Удаление — через Прочитать(), Выбран() и Удалить().
// Общий модуль с флагом «Сервер», например РаботаСКурсамиСервер
Процедура ЗаписатьКурс(Валюта, ДатаКурса, Курс, Кратность = 1) Экспорт
МенеджерЗаписи = РегистрыСведений.КурсыВалют.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = НачалоДня(ДатаКурса);
МенеджерЗаписи.Валюта = Валюта;
МенеджерЗаписи.Курс = Курс;
МенеджерЗаписи.Кратность = Кратность;
МенеджерЗаписи.Записать(); // запись с тем же ключом будет заменена
КонецПроцедуры
// Возвращает Истина, если запись была и её удалили
Функция УдалитьКурс(Валюта, ДатаКурса) Экспорт
МенеджерЗаписи = РегистрыСведений.КурсыВалют.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = НачалоДня(ДатаКурса);
МенеджерЗаписи.Валюта = Валюта;
МенеджерЗаписи.Прочитать();
Если Не МенеджерЗаписи.Выбран() Тогда
Возврат Ложь;
КонецЕсли;
МенеджерЗаписи.Удалить();
Возврат Истина;
КонецФункции
Период и Валюта образуют ключ. Записать() без параметра работает с замещением: если курс валюты на этот день уже есть, он перезаписывается, другие записи не затрагиваются. Записать(Ложь) на существующем ключе вызовет исключение. При периодичности «День» курсы, записанные в 10:00 и в 15:00, — одна запись: платформа приводит период к началу интервала. НачалоДня() делает это явно, чтобы ключ при чтении и удалении совпадал с записанным. Выбран() возвращает Истина, только если Прочитать() нашёл запись.
Чтобы изменить один ресурс и не затереть остальные, запись сначала читают. Пример для модуля формы обработки: клиентская команда вызывает серверную функцию.
// Модуль формы обработки. Реквизиты объекта: Валюта, ДатаКурса, Кратность
&НаКлиенте
Процедура ИзменитьКратность(Команда)
Если ИзменитьКратностьНаСервере(Объект.Валюта, Объект.ДатаКурса, Объект.Кратность) Тогда
ПоказатьОповещениеПользователя("Кратность изменена");
Иначе
ПоказатьПредупреждение(, "Курс на эту дату не найден");
КонецЕсли;
КонецПроцедуры
&НаСервереБезКонтекста
Функция ИзменитьКратностьНаСервере(Валюта, ДатаКурса, Кратность)
МенеджерЗаписи = РегистрыСведений.КурсыВалют.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Период = НачалоДня(ДатаКурса);
МенеджерЗаписи.Валюта = Валюта;
МенеджерЗаписи.Прочитать();
Если Не МенеджерЗаписи.Выбран() Тогда
Возврат Ложь;
КонецЕсли;
МенеджерЗаписи.Кратность = Кратность; // Курс остаётся тем, что прочитан из базы
МенеджерЗаписи.Записать();
Возврат Истина;
КонецФункции
Без Прочитать() вызов Записать() заменил бы запись целиком, и Курс стал бы нулевым. Чтение идёт строго по ключу: курс, установленный 14-го, на 15-е число не прочитается. Значение «на дату» получают через РегистрыСведений.КурсыВалют.ПолучитьПоследнее(НаДату, Новый Структура("Валюта", Валюта)) или срезом последних в запросе. Директива &НаСервереБезКонтекста не передаёт на сервер данные формы, только параметры, поэтому вызов лёгкий.
Набор записывает сразу группу строк, а Отбор определяет, какие записи он заменит.
// Общий модуль с флагом «Сервер».
// Настройки — Соответствие: ключ — имя настройки, значение — строка
Процедура СохранитьНастройкиОрганизации(Организация, Настройки) Экспорт
НаборЗаписей = РегистрыСведений.НастройкиИнтеграции.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Организация.Установить(Организация);
Для Каждого КлючИЗначение Из Настройки Цикл
Запись = НаборЗаписей.Добавить();
Запись.Организация = Организация;
Запись.ИмяНастройки = КлючИЗначение.Ключ;
Запись.Значение = КлючИЗначение.Значение;
КонецЦикла;
// Все прежние записи организации удаляются, пишется содержимое набора
НаборЗаписей.Записать();
КонецПроцедуры
Процедура УдалитьНастройкиОрганизации(Организация) Экспорт
НаборЗаписей = РегистрыСведений.НастройкиИнтеграции.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Организация.Установить(Организация);
НаборЗаписей.Записать(); // пустой набор с отбором — удаление по отбору
КонецПроцедуры
При Записать() платформа удаляет все записи регистра, подходящие под отбор, и пишет содержимое набора. Отсюда три следствия: читать набор перед полной заменой не нужно; пустой набор с отбором удаляет записи по отбору; набор без отбора очистит весь регистр. Отбор работает только на равенство и только по периоду, измерениям и регистратору. Поля новых записей заполняйте теми же значениями, что стоят в отборе.
Частая ошибка: отбор только по организации и одна новая строка в наборе. Записать() удалит остальные настройки организации и оставит одну. Правильных вариантов два.
// Общий модуль с флагом «Сервер».
// Вариант 1: отбор по всем измерениям — заменяется только одна запись
Процедура УстановитьНастройку(Организация, ИмяНастройки, Значение) Экспорт
НаборЗаписей = РегистрыСведений.НастройкиИнтеграции.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Организация.Установить(Организация);
НаборЗаписей.Отбор.ИмяНастройки.Установить(ИмяНастройки);
Запись = НаборЗаписей.Добавить();
Запись.Организация = Организация;
Запись.ИмяНастройки = ИмяНастройки;
Запись.Значение = Значение;
НаборЗаписей.Записать();
КонецПроцедуры
// Вариант 2: дописать записи, ничего не удаляя
Процедура ДобавитьНастройки(Организация, Настройки) Экспорт
НаборЗаписей = РегистрыСведений.НастройкиИнтеграции.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Организация.Установить(Организация); // страховка
Для Каждого КлючИЗначение Из Настройки Цикл
Запись = НаборЗаписей.Добавить();
Запись.Организация = Организация;
Запись.ИмяНастройки = КлючИЗначение.Ключ;
Запись.Значение = КлючИЗначение.Значение;
КонецЦикла;
НаборЗаписей.Записать(Ложь); // ключ уже есть в базе — исключение
КонецПроцедуры
В первом варианте отбор стоит по всем измерениям, и замещается ровно одна запись — как у менеджера записи. Во втором Записать(Ложь) только добавляет строки; если такой ключ уже есть, будет исключение. Отбор во втором варианте — страховка: если кто-то уберёт Ложь, пострадают записи одной организации, а не весь регистр. На платформе 8.3.25 и новее у независимого регистра есть режим Записать(РежимЗамещения.Слияние): записи с совпадающим ключом обновляются, новые добавляются, остальные не трогаются.
Записи регистра, подчинённого регистратору, принадлежат документу. Их формируют в обработчике ОбработкаПроведения модуля объекта, а сам регистр отмечают на закладке «Движения» документа.
// Модуль объекта документа УстановкаЦен, табличная часть Товары (Номенклатура, Цена)
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
Движения.ЦеныНоменклатуры.Записывать = Истина;
Для Каждого СтрокаТовары Из Товары Цикл
Движение = Движения.ЦеныНоменклатуры.Добавить();
Движение.Период = Дата;
Движение.Номенклатура = СтрокаТовары.Номенклатура;
Движение.Цена = СтрокаТовары.Цена;
КонецЦикла;
КонецПроцедуры
Записывать = Истина велит платформе записать набор после обработки проведения. Регистратор и активность новым записям платформа заполняет сама, а при перепроведении набор с отбором по документу замещает старые движения. Период в подчинённом периодическом регистре задают явно, обычно датой документа.
Поправить движения уже проведённого документа из обработки можно набором с отбором по регистратору:
// Общий модуль с флагом «Сервер»
Процедура ИсправитьЦенуВДвижениях(Документ, Номенклатура, НоваяЦена) Экспорт
НаборЗаписей = РегистрыСведений.ЦеныНоменклатуры.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Документ);
НаборЗаписей.Прочитать();
Для Каждого Запись Из НаборЗаписей Цикл
Если Запись.Номенклатура = Номенклатура Тогда
Запись.Цена = НоваяЦена;
КонецЕсли;
КонецЦикла;
НаборЗаписей.Записать();
КонецПроцедуры
Без Отбор.Регистратор.Установить() такой набор в режиме замещения не записать — платформа выдаст исключение. Ручная правка живёт до перепроведения: документ перезапишет свои движения. Для постоянного исправления меняют документ и проводят его заново.
Менеджер записи на каждую строку — медленный путь: каждый Записать() выполняется отдельной транзакцией. Набор записывает сотни и тысячи строк за один вызов Записать().
// Общий модуль с флагом «Сервер».
// Курсы на одну дату. ТаблицаКурсов: колонки Валюта, Курс, Кратность
Процедура ЗагрузитьКурсыНаДату(ДатаКурсов, ТаблицаКурсов) Экспорт
Период = НачалоДня(ДатаКурсов);
НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Период.Установить(Период);
Для Каждого СтрокаТаблицы Из ТаблицаКурсов Цикл
Запись = НаборЗаписей.Добавить();
ЗаполнитьЗначенияСвойств(Запись, СтрокаТаблицы);
Запись.Период = Период;
КонецЦикла;
НаборЗаписей.Записать(); // курсы на эту дату заменяются целиком
КонецПроцедуры
// Первичная загрузка истории: таких ключей в регистре ещё нет.
// ТаблицаКурсов: колонки Период, Валюта, Курс, Кратность
Процедура ЗагрузитьИсториюКурсов(ТаблицаКурсов, РазмерПорции = 5000) Экспорт
НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
Для Каждого СтрокаТаблицы Из ТаблицаКурсов Цикл
ЗаполнитьЗначенияСвойств(НаборЗаписей.Добавить(), СтрокаТаблицы);
Если НаборЗаписей.Количество() >= РазмерПорции Тогда
НаборЗаписей.Записать(Ложь);
НаборЗаписей.Очистить();
КонецЕсли;
КонецЦикла;
Если НаборЗаписей.Количество() > 0 Тогда
НаборЗаписей.Записать(Ложь);
КонецЕсли;
КонецПроцедуры
Первая процедура идемпотентна: отбор по периоду заменяет все курсы на дату, и повторная загрузка того же файла не создаёт дублей. Вторая дописывает историю порциями через Записать(Ложь) и Очистить(): память не растёт, каждая порция держит блокировки недолго. Если порция упадёт на дубле ключа, предыдущие останутся в базе. Для атомарности «всё или ничего» цикл оборачивают в транзакцию, но тогда блокировки на весь объём держатся до фиксации. Дубли ключа во входной таблице убирают заранее: после приведения периода к дню их бывает больше, чем кажется.
Отбор набора умеет только «равно». Чтобы удалить записи по условию «меньше», «больше» или по значению ресурса, ключи выбирают запросом и удаляют по одному.
// Общий модуль с флагом «Сервер».
// Удаляет курсы, установленные раньше даты ДатаГраницы
Процедура УдалитьКурсыДоДаты(ДатаГраницы) Экспорт
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КурсыВалют.Период КАК Период,
| КурсыВалют.Валюта КАК Валюта
|ИЗ
| РегистрСведений.КурсыВалют КАК КурсыВалют
|ГДЕ
| КурсыВалют.Период < &ДатаГраницы";
Запрос.УстановитьПараметр("ДатаГраницы", ДатаГраницы);
Выборка = Запрос.Выполнить().Выбрать();
НаборЗаписей = РегистрыСведений.КурсыВалют.СоздатьНаборЗаписей();
Пока Выборка.Следующий() Цикл
НаборЗаписей.Отбор.Период.Установить(Выборка.Период);
НаборЗаписей.Отбор.Валюта.Установить(Выборка.Валюта);
НаборЗаписей.Записать(); // набор пуст — удаляется одна запись
КонецЦикла;
КонецПроцедуры
Каждая итерация ставит отбор по полному ключу и записывает пустой набор — удаляется одна запись. Объект набора создаётся один раз: Установить() перезаписывает значение отбора. Для сотен тысяч записей условие сужают, например, до одного года за запуск.
Когда значение читают, вычисляют и записывают обратно, два сеанса могут прочитать одно и то же и записать одинаковый результат. Классический пример — счётчик номеров. Подробнее о фиксации и откате — в статье про транзакции в 1С.
// Общий модуль с флагом «Сервер».
// Регистр НомераЗаявок: измерение Префикс (Строка), ресурс ПоследнийНомер (Число)
Функция СледующийНомер(Префикс) Экспорт
НачатьТранзакцию();
Попытка
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.НомераЗаявок");
ЭлементБлокировки.УстановитьЗначение("Префикс", Префикс);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
МенеджерЗаписи = РегистрыСведений.НомераЗаявок.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Префикс = Префикс;
МенеджерЗаписи.Прочитать();
Если Не МенеджерЗаписи.Выбран() Тогда
МенеджерЗаписи.Префикс = Префикс; // записи ещё нет: ключ заполняем заново
МенеджерЗаписи.ПоследнийНомер = 0;
КонецЕсли;
МенеджерЗаписи.ПоследнийНомер = МенеджерЗаписи.ПоследнийНомер + 1;
Номер = МенеджерЗаписи.ПоследнийНомер;
МенеджерЗаписи.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Возврат Номер;
КонецФункции
Исключительная блокировка по полю Префикс ставится до чтения, поэтому второй сеанс ждёт, пока первый зафиксирует транзакцию. В управляемом режиме обычное чтение внутри транзакции ничего не блокирует, и без Блокировка.Заблокировать() два пользователя получат один номер. НачатьТранзакцию() стоит прямо перед Попытка, а в блоке Исключение первым вызывается ОтменитьТранзакцию() — так рекомендуют стандарты разработки 1С. После неудачного Прочитать() ключ заполняется заново: на прежние значения полей менеджера не полагайтесь.
Записать(Ложь)) ключ, который уже есть в регистре. Уберите Ложь, если запись нужно заменить, или проверьте ключ заранее. Дубли ключа внутри одного набора платформа тоже не запишет.Записать() вызывает исключение, пока не установлен Отбор.Регистратор: записи такого регистра без документа-владельца не существуют.Движения.Записать() удалил лишнее.Период входит в ключ, поэтому это не изменение, а перенос: удалите запись со старым периодом и запишите новую. Оба действия выполняйте в одной транзакции, чтобы при ошибке не потерять значение.
Менеджер работает только с независимым регистром и всегда с одним ключом, зато умеет Прочитать(), Выбран() и Удалить(). Набор с отбором по всем измерениям даёт тот же результат, с неполным отбором заменяет группу записей и подходит для подчинённых регистров.
Нет: каждый Записать() сам выполняется в транзакции. Явная транзакция нужна, когда несколько записей должны сохраниться вместе или новое значение вычисляется из прочитанного.
Для независимого регистра создайте набор записей, не устанавливайте отбор и вызовите Записать(). Операция необратима, поэтому сначала сделайте копию базы.
Загрузка данных из внешней системы, перенос истории между регистрами или поиск причины, по которой пропадают записи, упираются в особенности конкретной конфигурации. Опишите задачу в форме размещения задания или выберите услугу по доработке 1С — программист оценит работу по вашей базе.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.