Feb.12

Developer Days 2007

dd20071.jpg Zoals elk jaar ook dit jaar weer de Developer Days. De Developer Days 2007 gaan plaatsvinden op woensdag 13 en donderdag 14 juni in de RAI in Amsterdam. Het evenement wordt gecombineerd met het ReMix event, een evenement dat speciaal bedoeld is voor web developers en designers.

Meer info kunnen je vinden op het blog van Arie Leeuwesteijn (Microsoft Developer Advisor).

Feb.10

SQL & Objects in the mix, een Object Relational Mapper (ORM)

Object Relational Mapping, als je geen idee hebt wat dat inhoudt lees dan door. In onderstaande artikeltje wil ik ingaan op een OR Mappertje dat ik onlangs zelf heb gebouwd. Ik ben echt heel benieuwd wat jij als lezer van de oplossing vindt. Dus… REAGEER!

Wat is een ORM?

Object Relational Mapping is kort weg gezegd een manier om objecten via een transformatieslag in een database op te slaan (manipuleren) en weer op te halen. De presentatie van het object binnen een database is dan een “gewoon” record met velden en waarden. Wordt via de mapper een record opgehaald dan vertaald dat zich in een object binnen je programmacode.

Aangezien een ORM juist het transformatiestuk voor zijn rekening neemt houdt dit in dat men nauwelijks nog SQL statements hoeft te schrijven voor de select, insert, update en delete acties. Men kan dus tegen een OR Mapper zeggen Save(Object); en het object wordt direct opgeslagen. Dit gebeurd weliswaar onder water middels automatisch gegenereerde inserts of updates, maar daar merk je als ORM gebruiker maar weinig van. Zie een OR Mapper dus als een soort generieke datalaag.

Een voorbeeld van een veelgebruikt (open source) OR Mapper is NHibernate.

Het belangrijkste waar het om draait binnen ORM software is het configureren van transformatiegedrag. Dit is uiteraard ook het meest tijdrovende gedeelte van het opzetten van een goed functionerende ORM datalaag. Al kost het minder tijd dan het schrijven van elk specifiek SQL statement voor alle mogelijke acties zoals in “old-fashion” code, maar toch…

Uitgangspunten

NHibernate bijvoorbeeld (maar corrigeer me als ik het mis heb) eist voor elke entity (data-class) een Xml-bestand met transformatieparameters. Dat is jammer, in de meeste gevallen hebben entities een standaard transformatiegedrag waarbij eigenlijk geen configuratie nodig zou moeten is. Het optuigen van een Xml-bestand vindt ik dan zonde van de tijd en een onnodige ballast (extra bestanden). Dat wilde ik dus niet en heb nu zelf iets gebouwd (in +/- 8 uur) wat voldoet aan voor mij belangrijke specificaties.

Dat NHibernate op bovengenoemde manier is ingericht is overigens geen verwijt. NHibernate is eigenlijk het zusje van Hibernate wat van origine een Java ORM pakket is. NHibernate is dus niet volledig gebaseerd op de mogelijkheden van het .NET framework.

Veelzijdig
Het kunnen communiceren via zo veel mogelijk technologieen met zoveel mogelijk databases. Dus ondersteuning voor OleDb, Odbc, Sql(Client) en als databronnen, flat-files, SQL Server, Access, MySQL, SQL Compact Edition etc…

Simpel en accuraat
De transformatie moet volledig via code te regelen zijn en in standaardsituaties nauwelijks extra code vereisen. Alles wat in code te regelen is, is later namelijk ook weer in een configuratiebestand te stoppen. De keuze is aan de ontwikkelaar.

Flexibel en krachtig
Ik wil gebruik kunnen blijven maken van SQL waar ik dat nodig vindt. SQL blijft tot nu toe (LINQ buiten beschouwing gelaten) de meeste krachtige zoekmethode binnen een database (welke anders?).

Caching
Het mappen van objecten van en naar DataTables in plaats van een DBMS is natuurlijk een handige feature. Bijvoorbeeld bij het bouwen van offline-clients of simpelweg het willen cachen van gegevens om de belasting van een DBMS te beperken.

De mapper die hieronder wordt beschreven en ter download wordt aangeboden voldoet aan bovenstaande voorwaarden.

De oplossing

De basis van de ORM zijn de klassen DataTableMapper en DbMapper. DataTableMapper klasse maakt het mogelijk objecten objecten van en naar een DataTable te mappen. De DbMapper maakt gebruik van de DataTableMapper en verzorgt het mappen van objecten van en naar een DBMS (DataBase Management Systeem kortweg database). Beidde klassen zijn generics en hun gedrag kan dan ook exact worden aangepast op de te mappen klassen. Aan elk wordt de te mappen klasse meegegeven.

Veelzijdig
Het geheel is gebaseerd op de System.Data.Common namespace ofwel de DbProviderFactory. Dit zijn een verzameling klassen die het mogelijk maken data access objecten te creeeren onafhankelijk van de gekozen provider (System.Data.OleDb, System.Data.SqlClient, System.Data.Odbc etc…).

Een voorbeeld class die we gaan gebruiken.

Simpel en accuraat
Om te beginnen met de ORM moet er voor de te mappen class een mapper worden aangemaakt.

public DbMapper(String Provider, String ConnectionString, String PrimaryTableName);

public static DbMapper PersonMapper = new DbMapper("System.Data.SqlClient", ConnectionString, "tblPerson_per");

Bovenstaande code creeert een mapper voor de Person klasse. De persoon wordt opgeslagen via de SQL Server provider naar een de ConnectionString opgegeven database. De betreffende tabel heet “tblPerson_per” maar kan ook achterwegen worden gelaten als de tabel dezelfde naam heeft als de class.
In dit voorbeeld is er vanuit gegaan dat de primary key van de genoemde tabel automatisch kan worden achterhaald (dat is in bijna alle gevallen zo). Verder wordt er vanuit gegaan dat de propertynamen van Person exact overeenkomen met de veldnamen in de tblPerson_per tabel. Wil je van deze voorwaarden afwijken dan kun je een andere constructor gebruiken waar deze gegevens op maat aan door kunt geven.

Het ophalen van een lijst met alle in de tabel beschikbare personen (List) kan met de volgende code:
PersonMapper.GetAll("SELECT * FROM tblPerson_per");
Je kunt je voorstellen dat elke variatie mogelijk is.

Wil je manipulaties uitvoeren zoals, delete, update en insert dan kan dat met de volgende methoden:

  • Delete(Person p) – verwijderd een persoon uit de database
  • Insert(Person p) – voegt een persoon toe in de database
  • Update(Person p) – werkt een persoon bij in de database
  • Save(Person p) – voert een insert of update uit in de database afhankelijk of het object al aanwezig is.
  • Flexibel en krachtig
    Voor de manipulaties worden standaard commands gebruikt deze zijn via properties van de betreffende mapper benaderbaar en aan te passen naar eigen wens. Hiervoor heb zijn er de volgende properties beschikbaar:

  • UpdateCommand – Wordt gebruikt voor het bijwerken van een record.
  • InsertCommand – Wordt gebruikt voor het toevoegen van een record.
  • DeleteCommand – Wordt gebruikt voor het verwijderen van een record.
  • ExistsCommand – Wordt gebruikt bij het bepalen (in de Save methode) of een Insert of Update moet worden uitgevoerd.
  • Naast het aanpassen van de commands is het mogelijk om voor of na het uitvoeren van een command via events extra acties te laten uitvoeren. Hiervoor is de op dat moment actieve de transaction en connection beschikbaar.

    Naast deze functies zijn er nog een aantal factory methoden beschikbaar zoals het aanmaken van een connection, command, dataadapter etc… Deze methoden retourneren het juiste type op basis van het opgegeven providertype. Is het providertype “System.Data.SqlClient” dan zal de factorymethod een SqlConnection, SqlCommand of SqlDataAdapter retourneren. Verder is er nog een factory method waarmee op basis van de propertyname een parameter kan worden gecreeerd die grotendeels automatisch wordt voorzien van de juiste gegevens (ColumnSource, DbType, PlaceHolder, etc…). Afhankelijk van de provider wordt vastgesteld wat bijvoorbeeld de Sql statement parameter placeholder moet zijn “?” of “@parametername”.

    Caching
    Om caching te bewerkstelligen moet er een DataTableMapper voor de Person class worden aangemaakt. Dit gaat alsvolgt:
    DataTableMapper PersonCache = PersonMapper.GetDataTableMapper(string.Format("SELECT * FROM {0}", PersonMapper.PrimaryTable));
    Dit resulteerd in een class waaraan Person object mee kunnen worden gemanipuleerd op dezelfde manier als de DbMapper. Er is echter 1 verschil. De manipulaties worden in een onderliggende DataTable opgeslagen. Functies als Save, Delete, Update, Insert etc.. zijn beschikbaar. De GetAll method werkt in vergelijking tot de DbMapper.GetAll method met een RowFilter (zoals bij een dataview) in plaats van een Sql statement. Uiteindelijk kan na alle manipulaties de inhoud van de DataTableMapper PersonCache worden opgeslagen in de database via de PersonMapper (DbMapper) met de methode PersonMapper.CommitDataTableMapper(PersonCache);.

    Conclusie

    Persoonlijk denk ik dat dit een redelijke elegante oplossing is voor het object versus database probleem. LINQ (Microsoft) gaat in iedergeval deels op een soorgelijke manier de strijd met deze 2 werelden proberen te beslechten. Van LINQ is echter nog steeds geen definitieve release beschikbaar. De gekozen oplossing is gebaseerd op praktijkervaring met ASP.NET applicaties maar nog niet getest met praktijksituaties (Wie meld zich aan?). Ik ben dus zeer benieuwd of deze ORM in de praktijk een lang leven beschoren is.

    Hieronder vindt je de source code van de ORM inclusief een demo console applicatie. LET OP: .NET Framework 2.0 is vereist (Visual Studio 2005).

    Have Fun!

    Downloads

  • OR Mapper & Demo Applicatie