Лучший способ смоделировать отношения "многие-к-одному" в NHibernate при работе с устаревшей БД?

Предупреждение - я новичок в NHibernate. Я знаю, что этот вопрос кажется простым - и я уверен, что есть простой ответ, но я уже некоторое время крутил колеса над этим. Я имею дело с устаревшей базой данных, которую действительно нельзя изменить структурно. У меня есть таблица с подробностями, в которой перечислены планы платежей, которые были приняты клиентом. Каждый план платежей имеет идентификатор, который ведет к справочной таблице, чтобы получить условия плана и т. д. В моей объектной модели у меня есть класс AcceptedPlan и класс Plan. Первоначально я использовал отношение «многие к одному» из таблицы сведений обратно в таблицу ссылок для моделирования этой взаимосвязи в NHibernate. Я также создал отношение «один ко многим», идущее в противоположном направлении от класса Plan к классу AcceptedPlan. Это было нормально, пока я просто читал данные. Я мог перейти к своему объекту Plan, который был свойством моего класса AcceptedPlan, чтобы прочитать детали плана. Моя проблема возникла, когда мне пришлось начать вставлять новые строки в таблицу деталей. Насколько я знаю, кажется, что единственный способ создать новый дочерний объект - это добавить его к родительскому объекту, а затем сохранить сеанс. Но я не хочу создавать новый родительский объект Plan каждый раз, когда хочу создать новую подробную запись. Это похоже на ненужные накладные расходы. Кто-нибудь знает, поступаю ли я неправильно?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
0
14 364
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

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

Объект клиента содержит ICollection PaymentPlans, которые представляют планы, принятые клиентом.

План PaymentPlan для клиента будет сопоставлен с использованием пакета, который использует таблицу сведений, чтобы установить, какой идентификатор клиента сопоставлен с какими PaymentPlans. При использовании каскада all-delete-orphan, если клиент был удален, будут удалены как записи из подробностей, так и платежные планы, принадлежащие клиенту.

Объект PaymentPlan содержит объект PlanTerms, который представляет условия плана платежей.

PlanTerms будет сопоставлен с PaymentPlan с использованием каскадного сопоставления «многие-к-одному» с сохранением-обновлением, которое просто вставит ссылку на соответствующий объект PlanTerms в PaymentPlan.

Используя эту модель, вы можете создать PlanTerms независимо, а затем, когда вы добавляете новый PaymentPlan клиенту, вы должны создать новый объект PaymentPlan, передав соответствующий объект PlanTerms, а затем добавить его в коллекцию соответствующего клиента. Наконец, вы должны сохранить клиента и позволить nhibernate каскадировать операцию сохранения.

В итоге вы получите объект Customer, объект PaymentPlan и объект PlanTerms, в котором клиент (таблица клиентов) владеет экземплярами PaymentPlans (таблица деталей), которые все соответствуют определенным PlanTerms (таблица плана).

У меня есть еще несколько конкретных примеров синтаксиса сопоставления, если это необходимо, но, вероятно, лучше всего проработать его с вашей собственной моделью, а у меня недостаточно информации о таблицах базы данных, чтобы предоставить какие-либо конкретные примеры.

Я не знаю, возможно ли это из-за того, что мой опыт NHibernate ограничен, но не могли бы вы создать класс BaseDetail, который имеет только свойства для Details, поскольку они отображаются непосредственно в таблице Detail.

Затем создайте второй класс, который наследуется от класса BaseDetail, который имеет дополнительный объект родительского плана, чтобы вы могли создать класс BaseDetail, когда вы хотите просто создать строку деталей и назначить ей PlanId, но если вам нужно заполнить полную деталь запись с объектом родительского плана можно использовать унаследованный класс Detail.

Я не знаю, имеет ли это большой смысл, но дайте мне знать, и я проясню дальше.

Я думаю, что проблема в том, что ваш объект AcceptedOffer содержит объект Plan, а затем ваш объект Plan, по-видимому, содержит коллекцию AcceptedOffers, содержащую объекты AcceptedOffer. То же самое и с клиентами. Я думаю, что тот факт, что объекты являются дочерними по отношению друг к другу, и является причиной вашей проблемы.

Точно так же то, что делает ваш AcceptedOffer сложным, состоит в том, что он выполняет две обязанности: он указывает предложения, включенные в план, и указывает на принятие клиентом. Это нарушает принцип единой ответственности.

Возможно, вам придется различать Предложение, входящее в план, и Предложение, которое принимается клиентами. Итак, вот что я собираюсь сделать:

  1. Создайте отдельный объект Offer, который не имеет состояния, например, у него нет клиента и у него нет статуса - в качестве атрибутов у него есть только OfferId и план, которому он принадлежит.
  2. Измените свой объект Plan, чтобы иметь коллекцию предложений (не обязательно принимать предложение в своем контексте).
  3. Наконец, измените объект AcceptedOffer, чтобы он содержал предложение, клиента и статус. Заказчик остался прежним.

Я думаю, что это в достаточной мере распутает ваши сопоставления NHibernate и проблемы с сохранением объектов. :)

Ответ принят как подходящий

Я бы воздержался от наличия дочернего объекта, содержащего своего логического родителя, когда вы это сделаете, он может очень быстро стать очень беспорядочным и очень рекурсивным. Я бы посмотрел, как вы собираетесь использовать модель предметной области, прежде чем делать что-то подобное. Вы можете легко сохранить ссылки на идентификаторы в таблицах и просто оставить их несопоставленными.

Вот два примера сопоставления, которые могут подтолкнуть вас в правильном направлении. Мне пришлось изменить имена таблиц и т. д., Но это может помочь. Я бы, вероятно, также предложил сопоставить StatusId с перечислением.

Обратите внимание на то, как сумка эффективно отображает таблицу деталей в коллекции.

<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping default-cascade = "save-update" xmlns = "urn:nhibernate-mapping-2.2">
    <class lazy = "false" name = "Namespace.Customer, Namespace" table = "Customer">
        <id name = "Id" type = "Int32" unsaved-value = "0">
            <column name = "CustomerAccountId" length = "4" sql-type = "int" not-null = "true" unique = "true" index = "CustomerPK"/>
            <generator class = "native" />
        </id>

        <bag name = "AcceptedOffers" inverse = "false" lazy = "false" cascade = "all-delete-orphan" table = "details">
          <key column = "CustomerAccountId" foreign-key = "AcceptedOfferFK"/>
          <many-to-many
            class = "Namespace.AcceptedOffer, Namespace"
            column = "AcceptedOfferFK"
            foreign-key = "AcceptedOfferID"
            lazy = "false"
           />
        </bag>

  </class>
</hibernate-mapping>


<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping default-cascade = "save-update" xmlns = "urn:nhibernate-mapping-2.2">
    <class lazy = "false" name = "Namespace.AcceptedOffer, Namespace" table = "AcceptedOffer">
        <id name = "Id" type = "Int32" unsaved-value = "0">
            <column name = "AcceptedOfferId" length = "4" sql-type = "int" not-null = "true" unique = "true" index = "AcceptedOfferPK"/>
            <generator class = "native" />
        </id>

        <many-to-one 
          name = "Plan"
          class = "Namespace.Plan, Namespace"
          lazy = "false"
          cascade = "save-update"
        >
        <column name = "PlanFK" length = "4" sql-type = "int" not-null = "false"/>
        </many-to-one>

        <property name = "StatusId" type = "Int32">
            <column name = "StatusId" length = "4" sql-type = "int" not-null = "true"/>
        </property>

  </class>
</hibernate-mapping>

Совет, который может (а может и не быть) полезен в NHibernate: вы можете сопоставить свои объекты с представлениями, как если бы представление было таблицей. Просто укажите имя представления как имя таблицы; пока все поля NOT NULL включены в представление и отображение будет работать нормально.

Не видел диаграммы вашей базы данных, пока писал.

<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping default-cascade = "save-update" xmlns = "urn:nhibernate-mapping-2.2">
    <class lazy = "false" name = "Namespace.Customer, Namespace" table = "Customer">
        <id name = "Id" type = "Int32" unsaved-value = "0">
            <column name = "customer_id" length = "4" sql-type = "int" not-null = "true" unique = "true" index = "CustomerPK"/>
            <generator class = "native" />
        </id>

        <bag name = "AcceptedOffers" inverse = "false" lazy = "false" cascade = "all-delete-orphan">
            <key column = "accepted_offer_id"/>
            <one-to-many class = "Namespace.AcceptedOffer, Namespace"/>
        </bag>

  </class>
</hibernate-mapping>


<?xml version = "1.0" encoding = "utf-8" ?>
<hibernate-mapping default-cascade = "save-update" xmlns = "urn:nhibernate-mapping-2.2">
    <class lazy = "false" name = "Namespace.AcceptedOffer, Namespace" table = "Accepted_Offer">
        <id name = "Id" type = "Int32" unsaved-value = "0">
            <column name = "accepted_offer_id" length = "4" sql-type = "int" not-null = "true" unique = "true" />
            <generator class = "native" />
        </id>

        <many-to-one name = "Plan" class = "Namespace.Plan, Namespace" lazy = "false" cascade = "save-update">
            <column name = "plan_id" length = "4" sql-type = "int" not-null = "false"/>
        </many-to-one>

  </class>
</hibernate-mapping>

Вероятно, это должно помочь (я сделал только примеры сопоставлений для коллекций, вам нужно будет добавить другие свойства).

Другие вопросы по теме