Как правильно хранить список значений в одной строке табличной части документа 1С?

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

Довольно часто в процессе разработки мы сталкиваемся с необходимостью предоставить пользователю возможность выбора нескольких вариантов значений для одной конкретной строки табличной части документа. На первый взгляд задача кажется тривиальной: мы добавляем на форму колонку с типом СписокЗначений, а данные пытаемся сохранять в реквизит объекта с типом ХранилищеЗначения или используем сериализацию в строку. Давайте подробно разберем эту ситуацию, проанализируем, почему такой подход в корне неверен, и вместе спроектируем правильную архитектуру.

Почему использование типа "ХранилищеЗначения" — это антипаттерн?

Рассмотрим подробнее логику работы платформы 1С. На уровне базы данных 1С строго использует реляционную модель. Табличная часть — это классическая связь «один ко многим» по отношению к самому документу. Платформа физически не поддерживает вложенные многомерные структуры (вложенные таблицы внутри строк таблиц). Попытка поместить полноценную коллекцию в одно поле нарушает базовые правила нормализации баз данных.

Если мы решим использовать ХранилищеЗначения или преобразовывать список через функцию ЗначениеВСтрокуВнутр, мы неизбежно столкнемся с критическими проблемами бизнес-логики:

  1. Полная изоляция от запросов и системы отчетов. Данные в хранилище лежат в сериализованном бинарном виде. Сервер баз данных (СУБД) и язык запросов 1С не умеют читать такие поля. Мы физически не сможем вывести эти значения в отчет на СКД, сгруппировать по ним данные или наложить отбор стандартными средствами.
  2. Потеря контроля ссылочной целостности. Механизмы 1С тщательно отслеживают связи между объектами (например, система не даст удалить элемент справочника, если он выбран в документе). Однако платформа «не видит» ссылок, спрятанных внутри бинарного поля ХранилищеЗначения. Если пользователь удалит элемент справочника, система не выдаст предупреждений, а при следующем открытии нашего списка мы получим «битую ссылку» (известную всем как <Объект не найден>).
  3. Избыточная нагрузка на сервер. Процесс сериализации и десериализации данных из хранилища при каждом обращении к строке требует дополнительных ресурсов и существенно замедляет работу интерфейса.

Правильное архитектурное решение: Две связанные табличные части

Выясним, как решают эту задачу опытные разработчики. Общепринятый паттерн (best practice) для хранения таких данных — это создание второй (подчиненной) табличной части. Это позволит сохранить базу данных плоской и прозрачной для любых отчетов. При необходимости можно воспользоваться примером реализации механизма связывания главной и подчиненной таблиц на управляемых формах.

Разберем структуру данных по шагам:

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

Как реализовать удобный интерфейс для пользователя

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

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

Посмотрим на примерный псевдокод, который иллюстрирует сборку данных перед отображением пользователю:


// Пример обработчика формы
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
    
    Для Каждого СтрокаГлавнойТЧ Из ТекущийОбъект.ГлавнаяТаблица Цикл
        
        // Ищем связанные значения в подчиненной таблице
        Отбор = Новый Структура("КлючСвязи", СтрокаГлавнойТЧ.КлючСтроки);
        СтрокиЗначений = ТекущийОбъект.СкрытаяТаблицаЗначений.НайтиСтроки(Отбор);
        
        СписокДляФормы = Новый СписокЗначений;
        Для Каждого СтрЗначение Из СтрокиЗначений Цикл
            СписокДляФормы.Добавить(СтрЗначение.Значение);
        КонецЦикла;
        
        // Заполняем реквизит формы для отображения пользователю
        СтрокаДанныхФормы = ЭтотОбъект.ГлавнаяТаблица.НайтиПоИдентификатору(СтрокаГлавнойТЧ.ПолучитьИдентификатор());
        Если СтрокаДанныхФормы <> Неопределено Тогда
            СтрокаДанныхФормы.ВизуальныйСписокЗначений = СписокДляФормы;
        КонецЕсли;
        
    КонецЦикла;

КонецПроцедуры

И аналогично проанализируем сохранение данных в базу:


&НаСервере
Процедура ПередЗаписьюНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
    
    // Очищаем старые данные
    ТекущийОбъект.СкрытаяТаблицаЗначений.Очистить();
    
    // Перебираем визуальную таблицу формы
    Для Каждого СтрокаФормы Из ЭтотОбъект.ГлавнаяТаблица Цикл
        
        Если ЗначениеЗаполнено(СтрокаФормы.ВизуальныйСписокЗначений) Тогда
            Для Каждого ЭлементСписка Из СтрокаФормы.ВизуальныйСписокЗначений Цикл
                
                // Добавляем запись в скрытую ТЧ объекта
                НоваяСтрокаЗначений = ТекущийОбъект.СкрытаяТаблицаЗначений.Добавить();
                НоваяСтрокаЗначений.КлючСвязи = СтрокаФормы.КлючСтроки;
                НоваяСтрокаЗначений.Значение = ЭлементСписка.Значение;
                
            КонецЦикла;
        КонецЕсли;
        
    КонецЦикла;

КонецПроцедуры

Подведем итог: Данный подход (использование связанных табличных частей) позволяет совместить удобный пользовательский интерфейс и правильную архитектуру хранения данных «под капотом». Мы сохраняем всю мощь языка запросов, целостность ссылок и возможность легко использовать выбранные значения в любых отчетах и регистрах системы.

← На главную