NHibernate устанавливает внешний ключ при вторичном обновлении, а не при начальной вставке, нарушая ограничение Not-Null для ключевого столбца - PullRequest
6 голосов
/ 30 ноября 2010

У меня проблема с тем, что должно быть довольно простым (я бы подумал) сценарием использования NHibernate.

У меня есть классическая сущность Parent и Child, например:

public class Parent 
{
    public virtual int ParentId { get; set; }
    public virtual string Name { get; set; }
    public virtual IList<Child> Children { get; set; }
}

public class Child
{
    public virtual int ChildId { get; set; }
    public virtual Parent Parent { get; set; }
    public virtual string Name { get; set; }
}

И отображения следующим образом:

public class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        Id(x => x.ParentId).GeneratedBy.Native();
        Map(x => x.Name);
        HasMany(x => x.Children).KeyColumn("ParentId").Cascade.SaveUpdate();
    }
}

public class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        Id(x => x.ChildId).GeneratedBy.Native();
        Map(x => x.Name);
        References(x => x.Parent).Column("ParentId").ReadOnly().Not.Nullable();
    }
}

Наконец, у меня есть простые тесты:

   [Test]
    public void Test_save_family()
    {
        var parent = new Parent();
        var child = new Child {Parent = parent};
        parent.Children = new List<Child>{child};

        SessionManager.WithSession(
            session =>
                {
                    session.Save(parent);
                    session.Flush();
                });

    }

Сбой теста с System.Data.SqlClient.SqlException: Невозможно вставить значение NULLв столбец «ParentId».Это верно в том смысле, что столбец не имеет значения NULL, но почему он вставляет значение NULL?

Если я удаляю ограничение NULL, сохранение работает, поскольку NHibernate сначала вставляет родительский элемент, затем вставляет дочерний элемент, а затем обновляет ParentIdстолбец дочерней записи, как показано в следующих выходных данных:

NHibernate: INSERT INTO [Parent] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = NULL
NHibernate: INSERT INTO [Child] (Name) VALUES (@p0); select SCOPE_IDENTITY();@p0 = NULL
NHibernate: UPDATE [Child] SET ParentId = @p0 WHERE ChildId = @p1;@p0 = 2, @p1 = 1

Мне это кажется странным, так как почти во всех случаях столбцы внешнего ключа этого вида объявляются не обнуляемыми, и поэтому внешний ключ должен быть предоставлен ввставить.Так почему же NHibernate не устанавливает внешний ключ при начальной вставке дочерней строки и как это исправить?

1 Ответ

7 голосов
/ 30 ноября 2010

Несколько проблем с вашим отображением ... У вас двунаправленные отношения, и NHibernate необходимо знать, каким образом его обновить. В мире OO ссылки идут только в одном направлении, и NHibernate не может знать, что Parent-> Children - это тот же FK, что и Child-> Parent. Прямо сейчас у вас есть Child-> Parent, установленный в ReadOnly (). Это говорит NHibernate не обновлять это свойство. Таким образом, он пытается вставить Child (с нулевым родителем), а затем обновить FK со стороны Parent. Это не работает, если у вас есть ненулевое ограничение на ваш FK. Обычный способ отобразить это - использовать Inverse = true на родительской стороне и позволить ребенку беспокоиться о постоянстве. (Ваша задача в модели OO - обеспечить, чтобы коллекция Parent-> Children содержала тот же набор ссылок, что и набор отношений Child-> Parent.)

public class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        Id(x => x.ParentId).GeneratedBy.Native();
        Map(x => x.Name);
        HasMany(x => x.Children).KeyColumn("ParentId").Inverse().Cascade.SaveUpdate();
    }
}

public class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        Id(x => x.ChildId).GeneratedBy.Native();
        Map(x => x.Name);
        References(x => x.Parent).Column("ParentId").Not.Nullable();  // Removed ReadOnly()
    }
}

Операторы SQL, отправляемые в базу данных при сохранении, теперь:

INSERT INTO [Parent]
           (Name)
VALUES     ('P1' /* @p0 */)
select SCOPE_IDENTITY()

INSERT INTO [Child]
           (Name,
            ParentId)
VALUES     ('C1' /* @p0 */,
            1 /* @p1 */)
select SCOPE_IDENTITY()
...