Как устроено значение объекта в 1С 8.3 и что с ним делать в коде
Содержание
Значение объекта в 1С — это не «то, что лежит в поле», а один из двух разных видов данных: ссылка (указатель на запись в базе) или объект (полная копия записи в памяти, которую можно изменить и записать). Переменная Номенклатура в коде почти всегда хранит ссылку типа СправочникСсылка.Номенклатура. Чтобы поменять реквизит, нужен объект: Ссылка.ПолучитьОбъект().
Частая ошибка начинающих — присвоить реквизиту ссылки новое значение и ждать, что оно сохранится. Ссылка только читает. Всё ниже — про платформу 1С:Предприятие 8.3 и встроенный язык, от конкретной конфигурации не зависит.
Ссылка и объект: что хранится в памяти
У каждого объекта метаданных ссылочного типа (справочник, документ, план видов характеристик, план счетов
, бизнес-процесс, задача) платформа создаёт несколько типов. Для справочника Номенклатура это:
| Тип | Что хранит | Можно менять? |
|---|---|---|
СправочникСсылка.Номенклатура | тип и уникальный идентификатор записи | нет, только читать |
СправочникОбъект.Номенклатура | все реквизиты и табличные части, прочитанные в память | да, затем Записать() |
СправочникВыборка.Номенклатура | курсор по нескольким записям | нет |
СправочникМенеджер.Номенклатура | точку входа: поиск, создание, выборка | — |
Ссылка весит мало. Её можно передавать между клиентом и сервером, класть в массив, хранить в реквизите другого документа. В базе ссылка — это пара «тип + уникальный идентификатор».
Объект тяжелее. При ПолучитьОбъект() платформа читает из базы запись целиком, вместе с табличными частями. У документа на 5 000 строк товаров это ощутимо. Объект существует только на сервере (и в толстом клиенте), на тонкий клиент его не передать.
💡 Когда пишете Ссылка.Наименование, платформа читает данные объекта, а не одно поле. В цикле по тысяче ссылок это тысяча чтений. Для одного-двух реквизитов в типовых конфигурациях на БСП есть функции ОбщегоНазначения.ЗначениеРеквизитаОбъекта() и ОбщегоНазначения.ЗначенияРеквизитовОбъекта() — они делают запрос только к нужным полям.
Значение и ссылка в обычных переменных
Коллекции в 1С (Массив, Структура, Соответствие, ТаблицаЗначений) при присваивании не копируются:
Список1 = Новый Массив;
Список1.Добавить(10);
Список2 = Список1;
Список2.Добавить(20);
// Список1.Количество() = 2: обе переменные указывают на один массив
Параметры процедур по умолчанию тоже передаются по ссылке. Если процедура не должна менять переданное, объявите параметр с Знач. Для настоящей копии структуры с вложенными коллекциями в БСП есть ОбщегоНазначения.СкопироватьРекурсивно().
Какие типы значений бывают и чем они отличаются
Все значения встроенного языка удобно разложить на пять групп.
- 🔹 Примитивные:
Число,Строка,Дата,Булево,Неопределено,Null,Тип. Копируются при присваивании, сравниваются по содержимому. - 🔗 Ссылочные:
ДокументСсылка.*,СправочникСсылка.*,ПеречислениеСсылка.*и т. п. Сравниваются по идентификатору: две ссылки на одну запись равны. - 📦 Объектные:
ДокументОбъект.*,СправочникОбъект.*. Это изменяемые копии записей. - 🗂 Наборы записей регистров:
РегистрСведенийНаборЗаписей.*,РегистрНакопленияНаборЗаписей.*. У регистров ссылок нет, запись идентифицируется отбором по измерениям и регистратору. - 🧰 Универсальные коллекции:
Массив,Структура,Соответствие,СписокЗначений,ТаблицаЗначений,ДеревоЗначенийи их фиксированные варианты.
Неопределено и Null — не одно и то же
Неопределено — значение по умолчанию у новой переменной и у пустого реквизита составного типа. Null приходит из запроса, когда левое соединение не нашло пару. Код Если Значение = Неопределено не поймает Null. В тексте запроса обрабатывайте это через ЕСТЬNULL(), в коде — сравнением с Null.
Пустая ссылка
У реквизита ссылочного типа, который не заполнили, значение не Неопределено, а пустая ссылка этого типа. Проверка:
Если Ссылка.Пустая() Тогда
// реквизит не заполнен
КонецЕсли;
// универсально для любого типа
Если Не ЗначениеЗаполнено(Значение) Тогда
// пусто: 0, "", пустая дата, пустая ссылка, Неопределено
КонецЕсли;
ЗначениеЗаполнено() удобнее, когда тип заранее неизвестен.
Методы объекта: чтение, изменение, запись
Типовой цикл работы с объектом короткий: получить, изменить, записать.
Ссылка = Справочники.Номенклатура.НайтиПоКоду("00-000123");
Если Ссылка.Пустая() Тогда
Сообщить("Элемент не найден");
Возврат;
КонецЕсли;
НоменклатураОбъект = Ссылка.ПолучитьОбъект();
НоменклатураОбъект.Наименование = "Кабель ВВГ 3х2,5";
НоменклатураОбъект.Записать();
НайтиПоКоду() и НайтиПоНаименованию() при неудаче возвращают пустую ссылку, а не Неопределено. Поэтому проверка именно через Пустая().
Методы, которые нужны чаще всего
| Метод | Что делает | Где вызывать |
|---|---|---|
ПолучитьОбъект() | читает объект по ссылке | сервер |
Записать() | сохраняет объект; у документа принимает режим записи | сервер |
Записать(РежимЗаписиДокумента.Проведение) | записывает и проводит документ | сервер |
Модифицированность() | показывает, меняли ли объект после чтения | сервер |
Скопировать() | создаёт новый несохранённый объект с теми же данными | сервер |
Заблокировать() | ставит объектную блокировку перед правкой | сервер |
Прочитать() | перечитывает объект из базы, отменяя изменения в памяти | сервер |
Метаданные() | возвращает объект метаданных для анализа структуры | сервер |
УстановитьПометкуУдаления(Истина) | помечает на удаление | сервер |
У нового объекта, созданного через Справочники.Номенклатура.СоздатьЭлемент(), ссылка пустая до первой записи. Если ссылка нужна раньше, например для связанных записей в одной транзакции, назначьте её заранее методом УстановитьСсылкуНового().
ДополнительныеСвойства и ОбменДанными.Загрузка
Два приёма, которые знает каждый, кто писал обработки массовых изменений:
Объект.ДополнительныеСвойства— структура, которая живёт только в памяти. Через неё передают флаги в подписки на события и в модуль объекта, не создавая лишних реквизитов.Объект.ОбменДанными.Загрузка = Истина— запись без выполнения проверок и большей части кода в обработчикахПередЗаписьюиПриЗаписи(там это условие проверяется в типовом коде). Так пишут при загрузке данных из обмена.
Режим ОбменДанными.Загрузка отключает проверки и часть логики конфигурации. Документ не перепроведётся, связанные регистры могут не обновиться. Используйте его только когда точно знаете, какую логику пропускаете.
Объект в управляемой форме: реквизит «Объект»
В форме элемента или документа основной реквизит называется Объект, но это не СправочникОбъект. Это ДанныеФормыСтруктура — копия данных, приспособленная для передачи на клиент. Методов Записать() и ПолучитьОбъект() у неё нет.
Чтобы вызвать метод модуля объекта, на сервере нужно преобразование:
&НаСервере
Процедура ЗаполнитьНаСервере()
ДокументОбъект = РеквизитФормыВЗначение("Объект");
ДокументОбъект.ЗаполнитьТовары(); // экспортный метод модуля объекта
ЗначениеВРеквизитФормы(ДокументОбъект, "Объект");
КонецПроцедуры
Пара РеквизитФормыВЗначение / ЗначениеВРеквизитФормы работает с реквизитом по имени. Для произвольных данных формы есть ДанныеФормыВЗначение() и ЗначениеВДанныеФормы(). Так же реквизит формы типа ТаблицаЗначений превращается в настоящую таблицу значений, у которой работают Свернуть(), Сортировать() и Скопировать().
💡 После ЗначениеВРеквизитФормы форма показывает новые данные, но в базу ничего не записано. Записывает пользователь кнопкой формы или ваш код через Записать() у объекта.
Как привести значение к нужному типу и получить ссылку
Тип значения узнают функцией ТипЗнч() и сравнивают с Тип():
Если ТипЗнч(Значение) = Тип("ДокументСсылка.РеализацияТоваровУслуг") Тогда
// работаем с реализацией
ИначеЕсли ТипЗнч(Значение) = Тип("Число") Тогда
// число
КонецЕсли;
Для реквизитов составного типа удобна проверка по описанию: Новый ОписаниеТипов("Число, Строка").СодержитТип(ТипЗнч(Значение)).
Строка, число, дата
| Задача | Как сделать | Подвох |
|---|---|---|
| Число в строку без пробелов | Формат(Сумма, "ЧГ=0") | Строка(1234567) даст разделители групп |
| Число в строку для обмена | XMLСтрока(Сумма) | разделитель — точка, без пробелов |
| Строка в число | XMLЗначение(Тип("Число"), "12.5") | строка должна быть в формате XML |
| Безопасное приведение | Новый ОписаниеТипов("Число").ПривестиЗначение(Значение) | непреобразуемое станет 0 |
| Строка в дату | Дата("20261007") | ожидается формат ГГГГММДД или ГГГГММДДччммсс |
| Строка в булево | XMLЗначение(Тип("Булево"), "true") | только true / false |
Ссылка из строки и обратно
Строка(Ссылка) возвращает представление («Кабель ВВГ 3х2,5»), а не идентификатор. Для хранения и передачи нужен идентификатор:
// ссылка -> строка
СтрокаУИД = Строка(Ссылка.УникальныйИдентификатор());
// строка -> ссылка
УИД = Новый УникальныйИдентификатор(СтрокаУИД);
Ссылка = Справочники.Номенклатура.ПолучитьСсылку(УИД);
ПолучитьСсылку() вернёт ссылку, даже если такой записи в базе нет. Проверьте её: если Ссылка.ПолучитьОбъект() вернул Неопределено, ссылка битая. В отчётах и формах такие ссылки видны как <Объект не найден> с идентификатором в скобках.
Сериализация: сохранить значение и восстановить его
Сериализация нужна, чтобы положить значение в файл, передать по HTTP, сохранить в реквизит или регистр. Способов четыре, и они не взаимозаменяемы.
| Способ | Что умеет | Для чего |
|---|---|---|
ЗначениеВСтрокуВнутр() / ЗначениеИзСтрокиВнутр() | почти любое значение, включая коллекции | обмен внутри одной базы, отладка |
СериализаторXDTO.ЗаписатьXML() / ПрочитатьXML() | объекты, ссылки, наборы записей, коллекции | выгрузка и перенос объектов между базами |
ЗаписатьJSON() | примитивы, Структура, Соответствие, Массив | REST и HTTP-сервисы |
ХранилищеЗначения | любое сериализуемое значение, со сжатием | реквизит типа «Хранилище значения» |
Выгрузка объекта в XML-строку и обратно:
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку();
СериализаторXDTO.ЗаписатьXML(ЗаписьXML, ДокументОбъект);
ТекстXML = ЗаписьXML.Закрыть();
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(ТекстXML);
Восстановленный = СериализаторXDTO.ПрочитатьXML(ЧтениеXML);
ЧтениеXML.Закрыть();
Хранилище значения со сжатием:
Хранилище = Новый ХранилищеЗначения(ТаблицаНастроек, Новый СжатиеДанных(9));
// ... запись Хранилище в реквизит объекта
ТаблицаНастроек = Хранилище.Получить();
Формат ЗначениеВСтрокуВнутр внутренний. Не стройте на нём обмен с внешними системами и не храните результат годами: после изменения метаданных строка может не восстановиться.
При ЗаписатьJSON() ключи Соответствия должны быть строками, а ссылки и даты так просто не запишутся. Ссылку перед выгрузкой превращайте в строку идентификатора, дату — в XMLСтрока(Дата).
Ошибки при работе со значениями объектов
Большинство ошибок здесь — следствие путаницы между ссылкой, объектом, Неопределено и Null.
Третья типичная история — объект прочитали в начале длинной процедуры, а записали в конце. За это время другой пользователь успел изменить ту же запись. Платформа откажет в записи, потому что версия данных в памяти устарела. Решение простое: читайте объект как можно ближе к записи, ставьте Заблокировать() сразу после ПолучитьОбъект() и не держите объект в памяти дольше, чем нужно. В массовых обработках ловите исключение Попытка ... Исключение, пишите в журнал ссылку и текст ОписаниеОшибки() и переходите к следующему элементу, а не обрываете весь цикл.
Ещё одна ловушка — сравнение объекта со ссылкой. ДокументОбъект = Ссылка всегда даёт Ложь, даже если это одна запись. Сравнивайте ДокументОбъект.Ссылка = Ссылка.
Если в обработке массовой правки объекты пишутся в цикле, сначала прогоните её на копии базы. Ошибка в цикле по 10 000 документов перезапишет данные во всех, а откат возможен только из резервной копии.