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