Понимание внешнего ключа not-null = true и обратное поведение в отношениях "один к одному" с NHibernate

Я пытаюсь заставить NHibernate использовать многие стороны коллекции для управления двунаправленной ассоциацией для моделирования отношения "один к одному".

Родительский класс и карта:

public class Parent
{
    private ICollection<Child> children;
    public Parent()
    {
        this.children = new HashedSet<Child>();
    }
    public virtual Guid Id { get; protected internal set; }
    public virtual Child Child
    {
        get { return children.FirstOrDefault(); }
        set
        {
            {
                this.children.Clear();
                if (value != null)
                {
                    this.children.Add(value);
                }
            }
        }
    }
}

public class ParentMap : ClassMap<Parent>
{
    public ParentMap()
    {
        this.Id(x => x.Id)
            .GeneratedBy.GuidComb();
        this.HasMany<Child>(Reveal.Member<Parent>("children"))
            .Access.Field()
            .Cascade.All()
            .Not.Inverse()
            .AsSet();
    }
}

Детский класс и карта:

public class Child
{
    public virtual Guid Id { get; protected internal set; }
    public virtual Parent Parent { get; set; }
}

public class ChildMap : ClassMap<Child>
{
    public ChildMap()
    {
        this.Id(x => x.Id)
            .GeneratedBy.GuidComb();
        this.References(x => x.Parent)
            .Not.Nullable()
            .Cascade.All();
    }
}

Следующий код создает две вставки и обновление:

var parent = new Parent();
var child = new Child();
parent.Child = child;
child.Parent = parent;
session.Save(parent);
session.Flush();

Обратите внимание на дублирующий SQL-код для второй вставки и следующее обновление:

exec sp_executesql N'INSERT INTO [Parent] (Id) VALUES (@p0)',N'@p0 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401'
go
exec sp_executesql N'INSERT INTO [Child] (Parent_id, Id) VALUES (@p0, @p1)',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401',@p1='B78C4461-A217-47FC-BE02-9EF30117140A'
go
exec sp_executesql N'UPDATE [Child] SET Parent_id = @p0 WHERE Id = @p1',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='AA5A146E-E3F5-4373-B7A8-9EF301171401',@p1='B78C4461-A217-47FC-BE02-9EF30117140A'
go

Пока этот код создает печально известный not-null property references a null or transient value inverse:

var parent = new Parent();
var child = new Child();
parent.Child = child;
//child.Parent = parent;
session.Save(parent);
session.Flush();

Я нашел множество сообщений об этом, но еще не нашел окончательного руководства о том, как делать нуль-к-одному, с inverse=false на стороне one.

Я пробовал метод "один-ко-многим/один-к-одному", упомянутый здесь.

Кроме того, я нашел несколько открытых проблем в NHibernate о (не) nullable Foreign Keys: NH-941, NH-1050 и т.д.

Что я делаю неправильно?

Редактировать 2011-05-30

Итак, мое временное решение состоит в том, чтобы пойти на стандартную настройку inverse=true на много сторон и сделать некоторую магию в настройщике родителя:

public virtual Child Child
{
    get { return children.FirstOrDefault(); }
    set
    {
        {
            this.children.Clear();
            if (value != null)
            {
                value.Parent = this;
                this.children.Add(value);
            }
        }
    }
}

Но я все еще сбиваюсь с толку по поведению inverse=false, которое должно быть эквивалентно inverse=true на стороне много-друг (интересно, FluentNhibernate не позволяет ManyToOnePart устанавливать inverse=true как этот рекомендует).

Ответ 1

Когда использовать inverse = "true | false"

inverse атрибут используется, чтобы помочь NHibernate узнать, какую сторону отношений следует использовать для сохранения отношений. Не обратная сторона (обратите внимание на двойной отрицательный) - это сторона, которая будет сохраняться. Если ни одна из сторон не является обратной, то связь будет сохраняться дважды, как в случае с INSERT за которым сразу же следует приведенный выше пример UPDATE. Если обе стороны обратные, связь не будет сохраняться вообще, поэтому важно правильно установить обратное.

Мне нравится думать об inverse следующим образом. Я не знаю, официально это работает или нет, но это помогает моему миру понять:

многие-к-одному

many-to-one всегда inverse="false". Они всегда используются для сохранения связи с базой данных. Так как они всегда inverse="false", указывать их не нужно, поэтому NHibernate (и, следовательно, Fluent NHibernate) не предоставляет опцию для этого.

(Я сталкивался только одна ситуация, где я хотел бы указать inverse="true" на many-to-one. Если у вас есть one-to-many list на одной стороне и many-to-one с другой Кроме того, у вас должна быть возможность позволить list управлять отношениями, чтобы NHibernate мог позаботиться о настройке значений index для вас. В его нынешнем виде вы должны добавить свойство в дочерний класс и управлять значениями index самостоятельно.)

один к одному

one-to-one всегда inverse="true". Они никогда не существуют без id или " many-to-one на другой стороне отношений, которые позаботятся о сохранении отношений. Поскольку inverse значение всегда одинаково, указывать его не нужно, поэтому указание его не поддерживается.

Коллекции

Коллекции, такие как bag, list, set и т.д., Могут быть или не быть частью двунаправленных отношений. Если они существуют сами по себе (возможно, мешок с строковыми элементами), то они должны быть inverse="false" (по умолчанию), потому что никто не будет нести ответственность за сохранение отношений. Если они существуют в сочетании с другими отношениями, однако (как и ваши традиционные отношения один-ко-многим/многие-к-одному) они должны быть указаны как inverse="true".

В случае коллекций " many-to-many где у вас есть коллекция по обе стороны отношения, пометьте одну из них как inverse="true" а другую оставьте по умолчанию inverse="false". Опять же, дело в том, что одна сторона отношений должна быть не обратной. Какую сторону выбрать? Например, если мы возьмем отношения "многие ко многим" между пользователями и ролями, у вас, вероятно, будет много пользователей и несколько ролей. По моему мнению, вы должны отобразить Role.Users как inverse="true" и позволить User.Roles управлять отношениями, так как это меньший набор данных для работы и, вероятно, коллекция, которая вас больше всего интересует.

(На самом деле, я бы вообще не хотел включать Role.Users в модель. Предположим, что роль "Клиент" имеет 100 000 пользователей. Тогда customerRole.Users - непригодная бомба с ленивой загрузкой, ожидающая взрыва.)

... вернуться к вашему вопросу...

Так как на самом деле не имеет значения, какая сторона отношения обратная, если только одна сторона не обратная, то нужно сделать one-to-one сторону обратной, поскольку именно так NHibernate хочет это сделать. Не боритесь с инструментом за вещи, которые не имеют значения. В предоставленных вами сопоставлениях по существу обе стороны отношения были помечены как не обратные, что привело к тому, что отношение сохранялось дважды. Следующие сопоставления должны работать лучше для вас:

public class Parent
{
    public virtual Guid Id { get; set; }
    public virtual Child Child { get; set; }
}

public class ParentClassMap : ClassMap<Parent>
{
    public ParentClassMap()
    {
        Id(x => x.Id);
        HasOne(x => x.Child)
            .PropertyRef(x => x.Parent)
            .Cascade.All();
    }
}

public class Child
{
    public virtual Guid Id { get; set; }
    public virtual Parent Parent { get; set; }
}

public class ChildClassMap : ClassMap<Child>
{
    public ChildClassMap()
    {
        Id(x => x.Id);
        References(x => x.Parent)
            .Not.Nullable()
            .Unique()
            .Cascade.SaveUpdate();
    }
}

... что приводит к следующему SQL из вашего кода вставки теста:

exec sp_executesql N'INSERT INTO [Parent] (Id) VALUES (@p0)',N'@p0 uniqueidentifier',@p0='925237BE-558B-4985-BDA2-9F36000797F5'
exec sp_executesql N'INSERT INTO [Child] (Parent_id, Id) VALUES (@p0, @p1)',N'@p0 uniqueidentifier,@p1 uniqueidentifier',@p0='925237BE-558B-4985-BDA2-9F36000797F5',@p1='BE6D931A-8A05-4662-B5CD-9F36000797FF'

Нет запроса на обновление!