Как обновить уникальный идентификатор (УИД) в табличной части справочника 1С и избежать ошибки 'Поле объекта не обнаружено'?

Программист 1С v8.3 (Управляемые формы)
← На главную

При работе с данными в системе 1С:Предприятие, особенно при массовом обновлении реквизитов табличных частей, разработчики часто сталкиваются с необходимостью корректировки уникальных идентификаторов (УИД). В этой статье мы подробно разберем, как правильно обновить поле типа УникальныйИдентификатор в табличной части справочника, исправить распространенные ошибки и оптимизировать процесс.

Предположим, у нас есть справочник РесурсныеСпецификации, который содержит табличную часть Трудозатраты. В этой табличной части присутствует реквизит КлючСвязи с типом УникальныйИдентификатор. Наша задача — найти все строки табличной части, где КлючСвязи имеет нулевое (пустое) значение, и присвоить им новый, уникальный идентификатор.

Анализируем исходную проблему и ошибку

Изначально разработчик столкнулся с проблемой, пытаясь обновить КлючСвязи следующим образом:


// ... код запроса и выборки
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    Загрузка = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
    уид=Строка(ВыборкаДетальныеЗаписи.КлючСвязи);
    Если уид= "00000000-0000-0000-000000000000" Тогда
        Загрузка.трудозатраты,КлючСвязи = Новый УникальныйИдентификатор(); // Вот здесь ошибка
        Загрузка.Записать();
    КонецЕсли;
КонецЦикла;

На строке Загрузка.трудозатраты,КлючСвязи = Новый УникальныйИдентификатор(); система выдает ошибку "Поле объекта не обнаружено ключСВязи". Выясним причину этой ошибки.

  1. Синтаксическая ошибка: Выражение Загрузка.трудозатраты,КлючСвязи является синтаксически неверным. Запятая не используется для доступа к реквизитам объекта в данном контексте. Правильный синтаксис для доступа к реквизиту должен использовать точку.
  2. Неверный объект: Даже если бы синтаксис был Загрузка.трудозатраты.КлючСвязи, это все равно было бы неверно. Поле трудозатраты объекта Загрузка (который является объектом справочника РесурсныеСпецификации) — это не отдельный реквизит с типом УникальныйИдентификатор, а коллекция строк табличной части. У каждой строки этой коллекции есть свои реквизиты, включая КлючСвязи. Соответственно, чтобы обновить КлючСвязи, нам необходимо сначала получить конкретную строку из табличной части трудозатраты, а затем уже обращаться к реквизиту этой строки.

Таким образом, основная проблема заключается в том, что мы пытаемся обратиться к реквизиту КлючСвязи напрямую через коллекцию табличной части, вместо того чтобы сначала выбрать конкретную строку.

Оптимизируем запрос для повышения производительности

Прежде чем исправлять код обновления, давайте усовершенствуем запрос. Изначальный запрос выбирает все строки табличной части, а фильтрация по пустому УИД происходит уже после выборки данных в цикле 1С. Это неэффективно, особенно для больших баз данных. Мы можем значительно ускорить процесс, если выполним фильтрацию непосредственно на уровне базы данных, используя условие ГДЕ в запросе.

Также, для обновления конкретной строки табличной части нам потребуется знать ее позицию. В системе 1С:Предприятие каждая строка табличной части имеет свой номер. Этот номер можно получить, добавив в запрос поле НомерСтроки. Важно помнить, что НомерСтроки в 1С начинается с 1, а индексация коллекций в языке 1С (как и во многих других языках программирования) начинается с 0. Поэтому при обращении к строке табличной части по индексу, нам потребуется вычесть 1 из значения НомерСтроки.

Рассмотрим, как будет выглядеть оптимизированный запрос:


Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ПустойУИД", Новый УникальныйИдентификатор("00000000-0000-0000-000000000000")); // Создаем параметр для пустого УИД
Запрос.Текст =
    "ВЫБРАТЬ
    |    РесурсныеСпецификацииТрудозатраты.НомерСтроки КАК НомерСтроки, // Добавляем НомерСтроки для идентификации строки
    |    РесурсныеСпецификацииТрудозатраты.КлючСвязи КАК КлючСвязи,
    |    РесурсныеСпецификацииТрудозатраты.ВидРабот КАК ВидРабот,
    |    РесурсныеСпецификацииТрудозатраты.Этап КАК Этап,
    |    РесурсныеСпецификацииТрудозатраты.Ссылка КАК Ссылка
    |ИЗ
    |    Справочник.РесурсныеСпецификации.Трудозатраты КАК РесурсныеСпецификацииТрудозатраты
    |ГДЕ
    |    РесурсныеСпецификацииТрудозатраты.КлючСвязи = &ПустойУИД"; // Фильтруем данные прямо в запросе

Теперь наш запрос будет возвращать только те строки табличной части, у которых КлючСвязи пуст, и для каждой такой строки будет доступен ее НомерСтроки и ссылка на родительский объект справочника.

Правильно обновляем строки табличной части

Имея оптимизированный запрос, мы можем перейти к корректному обновлению данных. Разберем по шагам, как это сделать:

  1. Выполнение запроса и обход выборки: Выполняем запрос и обходим полученные результаты с помощью цикла Пока ВыборкаДетальныеЗаписи.Следующий() Цикл.
  2. Получение объекта справочника: Внутри цикла для каждой найденной строки табличной части нам понадобится получить объект справочника-владельца. Это делается с помощью метода ПолучитьОбъект() от ссылки: Загрузка = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();.
  3. Доступ к конкретной строке табличной части: Теперь, когда у нас есть объект справочника (Загрузка) и его табличная часть (Загрузка.Трудозатраты), а также НомерСтроки из выборки, мы можем получить нужную строку табличной части. Помним про индексацию с нуля: СтрокаТЧ = Загрузка.Трудозатраты[ВыборкаДетальныеЗаписи.НомерСтроки - 1];.
  4. Присвоение нового УИД: Как только мы получили ссылку на нужную строку табличной части (СтрокаТЧ), мы можем присвоить новый уникальный идентификатор ее реквизиту КлючСвязи: СтрокаТЧ.КлючСвязи = Новый УникальныйИдентификатор();.
  5. Запись объекта: После всех изменений необходимо записать модифицированный объект справочника, чтобы изменения сохранились в базе данных: Загрузка.Записать();.

Вот полный исправленный и оптимизированный код:


Запрос = Новый Запрос;
Запрос.УстановитьПараметр("ПустойУИД", Новый УникальныйИдентификатор("00000000-0000-0000-000000000000"));
Запрос.Текст =
    "ВЫБРАТЬ
    |    РесурсныеСпецификацииТрудозатраты.НомерСтроки КАК НомерСтроки,
    |    РесурсныеСпецификацииТрудозатраты.КлючСвязи КАК КлючСвязи,
    |    РесурсныеСпецификацииТрудозатраты.ВидРабот КАК ВидРабот,
    |    РесурсныеСпецификацииТрудозатраты.Этап КАК Этап,
    |    РесурсныеСпецификацииТрудозатраты.Ссылка КАК Ссылка
    |ИЗ
    |    Справочник.РесурсныеСпецификации.Трудозатраты КАК РесурсныеСпецификацииТрудозатраты
    |ГДЕ
    |    РесурсныеСпецификацииТрудозатраты.КлючСвязи = &ПустойУИД";

ВыборкаДетальныеЗаписи = Запрос.Выполнить().Выбрать();
Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
    // Получаем объект-владелец справочника
    Загрузка = ВыборкаДетальныеЗаписи.Ссылка.ПолучитьОбъект();
    
    // Получаем нужную строку табличной части по НомеруСтроки
    // Важно: НомерСтроки в запросе начинается с 1, а индекс коллекции в 1С с 0
    СтрокаДокумента = Загрузка.Трудозатраты[ВыборкаДетальныеЗаписи.НомерСтроки - 1];
    
    // Присваиваем новый уникальный идентификатор реквизиту КлючСвязи этой строки
    СтрокаДокумента.КлючСвязи = Новый УникальныйИдентификатор();
    
    // Записываем измененный объект справочника
    Загрузка.Записать();
КонецЦикла;

Обратите внимание, что переменная НомерСтроки в коде выше берется непосредственно из выборки запроса (ВыборкаДетальныеЗаписи.НомерСтроки), а не определяется как отдельная переменная. Это позволяет избежать ошибки "Переменная не определена (НомерСтроки)", с которой столкнулся пользователь в ходе обсуждения.

Дополнительные рекомендации и оптимизация

Рассмотрим несколько дополнительных моментов, которые помогут улучшить производительность и надежность кода при массовых изменениях:

  1. Оптимизация ПолучитьОбъект(): Если один и тот же объект справочника содержит несколько строк табличной части, требующих обновления, нет необходимости вызывать ПолучитьОбъект() для каждой строки. Мы можем группировать строки по ссылке на родительский объект и получать объект один раз для всех его изменяемых строк. Например, можно использовать структуру для хранения объектов, уже полученных в рамках текущего цикла, чтобы избежать повторного чтения из базы данных.
  2. 
    // Пример оптимизации ПолучитьОбъект()
    КэшОбъектов = Новый Карта(); // Или Новый Соответствие()
    Пока ВыборкаДетальныеЗаписи.Следующий() Цикл
        СсылкаНаОбъект = ВыборкаДетальныеЗаписи.Ссылка;
        Если КэшОбъектов.Содержит(СсылкаНаОбъект) Тогда
            Загрузка = КэшОбъектов.Получить(СсылкаНаОбъект);
        Иначе
            Загрузка = СсылкаНаОбъект.ПолучитьОбъект();
            КэшОбъектов.Вставить(СсылкаНаОбъект, Загрузка);
        КонецЕсли;
            
        // Дальнейшая логика обновления строки
        СтрокаДокумента = Загрузка.Трудозатраты[ВыборкаДетальныеЗаписи.НомерСтроки - 1];
        СтрокаДокумента.КлючСвязи = Новый УникальныйИдентификатор();
    КонецЦикла;
        
    // После цикла обхода выборки, записываем все кэшированные объекты
    Для Каждого ЭлементКэша Из КэшОбъектов Цикл
        ЭлементКэша.Значение.Записать();
    КонецЦикла;
    
  3. Использование транзакций: При массовом изменении данных рекомендуется обернуть операцию в транзакцию. Это гарантирует атомарность операции: либо все изменения будут успешно применены, либо ни одно из них. Это особенно важно в случае сбоев.
  4. 
    НачатьТранзакцию();
    Попытка
        // ... Ваш код обновления данных ...
        ЗафиксироватьТранзакцию();
    Исключение
        ОтменитьТранзакцию();
        ВызватьИсключение; // Или обработать ошибку
    КонецПопытки;
    
  5. Индексы базы данных: Убедитесь, что поле КлючСвязи в табличной части имеет индекс. Это значительно ускорит выполнение запросов с условием ГДЕ по этому полю, так как базе данных не придется выполнять полное сканирование таблицы. Платформа 1С автоматически создает индексы для ряда стандартных реквизитов, но для добавленных вручную реквизитов, особенно типа УникальныйИдентификатор, может потребоваться ручная установка индекса через конфигуратор.
  6. Учет блокировок: При работе в многопользовательской среде длительные операции записи могут вызывать блокировки. Если справочник часто используется, рассмотрите возможность использования механизма блокировок данных (например, БлокировкаДанных) или разбиения операции на более мелкие части.
  7. Фоновые задания: Для очень больших объемов данных или операций, не требующих немедленного выполнения, целесообразно выносить такие задачи в фоновые задания (регламентные задания или фоновые процессы), чтобы не блокировать работу пользователей.
  8. Получение УИД ссылки в запросе (актуально для последних версий платформы): Начиная с версии платформы 8.3.22, появилась возможность получать уникальный идентификатор ссылки непосредственно в запросе, что в некоторых сценариях может упростить работу, если нужен только УИД, а не сам объект. Однако для изменения данных все равно потребуется ПолучитьОбъект().

Следуя этим рекомендациям, вы сможете не только корректно обновить уникальные идентификаторы в табличных частях справочников, но и обеспечить высокую производительность и стабильность работы вашей информационной системы 1С:Предприятие.

← На главную