Как добавить новый объект в список IList, отображаемый как один ко многим с помощью NHibernate?

Моя модель содержит класс Section, который имеет упорядоченный список Statics, которые являются частью этого раздела. Без всех остальных свойств реализация модели выглядит так:

public class Section
{
    public virtual int Id { get; private set; }
    public virtual IList<Static> Statics { get; private set; }
}

public class Static
{
    public virtual int Id { get; private set; }
}

В базе данных связь реализована как «один ко многим», где таблица Static имеет внешний ключ, указывающий на Section, и целочисленный столбец Position для хранения позиции индекса в списке, частью которого она является.

Сопоставление выполняется в Fluent NHibernate следующим образом:

public SectionMap()
{
    Id(x => x.Id);
    HasMany(x => x.Statics).Cascade.All().LazyLoad()
            .AsList(x => x.WithColumn("Position"));
}

public StaticMap()
{
    Id(x => x.Id);
    References(x => x.Section);
}

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

  • mySection.Statics.Add(myStatic)
  • session.Update(mySection)
  • session.Save(myStatic)

но самое близкое, что я получил (с использованием первых двух операторов), - это чтение исключения SQL: «Невозможно вставить значение NULL в столбец« Позиция »». Очевидно, здесь делается попытка INSERT, но NHibernate, похоже, не добавляет автоматически позицию индекса к оператору SQL.

Что я делаю неправильно? Я что-то упускаю в сопоставлениях? Нужно ли мне выставлять столбец Position как свойство и самому назначать ему значение?

РЕДАКТИРОВАТЬ: По-видимому, все работает, как ожидалось, если я удалю ограничение NOT NULL для столбца Static.Position в базе данных. Я предполагаю, что NHibernate делает вставку и сразу после обновления строки со значением Position.

Хотя это ответ на вопрос, я не уверен, что он лучший. Я бы предпочел, чтобы столбец Position не допускал значения NULL, поэтому я все же надеюсь, что есть способ заставить NHibernate предоставлять значение для этого столбца непосредственно в операторе INSERT.

Таким образом, вопрос остается открытым. Какие-нибудь другие решения?


person Jørn Schou-Rode    schedule 10.08.2009    source источник


Ответы (3)


При использовании двунаправленного отношения «один ко многим» в NHibernate один из концов должен быть «обратным». Лучше всего установить конец с коллекцией как обратный, поскольку это позволяет избежать ненужных операторов SQL и позволяет столбцу id быть «не нулевым».

В разделе 6.4 документации вы можете найти следующее примечание:

Очень важное примечание: если столбец ассоциации объявлен NOT NULL, NHibernate может вызвать нарушение ограничений при создании или обновлении ассоциации. Чтобы предотвратить эту проблему, вы должны использовать двунаправленную связь с многозначным концом (набором или сумкой), помеченным как inverse = "true". См. Обсуждение двунаправленных ассоциаций далее в этой главе.

Итак, вам нужно добавить .Inverse () к вашему сопоставлению HasMany в SectionMap.

public SectionMap()
{
    Id(x => x.Id);
    HasMany(x => x.Statics)
        .Cascade.All()
        .LazyLoad()
        .Inverse()
        .AsList(x => x.WithColumn("Position"));
}

Вам также, вероятно, понадобится метод Add и Remove в разделе, который устанавливает / сбрасывает ссылку на статику, а также добавляет / удаляет статику в / из своей собственной коллекции:

public virtual void AddStatic(Static static)
{
    Statics.Add(static);
    static.Section = this;
}


public virtual void RemoveStatic(Static static)
{
    Statics.Remove(static);
    static.Section = null;
}

Эти методы обеспечивают точность ссылок с обеих сторон отношения.

Согласно разделу 6.8 документов, NHibernate не поддерживают двунаправленные отношения при использовании индексированных коллекций:

Обратите внимание, что NHibernate не поддерживает двунаправленные ассоциации «один ко многим» с индексированной коллекцией (список, карта или массив) в качестве конца «многие», вы должны использовать сопоставление набора или пакета.

Итак, если у вас все еще возникают проблемы, подумайте об использовании однонаправленных отношений вместо двунаправленных, однако это может означать, что ваш столбец внешнего ключа должен иметь значение NULL (согласно примечанию в начале сообщения). В противном случае вам, возможно, придется отображать свою коллекцию как сумку или набор вместо списка.

person Erik Öjebo    schedule 10.08.2009
comment
Хорошо, мне кажется разумным. Но мне все равно нужно будет сделать столбец Section.Position в схеме базы данных допускающим значение NULL, верно? - person Jørn Schou-Rode; 10.08.2009
comment
Я не уверен, так как вы используете список. Если вы используете сумку или набор вместо списка, столбец не обязательно должен допускать значение NULL. Я немного обновил ответ, и, согласно документам, двунаправленные отношения не поддерживаются для списка, карты или массива. Таким образом, использование List с ненулевым столбцом внешнего ключа кажется немного сложным. - person Erik Öjebo; 10.08.2009
comment
Если я использую сумку или набор, колонка даже не понадобится, верно ?. В этом случае мне нужен порядок, и поэтому список кажется правильным выбором. - person Jørn Schou-Rode; 10.08.2009
comment
Аргх - я тут запуталась. В моем случае, если сделать столбец Static.Position обнуляемым, он заработал. Я не пробовал пометить поле Static.SectionID как допускающее значение NULL - это то, что вы предлагаете? Также: какую часть, Section или Static, вы бы сказали NHibernate сохранять / обновлять при добавлении нового Static? - person Jørn Schou-Rode; 10.08.2009
comment
Удален предыдущий комментарий, я знаю, что он работает с сумкой, но не знаю в списке. Сумка отображается в IList просто отлично, если это единственная причина, по которой вы используете list. Я знаю, что если я просто добавлю ссылку на FNH, по умолчанию он будет bag. В любом случае, с настройкой каскадов вам нужно только сохранить родительский элемент. Кроме того, не беспокойтесь об обновлении, если вам действительно не нужна поддержка обновлений (сохранение сохраненной сущности не в сеансе). В противном случае сеанс будет отслеживать изменения в коллекции и вставлять дочерние элементы при сбросе / фиксации. Если вы хотите, чтобы он удалял дочерних элементов, удаленных из коллекции, вам понадобится AllDeleteOrphans. - person anonymous; 10.08.2009
comment
Мне нужно отобразить в виде списка, если порядок многих элементов важен, верно? При выполнении этого сопоставления мне нужен столбец Position для многих элементов, и для того, чтобы все работало, мне, по-видимому, нужно сделать этот столбец допускающим значение NULL, хотя в этом не должно быть необходимости. В ответе Эрика много хороших моментов, но он не предлагает решения моей проблемы. Мне действительно нужно сделать Position допускающим значение NULL? знак равно - person Jørn Schou-Rode; 17.08.2009

В таблице Static у вас есть поле под названием «SectionID» или что-то подобное. Пусть это поле имеет значение NULLable: NHibernate сначала добавляет новую запись, а затем обновляет указанный идентификатор.

Я обнаружил это в своей базе данных, и я тоже удивлен: почему NH не любит правильные ссылки на таблицы на уровне базы данных?

person twk    schedule 10.08.2009

Я подумал, что добавлю свои 0,02 доллара только потому, что я пришел сюда, чтобы решить вашу проблему, и, похоже, ни один из ответов на самом деле не работает.

Вы почти готовы к своему решению. Ваше сопоставление для HasMany верное. Проблема в том, что, как вы сказали, позиция не обновляется в базе данных (FWIW, когда вы устанавливаете ее на null, она «работает» только потому, что NULL вставляется в базу данных; для меня это не совсем работает;) ).

Я нахожу это немного странным, вам также нужно отобразить позицию в вашем статическом классе и, очевидно, включить свойство Position в Static. Однако вы не хотите, чтобы свойство Position было доступно для записи, потому что его значение определяется тем, где Static находится в списке.

Добавьте следующее в Static

public virtual int Position 
{
    get 
    {
        // Will throw exception if Section is null
        // or Section.Statics is null...
        return Section.Statics.IndexOf(this);
    }
    protected set 
    {
    }
}

При этом в столбце Position в базе данных будет обновлено (не забудьте сопоставить свою позицию в StaticMap).

Я предполагаю, что прокси NHib для Static может обновлять поле Position в зависимости от его позиции в списке (какой-то магией, большей, чем у меня есть), а затем сохраняется в db.

person Rob Gray    schedule 24.01.2013
comment
У меня была такая же ситуация, у меня было два объекта, которые буквально имели одно и то же сопоставление списка (за исключением, конечно, ключевого столбца), но один вставлял позицию, а другой нет. Это решение сработало для меня, за исключением того, что мой список был частным и предоставлялся через свойство IEnumerable, поэтому я добавил метод IndexOf для родительского элемента (в данном случае раздела), чтобы удовлетворить эту реализацию. - person docmanhattan; 10.05.2014