Лучший способ моделировать отношения "Многие к одному" в NHibernate при работе с устаревшей базой данных?
-
08-06-2019 - |
Вопрос
Предупреждение - я очень новичок в NHibernate.Я знаю, этот вопрос кажется простым - и я уверен, что на него есть простой ответ, но я уже некоторое время ломаю голову над этим вопросом.Я имею дело с устаревшей базой данных, которая действительно не может быть изменена структурно.У меня есть таблица сведений, в которой перечислены планы платежей, которые были приняты клиентом.Каждый платежный план имеет идентификатор, который ссылается на справочную таблицу, чтобы ознакомиться с условиями плана и т.д.В моей объектной модели у меня есть класс AcceptedPlan и класс Plan.Первоначально я использовал отношение "многие к одному" из таблицы сведений обратно в таблицу ссылок, чтобы смоделировать это отношение в NHibernate.Я также создал отношение "один ко многим", идущее в противоположном направлении от класса Plan к классу AcceptedPlan.Это было нормально, пока я просто считывал данные.Я мог бы перейти к своему объекту Plan, который был свойством моего класса AcceptedPlan, чтобы прочитать детали плана.Моя проблема возникла, когда мне пришлось начать вставлять новые строки в таблицу сведений.Из моего чтения кажется, что единственный способ создать новый дочерний объект - это добавить его к родительскому объекту, а затем сохранить сеанс.Но я не хочу создавать новый родительский объект плана каждый раз, когда я хочу создать новую подробную запись.Это кажется ненужными накладными расходами.Кто-нибудь знает, не иду ли я по этому пути неправильно?
Решение
Я бы предпочел не иметь дочерний объект, содержащий своего логического родителя, это может довольно быстро стать очень запутанным и очень рекурсивным, когда вы это делаете.Я бы взглянул на то, как вы собираетесь использовать модель предметной области, прежде чем делать что-то подобное.Вы можете легко сохранить ссылки на идентификаторы в таблицах и просто оставить их не сопоставленными.
Вот два примера сопоставлений, которые могут подтолкнуть вас в правильном направлении, мне приходилось использовать имена таблиц adlib и т.д., Но, возможно, это могло бы помочь.Я бы, вероятно, также предложил сопоставить 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>
Другие советы
Не видел вашей схемы базы данных, пока писал.
<?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>
Вероятно, это должно сработать (я сделал только примеры сопоставлений для коллекций, вам нужно будет добавить другие свойства).
Подход, который я бы использовал для моделирования этого, заключается в следующем:
Объект Customer содержит ICollection <PaymentPlan> Планы платежей, которые представляют планы, принятые клиентом.
Платежный план для Клиента будет сопоставлен с помощью пакета, который использует таблицу сведений, чтобы установить, какой идентификатор клиента сопоставлен с какими платежными планами.Используя cascade all-delete-orphan, если клиент был удален, будут удалены как записи из сведений, так и планы платежей, принадлежащие клиенту.
Объект PaymentPlan содержит объект PlanTerms, который представляет условия плана платежей.
PlanTerms будут сопоставлены с PaymentPlan с использованием каскадного сохранения-обновления сопоставления "многие к одному", которое просто вставит ссылку на соответствующий объект PlanTerms в PaymentPlan.
Используя эту модель, вы могли бы создавать PlanTerms независимо, а затем, когда вы добавляете новый PaymentPlan клиенту, вы создаете новый объект PaymentPlan, передающий соответствующий объект PlanTerms, а затем добавляете его в коллекцию соответствующего клиента.Наконец, вы бы сохранили Клиента и позволили nhibernate выполнить каскадную операцию сохранения.
В итоге вы получите объект Customer, объект PaymentPlan и объект PlanTerms с Клиентом (таблица customer), владеющим экземплярами PaymentPlans (таблица details), которые все привязаны к конкретным PlanTerms (таблица plan).
У меня есть еще несколько конкретных примеров синтаксиса сопоставления, если требуется, но, вероятно, лучше всего проработать это с помощью вашей собственной модели, и у меня недостаточно информации о таблицах базы данных, чтобы предоставить какие-либо конкретные примеры.
Я не знаю, возможно ли это из-за того, что мой опыт работы с NHibernate ограничен, но не могли бы вы создать класс BaseDetail, который имеет только свойства для Деталей, поскольку они отображаются непосредственно в таблицу Detail.
Затем создайте второй класс, наследующий от класса BaseDetail, который имеет дополнительный родительский объект Plan, чтобы вы могли создать класс BaseDetail, когда хотите просто создать строку сведений и назначить ей PlanID, но если вам нужно заполнить полную запись сведений родительским объектом plan, вы можете использовать унаследованный класс Detail.
Я не знаю, есть ли в этом какой-то смысл, но дайте мне знать, и я уточню подробнее.
Я думаю, проблема, с которой вы столкнулись здесь, заключается в том, что ваш объект AcceptedOffer содержит объект Plan, а затем ваш объект Plan, по-видимому, содержит коллекцию AcceptedOffers, которая содержит объекты AcceptedOffer.То же самое и с Клиентами.Я думаю, причина вашей проблемы в том, что объекты являются дочерними друг от друга.
Аналогично, что делает ваше принятое предложение сложным, так это то, что у него есть две обязанности:это указывает на предложения, включенные в план, это указывает на принятие клиентом.Это нарушает Принцип Единой ответственности.
Возможно, вам придется проводить различие между Предложением, которое действует в рамках Плана, и Предложением, которое принимается клиентами.Итак, вот что я собираюсь сделать:
- Создайте отдельный объект предложения, у которого нет состояния, например, у него нет клиента и у него нет статуса - у него есть только offerId и План, к которому он принадлежит, в качестве его атрибутов.
- Измените свой объект Плана, чтобы у него была коллекция предложений (в его контексте необязательно должно быть акцептованное предложение).
- Наконец, измените свой объект AcceptedOffer таким образом, чтобы он содержал Предложение, Клиента и Статус.Клиент остается тем же самым.
Я думаю, что это в достаточной степени распутает ваши NHibernate-сопоставления и проблемы с сохранением объектов.:)
Совет, который может быть (а может и не быть) полезен в NHibernate:вы можете сопоставить свои объекты с представлениями, как если бы представление было таблицей.Просто укажите имя представления в качестве имени таблицы;пока все ненулевые поля включены в представление и сопоставление, оно будет работать нормально.