处理遗留数据库时在 NHibernate 中建模多对一关系的最佳方法?

Posted

技术标签:

【中文标题】处理遗留数据库时在 NHibernate 中建模多对一关系的最佳方法?【英文标题】:Best way to model Many-To-One Relationships in NHibernate When Dealing With a Legacy DB? 【发布时间】:2008-08-14 11:56:29 【问题描述】:

警告 - 我对 NHibernate 很陌生。我知道这个问题看起来很简单——而且我敢肯定有一个简单的答案,但我已经在这个问题上旋转了一段时间。我正在处理一个在结构上确实无法改变的遗留数据库。我有一个详细信息表,其中列出了客户已接受的付款计划。每个付款计划都有一个 ID,它链接回参考表以获取计划的条款、条件等。在我的对象模型中,我有一个 AcceptedPlan 类和一个 Plan 类。最初,我使用从 detail 表到 ref 表的多对一关系在 NHibernate 中对这种关系进行建模。我还创建了一个从 Plan 类到 AcceptedPlan 类的相反方向的一对多关系。当我只是读取数据时,这很好。我可以转到我的 Plan 对象,这是我的 AcceptedPlan 类的一个属性来阅读计划的详细信息。当我不得不开始向详细信息表中插入新行时,我的问题就出现了。根据我的阅读,似乎创建新子对象的唯一方法是将其添加到父对象,然后保存会话。但是我不想每次创建新的详细记录时都必须创建一个新的父 Plan 对象。这似乎是不必要的开销。有谁知道我是否以错误的方式处理这件事?

【问题讨论】:

【参考方案1】:

我会避免让子对象包含其逻辑父对象,当您这样做时,它会很快变得非常混乱和非常递归。在你做那种事情之前,我会先看看你打算如何使用域模型。您仍然可以轻松地在表中保留 ID 引用,而无需将它们映射。

这里有两个示例映射可能会推动您朝着正确的方向前进,我不得不即兴发挥表名等,但它可能会有所帮助。我可能还建议将 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>

【讨论】:

【参考方案2】:

我在写的时候没有看到你的数据库图。

<?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>

应该可以解决问题(我只为集合做了示例映射,您必须添加其他属性)。

【讨论】:

【参考方案3】:

我将采取的建模方法如下:

Customer 对象包含一个 ICollection PaymentPlans,表示客户已接受的计划。

将使用包映射到客户的 PaymentPlan,该包使用详细信息表来确定哪个客户 ID 映射到哪个 PaymentPlan。使用 cascade all-delete-orphan,如果客户被删除,来自 details 的条目和客户拥有的 PaymentPlans 都将被删除。

PaymentPlan 对象包含一个表示付款计划条款的 PlanTerms 对象。

将使用多对一映射级联保存更新将 PlanTerms 映射到 PaymentPlan,这只会将相关 PlanTerms 对象的引用插入到 PaymentPlan 中。

使用此模型,您可以独立创建 PlanTerms,然后当您向客户添加新的 PaymentPlan 时,您将创建一个新的 PaymentPlan 对象,传入相关的 PlanTerms 对象,然后将其添加到相关客户的集合中。最后你要保存 Customer 并让 nhibernate 级联保存操作。

您最终会得到一个 Customer 对象、一个 PaymentPlan 对象和一个 PlanTerms 对象,其中 Customer(客户表)拥有 PaymentPlans 实例(详细信息表),这些实例都遵守特定的 PlanTerms(计划表)。

如果需要,我有一些更具体的映射语法示例,但最好使用您自己的模型完成它,而且我没有足够的数据库表信息来提供任何具体示例。

【讨论】:

【参考方案4】:

我不知道这是否可能是因为我的 NHibernate 经验有限,但是您能否创建一个 BaseDetail 类,该类仅具有 Details 的属性,因为它们直接映射到 Detail 表。

然后创建第二个类,该类继承自具有附加父计划对象的 BaseDetail 类,因此当您只想创建一个详细信息行并将 PlanId 分配给它时,您可以创建一个 BaseDetail 类,但如果您需要填充带有父计划对象的完整详细信息记录,您可以使用继承的详细信息类。

我不知道这是否有意义,但请告诉我,我会进一步澄清。

【讨论】:

【参考方案5】:

我认为您遇到的问题是您的 AcceptedOffer 对象包含一个 Plan 对象,然后您的 Plan 对象似乎包含一个包含 AcceptedOffer 对象的 AcceptedOffers 集合。与客户相同。我认为,这些对象是彼此的孩子这一事实是导致您出现问题的原因。

同样,AcceptedOffer 复杂的原因在于它有两个职责:表示计划中包含的报价,表示客户接受。这违反了单一职责原则。

您可能必须区分计划下的优惠和客户接受的优惠。所以这就是我要做的:

    创建一个单独的 Offer 对象,它没有状态,例如,它没有客户,也没有状态 - 它只有一个 OfferId 和它所属的计划作为其属性。 修改您的 Plan 对象以具有 Offers 集合(它不必在其上下文中接受要约)。 最后,修改 AcceptedOffer 对象,使其包含要约、客户和状态。客户保持不变。

我认为这将充分解决您的 NHibernate 映射和对象保存问题。 :)

【讨论】:

【参考方案6】:

在 NHibernate 中可能(或可能不会)有用的提示:您可以将对象映射到视图,就好像视图是一个表一样。只需将视图名指定为表名即可;只要所有 NOT NULL 字段都包含在视图和映射中,它就可以正常工作。

【讨论】:

以上是关于处理遗留数据库时在 NHibernate 中建模多对一关系的最佳方法?的主要内容,如果未能解决你的问题,请参考以下文章

您是不是会将 NHibernate 用于具有部分无法控制的遗留数据库的项目?

用NHibernate处理带属性的多对多关系

许多实体到一个联结表 NHibernate 建模

流畅的 NHibernate 和朋友关系

DAO/Repository/NHibernate 和处理边缘数据库案例

使用 NHibernate 创建数据库视图