NHibernate Equality: как я могу гарантировать, что из многих «равных» объектов .NET сохраняется только одна строка? - PullRequest
1 голос
/ 04 марта 2010

Как мне пройти следующий тест для прохождения 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 ]

3 голосов
/ 04 марта 2010

Я полагаю, что вы решаете проблему не с того угла.

Если это ваш фактический домен, вы должны использовать Компоненты или Типы пользователей для Точек, а не отдельную таблицу. Точка явно имеет семантику типа значения.

Чтение http://nhibernate.info/doc/nh/en/index.html#components и http://nhibernate.info/doc/nh/en/index.html#mapping-types-custom

2 голосов
/ 04 марта 2010

NHibernate не распознает два экземпляра Point в вашем примере как одинаковые, потому что они имеют разные идентификаторы. Похоже, вы используете GUID в качестве первичного ключа в своей таблице очков, и у каждого создаваемого вами пункта будет свой GUID.

Я думаю, что то, что вы ищете, называется составным идентификатором , как описано здесь . Однако обратите внимание, что в руководстве NHibernate говорится, что составные ключи существуют только для поддержки устаревших баз данных, и они настоятельно рекомендуют против составных ключей :

«К сожалению, такой подход к составные идентификаторы означает, что постоянный объект является его собственным идентификатор. Нет удобного «обрабатывать», кроме самого объекта. Вы должны создать экземпляр сам постоянный класс и заполнить его свойства идентификатора прежде чем вы сможете загрузить () постоянный состояние, связанное с композитом ключ ".

Вместо этого в руководстве предлагается использовать компонент в качестве составного идентификатора .

Лично я хотел бы сохранить GUID такими, какие они есть, и затем добавить логику на прикладной уровень, который предотвращает дублирование точек, вместо того, чтобы применять его в базе данных; но все зависит от индивидуальных потребностей вашего приложения.

1 голос
/ 04 марта 2010

Обычно NHibernate определяет, сохранен ли объект по его идентификатору.

Так что, если вы реализуете атрибут Id, чтобы возвращать один и тот же Id для всех «равных» объектов, все должно быть в порядке (я думаю).

Так что попробуйте, если это пройдет ваш тест (внесите соответствующие изменения в отображение):

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

Вы также можете использовать другое значение для идентификатора, так как HashCode не гарантированно будет уникальным в этом случае.

0 голосов
/ 04 марта 2010

Это означает, что вы хотите, чтобы все точки, имеющие координаты (1, 1, 1), рассматривались только как одна. Несмотря на то, что это не поощряется в рамках практики NHibernate, вы можете сделать это с помощью составных идентификаторов.

Перейдя по этой ссылке: Глава 7. Сопоставление компонентов , а вы прокрутите вниз до 7.4, где объясняется составной идентификатор. Тогда ваше отображение должно выглядеть так:

<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 в качестве идентификатора вашего класса, поскольку он больше не является идентификатором. Теперь ваш идентификатор - это ваши координаты 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 настоятельно рекомендует, чтобы каждая таблица имела свой собственный идентификатор базы данных, и этот идентификатор не должен быть значимым значением домена, таким как, например, номер счета. У вас будет номер счета и идентификатор вашей БД. Этот составной идентификатор сохранен для совместимости с устаревшими версиями.

...