Понимание внешнего ключа not-null = true и обратного поведения в отношениях «ноль-к-одному» с NHibernate - PullRequest
11 голосов
/ 31 мая 2011

Я пытаюсь заставить 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 по поводу (не) обнуляемых внешних ключей: 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 Ответ

31 голосов
/ 05 августа 2011

Когда использовать 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'

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

...