Лучший способ смоделировать отношения "многие-к-одному" в NHibernate при работе с устаревшей БД?
Предупреждение - я новичок в NHibernate. Я знаю, что этот вопрос кажется простым - и я уверен, что есть простой ответ, но я уже некоторое время крутил колеса над этим. Я имею дело с устаревшей базой данных, которую действительно нельзя изменить структурно. У меня есть таблица с подробностями, в которой перечислены планы платежей, которые были приняты клиентом. Каждый план платежей имеет идентификатор, который ведет к справочной таблице, чтобы получить условия плана и т. Д. В моей объектной модели у меня есть класс AcceptedPlan и класс Plan. Первоначально я использовал отношение «многие к одному» из таблицы сведений обратно в таблицу ссылок для моделирования этой взаимосвязи в NHibernate. Я также создал отношение «один ко многим», идущее в противоположном направлении от класса Plan к классу AcceptedPlan. Это было нормально, пока я просто читал данные. Я мог перейти к своему объекту Plan, который был свойством моего класса AcceptedPlan, чтобы прочитать детали плана. Моя проблема возникла, когда мне пришлось начать вставлять новые строки в таблицу деталей. Насколько я знаю, кажется, что единственный способ создать новый дочерний объект - это добавить его к родительскому объекту, а затем сохранить сеанс. Но я не хочу создавать новый родительский объект Plan каждый раз, когда хочу создать новую подробную запись. Это похоже на ненужные накладные расходы. Кто-нибудь знает, поступаю ли я неправильно? Я не хочу создавать новый родительский объект Plan каждый раз, когда я хочу создать новую подробную запись. Это похоже на ненужные накладные расходы. Кто-нибудь знает, поступаю ли я неправильно? Я не хочу создавать новый родительский объект Plan каждый раз, когда я хочу создать новую подробную запись. Это похоже на ненужные накладные расходы. Кто-нибудь знает, поступаю ли я неправильно?
Ответов (6)6
Я бы воздержался от наличия дочернего объекта, содержащего своего логического родителя, когда вы это сделаете, он может очень быстро стать очень беспорядочным и очень рекурсивным. Я бы посмотрел, как вы собираетесь использовать модель предметной области, прежде чем делать что-то подобное. Вы можете легко сохранить ссылки на идентификаторы в таблицах и просто оставить их несопоставленными.
Вот два примера сопоставления, которые могут подтолкнуть вас в правильном направлении. Мне пришлось изменить имена таблиц и т. Д., Но это может помочь. Я бы, вероятно, также предложил сопоставить 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>
Подход, который я бы использовал для моделирования этого, выглядит следующим образом:
Объект клиента содержит ICollection <PaymentPlan> 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.
Я не знаю, имеет ли это большой смысл, но дайте мне знать, и я проясню дальше.
I think the problem you have here is that your AcceptedOffer object contains a Plan object, and then your Plan object appears to contain an AcceptedOffers collection that contains AcceptedOffer objects. Same thing with Customers. The fact that the objects are a child of each other is what causes your problem, I think.
Likewise, what makes your AcceptedOffer complex is it has a two responsibilities: it indicates offers included in a plan, it indicates acceptance by a customer. That violates the Single Responsibility Principle.
Возможно, вам придется различать Предложение, входящее в план, и Предложение, которое принимается клиентами. Итак, вот что я собираюсь сделать:
- Создайте отдельный объект Offer, который не имеет состояния, например, у него нет клиента и у него нет статуса - в качестве атрибутов у него есть только OfferId и план, которому он принадлежит.
- Измените свой объект Plan, чтобы он имел коллекцию предложений (не обязательно принимать предложение в своем контексте).
- Наконец, измените объект AcceptedOffer, чтобы он содержал предложение, клиента и статус. Заказчик остался прежним.
Я думаю, что это в достаточной мере распутает ваши сопоставления NHibernate и проблемы с сохранением объектов. :)
Не видел диаграммы вашей базы данных, пока писал.
<?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>
Вероятно, это должно помочь (я сделал только примеры сопоставлений для коллекций, вам нужно будет добавить другие свойства).