I'm currently working on my first NHibernate project.
To test it out I am building an entity graph and that graph I try to persist to the database. When I add children to the parent via the add on the IList the insert is not working in the database because I get null column exceptions on the database (code snippet 1)
Snippet 1:
var country = new Country();
var countryLocale = new CountryLocale { LangCode = "nl-nl", Name = "Nederland" };
country.CountryLocales.Add(countryLocale);
var city = new City();
var cityLocale = new CityLocale { LangCode = "nl-nl", Name = "Amsterdam" };
city.CityLocales.Add(cityLocale);
country.Cities.Add(city);
Snippet 1 Error:
Cannot insert the value NULL into column 'CountryId', table 'ArtWorld.dbo.City'; column does not allow nulls. INSERT fails.\r\nThe statement has been terminated.
snippet 2:
var country = new Country();
var countryLocale = new CountryLocale { Country = country,LangCode = "nl-nl",
Name = "Nederland" };
var city = new City{Country = country};
var cityLocale = new CityLocale { City = city, LangCode = "nl-nl",
Name = "Amsterdam" };
session.SaveOrUpdate(country);
City and CityLocale Map:
public class CityMap : ClassMap<City>
{
public CityMap()
{
Table("City");
Id(x => x.Id);
References(x => x.Country).Column("CountryId").Cascade.SaveUpdate();
HasMany(x => x.CityLocales).KeyColumn("CityId").Cascade.SaveUpdate();
}
}
public class CityLocaleMap : ClassMap<CityLocale>
{
public CityLocaleMap()
{
Table("City_Locale");
Id(x => x.Id);
Map(x => x.LangCode).Not.Nullable();
Map(x => x.Name).Not.Nullable();
References(x => x.City).Column("CityId").Cascade.SaveUpdate();
}
}
this is my Country/Countrylocale map:
public class CountryMap : ClassMap<Country>
{
public CountryMap()
{
Table("Country");
Id(x => x.Id);
HasMany(x => x.Cities).KeyColumn("CountryId").Cascade.SaveUpdate();
HasMany(x => x.CountryLocales).KeyColumn("CountryId").Cascade.SaveUpdate();
}
}
public class CountryLocaleMap :ClassMap<CountryLocale>
{
public CountryLocaleMap()
{
Table("Country_Locale");
Id(x => x.Id);
Map(x => x.LangCode).Not.Nullable();
Map(x => x.Name).Not.Nullable();
References(x => x.Country).Column("CountryId").Cascade.SaveUpdate();
}
}
NHibernate Mapping changes :
On the Country
side you have
HasMany(x => x.CountryLocales).KeyColumn("CountryId").Cascade.SaveUpdate()
On the CountryLocale side you have
References(x => x.Country).Column("CountryId").Cascade.SaveUpdate();
One of the relations should be marked as inverse using .Inverse()
.
The .Cascase.SaveUpdate()
makes sure that NHibernate manages the life cycle of the child entity CountryLocale
when a transient child is added to the parent Country
's list of CountryLocales
. If you dont want to explicitly handle the life cycle of CountryLocale yourself , I would suggest to mark the Many-To-One side from CountryLocale
-> Country
as Inverse
.
There are two ways you could make your referencing Country
in CountryLocale
less of a sour eye and more intutive
If Country manages the Locales
Add a method in Country
which will manage the Locales which are being added. A client (user of Country
and CountryLocale
need not explicitly play with the references)
public virtual bool AddCountryLocales(CountryLocale locale)
{
if(!this.CountryLocales.Contains(locale))
{
locale.Country =this;
this.CountryLocales.Add(locale);
return true;
}
return false;
}
CountryLocale is more domain driven
By redefining it such that you cannot have a CountryLocale
without a Country
public class CountryLocale
{
public CountryLocale(Country country)
{
this.Country = country;
}
//you need a no-agrument constructor for NHibernate
protected CountryLocale()
{
}
}