Каскадные множественные отношения
У меня довольно распространенный сценарий, у меня есть две таблицы, которые я хотел бы связать вместе, в частности, Страна и Язык.
Я указал мои отображения следующим образом:
public LanguageMapping()
{
Table("Languages");
Id(x => x.Id, "LANG_ID").GeneratedBy.Identity();
HasMany(x => x.CountriesLanguages)
.Cascade
.SaveUpdate()
.KeyColumn("LANG_ID");
}
public CountryMapping()
{
Table("COUNTRIES")
Id(x => x.Id, "COUNTRY_ID").GeneratedBy.Identity();
HasMany(x => x.Languages)
.Cascade
.SaveUpdate()
.KeyColumn("COUNTRY_ID");
}
public CountryLanguageMapping()
{
Table("COUNTLANG");
Id(x => x.Id, "COUNTLANG_ID").GeneratedBy.Identity();
References(x => x.Country,"COUNTRY_ID").Cascade.SaveUpdate().Not.Nullable();
References(x => x.Language, "LANG_ID").Cascade.SaveUpdate().Not.Nullable();
}
Итак, все это выглядит хорошо. (обратите внимание, я взял данные для упрощения). Поэтому я говорю, что для присоединения к столу должны быть указаны и страна, и язык, чтобы быть действительными, что кажется достаточно справедливым.
Во всех сценариях, которые я собираюсь обсудить, в сессии нет данных.
Если я делаю что-то подобное, все хорошо.
var country = new DboCountry
{
Name = "UK",
Code = "GB",
};
var language = new DboLanguage()
{
Code = "EN",
Name = "English"
};
var countryLanguage = new DboCountryLanguage
{
Country = country,
Language = language,
IsDefault = true,
LanguageCode = "en-GB"
};
dataSession.Save(countryLanguage);
Все хорошо. Но я не всегда хочу так работать. Если я попытаюсь сделать это, как показано ниже, то это будет с исключением, подразумевая, что язык не был установлен, и я нарушаю условие обнуления..., хотя я ожидаю, что каскад справится с этим..,
var language = new DboLanguage()
{
Code = "EN",
Name = "English"
};
var countryLanguage = new DboCountryLanguage
{
Language = language,
IsDefault = true,
LanguageCode = "en-GB"
};
var country = new DboCountry
{
Name = "UK",
Code = "GB",
Languages = new []{countryLanguage}
};
dataSession.Save(country);
и я получаю NHibernate.PropertyValueException : not-null property references a null or transient value IDL.Common.ControlCloud.Orm.Dto.DboCountryLanguage.Country
Почему каскад не улавливает необходимость заполнения внешнего ключа страны?
1 ответ
Хитрость в том, что то, что ясно на уровне реляционных БД, должно быть явно установлено на уровне C# (сущности).
У нас есть это четкое определение CountryLanguage
public CountryLanguageMapping()
{
...
References(x => x.Country,"COUNTRY_ID")
...
// here
.Not.Nullable();
Итак, мы инструктируем NHibernate:
всякий раз, когда есть
CountryLanguage
Например, проверьте, что его ссылкаCountry
не является нулевым
Но то, что мы можем видеть в коде:
var language = ...
// create instance of CountryLanguage
var countryLanguage = new DboCountryLanguage
{
Language = language,
IsDefault = true,
LanguageCode = "en-GB"
};
// at this moment the countryLanguage.Country == null
// we (I really mean we, not NHibernate) did not set that value
// next we do create Country
var country = new DboCountry
{
Name = "UK",
Code = "GB",
Languages = new []{countryLanguage}
};
// and while we assigned that country will have some item in Languages collection
// the countryLanguage.Country is still null
// NHibernate cannot continue below
// because we did not fulfill the contract .Not.Nullable();
dataSession.Save(country);
Решение здесь состоит в том, чтобы явно установить оба направления (почти золотое правило)
var country = new DboCountry
{
Name = "UK",
Code = "GB",
Languages = new List<CountryLanguage>{countryLanguage},
};
countryLanguage.Country = country;
Теперь NHibernate имеет достаточно информации, чтобы действовать правильно
Продлить. Если мы правильно сделаем оба отношения в C#, мы также сможем улучшить обработку SQL INSERT UPDATE, используя обратную установку:
Table("COUNTRIES")
Id(x => x.Id, "COUNTRY_ID").GeneratedBy.Identity();
HasMany(x => x.Languages)
.Cascade
.SaveUpdate()
.KeyColumn("COUNTRY_ID")
.Inverse() // here we go
;
Это будет указывать NHibernate использовать более эффективные операторы SQL. Но опять же это требует, чтобы обе стороны отношения были правильно назначены в C#. Позже, когда объекты уже находятся в БД и затем загружаются NHibernate, будут установлены оба отношения. Это работа NHibernate. Но это происходит во время чтения из БД