【问题标题】:NHibernate: mapping single column from many-to-one to a primitive typeNHibernate:将单列从多对一映射到原始类型
【发布时间】:2009-10-24 11:15:32
【问题描述】:

我有以下映射:

<set name="People" lazy="true" table="ProjectPeople">
  <key column="ProjectId" />
  <composite-element class="PersonRole">
    <many-to-one name="Person" column="PersonId" cascade="save-update" not-null="true" />
    <many-to-one name="Role" column="RoleId" cascade="save-update" not-null="true"  />
  </composite-element>
</set>

现在,我真的不想为域中的角色创建一个单独的类,我只需要角色名称。但是,在 DB Roles 中仍应规范化为单独的表 Role (Id, Name)

如何映射它以便人们使用以下 PersonRole 类?

public class PersonRole {
    public virtual Person Person { get; set; }
    public virtual string Role { get; set; }
}

更新:增加了赏金,这似乎是一个不仅对我有用的问题。

【问题讨论】:

  • 我认为通过不创建类 Role,您的 java 对象层次结构和数据库模型之间存在差异。您的数据库有 Role(Id, Name) - 没有两个 Role 记录应该具有相同的名称;更新角色名称时,所有 PersonRoles 都应反映更改。所有具有“USER”角色的 PersonRoles 都应该引用同一个 Role 对象。在 Java 代码中使用字符串,您为客户端代码提供了一种分配任意值的方法,而不仅仅是现有的表值。您可能会导致表中出现重复的名称条目。无论如何,您都可以绕过 Hibernate 来执行此操作,但恕我直言,这并不明智。
  • 在你的 java 对象层次结构和你的数据库模型之间存在差异是我拥有一个高级映射框架的重点——所以我可以让对象按对象建模规则,以及由 db 规则建模的 db。现在我确实希望允许客户端生成任意值,并希望将这些值添加到数据库中,或者如果这里已经存在相同的值,则可以重复使用(解决重复问题)。
  • 似乎您实际上是在枚举器模式之后,但没有声明 enum 类型。如果您允许 Role 成为具有用于更新/添加等的关联 DAO 和唯一约束的类,这确实是最简单的。我在更新我的答案时暗示了使用IDaoXXX 的做法。请注意,“代码膨胀”并不总是“糟糕的膨胀”:使编程这种业务规则(如果存在,则可重用,但可更新)要容易得多。这种类型的业务规则应该从不进入映射(实体)层(除了约束),而应该进入访问(dao)层。

标签: nhibernate nhibernate-mapping many-to-one


【解决方案1】:

您实际上不会得到您希望的答案,仅仅是因为这是不可能的。 (N)Hibernate 是一个 Object-Relational-Mapping 框架,支持three kinds of mapping 策略:

  • 按类层次结构表
  • 每个子类的表
  • 每个具体类的表

它还允许您通过使用formulasql-insert 等来偏离这一点,但正如您所发现的那样,这些最终只会让您更加痛苦,不被 Hibernate 社区所鼓励并且是不好的为了代码的可维护性。

解决方案?

其实很简单。您不想为Role 使用类。我假设您的意思是您不想公开 Role 类型的类,并且您不想一直输入prObject.Role.Name。只是prObject.Role,它应该返回一个字符串。您有多种选择:

  1. 在 PersonRole 中使用内部类,该类可以是内部的或私有的。添加设置和更新成员字段的属性 Role;
  2. 使用内部类。添加设置和更新成员字段的属性 Role;

让我们检查选项 2:

// mapped to table Role, will not be visible to users of your DAL
// class can't be private, it's on namespace level, it can when it's an inner class
internal class Role 
{
    // typical mapping, need not be internal/protected when class is internal
    // cannot be private, because then virtual is not possible
    internal virtual int Id { get; private set; }
    internal virtual string Name { get; set; }
}

// the composite element
public class PersonRole
{
    // mapped properties public
    public virtual Person Person { get; set; }

    // mapped properties hidden
    internal virtual Role dbRole { get; set; }

    // not mapped, but convenience property in your DAL
    // for clarity, it is actually better to rename to something like RoleName
    public string Role     /* need not be virtual, but can be */
    { 
        get
        {
            return this.dbRole.Name;
        }
        set
        {
            this.dbRole.Name = value;    /* this works and triggers the cascade */
        }
    }
}

并且映射看起来像预期的那样。结果:您没有违反每类一个表的规则(编辑:asker 说他明确想要违反该规则,并且 Hib 支持它,这是正确的),但是您已经通过使用典型的面向对象技术隐藏对象以防止修改和访问。所有 NH 功能(级联等)仍按预期工作。

(N)Hibernate 就是关于这种类型的决策:如何在不牺牲清晰度、简洁性或可维护性或违反 OO 或 ORM 规则的情况下为您的数据库制定一个经过深思熟虑且安全的抽象层。


更新(q.关闭后)

我在处理此类问题时经常使用的其他出色方法是:

  • 正常创建映射(即每个表一个类,我知道你不喜欢它,但这是最好的)并使用扩展方法:

     // trivial general example
     public static string GetFullName(this Person p)
     {
         return String.Format("{0} {1}", p.FirstName, p.LastName);
     }
    
     // gettor / settor for role.name
     public static string GetRoleName(this PersonRole pr)
     {
         return pr.Role == null ? "" : pr.Role.Name;
     }
     public static SetRoleName(this PersonRole pr, string name)
     {
         pr.Role = (pr.Role ?? new Role());
         pr.Role.Name = name;
     }
    
  • 正常创建您的映射,但使用partial classes,它使您能够以您喜欢的任何方式“装饰”您的班级。优点:如果您使用生成的表映射,您可以根据需要多次重新生成。当然,部分类应该放在单独的文件中,因此考虑到您希望减少“臃肿”,这可能不是一个好的场景。

     public partial class PersonRole
     {
         public string Role {...}
     }
    
  • 也许最简单:只需为Role 重载ToString(),这使其适合在String.Format 和朋友中使用,但当然不能使其可分配。默认情况下,每个实体类或 POCO 都应该有一个 ToString() 重载。

虽然可以直接使用 NHibernate 执行此操作,但 q.在我有时间看之前已经关闭(没有人错,我只是没有时间)。如果我有时间通过​​ Hibernate HBM 映射进行更新,我会更新,即使我不同意这种方法。当最终结果对其他程序员来说不太清楚并且整体不太清楚时,与 Hib 的高级概念搏斗是不好的(该表去了哪里?为什么该表没有 IDao 抽象?另见NHibernate Best PracticesS#arp)。不过,这个练习还是很有趣的。

考虑cmets关于“最佳实践”:在典型情况下,不应该只是“一个表一个类”,而是每个表一个IDaoXXX,一个DaoConcreteXXX和一个GetDaoXXX,你使用类/接口层次结构区分只读表和读/写表。每个表至少有四个类/代码行。这通常是自动生成的,但为您的数据层 (dal) 提供了非常清晰的访问层 (dao)。数据层最好尽可能保持简洁。这些“最佳实践”都不会阻止您使用扩展方法或部分方法将Role.Name 移动到Role

这些是最佳一般做法。在某些特殊或典型情况下,这并不总是可行、可行甚至是必要的。

【讨论】:

  • 这是一个更好的解决方案。
  • 感谢您撰写详细的答案。但是,我不同意你的很多观点。三种映射策略只与继承相关,如果它是唯一的映射方式,NHibernate 就不是一个完整的数据映射器,它会是一个简单的活动记录式映射。例如,我可以映射字符串集合,NHibernate 不需要代理字符串就可以工作。
  • 我也不同意不良做法。上课只是因为我有桌子,没有逻辑对我来说似乎是一种代码味道,并且是贫血的域模型的标志(这个类基本上是一个 DTO)。在您的示例中,您在角色中有 Id。我在类中没有 Id,所以它实际上是一个单一属性(名称)类。我知道将属性映射到相关列在技术上需要什么——所需要的只是该列的“插入,除非存在”。 NHibernate 似乎不支持这一点,但这并不意味着这是一种不好的做法或不应该被支持。
  • 我显然误解了您的部分问题及其背后的动机,请注意,鉴于某个项目领域,这只是一种不好的做法,我只能提供一般性建议,不了解您的项目。我还没有发明 NH,我也不是第一个说“打破”一桌一课规则是“坏”的人,尤其是。在企业场景中以及使用 S#arp 或类似架构时,我可以理解为什么您有时会以不同的方式使用它。您可能想要的是映射到 IESI ISet / HashedSet,我看看能否为您的用例提供一个示例。
  • PS:虽然我同意“如果不支持,并不意味着这是不好的做法”,反之亦然:如果是支持,这不一定是好的做法(即多对多、顶级集合等)。
【解决方案2】:

我个人会创建一个像 Yassir 这样的角色类

但是,如果您想使用当前的结构,请创建一个视图,其中包含您的人员表的外键和角色描述。

修改 Set 映射表以指向您的新视图 然后修改您的角色映射,使其成为一个属性,而不是多对一映射。

但是,我认为采用这种方法意味着您将无法更新您的角色,因为它正在重新引用视图。

编辑:要更新角色,您可以将 &lt;sql-insert&gt;,&lt;sql-update&gt; and &lt;sql-delete&gt; 添加到您的映射文件中,以便 cascade-all 工作

【讨论】:

  • 我希望能够以这种方式级联插入新角色,否则最简单的方法是使用公式列。
  • 有趣的是你接受了这个作为赏金,正如你之前所说的那样,这不是你所追求的方法。
  • 是的,因为我被 SO 强迫接受了一些东西。这个答案基本上说我不能在没有黑客的情况下做到这一点,这是不幸的,但我没有不同意的观点。我喜欢你的回答,但我认为你在几点(我在评论中指定)是不正确的。这不是一个容易的选择,如果允许的话,我会同时接受或不接受。
【解决方案3】:

如果我是你,我认为不可能将多对一映射到原始类型,我会在模型中添加一个 Role 类

【讨论】:

【解决方案4】:

这是整个 OO 纯粹主义事物的最大转折点。 当然,目标是拥有一个工作应用程序。不是完美类层次结构的某个版本。那么,如果您必须编写“prObject.Role.Name”而不是“prObject.Role”怎么办。这如何帮助您制作更好更可靠的程序?

从应用程序设计纯粹主义者的角度来看,您想要的完全是错误的。一个人可以有多个角色,一个角色通常可以分配给几个人。 当无延迟的数据模型是每个人有多个角色时,为什么还要费尽心思来强制执行一个不切实际的每个人一个角色的类层次结构?

如果您确实有“每个人只有一个角色”的规则,那么它应该反映在底层数据模型中。

【讨论】:

  • 好吧,我确实每个人都有很多角色,这就是 PersonRole 类的重点,而不是在 Person 上拥有 Role。现在,拥有 Role 比拥有 Role.Name 更可取,因为我不想将 Role 作为一个实体进行管理——检查它是否已经存在等。我只想设置或获取它,并让 NHibernate 管理存储。这不仅是纯粹主义,而且当您不必管理某些事情时也容易得多。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-01-29
  • 1970-01-01
相关资源
最近更新 更多