тут вопрос семантики ссылок. Если у вас репозиторий возвращает "копию" объекта а не ссылку на него, то update нужен. Если вы предпочитаете работать с репозиторием как с коллекцией, которая возвращает вам ссылку - то ссылка у репозитория на объекты выданные есть и нужно просто сообщить кому-то кто явно выше места использования репозитория что "операция завершена - комить изменения в базу". Это вот работа unit of work.
На одном объекте это все может выглядеть эквивалентно и потому могут возникать сложности с пониманием подхода. Рассматривать надо вопрос "границ транзакции" и как они определяются в системе. Тут можно взять 2 крайности: отсутствие явных границ в коде и тотальное разбиение всего стэйта с использованием паттерна агрегат.
Классика которую можно встретить в подавляющем большинстве проектов с ОРМ-ками:
- достаем сущность
- передаем сущность в какой-нибудь сервис "сделай что-то"
- этот сделай что-то достает другую сущность по связи и передает ее другому сервису "сделай вот это вот с ним"
Теперь у нас выбор - либо мы вручную следим за тем что поменялось и вручную апдейтим (явно) либо мы хотим что бы система сама за этим следила. Почему? Отношения объектов могут быть нетривиальны, и если мы мутируем стэйт в сервисах то либо мы размазываем персистенс по всей логики приложения (что бы отслеживать изменения) либо мы будем эти изменения неявно трекать снаружи запоминая какие объекты наш репозиторий возвращал и какие объекты по связям загружались.
Вот это "трекать снаружи" - это и есть unit of work паттерн. Он в целом (как может подсказывать название) и является нашей границей транзакции - тот стэйт который мог быть изменен в процессе операции. Этот подход очень выгоден когда не хочется вообще следить за такими мелочами как "кто какой стэйт трогает в системе". Это удобно и для большинства людей проще. Позволяет избавиться от целой кучи очень сложной логики которая может пораждать баги.
Минус паттерна - сам он очень сложен в реализации, очень требователен к ресурсам (по факту это такой dirty matching стэйта) и в целом как говаривал Фаулер "сделать универсальный UoW в рамках проекта не окупается". Если вам уже достался готовый (от гибернейта например или вашей ORM) то вы получаете это бесплатно и грех не пользоваться.
Другая крайность - это более жесткие требования к операциям и стэйту. Паттерн агрегат по сути рекламентирует что "никаких достал сущности снаружи по связям" быть не может - вы достаете граф объектов целиком и он сохраняется целиком. Можно делать дифы на уровне отдельных агрегатов и т.д. То есть наш агрегат и есть unit of work в этом смысле. Просто существенно проще.
Причем мы можем так же либо трекать ссылку на агрегаты возвращаемые репозиторием а потом коммитить изменения, либо - если мы пойдем еще дальше и скажем что один агрегат одна логическая транзакция, мы можем в целом инкапсулировать контроль управления за процедурой обновления в репозиторий. Передать мол в методы получения стэйта метод операции над агрегатом:
repo.get(aggregateId, (aggregate) => aggregate.changeState())
и наш get сам уже разберется когда там чего коммитить и т.д. Тут уже вопрос хотим ли мы так упарываться. подход при котором мы репозиторий снаружи просим закоммитить все изменения все так же хорош. Поскольку стэйт агрегата не меняется снаружи мы получаем такой вот компромис между удобством (не надо трекать сильно изменения по приложению) и сложностью реализации (все же трекать агрегаты проще чем весь граф объектов который мы можем по связям загрузить)