При кажущейся простоте задачи из анонса, у меня набралось почти два десятка обстоятельств, которые могут повлиять на выбор способа решения. Разумеется, все нижесказанное справедливо для более общей задачи: как "привязать" к элементу справочника таблицу с необъектными данными, неважно, будь то список детей, складов или номенклатуры. Учет детей взят исключительно в качестве примера, для удобства демонстрации.
Итак, предположим, нам необходимо вести учет детей наших сотрудников (считаем, что справочник сотрудников у нас уже есть). Для простоты возьмем, что нам требуется хранить только имя и год рождения ребенка. Как было указано выше, будем рассматривать два варианта - табличную часть и регистр сведений. Попытаемся выявить как можно больше различий между этими двумя вариантами реализации, их достоинства и недостатки, подумаем, как эти недостатки нейтрализовать.
Вариант 1: Справочник Сотрудники с табличной частью
Вариант 2: Справочник Сотрудники2 и регистр сведений Дети с ведущим измерением Сотрудник
1. Удобство ввода
По удобству ввода информации первый вариант безусловно смотрится выигрышнее: гораздо удобней редактировать данные напрямую в таблице, чем открывать в отдельном окне каждую запись регистра
против
Что можно сделать со вторым вариантом? Добавить таблицу на форму, при чтении формы ее заполнять, а при записи - записывать в регистр. Тогда внешне форма элемента справочника не будет отличаться от варианта 1.
&НаСервере
Процедура ПриЧтенииНаСервере(ТекущийОбъект)
Набор = РегистрыСведений.Дети.СоздатьНаборЗаписей();
Набор.Отбор.Сотрудник.Установить(Объект.Ссылка);
Набор.Прочитать();
ТаблицаДети.Загрузить(Набор.Выгрузить());
КонецПроцедуры
&НаСервере
Процедура ПриЗаписиНаСервере(Отказ, ТекущийОбъект, ПараметрыЗаписи)
Набор = РегистрыСведений.Дети.СоздатьНаборЗаписей();
Набор.Отбор.Сотрудник.Установить(ТекущийОбъект.Ссылка);
Набор.Загрузить(ТаблицаДети.Выгрузить());
Для каждого Запись Из Набор Цикл
Запись.Сотрудник = ТекущийОбъект.Ссылка;
КонецЦикла;
Набор.Записать();
КонецПроцедуры
Обратите внимание: необходимо использовать именно ПриЧтенииНаСервере и ПриЗаписиНаСервере, а не ПриСозданииНаСервере, ПередЗаписьюНаСервере или ПослеЗаписиНаСервере.
Да, и надо не забывать устанавливать признак модифицированности при редактировании таблицы. Проще всего это сделать, поставив у таблицы флажок Сохраняемые данные.
Таким образом второй вариант у нас разделился на два: 2а - с редактированием записей регистра и 2б - с редактированием в таблице формы и сохранением набора записей.
2. Отмена изменений
Предположим, что пользователь, изменив какой-нибудь элемент справочника, хочет закрыть его, не сохраняя данные. Очевидно, что в варианте 1 и 2б это возможно, а в варианте 2а - нет. Также при создании нового элемента в варианте 2а невозможно добавить детей, не сохранив самого сотрудника. Эти два случая могут вызвать у неопытного пользователя вопросы и, возможно, привести к ошибкам.
3. Объектные блокировки
Платформа содержит неплохой механизм объектных блокировок, не позволяющий одновременного редактирования объектов. В нашем случае для варианта 1 все хорошо - два пользователя не могут одновременно редактировать список детей, в варианте 2а - список изменять могут, не могут только одновременно редактировать одну строку, в варианте 2б все печально - во время редактирования таблицы с детьми, другой пользователь или процесс может изменить этот список, и при окончании редактирования все изменения другого пользователя пропадут. Что тут можно сделать? ЗаблокироватьДанныеДляРедактирования здесь не поможет, единственный вариант - запоминать первоначальный набор записей и проверять его перед записью (конечно, проверка и запись нового набора должна сопровождаться управляемой блокировкой).
4. Создание копированием
Про создание копированием разработчики часто либо вообще забывают, либо вспоминают в последний момент. В первом варианте табличная часть с детьми, как компонент объекта, копируется в новый объект автоматически. Со вторым вариантом несколько сложнее. Чтобы получить аналогичный эффект, необходимо для варианта 2б: при создании формы заполнять таблицу данными копируемого объекта, а для варианта 2а: прочитать и запомнить табличную часть копируемого объекта, и при первой записи сотрудника заполнить подчиненный набор записей с детьми.
5. Отборы в списке
Здесь все просто: в первом варианте отбор по табличной части работает "из коробки":
Во втором варианте это так не работает. Вообще.
6. История изменений
История данных (история изменений) - относительно новый механизм, который пока не получил большого распространения (по-моему даже БСП его еще не использует). В случае с табличной частью он работает ожидаемо, мы видим список версий, можем их сравнить и увидеть отличия, в том числе в строках табличной части:
А вот с регистрами все по-другому.
Очевидно, что историю изменений надо смотреть у записей регистра. Так вот, в варианте 2а мы редактируем каждую запись по отдельности, т.е. вместо регистрации одного факта изменения списка детей сотрудников, мы получаем отдельные, не связанные друг с другом записи в истории изменений. К тому же работа с записью в этом случае идет через менеджер записи, а значит неявно через два набора записей, т.е. одно изменение порождает две записи в истории. Это приводит к тому, что мы в принципе не можем увидеть события изменения - только удаление и добавление, даже в том случае, когда меняется ресурс или реквизит регистра.
В варианте 2б немного иначе. В нем мы записываем записи набором. Здесь платформа понимает, что в каких-то записях набора мы можем изменить ресурс или реквизит, и правильно указывает флаг изменения. Но изменение одного из измерений платформа также считает удалением/добавлением:
7. Регистрация факта изменений
Этот пункт напрямую связан с предыдущим. Если мы захотим сделать свою регистрацию изменений, например для того, чтобы сохранять версии, или для отслеживания "дельты" при изменениях каких-либо данных (конечно же, речь сейчас не о детях), или для подсчета объема сделанной работы, то в случаях с регистром сведений это сделать в полном объеме либо вообще нельзя (если у нас происходят изменения посредством менеджера), либо достаточно трудоемко. Ведь теоретически запись регистра может осуществляться с разными по составу отборами. Для табличных частей определение факта изменения и содержание этого изменения гораздо проще.
8. Общие реквизиты
Здесь все просто - в табличных частях их использовать нельзя, в регистрах - можно. Зачем они нужны? Ну, например, может быть общий реквизит ДатаПоследнегоИзменения или ИдентификаторДляОбменаСоСтороннейСистемой.
9. Получение объекта
Цитата с ИТС: "При чтении отдельных реквизитов объекта из базы данных следует иметь в виду, что вызов метода ПолучитьОбъект или обращение к реквизитам объекта через точку от ссылки приводит к загрузке объекта из базы целиком, вместе с его табличными частями." Если табличных частей несколько и/или они объемные по количеству строк или колонок, то это может отрицательно сказаться на производительности. В таком случае вынос табличной части в регистр может дать положительный эффект, особенно если данные табличной части используются достаточно редко.
10. Права доступа
Тут все очевидно: для первого варианта доступ к сотруднику и его детям определяются одними и теми же ролями, для второго - могут быть разные роли. Это может иметь смысл, когда разные пользователи имеют разные уровни доступа. При отсутствии прав на доступ к данным о детях вариант 2а будет работать автоматически, в варианте 2б в коде надо дописать необходимые проверки.
11. Удаление
Еще один момент, про который часто забывают при разработке и тестировании. Я имею в виду не пометку на удаление, а удаление из базы с контролем ссылочной целостности. В регистре мы отчасти можем управлять автоматическим удалением записей при удалении объекта, на который ссылается измерение (с ресурсом и реквизитом это не работает). Достаточно использовать свойство Ведущее. Автоматическое удаление строк табличной части происходит только при удалении самого объекта.
12. Порядок работы с данными
В некоторых случаях организация работы с данными может быть довольно сложна. Так, данные табличных частей могут обрабатываться отдельно от данных объекта либо разными пользователями, либо в разных бизнес-процессах. В этом случае может быть целесообразно реализовать отдельные интерфейсы и, соответственно, выделить отдельный регистр.
13. Данные табличных частей всегда записываются вместе с объектом, в отличие от записей регистра. Это плохо.
Иногда данные табличных частей могут часто и/или интенсивно меняться. Чтобы не перезаписывать каждый раз объект (а при этом тратится время на выполнение обработчиков записи), лучше использовать отдельный регистр.
14. Данные табличных частей всегда записываются вместе с объектом, в отличие от записей регистра. Это хорошо.
Среди реквизитов объекта могут быть реквизиты, которые зависят от содержимого табличной части. Например: количество детей, общая сумма документа, строковое представление списка номенклатуры. В этом случае целесообразнее данные хранить в табличной части, а не в регистре, и расчет таких значений выполнять перед записью объекта. В противном случае, возможна ситуация, когда записи регистра могут быть изменены отдельно от изменения объекта, и сводный реквизит пересчитан не будет.
15. Удаление записей регистра
Нельзя забывать что записи регистра записываются и удаляются наборами. Нужно быть вдвойне осторожным, чтобы при записи пустого набора не забыть и не перепутать элементы отбора. Чтобы создать себе проблему достаточно одной строки РегистрыСведений.Дети.СоздатьНаборЗаписей().Записать(); Испортить содержимое табличных частей несколько сложнее.
16. Периодичность
Если таблица содержит в себе колонку вроде СрокНачалаДействия, то скорее всего лучше использовать периодический регистр сведений - дополнительно к хранению записи мы получаем удобный механизм среза последних (первых).
17. Нумерация
Иногда для строк таблицы важен порядковый номер или просто порядок последовательности записей. В этом случае предпочтительнее использовать табличную часть, где это есть "из коробки".
18. Уникальность набора полей
В некоторых случаях, для обеспечения целостности данных, необходим контроль уникальности набора колонок таблицы - логично тогда использовать регистр сведений. Но бывают ситуации, когда контроль уникальности не только не нужен, но и вреден - в таблице среди колонок нет ключа, даже набор всех полей не обеспечивает уникальности записи. В этом случае нужно либо использовать регистр, добавив уникальное измерение, либо использовать табличную часть, где такое уникальное поле есть - НомерСтроки. Бывают еще более сложные случаи, когда набор уникальных полей зависит от содержимого этих полей, в этом случае нет какой-либо общей рекомендации.
19. Длина индекса
Такое сообщение может возникнуть при попытке использовать набор измерений регистра, который порождает длинный ключ индекса. В этом случае, скорее всего, необходимо использовать табличную часть, в ней ключом является ссылка и номер строки, и такой проблемы не возникнет.
И последнее:
Хотя, возможно, оно должно быть первым. Проектирование должно начинаться с построения бизнес-модели и предваряться проведением анализа. При этом должны быть получены ответы на вопросы: 1) являются ли данные таблицы неотъемлемой частью элемента справочника? Например: список номенклатуры для накладной - неотъемлемая часть, а список произведенных ремонтов для оборудования - нет. 2) Согласно методики разработки 1С справочники предназначены для хранения условно-постоянной информации. Являются ли данные таблицы условно-постоянной информацией? 3) Являются ли строки таблицы объектами, на которые могут быть ссылки? Если да – то реализовывать таблицу надо вообще не регистром или табличной частью, а, например, подчиненным справочником. На вопросы необходимо ответить в рамках разрабатываемой системы, с учетом требований к разрабатываемой системе. В зависимости от ответа на эти вопросы должен быть сделан первоначальный выбор между рассматриваемыми вариантами.
Пример такого анализа:
дети сотрудников являются такими же физическими лицами, как и сотрудники, но в рамках разрабатываемой системы не предполагается работа с информацией о детях отдельно от сотрудников. Поскольку расчеты в системе по сотрудникам зависят от наличия и количества его детей, делаем вывод, что информация о детях - неотъемлемая часть данных сотрудника. Объем информации о детях одного сотрудника - небольшой. Так как информация о детях меняется редко, считаем ее справочной. Поэтому принимаем первоначальный вариант - реализация посредством табличной части.
В дальнейшем, после рассмотрения всех обстоятельств, технических моментов, вопросов доступа, UI/UX, наше решение может быть скорректировано.
Что же мы получили в итоге? Как видим, при всей кажущейся простоте поставленной задачи, есть немало обстоятельств, которые необходимо учесть при проектировании качественной системы. Очевидна необходимость тщательного анализа и требований заказчика, и структуры, и объема данных, а также методики работы с ними. Причем сделать такой анализ надо как можно раньше, чтобы потом не было мучительно больно(с)
На этом все. Как обычно приветствуются замечания / дополнения / комментарии.