Каскадные множественные отношения

У меня довольно распространенный сценарий, у меня есть две таблицы, которые я хотел бы связать вместе, в частности, Страна и Язык.

Я указал мои отображения следующим образом:

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. Но это происходит во время чтения из БД

Другие вопросы по тегам