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