1

我怎样才能通过 NHibernate 获得以下测试?

我认为只需覆盖实体类中的 Equals 和 GetHashCode 就足以让它按照我想要的方式工作。显然,对于非常微不足道的“点”对象,为相同的坐标保留多行是愚蠢的。我有两个坐标相同的点对象,我希望它们只保留到数据库中的一行。

    Point p1 = new Point(1, 1, 1);
    Point p2 = new Point(1, 1, 1);
    Assert.AreEqual(p1, p2); //Passes
    session.Save(p1);
    session.Save(p2);
    tx.Commit();
    IList<Point> points = session.CreateCriteria<Point>()
        .List<Point>();
    Assert.AreEqual(1,points.Count); //FAILS

我的点课看起来像这样:

public class Point
{
    public virtual Guid Id { get; set; }
    public virtual double X { get; set; }
    public virtual double Y { get; set; }
    public virtual double Z { get; set; }

    public Point(double x, double y, double z)
    {
        X = x; Y = y; Z = z;
    }
    public override bool Equals(object obj)
    {
        Point you = obj as Point;
        if (you != null)
            return you.X == X && you.Y == Y && you.Z == Z;
        return false;
    }

    public override int GetHashCode()
    {
        int hash = 23;
        hash = hash * 37 + X.GetHashCode();
        hash = hash * 37 + Y.GetHashCode();
        hash = hash * 37 + Z.GetHashCode();
        return hash;
    }
}
4

4 回答 4

3

我相信您是从错误的角度解决问题。

如果这是您的实际域,您应该使用组件或用户类型作为点,而不是单独的表。Point 显然具有值类型语义。

阅读http://nhibernate.info/doc/nh/en/index.html#componentshttp://nhibernate.info/doc/nh/en/index.html#mapping-types-custom

于 2010-03-04T01:47:42.900 回答
2

NHibernate 不会将示例中的两个 Point 实例识别为相同,因为它们具有不同的 ID。看起来您正在使用 GUID 作为 Points 表中的主键,并且您创建的每个 Point 都将具有不同的 GUID。

我认为您正在搜索的内容称为复合 ID,如此处所述。但是请注意,NHibernate 手册说复合键只存在于支持遗留数据库,他们强烈建议不要使用复合键

“不幸的是,这种复合标识符的方法意味着持久对象是它自己的标识符。除了对象本身之外,没有方便的“句柄”。您必须实例化持久类本身的实例并填充其标识符属性,然后才能加载() 与复合键关联的持久状态。”

相反,该手册建议您可以将组件用作复合标识符

就个人而言,我会考虑将 GUID 保持原样,然后将逻辑添加到应用程序层以防止重复点,而不是在数据库中强制执行;但这一切都取决于您的应用程序的个人需求。

于 2010-03-04T05:32:30.330 回答
1

通常 NHibernate 通过它的 id 确定对象是否被保存。

因此,如果您将实现 Id 属性以为所有“相等”对象返回相同的 Id,那应该没问题(我认为)。

因此,如果这使您的测试通过,请尝试(对映射进行适当的更改):

public class Point
{
    public virtual int Id { 
        get { this.GetHashCode() }
    }
// the rest

您也可以为 Id 使用其他值,因为 HashCode 不能保证以这种方式是唯一的。

于 2010-03-04T00:09:10.023 回答
0

这意味着您希望所有具有 (1, 1, 1) 坐标的点都被视为唯一一个。尽管在 NHibernate 实践中不鼓励这样做,但您可以使用复合 ID 这样做。

在此链接之后:第 7 章。组件映射,同时向下滚动到 7.4,其中解释了复合 ID。然后你的映射应该是这样的:

<class="Point" tables="POINTS">
  <composite-id name="CompId" class="PointCoord">
    <key-property name="X" type="System.Double">
    <key-property name="Y" type="System.Double">
    <key-property name="Z" type="System.Double">
  </composite-id>
  <property name="Id" type="System.Guid">
</class>

你的班级需要像这样分开:

public class PointCoord {
    public double X { get; set; }
    public double Y { get; set; }
    public double Z { get; set; }
}

public class Point : PointCoord {  
    public Guid Id { get; set; }
}

这样做,您将不需要将您的 Guid 保留为您班级的 Id,因为它不再是 Id。您的 ID 现在是您的坐标 X、Y 和 Z。然后,您必须相应地覆盖 Equals() 方法:

public override bool Equals(object obj) {
    if (obj == null)
        return false;

    if (((Point)obj) == null) 
        return false;

    Point p = (Point)obj;

    return this.X == p.X && this.Y == p.Y && this.Z == p.Z
}

顺便说一句,将此方法的重载以您的类类型作为输入参数总是好的,这样可以提供更好的性能:

public bool Equals(Point pt) {
    if (pt == null) 
        return false;

    return this.X == pt.X && this.Y == pt.Y && this.Z == pt.Z
}

然而,这通常不是一个好的做法,NHibernate 强烈建议每个表都有自己的 DB Id,并且这个 Id 不能是重要的域值,例如发票号。您将拥有发票编号和数据库 ID。这个复合标识的东西是为了遗留兼容性而保留的。

于 2010-03-04T06:15:04.170 回答