Fluent NHibernate Cascade Issue - попытка вставить идентификатор NULL

У меня есть следующие модели и сопоставления (далее фрагменты кода).

Один конкурс должен иметь несколько связанных с ним критериев (множественный выбор) с самого начала.

В настоящее время, используя приведенные ниже сопоставления Fluent NHibernate, когда я создаю новый объект Конкурса, заполняйте свойства, затем создайте 3 новых объекта CompetitionAnswer и добавьте их в свойство CompetitionAnswers (свойство Competition), я бы ожидал для вызова "Сохранить" на сеансе, который будет ВСТАВИТЬ 1 строку конкурса и 3 строки "Соревнование" в БД.

Однако, как только я попытаюсь вызвать Save на сеансе, он жалуется, что CompetitionId имеет значение null и не может вставить нуль в таблицу CompetitionAnswers для этого поля - это правильно, но это не должно, Я предположил, что NHibernate сначала создаст Конкурс, а затем использует вновь созданное значение IDENTITY (CompetitionId) в таблице CompetitionAnswers?

Конкурс (модель)

public virtual int CompetitionId { get; private set; }
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual IList<CompetitionAnswer> CompetitionAnswers { get; set; }

CompetitionAnswer (модель)

public virtual int CompetitionAnswerId { get; set; }
public virtual string Answer { get; set; }
public virtual Competition Competition { get; set; }

CompetitionMap (свободное отображение NHibernate)

public CompetitionMap()
{
    Id(x => x.CompetitionId)
        .GeneratedBy.Native();
    Map(x => x.Title);
    Map(x => x.Description);
    HasMany(x => x.CompetitionAnswers)
        .Cascade.AllDeleteOrphan()
        .KeyColumn("CompetitionId")
        .Inverse();
    Table("Competitions");
}

CompetitionAnswerMap (свободное отображение NHibernate)

public CompetitionAnswerMap()
{
    Id(x => x.CompetitionAnswerId)
        .GeneratedBy.Native();
    Map(x => x.Answer);
    References(x => x.Competition)
        .Column("CompetitionId");
    Table("CompetitionAnswers");
}

Вот пример кода, который я использовал для тестирования этого сценария, который генерирует ошибку:

Competition c = new Competition();

c.Description = "Description";
c.Title = "Title";

CompetitionAnswer a1 = new CompetitionAnswer { Answer = "Answer 1" };
CompetitionAnswer a2 = new CompetitionAnswer { Answer = "Answer 2" };
CompetitionAnswer a3 = new CompetitionAnswer { Answer = "Answer 3" };

c.CompetitionAnswers.Add(a1);
c.CompetitionAnswers.Add(a2);
c.CompetitionAnswers.Add(a3);

session.Save(c);

Точная ошибка, которую я получаю, как только она пытается сохранить:

Невозможно вставить значение NULL в столбец "CompetitionId", таблица 'CompetitionAnswers'; колонка не Разрешить null. INSERT терпит неудачу. утверждение завершено.

Кто-нибудь может пролить свет на то, почему это не работает в настоящее время?

Ответ 1

Я уверен, а не 100%, что проблема заключается в спецификации Inverse() в вашем сопоставлении CompetitionAnswers on Competition. Inverse() указывает, что дочерние записи отвечают за определение их отношения к родительскому элементу. Чаще всего "одна" сторона "один ко многим" (родительский) является "вершиной" графа объектов и "владеет" отношениями со своими дочерними элементами. У родителей есть дети, и решение о том, следует ли сохранить или отдать ребенка за усыновление, является родительским. Однако это не всегда так; у колледжа могут быть студенты, но это студенты, которые имеют реальную власть решать, куда они поедут. Здесь Студент является "вершиной" графика, а Школа - это монолитная запись, идентифицирующая посещаемость учеников. Студент может передавать в любое время; это их решение, и это действительно не меняет Школу каким-либо значимым образом, поэтому учащиеся несут ответственность за идентификацию себя как принадлежащих к Школе.

Ваше дело является первым: у соревнований есть соревнования, а у ребенка логически не сказано: "Я принадлежу к Конкурсу"; Конкурс вместо этого "владеет" своей коллекцией ответов. Удаление инструкции Inverse() должно сделать NH treat Competition "верхним" графа объекта, поэтому NH будет вставлять Конкурс, а затем в Конкурс, который теперь может ссылаться на их родительский идентификатор.

Еще одна вещь, не связанная с проблемой, но если вы сопоставляетесь с базой данных MS SQL Server, а столбец идентификатора определен как столбец идентификатора в БД, я бы указал GeneratedBy.Identity() для столбцов идентификатора. Native() ДОЛЖНО в конечном итоге использовать Identity, но также проверит, доступны ли методы HiLo или Sequence.

Ответ 2

References(x => x.Competition)
    .Column("CompetitionId")
    .Not.Nullable(); //add this

а также убедитесь, что ребенок указывает на родителя в дополнение к родительскому языку, содержащему дочерний элемент в коллекции. Это необходимо, когда родительский объект обратный.

a1.Competition = c; 

Был ли этот столбец non null на стороне базы данных, но отображался как NULL на стороне NH?

Ответ 3

Нет ничего плохого в ваших сопоставлениях. Вероятно, проблема связана с вашей базой данных. Если вы используете столбцы Identity для ваших первичных ключей, столбец CompetitionId должен иметь значение NULL. Как работает NHibernate, когда у вас есть новый объект с сохраненными дочерними элементами, он вставляет родительский объект и дочерние объекты. Затем он обновляет внешние ключи дочернего объекта новым идентификатором родительского объекта.

Я нашел это каскадное сохранение NHibernate