Er zijn veel ORM tools beschikbaar voor
.Net waarvan de belangrijkste hieronder genoemd.
- ADO.NET Entity Framework
- Linq to SQL- ADO .NET
- LLBL Gen
- NHibernate
ADO.Net Entity Framework
We hebben met ADO.NET Entity Framework geëxperimenteerd. Echter
viel deze optie al vrij snel af. Want de versie die wij gebruikten
(.NET 3.5, VS2008, SP1) kon classes uit een bestaande database
genereren. Daarbij waren de objecten "vervuild" met specifieke
Entity Framework attributen en overervingen. En laat dat nu net het
tegenovergestelde zijn van Domain Driven Design. Voor Visual Studio
2010 is er een nieuwe versie beschikbaar die wel op basis classes
een database kan genereren. Erg mooi, maar de database definiëren
zit niet het meeste werk in, en de classes blijven 'vervuild' met
Entity Framework objecten.
Linq to SQL
Linq is een techniek die we in het Venum project vaak toepassen
om resultaten te krijgen uit collecties door middel van een query.
Hierdoor lijkt Linq to SQL de perfecte keuze voor het Venum
project. Echter zit er een groot nadeel in het gebruik van Linq to
SQL, en wel het volgende: Linq to SQL vertaald classes 1 op 1 met
tabellen. Hierdoor heb je niet de gehele vrijheid in het
ontwikkelen van het Domein Model. Zo moeten alle properties bestaan
in de class die in de corresponderende tabel zijn gedefinieerd. Ook
is het tot nu toe voor ons onduidelijk hoe Linq to SQL om kan gaan
met overervingen.
LLBL Gen
Wij zijn niet diep in de techniek van LLBL Gen gedoken omdat
vrij snel duidelijk was dat het de code in het domein model
"vervuild" met LLBL Gen specifieke code.
NHibernate
NHibernate is behoorlijk complex om te leren maar dat lijkt
achteraf het enige nadeel van NHibernate. Want het voldoet aan alle
eisen die wij stellen aan de ORM tooling voor het Venum project.
Ten eerste hoeft het domein model niet "vervuild" te worden met
NHibernate attributen of overervingen. Ten tweede geeft NHibernate
de vrijheid de database naar keuze in te richten nadat het domein
model is gedefinieerd en eventueel visa versa als men Data Driven
Design wil toepassen. Wij maken gretig gebruik van deze vrijheid,
met als voorbeeld dat we voor een abstract class 1 tabel definiëren
met alle eigenschappen van de gerelateerde superclasses. Er zitten
echter nog wel wat "addertjes onder het gras" maar die zullen
worden beschreven in het volgende hoofdstuk.
Hoe wij ORM toepassen in Venum
In Venum hebben wij meerdere typen datalagen geïmplementeerd.
Hieronder vallen de volgende implementaties:
- Mock: Voor het testen van de applicatie
- WCF: Voor het communiceren met de webservice(s).
- Database: Voor het schrijven en lezen uit een database.
De bovenstaande implementaties implementeren dezelfde interface
hetgeen het mogelijk maakt een andere implementatie te injecteren
in de testomgeving dan in de productieomgeving.
In dit hoofdstuk laten we de Mock en de WCF implementatie van de
datalagen ongemoeid en zullen we enkel de database implementatie
onder de loep nemen. Dit is immers de enige implementatie die
gebruik maakt van ORM (NHibernate). Nergens anders in de applicatie
zullen we ORM gerelateerde referenties tegenkomen.
Onze ORM regels
ORM-tools moet weten hoe instanties van objecten in de database
opgeslagen worden. Hiervoor moeten 'mappings' worden gedefinieerd
die dit beschrijven.
Er zijn een paar basis-regels die wij hanteren voor ORM binnen
het Venum project.
Schoon domein model
We hebben gekozen om geen ORM gerelateerde code of attributen in
het domeinmodel te verwerken. Hiermee verliezen we aan de ene kant
opties om mapping simpel te maken, aan de andere kant winnen we op
het gebied van flexibiliteit. Alle mapping gebeurd aan de hand van
de xml bestanden in de database laag.
1 op 1 relaties
Alle 1 op 1 relaties worden in de database in dezelfde tabel
opgeslagen, tenzij de relatie null kan zijn.
Stel je hebt class Person met property Address van het type
GeoAddress:
public class Person
{
private GeoAddress m_address = new GeoAddress();
public string Name{ get; set; }
public GeoAddress Address
{
get { return m_address; }
set { m_address = value; }
}
public class GeoAddress
{
public string Street{ get; set; }
public string City { get; set; }
}
}
Dan wordt de tabel tblPerson als volgt gedefineerd:
|
tblPerson
|
|
Name
|
|
Street
|
|
City
|
Het voordeel van het opslaan van 1 op 1 relaties van het
domeinmodel in de database tables is dat er geen join statements in
de queries naar de database hoeven worden uitgevoerd wat de
snelheid van data opslaan en uitlezen bevorderd. Let wel, de
bovenstaande constructie gaat (helaas) niet op voor 1 op 1 relaties
die null kunnen zijn.
Overervingen
Verschillende overervingen van subclasses worden in 1 tabel
opgeslagen. Het exacte type van de instantie wordt door NHibernate
achterhaald aan de hand van een zogenoemde discriminator waarde.
Voor elke superclasse kan een discriminator waarde worden
gedefineerd waarmee NHibernate kan achterhalen welke waarden moeten
worden opgeslagen voor welk type. En nog belangrijker, welk type
superclass NHibernate moet retourneren bij het uitlezen van een
record uit de table.
Hieronder een voorbeeld van overervingen:
public class Boss: Person
{
public DateTime DateOfBirth { get; set; }
}
public class Employee: Person
{
public string CompanyName { get; set; }
}
public abstract class Person
{
public string Name{ get; set; }
}
Dan wordt de tabel als volgt gedefineerd, waarbij de
discriminator waarde in PersonTypeID wordt opgeslagen:
|
tblPerson
|
|
PersonTypeID
|
|
Name
|
|
DateOfBirth
|
|
CompanyName
|
Het voordeel van het opslaan van superclassen in een tabel met
de naam van de subclass is dat er geen waarden dubbel hoeven worden
beschreven voor NHibernate. Hierbij hoeft bijvoorbeeld maar 1 tabel
worden uitgebreid als de class Person wordt aangevuld met extra
properties.
Database validatie
Vrijwel alle validatie van instanties worden uitgevoerd door het
domein en zo min mogelijk door de database, hoewel deze wel veel
mogelijkheden daartoe biedt. Echter zou dat betekenen dat we
validatie dubbel op aan het implementeren zijn.
Alle kolommen van de tables in de database mogen null-waardes
bevatten, behalve de kolommen die als primaire sleutel zijn
gedefinieerd.
Wel worden er foreign keys gedefinieerd maar niet met oogpunt van
relationele integriteit, maar om NHibernate te helpen met
beschrijven van relaties. De naam van de foreign keys kunnen
namelijk gebruikt worden voor het beschrijven van relaties voor
NHibernate.
Onderhoud van data
Het schrijven van SQL-queries voor onderhoud van de data raden
wij af. Dit om twee redenen, namelijk:
1. Extra onderhoud van het product, de SQL queries zouden ook
moeten worden bijgewerkt als er veranderingen optreden in het
domeinmodel.
2. Er kan corrupte data ontstaan omdat men met behulp van SQL
queries kolommen met foutieve waarden kan vullen die niet meer goed
te mappen zijn met het domein model.
Om toch snel en effectief bijvoorbeeld onderhoud applicaties te
kunnen schrijven maken we gebruik van het domeinmodel met het
bijbehorende NHibernate project. Denk bijvoorbeeld aan het starten
van een nieuw project die gebruik maakt van de bovengenoemde
projecten. Hierbij wordt het relationele databeheer volledig aan
NHibernate overgelaten en wordt het data beheer volledig geschreven
in code aan de hand van instanties uit het domeinmodel die
gepersisteerd moeten worden.
In het vervolg van dit artikel kunt u informatie verwachten
met voorbeelden van NHibernate en over de architectuur van
Venum.