NHibernate Equality:我如何确保只有一行来自许多“相同”的.NET对象?

时间:2010-03-03 23:58:55

标签: c# nhibernate persistence equality

如何通过NHibernate进行以下测试?

我认为只需在实体类中重写Equals和GetHashCode就可以按照我想要的方式工作了。显然,对于“Point”对象来说,这是非常微不足道的,对于相同的坐标来说,保持多行是很愚蠢的。我有两个具有相同坐标的点对象,我希望它们只能持久保存到数据库中的一行。

    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 个答案:

答案 0 :(得分:3)

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

如果这是您的实际域,您应该使用Points的Components或UserTypes,而不是单独的表。 Point显然具有值类型语义。

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

答案 1 :(得分:2)

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

我认为您要搜索的内容称为复合ID as described here。但请注意,NHibernate手册说复合键仅用于支持旧数据库,而strongly recommend against composite keys

  

“不幸的是,这种做法   复合标识符表示a   持久对象是它自己的   标识符。没有方便   除了对象本身之外的“句柄”。   您必须实例化一个实例   持久化类本身和   填充其标识符属性   在你可以加载()持久性之前   与复合相关的状态   键“。

相反,手册建议您使用component as a composite identifier

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

答案 2 :(得分:1)

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

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

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

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

你也可以为Id使用其他一些值,因为不能保证HashCode是唯一的。

答案 3 :(得分:0)

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

点击此链接:Chapter 7. Component Mapping,向下滚动到7.4,其中解释了composite-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
}

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

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。这个复合id的东西是为了遗留兼容性而保留的。