Nimelt mõni aeg tagasi proovisin ja nüüd kasutan mappimiseks FluentNHibernatet. Alguses tundus see vaid hea mõttena ning kuna hetkel teen projektis suures osas ainult mina domeeni, mappimist jne siis refaktoorimist justkui ei tuleks ette. Siiski esimesed kasud juba leitud.
Nimelt Fluent aitab compiletime vigu leida, toetab refaktoorimist ning nHibernate puhul kaotab ära vajaduse xml (hbm) failide järele. Tegelikult küll mitte, sest fluenti idee on, et luuakse nHibernate jaoks siiski samasugused xml failid, kuid seda inmemory-na ehk mina kui arendaja neid ei näe (kui ma just nimelt ei taha näha).
Aga sisu ise:
On olemas generic ClassMap<T> ning lambda expressionite abil saab koodis entity-d ära mappida.
Näide:
public class HorseMap : ClassMap<Horse>
{
public HorseMap()
{
DefaultAccess.AsReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
Id(x => x.Id).GeneratedBy.Native()
.WithUnsavedValue(0);
HasMany<HorseRecord>(x => x.HorseRecords)
.WithKeyColumn("HorseId")
.Cascade
.AllDeleteOrphan();
HasMany<HorseStatistics>(x => x.HorseStatistics)
.WithKeyColumn("HorseId")
.Cascade
.AllDeleteOrphan();
HasMany<HorsePerformance>(x => x.PastPerformances)
.WithKeyColumn("HorseId")
.Cascade
.AllDeleteOrphan();
References(x => x.Sex, "Sex");
Map(x => x.Age);
Map(x => x.AtgId);
Map(x => x.HorseNameAndNationality);
Map(x => x.IsRegisteredInSweden, "IsSwedenReg");
Map(x => x.IsThoroughbred);
Map(x => x.Name);
Map(x => x.NationalityInBorned, "NationalityBorn");
Map(x => x.NationalityInOwnered, "NationalityOwner");
Map(x => x.NationalityInRaised, "NationalityRaised");
}
}
Nagu näha saab enamus mappinguid valmis teha stringe kasutamata (mis mulle eriti meeldib). Kuna ma ise ei loonud andmebaasi ning mappimine käib andmebaas –> domeen –> mappingud viisil, siis mõnes kohas siiski baasi columnname ei ole sama, mida ma property-na tahan kasutada.
Eelmises posituses viitasin ITimeStamped interfacele ning ka siinkohas on Fluent lähenemine igati kasulik. Nimelt on mul default property-d mapitud ära baas class-i ning nii on vähem koodikordavust.
public class TimeStampClassMap<T> : ClassMap<T> where T : ITimeStamped
{
public TimeStampClassMap()
{
DefaultAccess.AsReadOnlyPropertyThroughCamelCaseField(Prefix.Underscore);
Id(x => x.Id).GeneratedBy.Native()
.WithUnsavedValue(0);
Map(x => x.DateLastModified)
.TheColumnNameIs("LastUpdated");
}
}
ning sellest class-ist tulenev entity:
public partial class LicenseOwnerMap : TimeStampClassMap<LicenseOwner>
{
public LicenseOwnerMap()
{
WithTable("Start_LicenseOwner");
References(x => x.Owner)
.TheColumnNameIs("LicenseOwnerId")
.Cascade
.SaveUpdate();
References(x => x.Status)
.TheColumnNameIs("Status");
References(x => x.Type)
.TheColumnNameIs("Type");
}
}
Mappinguid on lisaks veel eriti hea testida:
[Test]
[Category("Integration")]
public void VerifyMapping_IsSuccessful()
{
new PersistenceSpecification<T>(Dao.Session)
.VerifyTheMappings();
}
Eelnevas on T Fluentiga mappitud entity. Siinkohal tegelikult tehakse seda, et luuakse uus T ning setitakse kõik default valued property-teks ning salvestatakse baasi. Teises sessioonis tehakse päring ning võrreldakse tulemusi algupärase objektiga. Lõpuks lastakse loodud objekt baasist maha.
Tegelikult saab testimist veidi põhjalikumaks teha ning anda ette väärtused, mida testida. See just kaslik siis References ning HasMany puhul. Näide siiski lihtne string-DateTime
[Test]
[Category("Integration")]
public void VerifyMapping_IsSuccessful()
{
new PersistenceSpecification<Log>(Dao.Session)
.CheckProperty(x=>x.Date, DateTime.Now)
.CheckProperty(x=>x.Logger, NewString("Logger"))
.VerifyTheMappings();
}
Millest seni puudust tundnud? Default Cascade entity tasandil (võibolla ei ole veel leidnud) ning private fieldide mappimine. Kuid viimase kohta käib vastavas arendajate google group-sis elav arutelu ehk ainult ajaküsimus, millal olemas.