Read Jesper_Veggerby.pdf text version

DATALOGISK INSTITUT

DET NATURVIDENSKABELIGE FAKULTET AARHUS UNIVERSITET

Hovedopgave

Master i Informationsteknologi linien i Softwarekonstruktiona

Object-Relational Mappers i .NET

af

Jesper Veggerby

15. juni 2010

Jesper Veggerby, studerende

Jørgen Lindskov Knudsen, vejleder

Datalogisk Institut Aarhus Universitet Åbogade 34 8200 Århus N

Tlf.: 89425600 Fax: 89425601 E-mail: [email protected] http://www.cs.au.dk/da

Indholdsfortegnelse

1 English Summary ............................................................................................ 4 2 Indledning ...................................................................................................... 5 2.1 Terminologi og Forkortelser..................................................................... 5 2.2 Motivation............................................................................................... 6 2.3 Hypotese ................................................................................................. 7 3 Impedence Mismatch ..................................................................................... 8 3.1 Identitet .................................................................................................. 9 3.2 Associationer og Aggregeringer ............................................................... 9 3.3 Nedarvning ............................................................................................ 11 3.3.1 Table per Hierarchy......................................................................... 12 3.3.2 Table per Concrete Class ................................................................. 13 3.3.3 Table per Class ................................................................................ 14 3.4 Encapsulation ........................................................................................ 14 3.5 Polymorfisme ........................................................................................ 15 4 Sammenligningsgrundlag ............................................................................. 15 4.1 Features ................................................................................................ 15 4.2 Performance.......................................................................................... 16 4.3 Implementation..................................................................................... 16 5 ORM Key Features ........................................................................................ 16 5.1 Model First ............................................................................................ 17 5.2 Schema First .......................................................................................... 17 5.3 Code Only .............................................................................................. 17 5.4 OO Konstruktioner ................................................................................ 18 5.4.1 Associationer .................................................................................. 18 5.4.2 Nedarvning ..................................................................................... 18 5.5 Persistence Ignorance............................................................................ 18 5.6 Lazy Loading .......................................................................................... 18 5.6.1 SELECT N + 1 ................................................................................... 19 5.6.2 Ghost Objects ................................................................................. 20 5.7 LINQ Support ......................................................................................... 22 6 Udvalgte Mappers ........................................................................................ 23 6.1 ADO.NET Entity Framework ................................................................... 24 6.2 NHibernate ............................................................................................ 24 6.3 LightSpeed............................................................................................. 24 7 Metode ........................................................................................................ 25 7.1 Features ................................................................................................ 25 7.2 Performance og Implementation ........................................................... 25 7.3 Test Cases.............................................................................................. 29 Side 1 af 67

8 ADO.NET Entity Framework .......................................................................... 29 8.1 Feature Evaluering................................................................................. 29 8.2 Performance.......................................................................................... 30 8.2.1 Load by id ....................................................................................... 30 8.3 Implementation..................................................................................... 31 8.3.1 Mapning ......................................................................................... 31 8.3.2 Refactoring ..................................................................................... 36 8.3.3 Learning Curve, Dokumentation og Community .............................. 36 9 NHibernate .................................................................................................. 37 9.1 Feature Evaluering................................................................................. 37 9.2 Performance.......................................................................................... 38 9.2.1 SessionFactory ................................................................................ 38 9.2.2 NHibernate og LINQ ........................................................................ 38 9.2.3 Ghost Objects ................................................................................. 40 9.3 Implementation..................................................................................... 40 9.3.1 Mapning ......................................................................................... 40 9.3.2 Querying ......................................................................................... 41 9.3.3 One-Many inserts ........................................................................... 43 9.3.4 Learning Curve, Dokumentation og Community .............................. 43 10 LightSpeed ................................................................................................. 44 10.1 Feature Evaluering ............................................................................... 44 10.2 Performance........................................................................................ 45 10.2.1 Eager Loading ............................................................................... 45 10.2.2 LINQ ............................................................................................. 46 10.3 Implementation................................................................................... 47 10.3.1 Mapning ....................................................................................... 47 10.3.2 Eager loading ................................................................................ 49 10.3.3 Learning Curve, Dokumentation og Community ............................ 49 11 Sammenfatning .......................................................................................... 50 12 Alternativer ................................................................................................ 51 13 Konklusion ................................................................................................. 53 14 Referencer ................................................................................................. 55 15 Figurer ....................................................................................................... 60 16 Appendix A ­ Gængse RDBMS .................................................................... 61 17 Appendix B ­ Downloads ............................................................................ 62 18 Appendix C ­ ORM 3rd Party Tools ............................................................. 62 19 Appendix D ­ Test Case definitioner ........................................................... 63

Side 2 af 67

Side 3 af 67

1 English Summary

Throughout history relational databases have been widely used and are based on a well-known and proven technology. Developers have long used relational databases as a mean to persist and query data. With the adoption of the object-oriented programming paradigm, the relational model has followed along, although there are fundamental conceptual differences between the two paradigms. Object-Relational Mappers are applied as a technique that generically overcomes these differences ­ or at least remedies them, by mapping the classes in the object oriented system to the table in the database. There exist numerous ORM frameworks and choosing the right framework to use in a project or organization, based on needs and requirements can be difficult in this large market. The purpose of this work is to determine how the most popular ORM frameworks differ, and if it is possible to categorize a specific ORM to allow a qualified selection of the right ORM for a specific project or organization. This work outlines key features that define an ORM. These key features are evaluated against three popular .NET based ORM frameworks (Entity Framework, NHibernate and LightSpeed). Furthermore a sample mapping for all three using a domain model of a simplified e-Commerce system is subjected to testing. The conclusion of this work is that the three tested ORM frameworks have (almost) all the key features. The main finding though is that it is possible to categorize the tested ORM frameworks based upon the focus of the usage. In short the findings are: NHibernate is the most decoupled and flexible solution. Entity Framework is the simplest to develop for. LightSpeed has some unique features that make it a niche player in the market. Performance differences are negligible.

Side 4 af 67

2 Indledning

Den relationelle model som grundlag for databaser blev introduceret i 1969 (af E.F. Codd), i løbet af 1970'ere arbejde IBM på et database system baseret på Codd's model. Deres første prototyper var tilgængelige i 74/75 og i 78/79 var havde de tilføjet et strukturered query sprog, SQL. IBM's database system startede under navnet System R, gik i produktion under navnet SQL/DS og blev senere til Database 2 (DB2) som eksisterer den dag i dag. Relationelle databaser er blevet den mest udbredte database teknologi. Objekt-orienteret programmering (OOP) startede med spæde skridt i form af objekter i programmering allerede i sproget Simula 67 i 1960'erne. OOP blev til et begrem med Smalltalk i 70'erne, og har i løbet af 1990'erne vundet indpas som det dominerede paradigme i programmeringssprog, eksempler på dette er Java, C#, C++, Ruby, Python, etc. Sammen med OOP's indpas og relationelle databasers generelle udbredelse, har det været nærliggende at benytte disse databaser som persistens i OOP systemer. I de følgende kapitler analyseres de problemstillinger der ligger i den grundlæggende forskel mellem disse to paradigmer. De eksisterende løsninger beskrives og evalueres og alternative løsninger vil blive berørt.

2.1 Terminologi og Forkortelser

I det følgende vil der blive benyttet disse forkortelser og termer: OOP OODBMS RDBMS RM ORM EF, EF4 L2S NH LS NoSQL Object-Oriented Programming Object-Oriented Database Management System Relational Database Management System Relational Model Object/Relational Mapping eller Object/Relational Mapper ADO.NET Entity Framework og EF version 4 (inkl. i .NET 4.0) LINQ to SQL NHibernate LightSpeed En "bevægelse" der promoverer løsninger der ikke er Side 5 af 67

LSP SQL

baseret på relationelle databaser Liskov' Substitution Principle Structured Query Language

Generelt vil der blive brugt danske udtryk, med mindre den engelske pendant bliver brugt de-facto i det danske fagsprog. Referencer til entiteter, tabeller, klasser, properties, etc. er markeret med fed skrift, kode snippet eller blokke er markeret med Courier New font.

2.2 Motivation

Et program skrevet i et OOP-sprog beskriver et objekt system, der indeholder både tilstand (state) og opførsel (behaviour). Dette objekt-system udgør den eksekverbare del af programmet. En udviklingsmetode der benytter OOP til er Object Oriented Analysis & Design (OOA&D) (1). I OOA&D modelleres det domæne programmet håndterer (fx. Ordrer, Ordre-linier, Produkter, Kunder, etc. for et eCommerce system) i en domæne model/Domain Model. Under afvikling af programmet instantieres og manipuleres objekter i hukommelsen. Når programmet afsluttes vil disse objekter og deres tilstand forsvinde. For transiente objekter der kun eksisterer for at afvikle selve programmet, eg. elementer i selve brugergrænsefladen, er dette direkte ønsket, da ressourcer dermed bliver frigivet. Andre objekter skal kunne persisteres og være tilgængelig ved næste program afvikling, eg. objekterne i domæne modellen. For eksempel for eCommerce systemet skal ordrer persisteres til næste afvikling af programmet. Det kan også være at objekterne skal deles mellem forskellige programmer, eg. selve ordremodtagelsen, ordre behandlingen og fakturering kunne være 3 forskellige programmer. Der er flere forskellige måder at håndtere objekt persistens på, for eksempel: Serialisering Objekt Orienterede Databaser (OODBMS) Relationelle Databaser (RDBMS)

Serialisering og OODBMS vil ikke blive berørt nærmere, da disse historisk set har opnået moderat adoption som generiske persistensmekanismer.

Side 6 af 67

Relationelle Databaser har historisk set været en meget anvendt metode til at håndtere data på. Med modne systemer fra de allerstørste software leverandører på markedet, Oracle, Microsoft, IBM, Sun, etc., samt indtil flere Open Source alternativer, og versioner til alle platforme fra embeddede over PC til server baserede løsninger har relationelle databaser en udbredelse der gør dem til en persistens mekanisme der ikke kan ses bort fra. Mange virksomheder har derudover investeret betragtelige summer i store relationelle database infrastrukturer. Med struktureret programmering faldt valget at relationelle databaser meget naturligt, hvor de problematikker der er ifht. OOP ikke fandtes. Database kald blev udført med indlejrede SQL kald, bedst eksemplificeret via embedded SQL i COBOL (2). Resultaterne blev håndteret for det det var: data. Med paradigme skiftet fra struktureret programmering til objekt orienteret programmering, er den relationelle databases udbredelse fulgt med, som den mest almindelige metode til at persistere objekter på - selvom den relationelle model vs. OOP har visse problemstillinger (som uddybes senere). Hertil findes der på markedet mange løsninger til at håndtere persistens af objekter og afhjælpe, abstrahere eller i hvert fald minimere de konsekvenser den fundamentale forskel der er på de to paradigmer har. Disse kendes under ét som Object-Relational Mappers (O/RM eller ORM). Der evalueres på ORM's til Microsofts .NET framework, da dette har primær fokus i den organisation, jeg pt. er ansat i.

I den seneste tid har der været et voksende community for brug af databaser der ikke er baseret på en relationel model, en "bevægelse" der går under navnet NoSQL. NoSQL er en samling af forskellige approaches til persistens, og blandt nogle af dem som benytter NoSQL kan nævnes Google, Amazon, Twitter og Facebook.

2.3 Hypotese

I al sin enkelhed går ORM ud på at persistere objekters tilstand (state) i en relationel database (RDBMS). Dette opnås ved at der defineres en mapning imellem tabeller og kolonner fra den relationelle model til klasser og properties i OOP modellen. Side 7 af 67

Der findes mange O/R Mappers (ORM) i .NET verdenen (3) , både kommercielle og Open-Source. For hver eneste ORM, findes der en mere eller mindre seriøs holdning til hvad styrker og svagheder er ved alle de andre, og ikke mindst hvorfor lige netop den selv er den helt rigtige, se Figur 1 (4).

Hvordan adskiller de mest populære ORMs sig fra hinanden? Og hvilke parametre kan man opstille så man kan kvalificere valget af en ORM at basere sit projekt på?

Figur 1 How Fanboys See .NET Data Access Strategies

3 Impedence Mismatch

Der er som nævnt nogle helt fundamentale forskelle imellem den relationelle model og den objekt orienterede, som en ORM skal kunne løse eller i hvert fald håndtere.

Side 8 af 67

Overordnet set er der den basale forskel i hvordan de to paradigmer er opstået: "The object-oriented paradigm is based on proven software engineering principles. The relational paradigm, however, is based on proven mathematical principles." (5)

3.1 Identitet

I OOP taler man om at objekter har "identitet", i.e. et objekt repræsenterer en selvstændig entitet, det adskiller sig fra alle andre objekter. At et objekt har identitet er noget der er implicit i objekt systemet, eg. objektets tilstedeværelse i hukommelsen repræsenterer dets identitet. I den relationelle database optræder identiteter ved hjælp af database nøgler (primary keys) og er repræsenteret eg. vha. et unikt tal (i en given tabel angiver id = 87 en specifik række). Oftest er en nøgle dog kun unik pr. tabel, i.e. tabel A kan have en række med id = 87, og det kan tabel B også - der findes alternativer, e.g. uniqueidentifier/GUID (Global Unique Identifier) der kan sikre uniqueness på tværs af tabeller (endog uniqueness globalt, med så lille en sandsynlighed for kollision af dette er unikt i alle praktiske forhold (6)). Dette gør at man i objekt systemet skal tilknytte den relationelle models identitet som "faktisk" identitet for objektet. At den relationelle identitet og objektets identitet er uafhængige, kan ses ved at der kan eksistere to instanser af et objekt der repræsenterer den samme række/identitet fra databasen. En anden problemstilling med identitet er hvor og hvordan den relationelle identitet genereres. Idet objekterne (oftest) kreeres initielt i objekt systemet, er der ikke nogen kendskab til den identitet objektet vil have i databasen (som oftest vil denne først blive genereret når objektet persisteres i databasen), dvs. at selvom objektet har en identitet ifht. objekt systemet har det (endnu) ingen identitet ifht. databasen. Dette kan i praksis afhjælpes ved at benytte GUID som relationel identitet, da de med meget stor sandsynlighed kan genereres unikke i selve objekt systemet.

3.2 Associationer og Aggregeringer

Affødt af problematikken omkring identitet er der også en fundamental forskel i hvordan de to paradigmer håndterer referencer (associationer og aggregeringer i OOP og relationer i RM). Idet en reference mellem 2 entiteter (det værende sig objekter eller rækker i databasen), så dannes disse imellem to identiteter, dvs. i Side 9 af 67

OOP er associationer og aggregeringer direkte imellem objekternes identitet, e.g objekt A refererer objekt B. I RM er det imellem rækkernes keys (foreign keys), eg. række A med id A' refererer B' der er id på række B. En anden problematik kommer ifht. kardinalitet af relationer. Ægte one-one relationer kan kun modelleres i en relationel database, ved at de to tabeller har den samme primary key (Figur 2).

A PK,FK1 B' PK B B'

Figur 2 One-one relation via delt primary key

En anden metode er at er at benytte en standard one-many type relation, ie. i en tabel A med kolonne B' der er foreign key til tabellen B, og tilføje et unikt index på B' kolonen i A (Figur 3).

A PK FK1,U1 A' B' PK B B'

Figur 3 One-one relation via unique constraint/index

Many-many relationer modelleres vha en såkaldt link table (junction table, join table, association table, etc. (7)), (Figur 4).

AB A' PK,FK1 PK,FK2 A' B' PK

A PK

B B'

Figur 4 Many-many relation med link table

Side 10 af 67

Denne konstruktion er som sådan ikke en egenskab ved Tabel A eller B, men blot en liste over rækker i A og B der er knyttet logisk sammen (relateret) ­ det kan i princippet fortolkes som to one-many relationer fra en "virtuel" entitet AB. I et objekt system er det derimod objekterne A og B der refererer flere elementer af den anden klasse, ie. A har reference til flere B og B har reference til flere A, dvs. en link tabel eller klasse er ikke påkrævet. Konstruktionen i Figur 4 supporterer kun at A og B kan have én fælles reference, i.e. A og B kan kun være knyttet sammen én gang (idet foreign keys i link tabellen samtidig udgør primary key). Hvis det er nødvendigt at kunne have flere relationer imellem samme objekter kan dette gøres ved at have en explicit primary key (se Figur 5), men så er man muligvis ude i en situation hvor relationen i sig selv har en identitet (ie. hvilken af relationerne mellem A og B menes der?) hvor det kan være fornuftigt (hvis ikke påkrævet) at link tabellen har egen repræsentation i objekt systemet.

A PK A' PK FK1 FK2 AB Id A' B' PK B B'

Figur 5 Many-many relation med explicit primary key

3.3 Nedarvning

OOP har konceptet omkring nedarvning, hvilket ikke findes i den relationelle model. For at supportere persistens af nedarvede objekter skal der defineres en strategi for hvordan dette kan oversættes til den relationelle model. Der er 3 strategier med hver deres fordele og ulemper (5). Et nedarvningshierarki er ikke begrænset til at skulle bruge én af disse, men kan kombinere dem på den måde der er mest hensigtsmæssig ifht. den konkrete situation. Disse vil blive uddybet i det følgende, eksemplificeret ved det simple nedarvningshierarki vist i Figur 6.

Side 11 af 67

Product +Name +SKU

Book +Author +ISBN

Shoe +Size +Color

Figur 6 Simpelt nedarvnings hierarki

3.3.1 Table per Hierarchy Alle properties i alle subklasser gemmes i én tabel (Figur 7).

Product PK Id SKU Name Book_Author Book_ISBN Shoe_Size Shoe_Color

Figur 7 Table per Hierarchy

Oftest benyttes en "discriminator column" til at afgøre hvilken klasse der rent faktisk er tilfældet, eg. Type der kan have værdierne "Book" og "Shoe" eller 1 og 2. Fordele: Simpel struktur Simple queries til at afgøre hvilken subklasse et givent Product har.

Ulemper: Ikke optimalt ved store nedarvningshierarkier pga. antallet af kolonner

Side 12 af 67

Potentielt meget spildplads (specielt hvis subklasser har mange properties), pga. mange NULLABLE kolonner Udover den øverste superklasse kan der ikke benyttes constraints på kolonner (eg. der kan ikke specificeres en NOT-NULL constraint, eller der skal defineres en mere kompliceret constraint).

3.3.2 Table per Concrete Class Hver klasse med samtlige properties (ie. også superklassers) gemmes i hver sin tabel (Figur 8).

Product PK Id SKU Name

Book PK Id SKU Name Author ISBN PK

Shoe Id SKU Name Size Color

Figur 8 Table per Concrete Class

Fordele: Kan understøtte relativt store nedarvningshierarkier (specielt hvis de er flade) Simpelt at finde alle objekter af en given klasse

Ulemper: Primary keys fragmenteret/splittet over flere tabeller UNION operationer uundgåelig for selv simple queries Tabel skemaerne skal holdes in-sync, ie. SKU i Product, Book og Shoe skal (bør) have samme definition Side 13 af 67

SELECT statements vil være ineffektive, eg. et load af alle produkter vil give samme situation med kolonner som i "Table per Hierarchy" (i.e. alle kolonner skal repræsenteres), eller der skal eksekveres en query pr. tabel.

3.3.3 Table per Class Hver subklasse's explicit definerede properties gemmes i en tabel med foreign key til superklassen's tabel (Figur 9).

Product PK Id SKU Name

Book PK,FK1 Id Author ISBN

Shoe PK,FK1 Id Size Color

Figur 9 Table per Class

Fordele: Supporterer i princippet uendeligt store nedarvningshierarkier.

Ulemper: Specielt INSERT operationer er komplicerede da der skal oprettes foreign keys til "hoved tabellen". JOIN operationer er uundgåelige for selv simple queries.

3.4 Encapsulation

I OOP er selve implementation (behavior) og tilstand (state) indkapslet i et objekt og er kun tilgængeligt "on a need-to-know-basis". Det vil sige en klasse specificerer hvilket metoder og properties der kan tilgåes fra andre objekter (via access modifiers, eg. private, protected, internal, internal protected og public). Side 14 af 67

Den relationelle model derimod supporterer kun tilstand (data) og interfacet er principelt public (hvis man ser bort fra at diverse RDBMS løsninger supporter adgangsstyring på forskellige niveauer, så er det ikke en integreret del af den relationelle model).

3.5 Polymorfisme

Polymorfisme ("evnen til at antage mange skikkelser") i OOP betyder at en nedarvet klasse B kan erstatte/overskrive funktionalitet X() fra en superklasse A. Dvs. B.X() er logisk forskellig fra A.X() ­ omend kompatibelt i den forstand at funktionaliteten har samme signatur. Idet B is-a A, kan man i alle variable, metoder, etc. der refererer A "benytte" et objekt af klassen B, men funktionaliteten X() følger objektet. Dvs. i peudo-kode:

A a = new A(); a.X(); // kalder A.X() a = new B(); a.X(); // kalder B.X()

Da polymorfisme hænger meget sammen med nedarvning og behavior, er dette koncept ved OOP heller ikke supporteret i RM.

4 Sammenligningsgrundlag

Der vil i de efterfølgende kapitler blive foretaget en empirisk undersøgelse og sammenligning af de nævnte udvalgte ORMs på baggrund af følgende kriterier:

4.1 Features

Hvilke af følgende features supporteres af de enkelte ORM, og hvorledes implementeres det? Model first ­ se 5.1 nedenfor Schema first ­ se 5.2 nedenfor Code only ­ se 5.3 nedenfor OO konstruktioner o Associationer One-one Many-one Many-many o Nedarvning ­ se 3.3 ovenfor Side 15 af 67

Table-per-hierarchy Table-per-concrete class Table-per-class Mix af disse Persistence Ignorance/Supporterede RDBMS ­ se 5.5 below Lazy-loading ­ se 0 nedenfor LINQ Support ­ se 5.7 nedenfor

4.2 Performance

Hvordan oversættes navigering af objekt modellen til SQL kald til databasen? Kvalitativ analyse af genreret SQL Select N+1 ­ se 0 nedenfor

4.3 Implementation

Hvilken impact har det at bruge ORM'en på udviklingsprocessen/-projektet? Mapping o Hvordan? o Refactoring1 Learning curve o Getting Started Guides o Dokumentation Scope Kvalitet o Community Tooling & Extensions o Mapnings værktøjer (designere) o 3rd party add-ons

5 ORM Key Features

Herunder følger en uddybning af nogle af de beskrevne features i 4.1 ovenfor.

1

Som defineret på eg. Wikipedia "Code refactoring is the process of changing a computer program's source code without modifying its external functional behavior in order to improve some of the nonfunctional attributes of the software." (38)

Side 16 af 67

5.1 Model First

Ved "Model First" forstås den tilgang til ORM hvor der først laves en objekt model og derefter mappes denne til et database skema. Fordelene ved denne tilgang er at det er selve objekt-modellen der er i fokus. Denne metode er derfor specielt velegnet til OOA/D baserede projekter, hvor Domæne Modellen er central. "Model First" ORMs bruger ofte forskellige former for design værktøjer til at modellere objekt/domæne modellen og kan i mange tilfælde også generere et database skema udfra mapningen (dvs. inkl. field definitioner, primary og foreign keys, constraints, etc.)

5.2 Schema First

I "Schema First" tilgangen er databasen eller database skemaet tilgængeligt og denne mappes så til en objekt model. Denne metode er specielt velegnet til at bygge nye UI's (User Interfaces) på en eksisterende database. Dette kan være specielt nyttigt hvis der er tale om en (stor) eksisterende database og/eller en database der er optimeret ifht. et specifikt brug. "Schema First" fordrer enten at man tilpasser sin Domæne Model til databasen, eg. Active Record (8), eller at den benyttede ORM har tilpas fleksibilitet til at kunne mappe database skemaet ind i den ønskede domæne model.

5.3 Code Only

En "pendant" til "Model First" er "Code Only", hvor man betragter sin domæne model som værende 100% separat fra sin ORM, e.g. klasserne skrives manuelt eller designes/genereres dynamisk udfra et UML klasse diagram i.e. POCO2 klasser. "Code Only" tilgangen stiller det krav til ORM'en at det kan operere på objekter uden at have kode afhængighed, eg. ved at kræve nedarvning fra en specifik baseclass, her til, hvilket kræver specielle forhold eg. ifht. lazy-loading og indkapsling.

2

Plain Old CLR Objects, et pun på pendanten i Java POJO (Plain Old Java Objects). Betydningen er at det handler om simple objekter der ikke har nogen afhængigeder til andre frameworks eller lignende, eg. via attributter, nedarvning eller code-mæssigt (17)

Side 17 af 67

5.4 OO Konstruktioner

5.4.1 Associationer Supporteres many-many relationer uden at skulle mappe link tabellen til en klasse3, se Figur 4. 5.4.2 Nedarvning Hvilke typer (se 3.3 ovenfor) af nedarvnings mapning supporteres,

5.5 Persistence Ignorance

En (afledt) fordel ved at bruge en ORM er at man ofte vil blive RDBMS "agnostic"/uafhængig (persistence ignorance), i og med at håndteringen af persistens og querying foretages af ORM, kan database integrationen abstraheres "væk" i ORM'en. Dette betyder an en given ORM oftest vil kunne understøtte flere forskellige typer RDBMS, eg. både server baserede (som Oracle, MySQL og MS SQL) og/eller embeddede (som SQLite eller MS SQL CE).

5.6 Lazy Loading

Lazy-loading er den funktionalitet at et associeret (om det er del af en 1:1, 1:n eller n:n association er irrelevant) objekt ikke loades i samme query som parent objektet. Vi betragter klassediagrammet i Figur 10.

Order +Number

+Order +Lines 1 *

OrderLine +Quantity

-Lines *

+Product 1

Product +Name +ListPrice +SKU

Figur 10 Lazy-loading eksempel klasse diagram

3

Dette er for eksempel nødvendigt i Linq to SQL.

Side 18 af 67

Hvis et givet Order objekt loades vil de relaterede OrderLine's ved lazy-loading først blive hentet (e.g. SELECT * FROM OrderLine WHERE OrderNumber = @Number) når denne property navigeres. Ligeledes med OrderLine -> Product. Dette betyder at der kun bliver loadet det (og dermed udført de SQL statements) der er nødvendige. 5.6.1 SELECT N + 1 En ulempe ved lazy-loading er at det kræver at brugeren af ORM'en skal være opmærksom på brugen af associationer, ellers er det meget let at lave såkaldte SELECT N+1 situationer (9). En SELECT N + 1 situation er meget almindelig, i situationer hvor man har en serie af objekter der har en 1:1 (eller anden kardinalitet, men det ses typisk ved 1:1) relation til et andet objekt. For eksempel i e-handels systemer hvor en ordre, har en række ordre-linier der hver gælder for ét givet produkt. Hvis vi i vores program skal lave en ordre præsentation, der viser ordre header, ordre-linier og detaljer om produkterne herpå, kræver det naturligt at selve Ordre objektet loades. I vores brugergrænseflade skal derfor itereres over ordrelinier på ordren og vises eg. produkt navn og produkt SKU og bestilt antal. Idet der benyttes lazy-loading vil læsningen af Order.OrderLines property afføde et SELECT statement via ORM'en der loader alle OrdreLinier for den givne ordre. Antag at for en given Order er der N OrderLines. Når disse præsenteres i brugergrænsefladen vises produkt navnet, dette betyder at OrderLine.Product læses og igen afføder det via lazy-loading et SELECT statement imod databasen. Idet dette gøres for hver af de N ordre-linier vi har læst ind, eksekveres der N næsten ens SELECT statements imod databasen plus det SELECT statement der loadede alle OrderLines ind - heraf kommer navnet SELECT N + 1. SELECT N + 1 situationer kan for eksempel afhjælpes ved at pre-fetch informationer, fx. kunne ORM'en i ovenstående tilfælde instrueres i at loade det associerede Product sammen med en OrdreLinie.

Side 19 af 67

5.6.2 Ghost Objects En anden problematik er en kombination af Ghost Objects og nedarvning ifht. Liskov' Substitution Principle (10), som kan opstå hvis man benytter en ORM der anvender Ghost Objects til at opnå lazy-loading (som eg. NHibernate gør). Ghost Objects er proxy objekter der genereres automatisk ved runtime for at opnå lazy loading. Dette bruges for eksempel til Code Only (se 5.3 ovenfor)/POCO modeller hvor man for eksempel ikke har en base class der nedarves fra (som håndterer lazy-loading). Teknisk set er et Ghost Object en instans af subklasse til entitets klassen, illustreret i Figur 11.

Person +Name +Age

(Ghost) Person +Name +Age

Figur 11 Ghost Objects

Ghost objektet overrider properties og metoder fra base class (hvilket så stiller et explicit krav om at alt er martkere virtual, hvilket ikke er default i .NET, i modsætning til eg. Java) og implementerer disse som lazy-loading (i.e. værdien loades først ved navigering af property, invokation af metode, etc.). En af fordelene ved Ghost Objects er at man kan navigere en lazy-loaded property og tilgå enkelte properties på denne, eg. primært Id, uden at der genereres et kald til databasen (Id på child objektet er ved et one-many relation kendt på load tidspunkt af parent objektet, da foreign key ligger på parent tabellen). Hvis man på child objektet tilgår en property der ikke er tilgængelig vil denne blive lazy loaded fra database. Det betyder eg.:

var order = session.Get<Order>(id); // følgende vil ikke generere et kald til databasen da // CustomerId stammer fra Order tabellen Console.WriteLine(order.Customer.Id);

Side 20 af 67

// name er ikke kendt, så her vil der blive foretaget et // kald til databasen og data vil blive hydreret ind i // order.Customer Console.WriteLine(order.Customer.Name);

Eksemplificeret vil man vha. Ghost Objects kunne få det "virtuelle" nedarvnings hierarki som vist i Figur 12.

SuperClass +SuperX +SuperY 1

+Super * MainClass

SubClassA +SubAX

SubClassB +SubBX

(Ghost) SuperClass +SuperX +SuperY

(Ghost) SubClassA +SubAX

(Ghost) SubClassB +SubBX

Figur 12 Many-one relationer, ghost objects og nedarvning

MainClass, SuperClass, SubClassA og SubClassB er klasserne fra vores domæne model. For hver af disse klasser som supporerer lazy-loading vil der blive oprettet et Ghost Object, eg. (Ghost) SubClassA eller (Ghost) SuperClass. (Ghost) SubClassA vil override alle properties i SubClassA til lazy-load varianter, det samme vil ske for SuperClass, men vil ikke være i samme gren af nedarvningshierarkiet. Dvs. SubClassA is-a SuperClass er sand Men SubClassA is-a (Ghost) SuperClass er falsk

Side 21 af 67

Dette betyder at hvis vores model supporterer lazy-loading (MainClass har selvfølgelig også en Ghost, men denne er ikke vist i modellen) så vil load af MainClass betyde at vi laver et (pseudo-)select statement SELECT MainClassId, SuperClassId from MainClass. Med andre ord kender vi på initialiseringstidspunktet af MainClass kun Foreign Key på SuperClass tabellen. Dette er ikke nok til at afgøre hvilken reel klasse (SuperClass, SubClassA eller SubClassB) der reelt refereres (denne information ligger fx i en discriminator column på SuperClass tabel-per-hierarchy strukturen). Derfor vil MainClass.Super blive initialiseret med et (Ghost) SuperClass objekt. Derved får vi at vi ikke kan teste om MainClass.Super is-a SubClassA, på trods af at MainClass.Super rent faktisk kan referere til et validt SubClassA objekt. Dette overholder derfor ikke Lisskov's Substitutions Princip (10). "What is wanted here is something like the following substitution property: If for each object o1 of type S there is an object o2 of type T such that for all programs P defined in terms of T, the behavior of P is unchanged when o1 is substituted for o2 then S is a subtype of T."

5.7 LINQ Support

For netop .NET baserede ORMs er det en ekstra feature at supportere LINQ (Language INtegrated Query (11)). LINQ blev introduceret i .NET 3.5 og er en metode til at skrive queries direkte i managed kode. LINQ skal ikke forveksles med den metode SQL er embedded i e.g. COBOL (2), men er en abstraktion over (i princippet) mange forskellige underliggende query sprog.

from person in persons where person.Age > 37 select person.Name

Vil kunne alt efter hvad "persons" er (implementeret via IQueryable<T> interfacet) blive oversat til et SQL statement, en LDAP query, direkte evalueret i managed code imod objekter eller (som et tænkt eksempel) til udvælgelse af ens Friends på Facebook. Problematikken ved at skulle supportere LINQ i en ORM er oversættelsen fra managed code (via Expression Trees (12)) til det pågældende query sprog, eg. et

Side 22 af 67

Criteria API, query sprog (eg. ESQL og HQL) eller direkte til en SQL dialekt (eg. TSQL for MS SQL Server). For eksempel en relativt simpel LINQ query som følgende:

from person in persons where person.Birthday.Day < 10 select person

Er ikke helt så triviel i T-SQL:

SELECT * FROM Persons WHERE DATEADD(dd, 0, DATEDIFF(dd, 0, Birthday)) < 10

Og da managed kode er en del mere ekspressivt end SQL kan man sagtens lave LINQ queries der overhovedet ikke kan oversættes til SQL. Dette betyder at de udviklere der skal benytte LINQ providerne er nødt til at have et vist kendskab til hvilke metoder/expressions den pågældende LINQ provider kan oversætte, eller ihvertfald bestræbe sig på kun at benytte et simpelt "standard" set af operationer, e.g. ==, !=, &&, ||, etc. Problematikken ifht. dette gøres ikke mindre af at evt. usupporterede expressions, først viser sig runtime, da det først er her de evalueres. Derudover er der problemstillinger hvis man laver en generisk applikation der er database agnostisk, så kan det være meget forskelligt hvad de enkelte providere supporterer.

6 Udvalgte Mappers

Til Microsoft .NET frameworket findes en lang række ORM's, blandt andet: ADO.NET Entity Framework - Microsoft, incl. i .net 3.5 SP1 NHibernate ­ open source (13) LightSpeed ­ commercial (14) LLBLGen Pro ­ commercial (15) Castle ActiveRecord ­ open source (16), bygger oven på NHibernate og benytter Active Record design pattern (8) DataObjects.NET ­ commercial (17) SubSonic ­ open source (18) Side 23 af 67

LINQ to SQL ­ Microsoft, incl. i .net 3.5 og frem og mangle flere (3)

Der vil i det følgende blive kigget nærmere på 3 af disse, navnligt ADO.NET Entity Framework, NHibernate og LightSpeed. Baggrunden for at vælge disse 3 uddybes herunder.

6.1 ADO.NET Entity Framework

I .NET 3.5 introducerede Microsoft LINQ to SQL (L2S) som en persistens teknologi til frameworket der bygget på det introducerede LINQ (Language INtegrated Query) concept. LINQ to SQL, havde den fordel at det var meget simpelt, men samtidig havde det også den ulempe at det var meget simpelt, man kunne fx. ikke modellere nedarvning, n:n relationer uden at skulle inkludere en klasse til link tabellen (7). I .NET 3.5 SP1 introducerede Microsoft ADO.NET Entity Framework (EF) som et reelt ORM og satte samtidig L2S i en reel "deprecated" tilstand (19). Med det netop (2010-04-12 (20)) frigivne .NET 4.0 er næste version af EF frigivet (EF4), som indeholder flere features, som bringer den mere på højde med allerede etablerede ORM. EF er naturligt inkluderet da det er frameworkets eget svar på ORM. Der kigges på version 4 som er frigivet med .NET 4.0.

6.2 NHibernate

Oprindeligt baseret på Hibernate til Java, der begyndte tilbage i 2001. Hibernate blev ported til NHibernate, og den dag i dag forsøges stadig at holde kodebaserne feature kompatible således at NHibernate bygger på Hibernate erfaringer og vice versa. Første stable release af NHibernate kom i 2005 Både Hibernate og NHibernate er open-source og har et meget stort community. Dette sammen med historikken gør at NHibernate ligeledes er et helt oplagt valg at kigge på. Der kigges på version 2.1.2.

6.3 LightSpeed

LightSpeed vælges som det kommercielle alternativ til open-source løsningen og Microsoft's egen. Side 24 af 67

Derudover er LightSpeed valgt pga. det slår sig op med nedenstående key features Small & Lightning Fast Designed for Testability Convention over Configuration Kilde: http://www.mindscape.co.nz/products/LightSpeed/default.aspx En alternativ løsning der principelt ligeså godt kunne være valgt var LLBLGen Pro ­ som er en ORM der har udviklet sig fra at være simpelt DAL code generering (LLBLGen) til en reel ORM i 2003. LightSpeed er en rimelig ny ORM, med 1. release i 2007, men har siden da gennemgået 3 major versioner, så den p.t. er på verion 3.1 (pr. 12. juni 2010). Der kigges på version 3.1.

7 Metode

7.1 Features

På baggrund af dokumentation og andre kilder, evalueres og konsolideres features for de enkelte ORM's, jf. 4.1 ovenfor i et skema, om det er supporteret/muligt og under hvilke forudsætninger.

7.2 Performance og Implementation

Performance og Implementation evalueres ved en (empirisk) undersøgelse. Til brug i den empiriske undersøgelse af de udvalgte ORM's benyttes flg. domænemodel der modellerer et meget simpelt e-Commerce system, som vist i Figur 13.

Side 25 af 67

Order Address +Street +PostalCode * 1 1 +Address -Customer 1 Customer +Name +Address +Orders

+Order 1

+Lines *

OrderLine +Quantity +GetPrice() * -Lines

+Customer

1

+Product

Product +Name +ListPrice +SKU

* * 1 +Parent +SubCategories

+Products +Categories

VipCustomer +DiscountPercentage

Wholesaler +MinimumOrderQuantity

Category +Name

*

Figur 13 Klasse diagram for domæne model til empirisk undersøgelse

Modellen repræsenterer ikke best-practice hverken ifht. et e-Commerce system eller principper for OOP generelt. For eksempel vil man næppe modellere forskellige kundetyper ved nedarvning ­ i.e. nedarvning er en statisk konstruktion, så en "basis kunde" kan ikke (teoretisk set) skifte til at blive en VIP kunde, og en Wholesaler kan heller ikke samtidig være VIP. Denne konstruktion kunne for eksempel med fordel modelleres vha. et Role Pattern (21). Til dette behov opfylder modellen dog flg. behov, ifht. hvad der ønskes belyst: 1) "Identitet" fx. at det er det samme produkt (objekt!) der fås via OrderLine.Product som ved Category.Products 2) One-one relation (Customer <-> Address) 3) One-many relationer (Order <-> OrderLine og Category <-> Category Category.SubCategories) 4) Many-many relation (Product <-> Category) 5) Nedarvning (Customer -> VipCustomer + Wholesaler)

Side 26 af 67

6) 7) 8) 9)

a. Herunder de forskellige tilgange til nedarvnings mapning ­ disse evalueres separat. Simpel recursive reference (Category.Parent/Category.SubCategories) SELECT N+1 kan testes ved for en given Order at liste de Products der er bestilt på denne Ghost-problematikken kan testes ved Order.Customer Lazy-loading vs. Eager loading for associationer

NB. Product.Lines er bevidst gjort private for at udstille nogle problemstillinger. Databasen vil være en Microsoft SQL (Express) 2005 Server4 som kører lokalt for at udelukke påvirkninger fra netværk. Implementationen vil foregå via (unit) test cases i Visual Studio 2010 (ikke Express da denne ikke indeholder unit test) og data vil blive opsamlet via output fra unit tests, SQL Profiler og for EF og NH via Entity Framework Profiler (22) hhv. NHibernate Profiler (23) ­ de opstillede test cases og metodikken hertil er på ingen måder et udtryk for en best-practice brug af unit testing (24), det er udelukkende brugt som et pragmatisk værktøj til at eksekvere de ønskede teste5. Selve mapningen laves med udgangspunkt i at databasen eksisterer, således at opgaven består i at mappe et eksisterende database skema ind i vores domæne model (Schema First, 5.2). Database skemaet/diagrammet er illustreret i Figur 14.

4

Kan hentes gratis fra http://www.microsoft.com/sqlserver/2005/en/us/express.aspx alternativt SQL Express 2008 http://www.microsoft.com/sqlserver/2008/en/us/express.aspx 5 For eksempel er det ved at bruge unit testing til dette formål, muligt kun at afvikle udvalgte tests fra et givet scenarie

Side 27 af 67

Customer PK Id Name Type VIP_Discount WS_MinOrderSize AddressId Address PK Id Street PostalCode

FK1,U1

Product Order PK FK1 Id CustomerId PK Id Name Price SKU PK

Category Id Name ParentCategoryId

FK1

OrderLine PK FK1 FK2 Id OrderId ProductId Quantity

ProductCategory PK,FK2 PK,FK1 ProductId CategoryId

Figur 14 Database diagram for empirisk undersøgelse

Hvor Customer hierarkiet her er modelleret som et table-per-hierarchy, Type kolonnen benyttes som discriminator kolonne, diskriminator værdier ses herunder. ProductCategory er many-to-many link tabellen imellem Product og Category. Discriminator værdi (Type) 0 1 2 Class Customer (base class, standard kunde) VipCustomer Wholesaler

Databasen vil være præ-populeret med et simpelt dataset, en ORM test vil altid blive udført på en ny oprettet og ny initialiseret database. Al source code og output fra test resultater er tilgængelig i medfølgende .ZIP fil.

Side 28 af 67

7.3 Test Cases

Alle tests findes i projektet MIS.Tests i VS2010 solution'en som er tilgængelig via førnævnte URL. For at sikre at de samme tests bliver udfør på alle 3 ORM'er defineres disse via interfaces, som unit test'ene for de enkelte ORM'er implementerer. Dette sikrer samtidig at der er sammenfald mellem test navnene på tværs af tests. Formål og beskrivelse er defineret via XML documentation på interface'ene, disse interface er inkluderet i Appendix D ­ Test Case definitioner.

8 ADO.NET Entity Framework

8.1 Feature Evaluering

Features evalueres ved hjælp af dokumentation (25) for EF og implementationen: Feature Model first Schema first Code only Supporteret? Ja Ja Ja Kommentar Add > "Empty Model" Add > "Generate from Database" Det er muligt at bruge en eksisterende POCO model (26), men selve mapning kan ikke foretages via code (fjernet efter CTP3 release, og ikke inkluderet i RTW af .NET 4 (27)), dette skal stadig gøres via Entity Modellen. One-one supporteres kun med tabeller der deler primary key Dog ikke med explicit primary key på link tabellen Kaldes "Table-per-concrete-type" i EF. Er ikke supporteret af designeren, kræver redigering direkte i EDMX filen Kaldes "Table-per-type" i EF

One-one assoc. One-many assoc. Many-many assoc. Table-per-hierarchy Table-per-concrete-class

Ja Ja Ja Ja (28) (29) Ja (28) (30)

Table-per-class

Ja (28) (31)

Side 29 af 67

Mix nedarvings mapning Persistence Ignorance/Supporterede RDBMS Lazy-loading

Uvist6 Ja

Ja

MS SQL (32) native og 3rd party DB2, FireBird, Informix, MySQL, Oracle, PostgreSQL, SQLite, Sybase (33) Via en inherited baseclass (alle entiteter nedarver fra EntityObject), eller for POCO via Proxies (Ghost objects)

LINQ Support

Ja

8.2 Performance

Entity Framework udstiller ikke nogen interface eller metoder der gør det muligt at opsamle de SQL kald der foretages af frameworket. Til dette skal derfor bruges enten SQL Profiler for at se hvilke queries der bliver eksekveret på en specific database eller via Entity Framework Profiler (22) ­ hvor den sidstnævnte giver det bedst overblik over hvad der eksekveres hvornår. 8.2.1 Load by id Entity Framework udskiller sig fra de to andre testede ORMs ved ikke at have en explicit måde at loade et objekt ind fra databasen udfra en given primary key. Dette kan for eksempel gøres vha.

Context.Product.Single(p => p.Id == 1)

Der giver flg. SQL statement:

SELECT TOP (2) [Extent1].[Id] AS [Id], [Extent1].[Name] AS [Name], [Extent1].[Price] AS [Price] FROM [dbo].[Product] AS [Extent1] WHERE 1 = [Extent1].[Id]

Single skal, som navnet antyder, returnere præcis én entity, hvis query returnerer flere eller hvis der ikke returneres nogen entities, kastes en exception. Derfor inkluderes TOP (2) for at sikre at hvis der kommer mere end ét element

6

Det er ingen problem at modellere mixede nedarvningshierarkier i EF designeren, men der er ikke fundet dokumentation for at de kan lade sig gøre og det er heller ikke lykkedes at designe en model der kan kompilere.

Side 30 af 67

(minimum 2) så kan dette detekteres. I dette tilfælde er dette overflødigt da Id er primary key for tabellen, og der kan dermed kun returneres max én række.

8.3 Implementation

8.3.1 Mapning Selve mapningen i EF foregår via en designer indbygget i Visual Studio. Mapningen foregår i en Entity Model (EM) og der benyttes begreber som Conceptual Model (CM) og Storage Model (SM). CM er objekt/domæne-modellen som man vil modellere ud fra SM der er den relationelle model i databasen. EM består derfor af CM, SM og en mapning mellem CM & SM. Dette gemmes i en XML fil, .EDMX. På baggrund af Entity Modellen i EDMX filen kode genereres domæne modellen til C# klasser. Klasserne der genereres er pr. default defineret som public partial, dvs. det er muligt at lægge logik på klasserne, eg.:

public partial class OrderLine { public double GetPrice() { return this.Quantity * this.Product.Price * (this.Order.Customer is VipCustomer ? (this.Order.Customer as VipCustomer).DiscountPercentage / 100 : 1); } }

Designeren er meget intuitiv, og forudsætter et begrænset kendskab til koncepterne indenfor ORM, men som med alle 3 løsningner, hvis der er tale om meget mere end Active Record brug (som man reelt set kan opnå ved at bruge fx. LINQ to SQL) så fordrer det et større kendskab til problemstillingerne og løsningerne herpå, eg. nedarvning. 8.3.1.1 Many-many relationer Hvis man (som i 3.2 ovenfor) har en many-many relation der kan indeholde flere reference mellem de samme to entiteter, vil EF pr. default generere en ekstra entitet til associationen. Som nævnt i 3.2 er man muligvis i en situation hvor dette reelt er det man har brug for (i.e. relationen har en identitet). Side 31 af 67

Figur 15 Default many-many relation med explicit primary key i EF

EF supporterer ikke at denne property ikke mappes. Ie. hvis PersonRole entiteten fjernes og erstattes af en Person <-> Role many-many relation der benytter PersonRole tabellen som link table fåes flg. fejl:

Error 3025: Problem in mapping fragments starting at line 113: Must specify mapping for all key properties (PersonRole.Id) of table PersonRole.

Hvilket netop henviser til at relationen har identitet og derfor bør EF kende den. 8.3.1.2 Schema First Ved denne metode auto-genereres Entity modellen automatisk på baggrund af et database skema. For eksempel vil database skematet der benyttes i implementations testen (se Figur 14 i 7.2 ovenfor) give en autogenereret Entity Model som vist i Figur 16.

Side 32 af 67

Figur 16 Entity Model "Generate from Database"

De eneste forskelle fra denne til den "designede" domain model er: 1) Navnene på Category.Parent/Category.SubCategories (disse navne er heller ikke angivet noget sted i database skemaet). 2) Customer er ikke et nedarvnigshierarki, men da det er en konstruktion der ikke er supporteret i den relationelle model, kan dette ikke modelleres i databasen og dermed kan designeren heller ikke genkende denne. 3) Address -> Customer er mappet som en one-many relation 4) Product.Lines er ikke private, men dette kan ikke modelleres i databasen

Side 33 af 67

Hvis Address -> Customer forsøges laves om til en one-one relation som det er garanteret i databasen vha unique index på Customer.AddressId, gives flg. design time fejl:

Error 113: Multiplicity is not valid in Role 'Customer' in relationship 'FK_Customer_Address'. Because the Dependent Role properties are not the key properties, the upper bound of the multiplicity of the Dependent Role must be *.

Det er ikke direkt muligt at ændre access modifier på associationer i designeren, men det kan gøres manuelt i EDMX filen ved at tilføje GetterAccess og SetterAccess, eg.:

<NavigationProperty Name="Lines" Relationship="MISModel.FK_OrderLine_Product" FromRole="Product" ToRole="OrderLine" cg:GetterAccess="Private" cg:SetterAccess="Private" />

EF modellerer one-one relationer via delte primary keys, ie. Customer og Address skal have samme primary key. Tilpasningen af denne model til den ønskede domæne model er derfor meget triviel, se. Figur 17, men i scenarier hvor man har meget store databaser kan dette godt blive uoverskueligt.

Side 34 af 67

Figur 17 Entity Model efter tilpasning

8.3.1.3 Model First Ved denne metode oprettes en tom Entity Model, dette betyder at man skal oprette både den konceptuelle model og mapningen imod storage modellen. Hvis der ikke er en eksisterende database kan EF benyttes til at genrere denne ud fra mapningen. Side 35 af 67

Hvis man således ikke har en database og/eller har nogle specielle krav til denne, kan man benytte default mapningerne (convetion based) for nye entiteter og dermed få håndteret persistens med minimal indsats, ie. ved at designe sin konceptuelle model (dog vil der formodentlig skulle foretages mindre ændringer, eg. specificere længde på tekstfelter, etc.). 8.3.2 Refactoring Refactoring i EF foregår ved at det er muligt at ændre i den konceptuelle model (CM) som det ønskes (C# koden genereres på baggrund af denne), dvs. ændringer af property navne, kardinaliteter, klasse normaliseringer/denormaliseringer etc. Hvis der foretages ændringer af storage modellen (SM) kan dette kun "pushes" tilbage til databasen via en generering af et DDL (Data Definition Language) SQL script med udgangspunkt i en tom database ­ dvs. det er ikke muligt at opdatere en eksisterende database med ændringer automatisk fra EF. 8.3.3 Learning Curve, Dokumentation og Community Som nævnt under 8.3.1.2 ovenfor er designeren rimelig intuitiv og hvis man har en generel forståelse af ORM er det meget simpelt at modellere en Entity Model. Der er dog visse elementer af modelleringen der (som nævnt) ikke er supporteret direkte i designeren, eg. specifikation af access modifier på associationer og modellering af table-per-concrete-class nedarvningshierarkier. Dokumentationen (tilgængelig online) er meget omfattende, og indeholder mange How-to's, Walkthroughs og Quickstarts til at komme godt igang. Edge cases som eg. modelleringen af table-per-concrete-class er ikke dokumenteret (28), (30). Generelt vurderes Entity Framework at være simpelt at komme igang med. Der er et rimelig levende online community omkring blogging etc. der omhandler EF, samt MS har på Data Developer Center et Community site til EF (34). Andre steder hvor man kan søge hjælp og support fra community'et er eg. på StackOverflow (35).

Side 36 af 67

9 NHibernate

9.1 Feature Evaluering

Features evalueres ved hjælp af dokumentation og literatur (36) (37) og implementationen: Feature Model first Schema first Code only Supporteret? Ja Ja Ja Kommentar En del af det grundlæggende koncept, ie. at NHibernate at det "blot" er limen der faciliterer en et givet OOP system at persisteres i en givet relationel database

One-one assoc. One-many assoc. Many-many assoc.

Ja Ja Ja

Supporterer også situationen med en explicit primary key, hvis denne blot autogenereres af databasen (eg. en identity kolonne)

Table-per-hierarchy Table-per-concrete-class Table-per-class Mix nedarvings mapning Persistence Ignorance/Supporterede RDBMS Lazy-loading

Ja (38) Ja (38) Ja (38) Ja (38) Ja

Mange supporterede, se (39)

Ja

LINQ Support

Ja

Via Ghost Objects, kræver at alle properties og metoder er deklareret som virtual Ikke native (del af NHContrib (40))

For NHibernate foregår al mapningen i hånden, enten hvis man gør det via XML (HBM) filer, Fluent NHibernate eller den 3. mulighed ved at dekorere sin model med mapnings attributter.

Side 37 af 67

9.2 Performance

9.2.1 SessionFactory NHibernate benytter sig af Unit-of-Work (8) design pattern til at holde (styr på) de objekter der loades fra databasen, ændringer, transaktioner, etc7. Dette gøres via en "session" (et objekt der implementerer ISession fra NHibernate). En session initialiseres fra en klasse SessionFactory, som holder alt omkring mapninger, configuration, men genererer også de dynamisk Ghost Objects der benyttes til lazy-loading (5.6 ovenfor). Initialiseringen af SessionFactory er en tids-tung process. For vores simple model med 8 klasser, kan dette ses ved den første afviklede test i output er angivet:

NHibernateTest - Setup: 00:00:03.4408842

I de efterfølgende tests tager dette (ca).:

NHibernateTest - Setup: 00:00:00.0309500

Dvs. første initialisering af SessionFactory tager ca. 3,4 sekunder med 8 forholdvis simple klasser. Det betyder at for større modeller er opstarten (dvs. når AppDomain startes op, eg. ved opstart af en klient applikation, efter process recycling eller deployment af web applikation, etc.) forlænget med 3,4 sekunder pga. initialiseringen af NHibernate. Denne opstartstid er bla. afhænging af antal mappede klasser. 9.2.2 NHibernate og LINQ Hvis vi ser på flg. query til Customer_GetCategoriesForAllOrderedProducts() , som er besværliggjort af at Product.Lines ikke er med i modellen.

var products = from o in uow.OrderLines select o.Product; var categories = products .SelectMany(p => p.Categories) .Distinct(); Assert.IsTrue(categories.Any());

Dette resulterer i flg. SQL statement:

7

Det gør sig også gældende for både LightSpeed og Entity Framework

Side 38 af 67

SELECT count(* ) as y0_ FROM OrderLine this_

Hvilket tydeligvis ikke er korrekt. Problemet med dette er at C# koden kan compilere og rent C# kode mæssigt bør den give det korrekte resultat (ie. hvis vi udførte dette LINQ statement direkte på en in-memory collection af orderlines og dertilhørende objekt graf, ville vi gå hvad vi forventede). NHibernate's LINQ provider understøtter kun forholdsvis simple queries, dvs. hvis der skal eksekveres noget der er en lidt ud over SELECT * FROM Product WHERE Id = ??, kan vi risikere at NHibernate ikke kan generere en korrekt query. Eller hvad der er endnu værre (som jo faktisk var tilfældet her), den genererer en forkert query og unit testen er successfuld på et forkert grundlag. Der er bevidst en lille fejl i koden, navnligt at der assertes på categories.Any(). Dette er forkert da vi hermed laver .Any() operationen en del af vores query (IQueryable), hvor testens faktiske formål er at vise at query resultatet giver Category objekter tilbage. Dette kan "løses" ved, som der også er i de andre tests, at udøre en .ToList() på IQueryable når den ønskes eksekveret, dvs. inden .Any().

Assert.IsTrue(categories.ToList().Any());

Dette genererer nu en exception runtime.

Test method MIS.Tests.NHibernateTest.Customer_GetCategoriesForAllOrd eredProducts threw exception: System.InvalidCastException: Unable to cast object of type 'MIS.NHibernate.POCO.OrderLine' to type 'MIS.NHibernate.POCO.Category'.

Og flg. SQL statement bliver eksekveret:

SELECT this_.Id this_.Quantity this_.OrderId this_.ProductId FROM OrderLine this_ as as as as Id1_0_, Quantity1_0_, OrderId1_0_, ProductId1_0_

Dvs. projektionen select o.Product ikke bliver fortolket korrekt.

Side 39 af 67

Vi er i dette tilfælde nødt til at bruge HQL eller Criteria API for at generere en korrekt query. NB! Denne evaluering af NHibernate.Linq er baseret på den i skrivende stund nyeste version (frigivet i november 2009 ­ kun 2. release), dette kan (og vil formodentlig) ændre sig over tid. 9.2.3 Ghost Objects Problem stillingen mht. Ghost Objects, nedarvning og LSP (se 5.6 ovenfor) eksisteter for NHibernate. Dette kan testes ved modellen, eg. følgende kode:

var order = session.Get<MIS.NHibernate.POCO.Order>(1); Assert.IsInstanceOfType( order.Customer, typeof(VipCustomer));

Fejler med:

Assert.IsInstanceOfType failed. Expected type:<MIS.NHibernate.POCO.VipCustomer>. Actual type:<CustomerProxyb316635f117e466fabc43c186cfac9cf>.

Customer med Id 1 er vel at mærke VipCustomer ­ hvis vi vel at mærke tager udgangspunkt i en nyinitialiseret test database. Dette kan løses ved at eager loade Order.Customer, eller: "In practice, this is something that you would generally run into when you are violating the Liskov Substitution Principle, so my general recommendation is to just fix your design". (41)

9.3 Implementation

9.3.1 Mapning Mapningen i NHibernate kan (som nævnt) foregå på 3 forskellige måder: 1) Via HBM ­ i XML 2) Via FluentNHibernate (FH) ­ Fluent Interface (42) hvor mapningen skrives vha. kode 3) Via Attributes (ikke testet) ­ ved at dekorere sine klasser med attributter, eg. Side 40 af 67

[Class(Lazy=true)] public class Customer { [Id(Name = "Id")] [Generator(Class = "native")] public virtual int Id { get; set;} [Property] public virtual string Name { get; set;} }

Alle 3 metoder skal gøres manuelt, i.e. HBM filerne skal skrives i hånden, FH mapningerne skal kodes eller attributterne skal tilføjes manuelt. Der findes dog værktøjer der kan hjælpe med at genrerere HBM filer (e.g Db2Hbm (43)) og fra HBM filer til klasser (Hbm2Net (44)) hvilket giver en Active Record (8) model. Dette giver i princippet samme udgangspunkt som en reverse engineered model i en designer, men for store/større domæne modeller kan det være meget svært at danne sig et overblik over klasser, associationer og nedarvningner vis HBM modellen. Til at redigere i HBM XML filerne er der XML Schema Definitions (XSD) tilgengængelig i NHibernate distributionen, som kan indlæses i eg. Visual Studio og opnå IntelliSense8 i oprettelse/redigeringen af HBM filerne. FluentNHibernate er baseret på .NET kode og mapningen er .NET kode, så ifht. disse er der IntelliSense der kan hælpe ifht. mapningen af disse (det kræver dog at man er bekendt med udgangspunktet for mapningen, ie. klassen ClassMap<TEntity>). Der er naturligvis IntelliSense med Attribute tilgangen, her er hjælpen dog en smule mindre da man skal kende navnene (eller ihvertfald have en idé om hvad det kunne være) på de attributter man skal dekorere med. Mapningen i NHibernate er rimelig krævende både tidsmæssigt, forståelses og overbliks mæssigt. Det er ikke intuitivt og (som minimum) de første gange man skal mappe en database mod en model er det næsten givet at man skal have hjælp fra dokumentationen (som dog er meget god). 9.3.2 Querying I NHibernate er der 3 forskellige måder at skrive queries på:

8

IntelliSense er Microsoft's autocompletion i Visual Studio

Side 41 af 67

1) LINQ som er beskrevet i 9.2.2 ovenfor 2) Hibernate Query Language ­ HQL 3) Criteria API LINQ implementationen i NHibernate har som nævnt (i skrivende stund) sine problemer, det er eg. ikke muligt at lave "dynamiske" eager load (eg. for at undgå SELECT N+1) via en LINQ query. HQL er en SQL lignende syntax der bruges til at query mod NHibernate modellen. HQL er meget ekspressivt (45) og er den mest komplette query metode til NHibernate: "HQL is how NHibernate exposes most of its power (...)" (46). Problematikken med HQL er dog at det er string baseret (stringly-typed, et pun på strongly-type (47)), ie. det parses runtime af NHibernate (vha. ANTLR (48)). Det betyder at der ikke er nogen compile time checking imod objekt-modellen om query er valid, hvilket betyder at det besværliggør refactorings ­ ihvertfald ifht. at producere fejlfri refactorings. Lidt af samme problem er der med Criteria API, som er en hierarkisk opbygning af query i kode vha. et Fluent Interface (42). e.g.

this.Session .CreateCriteria<Product>("product") .CreateAlias("product.Categories", "category") .Add(Expression.Eq("category.Id", 2)) .List<Product>();

Dette giver alle Product'er i Category med Id = 2. Her er der igen benyttet "magic strings" der kan ændre sig med refactorings. Samlet set er quering via NHibernate meget expressivt, man kan stort set skrive alle former for queries. Det største problem ifht. at der er 3 metoder er at de har hver deres styrker, eg. HQL er det mest ekspressive, LINQ det mindst9, hvorimod LINQ er det mest udvikler venlige (det er en velkendt query model) og der er compile time check af LINQ (dog er LINQ provideren som nævnt p.t. mangelfuld)

9

NHibernate.Linq oversætter LINQ queries til Criteria API som genererer SQL, ie. "hvorfor opfinde den dybe tallerken?", men gør samtidig også at LINQ ikke kan blive mere expressiv end de begrænsninger der er i Criteria API.

Side 42 af 67

queryen, så det er den option der er lettest at vedligeholde, hvorimod HQL er er den sværeste at vedligeholde, ie. udviklere skal lære HQL, og refactoring er besværlig gjort at "magic strings" og skal ske manuelt.

Expressiv HQL Criteria API Udvikling/vedligehold

Figur 18 NHibernate query language optioner

NHibernate.Linq

Dvs. man kan komme i en situation hvor man er skal "blande" query sprog (som det eg. er tilfældet i vores simple test, omend alt godt kunne være skrevet via HQL). Dette betyder mere overhead ifht. udvikling, og mindre vedligeholdbar kode. 9.3.3 One-Many inserts I one-many relationer er det nødvendig explicit at specificere Parent objekt på Child objektet selvom denne tilføjes explicit til Parent.Child collection. Eg.:

var order = new Order(); order.Lines.Add(new OrderLine { Order = order });

9.3.4 Learning Curve, Dokumentation og Community Som tidligere antydet er NHibernate forbundet med et stort overhead ifht. kodning og mapning. Både modellen og mapningen skal skrives i hånden, og specielt ifht. mapningen er det ikke intuitivt at komme i gang med. Der er som nævnt også nogle udfordringer med hensyn til at komme i gang med at skrive de første queries og det er under alle omstændigheder forbundet med lidt ekstra arbejde da man enten up-front skal beslutte hvilket query metode man benytter generelt, eller også skal der case-by-case vurderes hvilken query teknik er mest passende til det pågældende behov. Til NHibernates fordel på dette punkt taler dog for at det har det mest ekspressive querying sprog. NHibernates store fordel er dog at det har et meget, meget stort community, det har historikken med sig (ie. der er med stor sandsynlighed andre der har oplevet Side 43 af 67

de samme fejl/udfordringer som man selv kunne støde på) og det lukrerer på synergien med det mindst ligeså store Java Hibernate community. Selvom NHibernate er en portering af Hibernate er features, etc. holdt i sync for at kunne opnå denne synergi effekt. Synergi effekten er også ifht. dokumentation. En stor del af dokumentationen e.g. HBM og HQL er ens for både Hibernate og NHibernate og der behøves derfor kun at vedligeholdes ét sæt kvalitets dokumentation for disse. Tidligere har NHibernate være anklaget for manglende dokumentation, på trods af at der er glimrende dokumentation tilgængelig (49). Support scenarier kan oftest være en show-stopper for Open Source adoption i store eller større virksomheder, til dette tilbyder NHibernate (via Hibernating Rhinos) (50) en kommerciel support option. 9.3.4.1 Addon libraries NHibernate community omfatter ikke blot support, blogs og hjælp. Der er også flere 3rd party add-on libraries til NHiberate. Der kan eg. nævnes: NHibernate.Validator ­ data validerings framework, findes både som attribute baseres og som et fluent interface NHibernate.Search ­ full text search engine baseret på Lucene.NET indexes NHibernate.Burrow ­ transaction management, etc. bla. i ASP.NET (stateless) baserede miljøer FluentNHibernate ­ fluent interface til mapning

10 LightSpeed

10.1 Feature Evaluering

Features evalueres ved hjælp af dokumentation (51) for LightSpeed og implementationen: Feature Model first Schema first Supporteret? Ja Ja Kommentar

Side 44 af 67

Code only One-one assoc. One-many assoc. Many-many assoc.

Nej Ja Ja Ja

Alle entiteter nedarves fra Entity<TId>, hvor TId er typen af den primary key/Id.

Table-per-hierarchy Table-per-concrete-class

Ja Ja

Table-per-class

Ja

Supporteres via såkaldte "Through Entities", dvs. principelt skal "link tabellen" mappes til en entity (dette kan dog håndteres automatisk af frameworken), men der kræves en explicit id på link tabellen. Single Table Inheritance Concrete Table Inheritance, dette er ikke nævnt i dokumentationen, men det er muligt at vælge via designeren. Class Table Inheritance, kræver en discriminator column på superklasse tabellen. Der er dog (formodentlig) en fejl i frameworket der gør at dette ikke virker hvis subklasse tabellerne ikke ligger i default skema. Amazon SimpleDB, DB2, FireBird (deprecated), MySQL, Oracle, PostgreSQL, SQL Server, SQL Server CE, VistaDB ­ enkelte med begrænset designer funktionalitet Via en inherited baseclass (alle entiteter nedarver fra Entity<Tid>)

Mix nedarvings mapning Persistence Ignorance/Supporterede RDBMS

Ja Ja

Lazy-loading LINQ Support

Ja Ja

10.2 Performance

10.2.1 Eager Loading Hvor NHibernate (pr. default) bruger JOIN til Eager Loading, Entity Framework bruger SUBSELECT, så tager LightSpeed en 3. approach: multi queries. Dette vil betyde for Eager Loading på fx. Order -> OrderLine -> Product vil blive eksekveret flg. 3 (forkortede) SQL statements (i én batch!): Side 45 af 67

SELECT [Order]... FROM [Order] WHERE [Order].[Id] = 1; SELECT [OrderLine]... FROM [OrderLine] WHERE [OrderLine].[OrderId] = 1; SELECT [Product]... FROM [Product] WHERE EXISTS ( SELECT [OrderLine].* FROM [OrderLine] WHERE [OrderLine].[ProductId] = [Product].[Id] AND [OrderLine].[OrderId] = 1 )

En simpel test af 1.000 iterationer på denne viser at LightSpeed bruger ca. 4,3s, EF ca. 6,1s og NH ca. 8,3s. (dvs. 1 eksekvering tager ca. det samme i ms). 10.2.2 LINQ I forbindelse med querying kom LightSpeed's LINQ implementation til kort overfor Customer_GetCategoriesForAllOrderedProducts(). Iflg. det direkte LINQ query:

var products = from o in uow.OrderLines select o.Product; var categories = products .SelectMany(p => p.Categories) .Distinct();

Som gav fejlen:

Mindscape.LightSpeed.LightSpeedException: Could not locate reverse association model

Side 46 af 67

Men ifht. mapningen er begge retninger af associationen tilgængelig (det er de altid i LightSpeed), og faktisk er Product.Lines public i denne model. Dette kan umiddelbart dog løses, omend det ikke er en god løsning, ved at forcere eksekvering af Product query'en og udføre SelectMany(p => p.Categories) på denne. Dette resulterer dog i en SELECT N+1, og det er ikke muligt at lave Named Aggregates (som bliver uddybet i 10.3.2 nedenfor) på en many-many relation, så denne løsning er ikke holdbar, specielt hvis der er tale om ret mange Product's.

10.3 Implementation

10.3.1 Mapning Mapningen foregår (som for Entity Framework, 8.3.1) via en designer integreret i Visual Studion. En LightSpeed model er som udgangspunkt tom, men der kan på tilsvarende måde som ved Entity Framework laves en reverse engineering af databasen, ved blot at drag-n-droppede ønskede tabeller ind fra en database via Server Explorer i Visual Studio. I Figur 19 er et eksempel på en reverse engineered database model fra eksemplet i 7.2 ovenfor.

Side 47 af 67

Figur 19 Auto genereret LightSpeed Model

Mapningen er rimelig intuitiv og kræver (igen tilsvarende Entity Framework) kun minimal kendskab til koncepterne for ORM, for at kunne modellere en rimelig simpel model. I modsætning til Entity Framework designeren er det muligt at foretage al mapningen via designeren, omend table-per-class (3.3.3) og table-per-concreteclass (3.3.2) mapningerne ikke har kunne bringes til at fungere (dette er dog absolut ikke nødvendigvis en fejl ved LightSpeed). Det er muligt at designe sin objekt-model udenfor designeren, men LightSpeed har som krav at entiteter skal baseres på Entity<TId> baseclass'en, hvilket udelukker en POCO tilgang (5.3) til ORM via LightSpeed. Model first (5.1) og Schema first (5.2) er begge mulige via designeren (ie. reverse engineering af tabellerne er ikke nødvendigt for at lave mapningen). LightSpeed har som det eneste framework indbygget data validering, dette kan selvfølgelig ses som både en styrke og en svaghed, eg. ifht. begrænsninger i validation (LightSpeed er ikke et validering framework men en ORM). Dog kan

Side 48 af 67

man benytte et andet validerings framework ovenpå, hvis LightSpeed's ikke er tilstrækkelig. 10.3.2 Eager loading LightSpeed' tilgang til eager loading er meget intuitiv. Der kan enten (som ved alle 3 ORM) specificeres på relationer at disse loades "Eager", dvs. sammen med parent objektet. Alternativt kan man i LightSpeed designeren angive Named Aggregates, for associationer. På selve query kan man angive at benytte en Named Aggregate, hvilket betyder at associationer med dette navn bliver Eager loaded, eg.:

var order = uow.Orders .WithAggregate("AllLines") .Single(o => o.Id == 1);

Idet "AllLines" er Named Aggregate på Order -> OrderLine associationen, vil ordre linier blive eager loaded sammen med ordren. Det er ikke muligt at bruge Named Aggregates sammen med FindById<TEntity>(id)og heller ikke for many-many relationer, hvorfor det er nødvendigt at bruge ovenstående LINQ syntax, dette kan være lidt uhensigtsmæssig da man skal bruge to forskellige metoder til at opnå det samme (ie. load et objekt ind via ID). 10.3.3 Learning Curve, Dokumentation og Community LightSpeed er rimelig let at komme igang med, reverse engineering af databasen og refactoring af modellen via designeren er meget intuitiv. LightSpeed har ikke det samme community som hverken Entity Framework og slet ikke som NHibernate. Dette gør at hjælp stort set er begrænset til MindScape's website og de officielle support kanaler (den kommercielle licens til LightSpeed tilbyder priority support). Dokumentationen på websitet er meget mangel fuld, eg. der er kun how-to dokumentation for mapningen og de simpleste querying scenarier.

Side 49 af 67

Der forefindes dog nogle gode screencasts der hjælper på at komme igang, og en rigtig god feature er at der indbygget i designeren kan genreres configuration og kode til setup af context etc.

Figur 20 Getting Started with LightSpeed integreret i designeren

11 Sammenfatning

LINQ til NHibernate er mangelfuld i sin nuværende inkarnation, dvs. man kan ikke lave en fuld applikation med selektive Eager Loading via LINQ interfacet eller hvis man benytter en smule komplicerede LINQ queries kan man ikke være sikker på at det virker efter hensigten (før det eksekveres runtime). Nedarvning er "lettest" i NHibernate, eg. i EF4 skal man redigere i EDMX filen manuelt for at supportere table-per-concrete class. LightSpeed har ikke kunne bringes til at fungere med de to mest komplicerede nedarvningsskemaer (og dermed heller ikke i det mixede scenarie). NHibernate er den bedste ORM til Schema First, eg. både LS og EF4 stiller krav omkring many-many link tabellen ­ LS skal have en explicit primary key, EF4 kan ikke have en explicit primary key. Side 50 af 67

I tabellen herunder er test resultaterne og feature evalueringerne opstillet i et skema, med udgangspunkt i det fokus ens projekt eller organisation har (ifht. brugen af ORM eller generelt). Fokus Fleksibilitet RAD10 Refactoring LINQ ORM NH EF LS EF Begrundelse Spænder bredest ifht. querying, mapning, community, etc. Learning curve, mapning, tilgengængelighed Understøtter incrementelle push tilbage til databasen Implementations mæssigt var EF den eneste der lykkedes med samtlige tests i første forsøg (eg. Customer_GetCategoriesForAllOrderedPr oducts) NH har POCO som udgangspunkt og modellen er generelt helt afkoblet fra framework (med mindre man bruger Attribute mapning) Ingen skilte sig ud, dog bruger NH noget tid til warmup11 Meget stort og levende community Alle framework supporterer dog Data Validation (eg. DataAnnotations for EF, og NHibernate.Validator for NH)

POCO

NH

Performance Community Native Data Validering

NH LS

12 Alternativer

I den senere tid har der været meget buzz omkring en bevægelse kaldet NoSQL (52). NoSQL promoter som navnet antyder en tilgang til persistens der ikke er baseret på SQL. Grunden til dette er er selvfølgelig at der er de tidligere nævnte udfordringer omkring de forskelligheder der er i det objekt-orienterede paradigme og den

10 11

Rapid Application Development Generelt set tager en enkelt query få millisekunder og det vil være sub-optimering at begynde at optimere ifht. dette ­ der er formodentlig andre dele af ens applikation der kan optimeres mere. Derudover kan det argumenteres at man skal op i rigtigt mange operationer (i.e. 100'er eller 1.000'er) før forskellen bliver mærkbar, og hvis man opererer med denne størrelse rækker er et OLTP (som en ORM er) værktøj formodentlig ikke det korrekte (54).

Side 51 af 67

relationelle model. Men også det faktum at SQL databaser skalerer forholdsvis dårligt ifht. data-intensive applikationer eller som backend på store websites. Mange af de største websites bruger ikke RDBMS som storage: Google ­ BigTable Amazon ­ Dynamo Twitter ­ Cassandra Facebook ­ Hadoop

Derudover er der (andre) open-source løsninger som eg. MongoDB, CouchDB og RavenDB. Fælles er for dem alle at de promoverer et skemaløst design således at ændringer i applikations behov (eg. domæne modellen) ikke skal modelleres i databasen. En anden key feature ved dem alle er at de kan skalerer horizontalt (for de fleste ved at bruge en distribueret arkitektur, hvor redundans primært er ifht. throughput end i form af reundans i data storage (omend dette også er muligt)) for at forøge performance, noget der ikke nødvendigvis er tilfældet eller letopnåeligt for RDBMS. Den reele metode for storage er ikke "defineret" i NoSQL, og antager flere forskellige skikkelser, bla.: Document Based o MongoDB o CouchDB o RavenDB Key-Value Based o Dynamo o BigTable Table Based o BigTable Object Based ...

Side 52 af 67

NB! BigTable har både karakteristika af en Table Based og en Key-Value Based database. Alternative løsninger har tidligere været OODBMS, men deres rolle er udspillet i tidens løb, bla. via manglende adoption.

13 Konklusion

I denne undersøgelse er der blevet analyseret på hvilke problemstillinger der er i forhold til at persistere en objekt-orienteret model i en relationel database. Fokus har været på denne metode, idet det historisk set er en meget anvendt metod (udbredelse, kendskab, implementation, etc.). Af dette er opstået en teknologisk løsning (eller skal man sige work-around) til generisk at håndtere persistens af et objekt i en SQL database: Object-Relation mappers (ORM). Der findes et stort antal ORMs, som det kan være svært at navigere imellem for at definere hvilken der er den rigtige til ens projekt eller organisation. Der blev opstillet flg. hypotese: Hvordan adskiller de mest populære ORMs sig fra hinanden? Og hvilke parametre kan man opstille så man kan kvalificere valget af en ORM at basere sit projekt på? Problemstillingerne imellem OOP og RM, er blevet sammenfattet til en feature liste, som man kan forvente at en ORM bør have for at kunne supportere en rimelig generisk mapning. Denne feature liste er sammen med en implementation og efterfølgende tests, blevet brugt som grundlag for at kunne danne et billede af hvordan 3 udvalgte (populære) .NET baserede ORMs forholder sig relativt til hinanden. Ikke overraskende er resultatet ikke entydig, hver har ORM sine fordele (og ulemper) og formålet med denne undersøgelse var netop at få synliggjort hvad forskellene reelt er og give en basis for at kunne foretage et kvalificeret valg der matcher de behov/krav ens projekt/organisation har. Det har på baggrund af de indsamlede data været muligt at kategorisere de 3 ORMs i forhold til hvilket fokus område et give projekt eller organisation har for brugen af ORM.

Side 53 af 67

Sammenfattende har sammenligningen givet flg. hoved punkter for de 3 ORMs: NHibernate er den mest afkoblede og fleksible løsning. Entity Framework er den simpleste udvikle til og den er med fra start. LightSpeed har nogle unikke features der gør den til en niche spiller. Performance forskellene er negligible.

Det er også klart at den relationelle model, uanset hvor god en ORM der benyttes, har nogle problemstillinger der gør at alternative løsninger (som eg. promoveres af NoSQL) vil komme frem og blive prøvet af. Tidligere har objekt-orienterede database systemer (OODBMS) været en mulig kandidat, disse er dog aldrig blevet en success. Andre alternativer der benyttes af nogle af de største websites er KeyValue Based og Document Based databaser.

Side 54 af 67

14 Referencer

1. Larman, Craig. Applying UML and Patterns. s.l. : Prentice Hall PTR, 2002. 2. IBM. Embedded SQL statements in COBOL applications. IBM DB2. [Online] [Citeret: 12. June 2010.] http://publib.boulder.ibm.com/infocenter/db2luw/v9r7/topic/com.ibm.db2.luw.a pdv.embed.doc/doc/c0006126.html. 3. List of object-relational mapping software. Wikipedia. [Online] [Citeret: 12. June 2010.] http://en.wikipedia.org/wiki/List_of_objectrelational_mapping_software#.NET. 4. Pang, K.V. How Fanboys See .NET Data Access Strategies. [Online] January 14, 2010. [Cited: June 10, 2010.] http://www.kevinwilliampang.com/post/HowFanboys-See-NET-ORMs.aspx. 5. Ambler, S.W. Agile Database Techniques - Effective Strategies for the Agile Software Developer. s.l. : Wiley & Sons, 2003. ISBN: 9780471202837. 6. Random UUID probability of duplicates. Wikipedia. [Online] [Citeret: 20. May 2010.] http://en.wikipedia.org/wiki/Uuid#Random_UUID_probability_of_duplicates. 7. Is there an official name for the many-to-many relationship table in a database schema? StackOverflow. [Online] [Citeret: 12. June 2010.] http://stackoverflow.com/questions/1429908/is-there-an-official-name-for-themany-to-many-relationship-table-in-a-database-s. 8. Fowler, M. Patterns of Enterprise Application Architecture. s.l. : AddisonWesley, 2002. ISBN 0321127420. 9. Alert: Select N+1. NHibernate Profiler. [Online] Hibernating Rhinos. [Citeret: 12. June 2010.] http://nhprof.com/Learn/Alerts/SelectNPlusOne. 10. Martin, R.C. The Liskov Substitution Principle. C++ Report. March 1996. available at http://www.objectmentor.com/resources/articles/lsp.pdf, original article B. Liskov, "Data Abstraction and Hierarchy", SIGPLAN Notices vol. 23, issue 5, May 1988..

Side 55 af 67

11. Microsoft. LINQ. MSDN. [Online] [Citeret: 10. June 2010.] http://msdn.microsoft.com/en-us/netframework/aa904594.aspx. 12. --. Expression Trees (C# and Visual Basic). MSDN. [Online] [Citeret: 10. June 2010.] http://msdn.microsoft.com/en-us/library/bb397951.aspx. 13. NHibernate Forge. [Online] [Citeret: 12. June 2010.] http://nhforge.org/. 14. MindScape LightSpeed. [Online] MindScape. [Citeret: 12. June 2010.] http://www.mindscape.co.nz/products/LightSpeed/. 15. LLBLGen Pro. [Online] [Citeret: 12. June 2010.] http://www.llblgen.com/defaultgeneric.aspx. 16. Casle ActiveRecord. [Online] [Citeret: 12. June 2010.] http://www.castleproject.org/activerecord/index.html. 17. DataObjects .NET. [Online] [Citeret: 12. June 2010.] http://www.xtensive.com/Products/DO/. 18. SubSonic. [Online] [Citeret: 12. June 2010.] http://www.subsonicproject.com/. 19. Microsoft. Update on LINQ to SQL and LINQ to Entities Roadmap. ADO.NET team blog. [Online] 29. October 2008. [Citeret: 12. June 2010.] http://blogs.msdn.com/adonet/archive/2008/10/29/update-on-linq-to-sql-andlinq-to-entities-roadmap.aspx. 20. Guthrie, S. Visual Studio 2010 and .NET 4 Released. ScottGu's Blog. [Online] 12. April 2010. [Citeret: 12. June 2010.] http://weblogs.asp.net/scottgu/archive/2010/04/12/visual-studio-2010-and-net4-released.aspx. 21. E. Gamma, R. Helm, R. Johnson, J. Vlissides. Design Patterns - Elements of Reusable Object-Oriented Software. s.l. : Addison-Wesley, 1995. 22. Entity Framework Profiler. [Online] Hibernating Rhinos. [Citeret: 13. June 2010.] http://efprof.com/. 23. NHibernate Profiler. [Online] Hibernating Rhinos. [Citeret: 14. June 2010.] http://nhprof.com/. 24. Osherove, R. The Art of Unit Testing. s.l. : Manning, 2009. ISBN 1933988274. Side 56 af 67

25. Microsoft. ADO.NET Entity Framework. MSDN. [Online] [Citeret: 12. June 2010.] http://msdn.microsoft.com/en-us/library/bb399572(v=VS.100).aspx. 26. --. Working with POCO Entities (Entity Framework). MSDN. [Online] [Citeret: 12. June 2010.] http://msdn.microsoft.com/enus/library/dd456853(v=VS.100).aspx. 27. --. Code Only ­ Further Enhancements. Entity Framework Design. [Online] 12. October 2009. [Citeret: 12. June 2010.] http://blogs.msdn.com/b/efdesign/archive/2009/10/12/code-only-furtherenhancements.aspx. 28. --. Entity Framework Mapping Scenarios. MSDN. [Online] [Citeret: 12. June 2010.] http://msdn.microsoft.com/en-us/library/cc716779(v=VS.100).aspx. 29. Fink, Gil. Table Per Hierarchy Inheritance in Entity Framework. Gil Fink on .Net. [Online] 25. January 2010. [Citeret: 12. June 2010.] http://blogs.microsoft.co.il/blogs/gilf/archive/2010/01/24/table-per-hierarchyinheritance-in-entity-framework.aspx. 30. --. Table Per Concrete Type Inheritance in Entity Framework. Gil Fink on .Net. [Online] 25. January 2010. [Citeret: 12. June 2010.] http://blogs.microsoft.co.il/blogs/gilf/archive/2010/01/25/table-per-concretetype-inheritance-in-entity-framework.aspx. 31. --. Table Per Type Inheritance in Entity Framework. Gil Fink on .Net. [Online] 22. January 2010. [Citeret: 12. June 2010.] http://blogs.microsoft.co.il/blogs/gilf/archive/2010/01/22/table-per-typeinheritance-in-entity-framework.aspx. 32. Microsoft. .NET Framework Data Provider for SQL Server (SqlClient) for the Entity Framework. MSDN. [Online] [Citeret: 12. June 2010.] http://msdn.microsoft.com/en-us/library/bb896309(v=VS.100).aspx. 33. Entity Framework ADO.NET providers. Wikipedia. [Online] [Citeret: 12. June 2010.] http://en.wikipedia.org/wiki/ADO.NET_Entity_Framework#Entity_Framework_AD O.NET_providers.

Side 57 af 67

34. Entity Framework Resources and Community. Data Developer Center. [Online] Microsoft. [Citeret: 13. June 2010.] http://msdn.microsoft.com/enus/data/ee712908.aspx. 35. Newest "entity-framework" Questions. StackOverflow. [Online] [Citeret: 13. June 2010.] http://stackoverflow.com/questions/tagged/entity-framework. 36. NHibernate - Relational Persistence for Idiomatic .NET. NHibernate Forge. [Online] [Citeret: 12. June 2010.] http://nhforge.org/doc/nh/en/index.html. 37. Kuaté, Pierre Henri, et al., et al. NHibernate in Action. s.l. : Manning Publications, 2009. ISBN 1932394923. 38. Inheritance Mapping. NHibernate Forge. [Online] [Citeret: 12. June 2010.] http://nhforge.org/doc/nh/en/index.html#inheritance. 39. SQL Dialects. NHibernate Forge. [Online] [Citeret: 12. June 2010.] http://nhforge.org/doc/nh/en/index.html#configuration-optional-dialects. 40. NH Contrib. SourceForge. [Online] [Citeret: 12. June 2010.] http://sourceforge.net/projects/nhcontrib/. 41. Rahien, Ayende. NHibernate, polymorphic associations and ghost objects. Ayende @ Rahien. [Online] [Citeret: 13. June 2010.] http://ayende.com/Blog/archive/2010/01/09/nhibernate-polymorphicassociations-and-ghost-objects.aspx. 42. Fowler, Martin. FluentInterface. MF Bliki. [Online] 20. December 2005. [Citeret: 14. June 2010.] http://martinfowler.com/bliki/FluentInterface.html. 43. How to use Db2hbm. NHibernate Forge. [Online] 25. February 2010. [Citeret: 14. June 2010.] http://nhforge.org/wikis/howtonh/how-to-use-db2hbm.aspx. 44. T4 Hbm2net alpha version. NHibernate Forge. [Online] 22. November 2009. [Citeret: 14. June 2010.] http://nhforge.org/media/p/546.aspx. 45. HQL: The Hibernate Query Language. Hibernate Community Documentation. [Online] [Citeret: 14. June 2010.] http://docs.jboss.org/hibernate/core/3.3/reference/en/html/queryhql.html. 46. Rahien, Ayende. NHibernate Queries ­ Should I use HQL or Criteria? Ayende @ Rahien. [Online] 1. June 2009. [Citeret: 14. June 2010.] Side 58 af 67

http://ayende.com/Blog/archive/2009/06/01/nhibernate-queries-ndash-should-iuse-hql-or-criteria.aspx. 47. New programming jargon you coined? StackOverflow. [Online] [Citeret: 13. June 2010.] http://stackoverflow.com/questions/2349378/new-programmingjargon-you-coined/2444303#2444303. 48. ANTLR Parser Generator. [Online] [Citeret: 14. June 2010.] http://www.antlr.org/. 49. Rahien, Ayende. NHibernate Documentation. Ayende @ Rahien. [Online] 23. August 2009. [Citeret: 14. June 2010.] http://ayende.com/Blog/archive/2009/08/23/nhibernate-documentation.aspx. 50. NHibernate Commercial Support. NHibernate Profiler. [Online] Hibernating Rhinos. [Citeret: 14. June 2010.] http://www.nhprof.com/CommercialSupport. 51. LightSpeed Documentation. [Online] [Citeret: 12. June 2010.] http://www.mindscape.co.nz/products/lightspeed/Help/Index.aspx. 52. NoSQL. Wikipedia. [Online] [Citeret: 14. June 2010.] http://en.wikipedia.org/wiki/NoSQL. 53. Plain Old CLR Object. Wikipedia. [Online] [Citeret: 12. June 2010.] http://en.wikipedia.org/wiki/POCO. 54. Code refactoring. Wikipedia. [Online] [Citeret: 13. June 2010.] http://en.wikipedia.org/wiki/Code_refactoring. 55. Rahien, Ayende. Benchmarks are useless, yes, again. Ayende @ Rahien. [Online] 15. August 2009. [Citeret: 13. June 2010.] http://ayende.com/blog/archive/2009/08/15/benchmarks-are-useless-yesagain.aspx.

Side 59 af 67

15 Figurer

Figur 1 How Fanboys See .NET Data Access Strategies ........................................... 8 Figur 2 One-one relation via delt primary key ...................................................... 10 Figur 3 One-one relation via unique constraint/index .......................................... 10 Figur 4 Many-many relation med link table ......................................................... 10 Figur 5 Many-many relation med explicit primary key ......................................... 11 Figur 6 Simpelt nedarvnings hierarki ................................................................... 12 Figur 7 Table per Hierarchy ................................................................................. 12 Figur 8 Table per Concrete Class .......................................................................... 13 Figur 9 Table per Class......................................................................................... 14 Figur 10 Lazy-loading eksempel klasse diagram ................................................... 18 Figur 11 Ghost Objects ........................................................................................ 20 Figur 12 Many-one relationer, ghost objects og nedarvning ................................ 21 Figur 13 Klasse diagram for domæne model til empirisk undersøgelse ................ 26 Figur 14 Database diagram for empirisk undersøgelse......................................... 28 Figur 15 Default many-many relation med explicit primary key i EF ..................... 32 Figur 16 Entity Model "Generate from Database" ................................................ 33 Figur 17 Entity Model efter tilpasning.................................................................. 35 Figur 18 NHibernate query language optioner ..................................................... 43 Figur 19 Auto genereret LightSpeed Model ......................................................... 48 Figur 20 Getting Started with LightSpeed integreret i designeren ........................ 50

Side 60 af 67

16 Appendix A ­ Gængse RDBMS

Server based o MS SQL o MySQL o PostgreSQL o Oracle

o mfl...

Embedded o SQLite - http://www.sqlite.org/ o VistaDB - http://vistadb.com/ o MS SQL Compact Edition http://www.microsoft.com/sqlserver/2008/en/us/compact. aspx o FireBird http://www.firebirdsql.org/dotnetfirebird/index.html

o mfl...

Side 61 af 67

17 Appendix B ­ Downloads

Source code, libraries samt output/resultater af tests http://veggerby.dk/mis2010/orm-result.zip Vejledning til afvikling er inkluderet i pakken .NET 4.0 http://www.microsoft.com/downloads/details.aspx?FamilyID=9cfb2d515ff4-4491-b0e5-b386f32c0992 NHibernate 2.1.2 http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NH ibernate-2.1.2.GA-bin.zip/download NHibernate LINQ 2.1.2 http://sourceforge.net/projects/nhibernate/files/NHibernate/2.1.2GA/NH ibernate.Linq-2.1.2-GA-Bin.zip/download FluentNHibernate 1.1 http://fluentnhibernate.org/downloads/releases/fluentnhibernate-1.1.zip LightSpeed Express http://www.mindscape.co.nz/products/LightSpeed/download.aspx NB! LightSpeed kan kun downloades som den seneste tilgængelige version. Version 3.1 som der er testet imod er inkluderet I source code pakken, ligesom denne indeholder alle andre libraries på nær .NET 4.0.

18 Appendix C ­ ORM 3rd Party Tools

Entity Framework: Entity Framework Profiler http://efprof.com/ Commercial license NHibernate:

Side 62 af 67

NHibernate Profiler http://nhprof.com/ Commercial license NHibernate Workbench http://sourceforge.net/projects/faticalabshqled/ Free, Open Source NHibernate Query Analyzer http://www.assembla.com/wiki/show/NHibernateQueryAnalyzer Free, Open Source Fluent NHibernate http://fluentnhibernate.org/ Free, Open Source

19 Appendix D ­ Test Case definitioner

Simpel querying

public interface ISimpleQueryTests { /// <summary> /// Get a product with a specific Id. /// Purpose: probably the most common operation. /// </summary> void Product_LoadProductById(); /// <summary> /// Gets products with specific Id's. /// Purpose: test the generation of dynamic 'IN [...]' statements, something that is /// not possible in SQL directly. /// </summary> void Product_LoadProductsWithIds(); /// <summary> /// Gets products with name containing the letter 'E'. /// Purpose: test the generation of 'LIKE' statements. /// </summary> void Product_LoadProductsWithNameContainingE(); /// <summary> /// Gets a list of customers who have orders with one or more orderline that has a

Side 63 af 67

/// quantity > 10. /// Purpose: test querying with 'JOIN' operations. /// </summary> /// <remarks> /// Indirectly tests one-many and many-one relations. /// </remarks> void Customer_LoadCustomersWithOrdersContainingOrderLinesWithQuant ityLargerThan10(); }

Association navigation

public interface IAssociationQueryTests { /// <summary> /// Gets the address of a specific customer. /// Purpose: validate the 1:1 association (i.e. in the direction og the FK). /// </summary> void Customer_GetAddressFromCustomer(); /// <summary> /// Gets the customer from a specific address. /// Purpose: validate the reverse direction from a 1:1 association /// (<see cref="IAssociationQueryTests.Customer_GetAddressFromCustomer" />, i.e. in the /// opposite direction of the FK). /// </summary> void Address_GetCustomerFromAddress(); /// <summary> /// Gets the orderlines of a specific order. /// Purpose: test a one-many relation. /// </summary> void Order_GetOrderLinesFromOrder(); /// <summary> /// Gets the order of a specific orderline. /// Purpose: test a many-one relation. /// </summary> void OrderLine_GetOrderFromOrderLine(); /// <summary> /// Gets the categories for a specific product. /// Purpose: test a many-many relation. /// </summary> void Product_GetCategoriesFromProduct();

Side 64 af 67

/// <summary> /// Gets the categories for all products on an order for a specific customer. /// Purpose: test deep traversal of associations. /// </summary> void Customer_GetCategoriesForAllOrderedProducts(); }

ORM Pitfalls ­ SELECT N+1 og Ghost/Nedarvning

public interface IPitfallsTests { /// <summary> /// Gets all products for a specific order. /// Purpose: this is a candidate for a SELECT N+1. /// </summary> void Order_GetProductsFromAllOrderLinesWithSelectNp1(); /// <summary> /// Gets all products for a specific order. /// Purpose: if <see cref="IPitfallsTests.Order_GetProductsFromAllOrderLinesWithSe lectNp1"/> /// produces a SELECT N+1 this should fix it. /// </summary> void Order_GetProductsFromAllOrderLinesFixedSelectNp1(); /// <summary> /// Gets customers lazy for a specific order. /// Purpose: test for ghost object, inheritance and LSP problem. /// </summary> void Order_GetCustomerGhostObjects(); }

Identitets tests

public interface IIdentityTests { /// <summary> /// Validates that a Product retrieved from an OrderLine is the same (.Equals()) as from a category and from get by Id. /// Purpose: validates object identity. /// </summary> void Product_IsProductTheSameFromVariousSources(); }

Insert tests

Side 65 af 67

public interface IInsertTests { /// <summary> /// Create a new root category. /// Purpose: test simple insert. /// </summary> void Category_CreateNewRootCategory(); /// <summary> /// Create a new subcategory. /// Purpose: test simple insert with reference. /// </summary> void Category_CreateNewSubCategory(); /// <summary> /// Create a new customer, with address. /// Purpose: test one-one assoc. insert. /// </summary> void Customer_CreateNewBasisCustomer(); /// <summary> /// Create a new VIPCustomer, with address. /// Purpose: test subclass save. /// </summary> void Customer_CreateNewVipCustomer(); /// <summary> /// Create new order. /// Purpose: test one-many insert. /// </summary> void Order_CreateNewOrder(); /// <summary> /// Create a new product and add this to some categories. /// Purpose: test many-many insert. /// </summary> void Product_CreateNewProductWithCategories(); }

Update tests

public interface IUpdateTests { /// <summary> /// Update a product. /// Purpose: test simple update. /// </summary> void Product_UpdateProduct(); /// <summary> /// Update quantity on an orderline of an order.

Side 66 af 67

/// Purpose: test update of an associated object. /// </summary> void Order_ChangeQuantityOnOrderLine(); /// <summary> /// Update an order with another customer. /// Purpose: test update of an association. /// </summary> void Order_ChangeToExistingCustomer(); /// <summary> /// Update an order with new customer. /// Purpose: test update with an inserted customer. /// </summary> void Order_ChangeToNewCustomer(); }

Side 67 af 67

Information

68 pages

Report File (DMCA)

Our content is added by our users. We aim to remove reported files within 1 working day. Please use this link to notify us:

Report this file as copyright or inappropriate

773389