hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/aa_pdf/hovedopgave_thomas_boel... · 4.4...

86
Institut for Datalogi Aarhus Universitet Åbogade 34 8200 Aarhus N Tlf.: 87154112 Fax: 87154115 E-mail: [email protected] http://www.cs.au.dk/da INSTITUT FOR DATALOGI SCIENCE AND TECHNOLOGY AARHUS UNIVERSITET Hovedopgave Master i Informationsteknologi linien i Softwarekonstruktion Design af en dynamisk datamodel af Thomas Boel Sigurdsson 14. juni 2012 Thomas Boel Sigurdsson, studerende Ira Assent, vejleder

Upload: truongliem

Post on 28-Mar-2018

213 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

Institut for Datalogi

Aarhus Universitet

Åbogade 34

8200 Aarhus N

Tlf .: 87154112

Fax: 87154115

E-mail: [email protected]

http://www.cs.au.dk/da

INSTITUT FOR DATALOGI SCIENCE AND TECHNOLOGY

AARHUS UNIVERSITET

Hovedopgave Master i Informationsteknologi

linien i Softwarekonstruktion

Design af en dynamisk datamodel

af Thomas Boel Sigurdsson

14. juni 2012

Thomas Boel Sigurdsson, studerende

Ira Assent, vejleder

Page 2: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

1

Indhold

1 Motivation for projektet .................................................................. 2

2 Problemformulering ......................................................................... 3

2.1 Funktionelle krav ......................................................................... 3

2.2 Arkitektoniske krav ...................................................................... 5

3 Metode og relateret arbejde ............................................................ 8

4 Resultater ...................................................................................... 11

4.1 Design af ER model .................................................................... 11

4.2 Design af databasemodel .......................................................... 12

4.3 Selvbeskrivende datamodel ....................................................... 17

4.4 Bootstrap data ........................................................................... 18

4.5 API til datavedligehold og forespørgsler ................................... 21

4.5.1 Oprettelse af objekter ........................................................... 22

4.5.2 Forespørgsler ........................................................................ 25

4.6 Måling og tuning af forespørgsler ............................................. 28

4.6.1 Import forberedelser ............................................................. 28

4.6.2 Import af data ....................................................................... 30

4.6.3 Forespørgsler ........................................................................ 31

4.6.4 Analyse af første forespørgsel ............................................... 36

4.6.5 Måling og tuning af øvrige forespørgsler .............................. 42

4.6.6 Måling af oprettelse, opdatering og sletning ........................ 46

4.6.7 Diskussion efter indledende analyse ..................................... 50

4.7 Alternativt XML baseret design af databasemodel ................... 52

4.8 Måling og tuning af ændrede forespørgsler .............................. 57

4.8.1 Ændrede SQL forespørgsler................................................... 57

4.8.2 Forsøg med ikke-indekserede attributter ............................. 62

4.8.3 Nye målinger af oprettelse, opdatering og sletning .............. 65

4.8.4 Diskussion efter opfølgende analyse .................................... 68

4.9 Performancemåling ................................................................... 69

5 Konklussion .................................................................................... 71

6 Referencer ..................................................................................... 73

7 Bilag ............................................................................................... 74

8 Appendiks ...................................................................................... 75

8.1 Appendiks 1 – Database create script ....................................... 75

8.2 Appendiks 2 – Forespørgsels API ............................................... 78

Page 3: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

2

1 Motivation for projektet Mange moderne forretningsapplikationer anvender en dynamisk

datamodel, frem for en konventionel datamodel, til repræsentation

af objekter i en relationel database. En dynamisk datamodel har,

under visse forhold, en række fordele sammenlignet med en

konventionel. Blandt disse er særligt, at den effektivt håndterer

store mængder attributter med spredt anvendelse. Desuden

muliggør den, at brugere kan oprette skræddersyede objekttyper og

attributter, uden at databasens relationelle skema ændres. En

dynamisk datamodel har imidlertid også typisk en række svagheder,

herunder potentielt ringere performance og mere komplekse SQL

forespørgsler i forhold til en konventionel datamodel.

Dynamiske datamodeller optræder i litteraturen under forskellige

navne. [EAV2007] benævner dem således Entity-Attribute-Value

modeller, mens de andre steder går under betegnelsen semantiske

datamodeller.

Rapporten relaterer sig til forfatterens virke hos Omada A/S i

København, som i en årrække har udviklet standardprodukter inden

for Identity Management, herunder Omada Identity Suite [OIS2012].

Omada blev stiftet i år 2000 og beskæftiger i dag knap 100

mennesker på kontorer i Danmark, Tyskland, England og USA.

Kundeporteføljen omfatter en række globale selskaber, bl.a. A.P.

Møller Mærsk, Ecco, Bayer og BMW.

Flere af Omadas produkter er baseret på dynamiske datamodeller

og er konstruerede til at kunne håndtere store datamængder.

Nærværende rapport er inspireret af et planlagt projekt hos Omada

med det formål, at fremstille en ny, genbrugelig dynamisk

datamodel, der kan anvendes som komponent i fremtidige

softwareprodukter.

Rapporten omhandler således designet af en dynamisk datamodel,

baseret på en relationel database.

Page 4: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

3

2 Problemformulering Rapporten har til formål at designe og evaluere en dynamisk

datamodel, som opfylder kravene beskrevet i det følgende.

2.1 Funktionelle krav Den dynamiske datamodel skal opfylde følgende funktionelle krav:

1. Den dynamiske datamodel skal understøtte

oprettelsen af brugerdefinerede objekttyper og

attributtyper

2. En attributtype skal kunne tildeles én af følgende

datatyper: String, Integer, DateTime, Boolean

eller Reference

3. En attributtype skal endvidere enten kunne

tildeles en enkelt værdi eller multiple værdier

4. Der skal være et API, som understøtter

oprettelse, læsning, opdatering og sletning af

objekter

5. API’et skal desuden understøtte et

forespørgselssprog

6. Objekter skal kunne have referencer til hinanden

og dermed indgå i en objektgraf

7. Objekter skal opbevares i en relationel database

Ad 1)

Det skal være muligt at oprette brugerdefinerede objekttyper og

attributtyper, som kan bindes sammen.

Eksempelvis skal man kunne oprette objekttypen Employee med de

tilhørende attributter: EmployeeID, FirstName, LastName,

DayOfBirth og Manager.

Ad 2)

En attributtype skal kunne antage én af følgende datatyper: String,

Integer, DateTime, Boolean eller Reference.

Datatypen Reference anvendes til at angive et andet objekt fra

modellen som værdi. Et eksempel kunne være førnævnte Manager

attribut, med hvilken en Employee kan referere en anden Employee,

som er førstnævntes daglige leder.

Datatypen skal kun kunne angives ved oprettelsen af en attributtype

– den skal ikke efterfølgende kunne ændres.

Page 5: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

4

Ad 3)

En attributtype skal enten kunne understøtte, at der angives én

enkelt værdi for den, eller alternativt at der kan angives et vilkårligt

antal værdier for den.

Denne angivelse skal ligeledes kun kunne angives ved oprettelsen af

en attributtype – den skal ikke efterfølgende kunne ændres.

Ad 4)

Anvendelsen af den dynamiske datamodel skal ske via et API med

følgende operationer:

CreateObject(attributeValues)

UpdateObject(objectId, attributeValues)

GetObject(objectId)

DeleteObject(objectId)

QueryObjects(query)

Ud over disse operationer, skal der være operationer til

vedligeholdelsen af objekttyper og attributtyper.

Ad 5)

Den dynamiske datamodel skal understøtte forespørgsler i et

simpelt sprog, der benævnes BQuery (BasicQuery). Sproget har

følgende syntaks:

<query> ::= ”/” <object type> ”[” <expressionlist> ”]”

<expressionlist> ::= <expression> |

<expression> <outeroperator> <expressionlist>

<outeroperator> ::= ”and” | ”or”

<expression> ::= <value> <inneroperator> <value>

<value> ::= <attribute> | <stringvalue> |

<integervalue> | <datetimevalue> |

<booleanvalue> | <referencevalue>

<inneroperator> ::= ”=” | ”<>” | ”<” | ”>” |

”<=” | ”>=” | ”contains”

<attribute> :== <text>

<stringvalue> :== ”’”<text>”’”

<integervalue> :== <integer>

<booleanvalue> :== ”true” | ”false”

Eksempel:

/Employee[FirstName = ’Thomas’ and LastName=’Sigurdsson’]

Page 6: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

5

Denne forespørgsel vil finde alle Employee objekter med fornavnet

”Thomas” og efternavnet ”Sigurdsson”.

De to values, der indgår i en expression, skal have samme datatype.

En attribut af datatypen String kan således kun indgå i en

ekspression med en anden attribut af datatypen String eller en

konstant strengværdi.

De listede inneroperators er kun gyldige for visse datatyper, som vist

i Figur 1.

Inner operator Gyldig for datatype

= String Integer DateTime Boolean Reference

<> String Integer DateTime Boolean Reference

< Integer DateTime

> Integer DateTime

<= Integer DateTime

>= Integer DateTime

contains String

Figur 1 – Operatorers gyldighed for datatyper

Ad 6)

Et objekt skal kunne referere andre objekter via attributter af

datatype Reference. Eksempelvis skal et Employee objekt kunne

referere sin nærmeste leder via en Manager attribut af datatype

Reference.

Ad 7)

Den dynamiske datamodels data skal opbevares i en relationel

database.

2.2 Arkitektoniske krav Designet af den dynamiske datamodel vil blive vurderet i forhold til

følgende arkitektoniske kvaliteter:

1. Performance og skalérbarhed

2. Designets kompleksitet

3. Pladsforbrug

Til vurderingen af designet vil der blive udarbejdet et antal

arkitektoniske prototyper.

Page 7: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

6

Ad 1)

Den dynamiske datamodel har følgende succeskriterier:

- Den skal kunne rumme 1,5 mio. objekter med i gennemsnit

5 attributværdier.

- Datamodellen skal opfylde følgende:

o Oprettelse af et objekt skal udføres på under 0,3

sekunder.

o Opdatering af et objekt skal udføres på under 0,2

sekunder.

o Læsning af et enkelt skal udføres på under 0,5

sekunder.

o En gennemsnitlig forespørgsel (se nedenfor) skal

udføres på under 2 sekunder.

o Sletning af et objekt skal udføres på under 0,5

sekunder.

Ovenstående forudsætter en almindelig serverkonfiguration efter

dagens standarder.

Det forventes, at forholdet mellem antal brugerudførte

forespørgsler og opdateringer af data vil være ca. 5:1. Dette bunder

i, at datamodellen forventes anvendt i et system med web

brugergrænseflade, hvor brugerne oftere søger efter og læser data

end de opdaterer dem.

Følgende eksempler på BQuery forespørgsler vurderes at være

repræsentative og gennemsnitlige:

”Find en medarbejder med et specifikt Id”

/Employee[ObjectId=’240031F2-5CD7-471D-BF73-F55AE02C4F82’]

”Find de medarbejdere der hedder Jens Hansen”

/Employee[FirstName=’Jens’ and LastName=’Hansen’]

”Find alle medarbejdere der hedder enten Olsen eller Hansen til

efternavn”

/Employee[LastName=’Olsen’ or LastName=’Hansen’]

”Find alle medarbejdere oprettet efter 15. marts”

/Employee[CreatedTime > 2012-03-15]

”Find de medarbejdere der er afdelingsledere”

Page 8: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

7

/Employee[IsManager = true]

”Find de medarbejdere der er ansat efter 2008”

/Employee[EmploymentYear > 2008]

”Find alle medarbejdere hvor ’assistant’ indgår i jobtitlen”

/Employee[JobTitle contains 'assistant']

Ad 2)

Det er en målsætning, at det udarbejde design ikke har en

unødvendig høj kompleksitet. Særligt er det ønskeligt, at de SQL

forespørgsler, der skal foretages som et resultat af en

objektforespørgsel, ikke bliver unødigt komplicerede.

Selvom opfyldelsen af målsætningen er vanskelig at verificere

objektivt, medtages den, da den vurderes at være væsentlig.

Ad 3)

Det udarbejdede design vil blive målt op mod alternative designs i

forhold til hvor meget plads de optager i databasen.

Page 9: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

8

3 Metode og relateret arbejde På baggrund af de opstillede krav til den dynamiske datamodel,

designes og implementeres et system, der kan indfri dem.

Designet læner sig op ad de retningslinier, der gives i [EAV2007];

Artiklen undersøger egnetheden af Entity-Attribute-Value (EAV)

database modellering i systemer til håndtering af kliniske data i

biomedicinske systemer. Forfatterne konkluderer, at EAV

modellering er velegnet, når mindst ét af følgende kriterier er

opfyldt:

Antallet af attributter i et system er højt og der er stor

spredning i anvendelsen af dem. Med dette menes, at der

ofte er stor forskel på antallet af potentielle attributter, som

et givet objekt kan anvende, versus det antal, som det rent

faktisk anvender.

De anvendte attributter og objekttyper er volative, dvs. at

nye attributter og objekttyper jævnligt oprettes og andre

nedlægges af brugerne.

Konventionel database modellering er mindre velegnet til spredte

attributter, da disse medfører et stort antal kolonner i tabellerne, af

hvilke kun få er udfyldte på en enkelt række. At de anvendte

attributter er volatile medfører desuden, at database skemaet og

systemets brugergrænseflade ofte skal ændres.

En EAV model siges at være række-orienteret, da hver række i en

EAV tabel repræsenterer en attributværdi for et objekt. Dette skal

ses i modsætning til en konventionel kolonne-orienteret model,

hvor en attribut repræsenteres af en kolonne.

I en EAV model repræsenteres et objekt af en række i en ”Objects”

tabel. Attributværdier repræsenteres af enten:

Flere kolonner (én per datatype) i én ”Attribute Value”

tabel.

Flere ”Attribute Value” tabeller (én per datatype) med én

enkelt værdikolonne.

Et centralt element i en EAV model er håndteringen af metadata om

de enkelte attributter. Disse anvendes til beskrivelse af

valideringsregler, præsentation og lovlige værdimængder.

Behandlingen af metadata er ofte kompleks, hvilket er prisen for at

have enkelhed i den relationelle datamodel.

Artiklen klassificerer data som enten konventionelle-, EAV- eller

hybride data. Hybride data betyder, at de ikke-spredte attributter på

Page 10: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

9

en objekttype repræsenteres konventionelt, mens de spredte

repræsenteres som EAV data.

En ulempe ved en EAV model, er at at række klassiske database

koncepter fungerer dårligt eller ikke kan anvendes:

Muligheden for at definere attribut-specifikke database

constraints på kolonner med attributværdier er dårlig, da en

sådan kolonne rummer værdier for flere attributter.

Af samme årsag er anvendelse af triggers heller ikke

velegnet.

Til tider er der behov for at konvertere data i en EAV model til

konventionelle data, dvs. til kolonneorienterede data. Dette

benævnes pivotering af data. Pivotering kan være relevant, hvis data

i EAV modellen skal overføres til et data warehouse med henblik på

rapportering.

En EAV model kan understøtte ad-hoc attribut centriske

forespørgsler, dvs. forespørgsler der kombinerer et antal ”og”

filtreringer på specifikke attributter. Det anbefales at implementere

funktionaliteten ved at udføre flere små SQL forespørgsler (én for

hver attribut filtrering) og gemme resultatet af hver i en temporær

tabel. Rækkerne i de temporære tabeller joines derefter sammen til

det endelige resultat.

***

I forbindelse med rapporten vil der blive udviklet et API, som kan

oprette, læse, opdatere og slette objekter. Som en del af

implementeringen vil der blive foretaget query tuning og analyse af

udførelsesplaner som beskrevet i [Silberschatz2011].

Forskellige alternativer til designet vil blive diskuteret i forhold til de

opstillede kvalitetsattributter. Der vil blive udviklet arkitektoniske

prototyper til måling af, om der er væsentlige forskelle mellem

alternativerne. Prototyperne vil særligt fokusere på performance,

skalérbarhed og pladsforbrug.

Page 11: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

10

Den udviklede løsning, samt de arkitektoniske prototyper, vil blive

implementeret baseret på MS SQL Server 2008 R2, C# og Microsoft

.Net Framework 4.0.

Alle målinger vil blive foretaget på et virtuelt HyperV image med

følgende konfiguration:

- Windows Server 2008

- SQL Server 2008 R2

- 4 GB RAM

- CPU med 4 kerner

Page 12: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

11

4 Resultater I det følgende gennemgås projektets resultater, opdelt i et antal

gennemførte design iterationer.

4.1 Design af ER model Vi udarbejder ER modellen vist i Figur 2, med henblik på at opfylde

de opstillede krav.

Figur 2 – ER model

Et Object udgør en instans af en ObjectType, som har bindinger til et

antal AttributeTypes. På en binding kan det angives, om et objekt af

typen kræver en værdi i den pågældende attribut. En AttributeType

rummer angivelse af, om et objekt kan have en eller flere værdier

for attributten. En AttributeType er knyttet til en DataType. Et

Object identificeres på et Id og kan have værdier for de

AttributeTypes, som er bundet til objektets type.

Page 13: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

12

4.2 Design af databasemodel På baggrund af ER modellen udarbejdes en konkret databasemodel,

som er vist i Figur 3.

Figur 3 – Databasemodel

Appendiks 1 indeholder DDL scriptet til oprettelse af databasens

tabeller, fremmednøgler og indeks.

Hovedparten af transformationen af ER modellen til database

modellen er triviel. Der dannes således tabeller for entiteterne

ObjectType, AttributeType, Object og DataType. 1-N relationen

AttributeDataType bliver til en kolonne i AttributeType tabellen. På

samme vis bliver 1-N relationen InstanceOf til en kolonne i Object

tabellen. Relationen AttributeBinding bliver til en selvstændig tabel,

dels fordi det er en N-N relation og dels fordi den har en attribut

(Requiresvalue). På samme vis (og af samme årsag) bliver

AttributeValue relationen til en selvstændig tabel. De dannede

tabeller har samme navne som entiteterne og relationerne i ER

modellen.

AttributeBinding

ObjectType

AttributeType

RequiresValue

AttributeType

Name

DataType

MultiValued

DataType

DataType

ObjectType

Name

Object

Id

ObjectType

AttributeValue

ObjectId

AttributeType

String

Integer

DateTime

Boolean

Reference

Page 14: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

13

Denne nye AttributeValue tabel er af særlig interesse; Vi har behov

for at kunne repræsentere attributværdier af forskellige datatyper.

Der er umiddelbart fire forskellige alternativer, der kan komme på

tale:

1. Der anvendes én kolonne til repræsentation af alle

attributværdier – uanset datatype.

2. Der anvendes flere kolonner til repræsentation af

attributværdier afhængig af datatype.

3. Der anvendes én kolonne af datatype xml til repræsentation

af alle attributværdier – uanset datatype.

4. AttributeValue tabellen splittes op i flere tabeller. Én per

datatype.

Ad 1)

I denne fremgangsmåde anvendes én kolonne til repræsentation af

alle attributværdier – uanset datatype. Denne kolonne kan være af

datatype nvarchar, da alle de datatyper vi ønsker at understøtte, kan

repræsenteres som strenge.

AttributeValue tabellen får følgende udseende:

CREATE TABLE AttributeValue(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

Value nvarchar(500) NOT NULL,

CONSTRAINT PK_AttributeVaue PRIMARY KEY CLUSTERED

(

ObjectId ASC,

AttributeType ASC,

Value ASC

))

Formatet af Value kolonnen afhænger af datatypen, eksempel:

1. ’Some value’ (String)

2. ’23531’ (Integer)

3. ’2012-03-11T13:35:00.0046276Z’ (DateTime)

4. ’True’ (Boolean)

5. ’C983FADF-237B-46AF-8DEA-285D49372B73’ (Reference)

Designet muliggør, at der kan defineres en primærnøgle for tabellen

bestående af ObjectId, AttributeType og Value. Ulempen ved

designet er, at vi ikke bruger databasens type-system og at der ikke

kan oprettes et meningsfuldt indeks på kolonnen, da den rummer

forskellige datatyper.

Page 15: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

14

Et alternativ til nvarchar, er at anvende datatypen sql_variant, som

er dynamisk af natur. Datatypen frarådes imidlertid eksplicit i

[EAV2007], da den er beregningsmæssigt ineffiktiv. Vi forfølger

derfor ikke denne mulighed yderligere.

Ad 2)

I denne fremgangsmåde anvendes flere kolonner til repræsentation

af attributværdier afhængig af datatype. AttributeValue tabellen får

følgende udseende:

CREATE TABLE AttributeValue(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

String nvarchar(500) NULL,

Integer int NULL,

DateTime datetime NULL,

Boolean bit NULL,

Reference uniqueidentifier NULL

)

De fem værdi-kolonner er null’able, da kun én af dem kan have en

værdi i en række. Som konsekvens af dette, kan der ikke defineres

en primærnøgle for tabellen, med mindre der oprettes en

surrogatnøgle.

SQL forespørgsler, der selekterer objekter med bestemte værdier i

bestemte attributter, bliver med fremgangsmåden mere komplekse

end i alternativ 1. Dette skyldes, at der, afhængig af en attributs’

datatype, skal ledes i forskellige kolonner.

Fremgangsmåden medfører et bredere tabel-skema end alternativ 1

og dermed et (lidt) højere pladsforbrug.

Page 16: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

15

Ad 3)

I denne fremgangsmåde anvendes en kolonne af datatype xml til

repræsentation af alle attributværdier. Vi definerer følgende XML

Schema i databasen:

CREATE XML SCHEMA COLLECTION AttributeValueSchema AS

'<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="value">

<xs:complexType>

<xs:attribute name="b_value" type="xs:boolean" use="optional"/>

<xs:attribute name="dt_value" type="xs:dateTime" use="optional"/>

<xs:attribute name="i_value" type="xs:integer" use="optional"/>

<xs:attribute name="s_value" type="xs:string" use="optional"/>

<xs:attribute name="r_value" type="xs:string" use="optional"/>

</xs:complexType>

</xs:element>

</xs:schema>'

Dernæst får AttributeValue tabellen følgende udseende:

CREATE TABLE AttributeValue(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

Value xml (CONTENT AttributeValueSchema) NOT NULL)

Med dette design er det ikke muligt at definere en primærnøgle for

tabellen, da xml kolonner ikke kan indekseres, med mindre der

indføres en surrogatnøgle.

Konceptet er, at én række i AttributeValue tabellen rummer én

værdi for én attribut. Alternativt kan man imidlertid tillade Value

kolonnen at rumme flere værdier for samme attribut (med et let

modificeret XML Schema). Fordelen ved denne fremgangsmåde er,

at primærnøglen så kan bestå af ObjectId og AttributeType. En

anden fordel er, at der så kommer færre rækker i AttributeValue

tabellen.

Page 17: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

16

Ad 4)

Et alternativ til AttributeValue tabellen, er at definere fem

forskellige tabeller, der hver skal rumme alle værdier for attributter

af én bestemt datatype:

CREATE TABLE AttributeValue_String(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

Value nvarchar(300) NOT NULL,

CONSTRAINT PK_AttributeVaue_String PRIMARY KEY CLUSTERED

(ObjectId ASC, AttributeType ASC, Value ASC)

)

CREATE TABLE AttributeValue_Integer(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

Value int NOT NULL,

CONSTRAINT PK_AttributeVaue_Integer PRIMARY KEY CLUSTERED

(ObjectId ASC, AttributeType ASC, Value ASC)

)

CREATE TABLE AttributeValue_DateTime(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

Value DateTime NOT NULL,

CONSTRAINT PK_AttributeVaue_DateTime PRIMARY KEY CLUSTERED

(ObjectId ASC, AttributeType ASC, Value ASC)

)

CREATE TABLE AttributeValue_Boolean(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

Value bit NOT NULL,

CONSTRAINT PK_AttributeVaue_Boolean PRIMARY KEY CLUSTERED

(ObjectId ASC, AttributeType ASC, Value ASC)

)

CREATE TABLE AttributeValue_Reference(

ObjectId uniqueidentifier NOT NULL,

AttributeType varchar(50) NOT NULL,

Value uniqueidentifier NOT NULL,

CONSTRAINT PK_AttributeVaue_Reference PRIMARY KEY CLUSTERED

(ObjectId ASC, AttributeType ASC, Value ASC)

)

Fremgangsmåden bevirker, at vi får fordelt data i flere tabeller,

hvilket er godt for performance. Til gengæld skal der foretages fem

joins i hver forespørgsel i stedet for én, hvilket trækker i den

modsatte retning.

Page 18: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

17

En fordel ved fremgangsmåden, sammenlignet med alternativ 2, er

imidlertid at de fem tabeller kan indekseres mere effektivt.

AttributeValue tabellen i alternativ 2 vil typisk have store mængder

null værdier i de fem værdi-kolonner, hvilket bevirker at indeks

bliver større end man kunne ønske sig.

SQL forespørgslerne baseret på denne fremgangsmåde bliver

(ligesom alternativ 2) mere komplekse end i alternativ 1. Dette

skyldes, at der, afhængig af en attributs’ datatype, skal joines med

forskellige tabeller.

***

Vi fravælger umiddelbart alternativ 1, da der ikke kan oprettes et

fornuftigt indeks og fordi metoden forhindrer os i at anvende

databasens type-system. Søgninger og sorteringer bliver som

konsekvens deraf for dyre.

Vi vælger derfor i første omgang at gå videre med alternativ 2 med

planen om senere at udforske alternativ 3.

Alternativ 4 vil vi ikke undersøge nærmere, da vi vurderer, at de

mange joins, som fremgangsmåden medfører, gør den mindre

velegnet.

4.3 Selvbeskrivende datamodel Vi ønsker, at alle data i datamodellen skal kunne opfattes som

objekter. Dette gælder både objekter af skræddersyede objekttyper

(så som ”Employee”), såvel som de indbyggede objekttyper

ObjectType, AttributeType og AttributeBinding. Målet er således, at

datamodellen bliver selvbeskrivende.

En selvbeskrivende datamodel er attraktiv, da den potentielt

forsimpler den funktionalitet, der senere hen skal baseres på

datamodellen. Dette kunne eksempelvis være en sikkerhedsmodel

(ikke omfattet af dette projekt), der muliggør definition af, hvem der

må oprette, læse, opdatere og slette objekter. Såfremt alle data

opfattes som objekter, kan en sådan sikkerhedsmodel anvendes til

skræddersyede objekttyper, såvel som de indbyggede objekttyper

(ObjectType, AttributeType og AttributeBinding).

Et andet eksempel kunne være et formularkoncept (ej heller

omfattet af dette projekt), der beskriver hvorledes et objekt af en

given type skal repræsenteres i et inddateringsskærmbillede. Når

alle data er objekter, kan et sådant formularkoncept anvendes til

skærmbilleder, der bruges til at konfigurere systemets objekttyper

Page 19: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

18

og attributter, såvel som til skræddersyede objekttyper.

I begge tilfælde medvirker den selvbeskrivelse datamodel til et pænt

og ensartet design.

Med henblik på at gøre datamodellen selvbeskrivende, besluttes

det, at en række i ObjectType tabellen også skal repræsenteres af

rækker i Object og AttributeValue tabellerne. Det samme gælder for

AttributeType og AttributeBinding tabellerne.

Designet medfører dataredundans, hvilket som udgangspunkt er

uhensigtsmæssigt. En mulig løsning på problemet er at udfase

ObjectType, AttributeType og AttributeBinding tabellerne. Dette vil

imidlertid efterlade os med en database med kun to tabeller (Object

og AttributeValue). Vi vurderer, at et sådant design har en række

væsentlige svagheder, herunder at det er intuitivt svært at forstå og

at vi i så fald ikke kan drage nytte af database indeks, constraints og

fremmednøgler. Vi beslutter derfor at acceptere dataredundansen,

med de ulemper den giver, herunder at data skal vedligeholdes to

steder.

4.4 Bootstrap data Foranlediget af, at datamodellen skal være selvbeskrivende,

udstyres den med en mængde bootstrap data (indbyggede data).

Disse data udgør et antal objekttyper, attributtyper og bindinger,

som skal være til stede i databasen, for at systemet kan fungere.

Data er specielle, da API’erne, der konstrueres senere hen, vil basere

sig på, at data er til stede i databasen. Data kan af denne årsag ikke

oprettes vha. API’erne, hvorfor det sker med et database script, som

er medtaget i bilag 4.

Object types

I datamodellen indbygges en række objekttyper, som er vist i Figur

4. Objekttyperne skal både optræde i ObjectType tabellen samt i

Object+AttributeValue tabellerne.

Objekttype Beskrivelse

ObjectType Et ObjectType objekt beskriver en objekttype.

AttributeType Et AttributeType objekt beskriver en attributtype, der kan bindes til en objekttype.

AttributeBinding Et AttributeBinding objekt beskriver bindingen af en AttributeType til en ObjectType.

Figur 4 – Indbyggede objekttyper

Page 20: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

19

Attribute types

I datamodellen indbygges en række attributtyper, som er vist i Figur

5. Attributtyperne skal både optræde i AttributeType tabellen samt i

Object+AttributeValue tabellerne.

Fire attributtyper er særlige:

ObjectId (angiver et unikt id for et objekt)

ObjectType (angivet hvilken type et objekt er af)

CreatedTime (angiver hvornår et objekt blev oprettet)

ChangedTime (angiver hvornår et objekt senest blev rettet)

Attributterne skal være bundet til alle objekttyper i systemet, hvilket

API’et får til opgave at sikre. Attributterne er desuden vedligeholdte

af systemet selv – det er således ikke muligt at ændre værdien af

dem.

Attributtype Datatype Multiple værdier

Beskrivelse

ObjectId Reference False Angiver et unikt Id for et objekt. Alle objekttyper skal have en binding til denne attribut. ObjectId attributten er speciel i og med den er af data type Reference. Den refererer imidlertid per definition altid sig selv. ObjectId tildeles af systemet i forb. med oprettelse af et objekt. ObjectId kan ikke opdateres.

ObjectType String False Angiver objekttypen for et objekt. Alle objekttyper skal have en binding til denne attribut. ObjectType tildeles af systemet i forb. med oprettelse af et objekt. ObjectType kan ikke opdateres.

CreatedTime DateTime False Angiver oprettelsestidspunktet for et objekt. Alle objekttyper skal have en binding til denne attribut. CreatedTime tildeles af systemet i forb. med oprettelse af et objekt. CreatedTime kan ikke opdateres.

ChangedTime DateTime False Angiver seneste ændringstidspunkt for et objekt. Alle objekttyper skal have en binding til denne attribut. ChangedTime tildeles af systemet i forb. med oprettelse af et objekt samt i forbindelse med alle efterfølgende opdateringer af objektet.

Name String False

Page 21: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

20

DisplayName String False Visningsnavn for et objekt.

DataType String False Angiver datatypen for en attributtype. Lovlige værdier er { String, Integer, DateTime, Boolean, Reference }

MultiValued Boolean False Angiver om en attributtyper tillader

at der angives mere end én værdi.

BoundAttribute Reference False Angiver den attributtype, som en binding er for.

BoundObjectType Reference False Angiver den objekttype, som en binding er for.

RequiresValue Boolean False Angiver om en binding kræver

angivelse af en værdi.

Figur 5 – Indbyggede attributtyper

Attribute bindings

I datamodellen indbygges følgende attributbindinger, som fremgår

af Figur 6. Attributbindingerne skal både optræde i AttributeBinding

tabellen samt i Object+AttributeValue tabellerne.

Objekttype Attributtype Kræver værdi

Beskrivelse

ObjectType ObjectId True

ObjectType ObjectType True

ObjectType CreatedTime True

ObjectType ChangedTime True

ObjectType DisplayName

ObjectType Name True Unikt navn for en objekttype.

AttributeType ObjectId True

AttributeType ObjectType True

AttributeType CreatedTime True

AttributeType ChangedTime True

AttributeType DisplayName

AttributeType Name True Unikt navn for en attributtype.

AttributeType DataType True Datatype for en attribut. Skal være én af følgende værdier: { String, Integer, DateTime, Boolean, Reference }

AttributeType MultiValued True Angiver om en attribut tillader, at et objekt har flere værdier for den.

AttributeBinding ObjectId True

AttributeBinding ObjectType True

AttributeBinding CreatedTime True

AttributeBinding ChangedTime True

AttributeBinding DisplayName

AttributeBinding BoundObjectType True Objekttype, som binding er for.

AttributeBinding BoundAttribute True Attributtype, som binding er for.

AttributeBinding RequiresValue True Angiver om et dataobjekt af objekttypen skal have en værdi for attributten.

Figur 6 – Indbyggede attributbindinger

Page 22: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

21

4.5 API til datavedligehold og forespørgsler Med henblik på at kunne vedligeholde og forespørge på data i den

dynamiske model, udvikles et API, der kan oprette, rette, slette og

forespørge på data ved hjælp af det definerede forespørgselssprog

(se afsnit 2.1).

API’et udvikles i C#, frem for i en stored procedure, da de sproglige

faciliteter er rigere i C#. Vi har bl.a. behov for at kunne definere

klasser og validere regulære udtryk, hvilket ikke umiddelbart er

muligt i TSQL. Ulempen ved valget er, at databasen dermed ikke kan

sikre sin egen integritet, men må stole på den eksterne logik i C#

API’et. Såfremt en stored procedure var anvendt, kunne de

relevante tabeller, via sikkerhedsmodellen i databasen, være

beskyttet mod direkte adgang. Adgang til tabellerne ville i stedet

udelukkende ske via stored procedures. En måde at komme omkring

dette problem, er kun at give en særlig databasebruger adgang til

tabellerne. API’et skal så udføre sine handlinger i databasen via

denne bruger.

De centrale klasser i API’et er vist i Figur 7.

-ObjectType : string

-Expressions

ObjectQuery

-OuterOperator

-Left : object

-InnerOperator

-Right : object

QueryExpression

1

*

+QueryObjects()

+CreateObject()

+UpdateObject()

+DeleteObject()

-DbConnection

-DbTransaction

ObjectController

-Id

-ObjectType : string

-Attributes : AttributeValueDictionary

DataObject

+Add()

+Remove()

+Clear()

-Keys

-Values

-Count

-AllowedAttributes

AttributeValueDictionary

1 1

Figur 7 – Klassediagram visende de centrale klasser i API’et

ObjectController klassen rummer metoder til at oprette, rette,

slette og forespørge på data. DataObject klassen repræsenterer et

enkelt objekt og rummer primært et Attributes dictionary med

attributværdier. Værdien/værdierne for en attribut i Attributes

repræsenteres med én af følgende datatyper: bool, DateTime, int, string, Guid, IEnumerable<DateTime>, IEnumerable<int>,

Page 23: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

22

IEnumerable<string> eller IEnumerable<Guid>. En attributs værdier

er således repræsenteret af IEnumerable<> hvis objektet har mere

end én attributværdi.

Ved oprettelse af en ObjectController, skal angives en åben

databaseforbindelse i form af et SqlConnection og SqlTransaction

objekt. Transaktionsobjektet er ikke påkrævet, såfremt der kun skal

læses data.

4.5.1 Oprettelse af objekter

API’ets ObjectController.CreateObject metode opretter et enkelt

objekt i databasen.

public Guid CreateObject(DataObject obj)

Metoden tager et DataObject objekt som argument (bemærk at

klassen hedder DataObject og ikke Object, da sidstnævnte er et

reserveret ord i C#).

Et eksempel på anvendelse af API’et, til oprettelse af et ”Person”

objekt, er vist nedenfor.

using (SqlConnection dbConnection =

new SqlConnection("User ID=sa;Password=<removed>;Initial

Catalog=DynamicDB_MProj;Data Source=VM-TBS3-DB"))

{

dbConnection.Open();

SqlTransaction dbTransaction =

dbConnection.BeginTransaction();

try

{

DataObject person = new DataObject("Person");

person["FirstName"] = "Jens";

person["LastName"] = "Hansen";

person["DisplayName"] = "Jens P. Hansen";

person["BirthDate"] = new DateTime(1982, 3, 25);

ObjectController objectController =

new ObjectController(dbConnection, dbTransaction);

objectController.CreateObject(person);

dbTransaction.Commit();

}

catch

{

dbTransaction.Rollback();

throw;

}

}

Figur 8 – Eksempel på oprettelse af objekt via API

Page 24: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

23

CreateObject metoden foretager en række valideringer, for at sikre

at integriteten bevares:

De anvendte attributter på et objekt skal findes i databasen

Der findes bindinger i databasen mellem objekttypen og de

anvendte attributtyper.

Værdierne for de anvendte attributter svarer til

attributtypens datatype.

Der må ikke være angivet flere værdier for en attribut, med

mindre den er markeret som ”MultiValued”.

Der skal være angivet en værdi for en attribut, hvis den på

sin binding har angivet, at der kræves en værdi.

I eksemplet i Figur 8 betyder dette, at angivelsen af

person["FirstName"] = "Jens";

medfører, at følgende valideringer udføres:

FirstName attributten findes

Person objekttypen har en binding til FirstName attributten

Værdien for FirstName er en string.

Der er kun angivet en enkelt værdi for FirstName.

Alle typer af objekter oprettes vha. ObjectController.CreateObject

metoden, inklusiv objekttyper, attributtyper og attributbindinger.

Dette giver en pæn symmetri i API’et og gør det nemt at anvende,

da man kun skal anvende ét API til det hele. Imidlertid kan

modellens selvdefinerende egenskaber potentielt være svært

tilgængelige. Det følgende eksempel (Figur 9) illustrerer begge dele.

Der oprettes først en objekttype ved navn ”Person”, dernæst to

attributtyper ”FirstName” og ”LastName” og efterfølgende to

bindinger mellem objekttypen og de nye attributtyper. Til sidst

oprettes et nyt objekt af den nye type ”Person”. Forbindelses- og

transaktionshåndtering er udeladt fra eksemplet.

Page 25: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

24

ObjectController objectController = new ObjectController(…);

// Create new ObjectType object

DataObject personType = new DataObject("ObjectType");

personType["Name"] = "Person";

Guid personTypeId = objectController.CreateObject(personType);

// Create new AttributeType objects

DataObject firstNameAttribute = new

DataObject("AttributeType");

firstNameAttribute["Name"] = "FirstName";

firstNameAttribute["DataType"] = "String";

firstNameAttribute["MultiValued"] = false;

Guid firstNameId =

objectController.CreateObject(firstNameAttribute);

DataObject lastNameAttribute = new DataObject("AttributeType");

lastNameAttribute["Name"] = "LastName";

lastNameAttribute["DataType"] = "String";

lastNameAttribute["MultiValued"] = false;

Guid lastNameId =

objectController.CreateObject(lastNameAttribute);

// Create new AttributeBinding objects

DataObject firstNameBinding = new

DataObject("AttributeBinding");

firstNameBinding["BoundObjectType"] = personTypeId;

firstNameBinding["BoundAttribute"] = firstNameId;

firstNameBinding["RequiresValue"] = false;

objectController.CreateObject(firstNameBinding);

DataObject lastNameBinding = new

DataObject("AttributeBinding");

lastNameBinding["BoundObjectType"] = personTypeId;

lastNameBinding["BoundAttribute"] = lastNameId;

lastNameBinding["RequiresValue"] = false;

objectController.CreateObject(lastNameBinding);

// Create an object of the new type

DataObject person = new DataObject("Person");

person["FirstName"] = "Thomas";

person["LastName"] = "Sigurdsson";

objectController.CreateObject(person);

Figur 9 – Eksempel på oprettelse af objekttype, attributter m.v.

Page 26: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

25

ObjectController.CreateObject metoden sørger for, at de

indbyggede objekttyper ”ObjectType”, ”AttributeType” og

”AttributeBinding” håndteres specielt. Dette inkluderer vedligehold

af støttetabellerne i databasen (ObjectType, AttributeType og

AttributeBinding). Hvis der oprettes et objekt af typen ”ObjectType”

sørger metoden således for, at der indsættes en række i ObjectType

tabellen. Ved oprettelse af et nyt ”ObjektType” objekt, oprettes der

automatisk ”AttributeBinding” objekter for de indbyggede

attributtyper, som skal være til stede på alle objekter i systemet:

”ObjectId”, ”ObjectType”, ”CreatedTime” og ”ChangedTime”.

4.5.2 Forespørgsler

ObjectController.QueryObject metoden muliggør udførsel af BQuery

(se afsnit 2.1) forespørgsler på data.

public List<DataObject> QueryObjects(string query)

Eksempel:

ObjectController controller = new ObjectController(cs);

List<DataObject> objs =

controller.QueryObjects("/Person[FirstName=’Thomas’]");

Figur 10 – Eksempel på udførsel af BQuery forespørgsel

Metoden validerer først BQuery forespørgslen vha. følgende

regulære udtryk:

^/(?<objtype>[A-Za-z0-9]+)

(\[((?<expression>\s*

(?<outerop>(and|or)?)\s*

(?<leftside>(\'[A-Za-z0-9\s]+\'|[0-9]+|((19|20)\d\d[-](0[1-

9]|1[012])[-](0[1-9]|[12][0-9]|3[01]))|true|false|[A-Za-z0-

9_]+|\'(\d|\w){8}-(\d|\w){4}-(\d|\w){4}-(\d|\w){4}-

(\d|\w){12}\'))\s*

(?<operator>(<|>|=|<=|>=|<>|contains))\s*

(?<rightside>(\'[A-Za-z0-9\s]+\'|[0-9]+|((19|20)\d\d[-](0[1-

9]|1[012])[-](0[1-9]|[12][0-9]|3[01]))|true|false|[A-Za-z0-

9_]+|\'(\d|\w){8}-(\d|\w){4}-(\d|\w){4}-(\d|\w){4}-

(\d|\w){12}\')))+)\])?$

Figur 11 – Regulært udtryk til validering af en BQuery forespørgsel

Dernæst oversættes forespørgslen til et ObjectQuery objekt, der

rummer et antal QueryExpression objekter, som hver repræsenterer

ét af forespørgslens udtryk (se Figur 7).

Efterfølgende danner metoden en SQL forespørgsel på baggrund af

ObjectQuery objektet vha. skabelonen vist i Figur 12.

Page 27: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

26

select o.Id, av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id = av.ObjectId

where o.ObjectType = @ObjectType

and av.AttributeType not in ('ObjectId', 'ObjectType')

and ( <expressions sql> )

Figur 12 – SQL skabelon som anvendes af QueryObjects

Der dannes og indsættes SQL udtryk i skabelonen (ved

<expressions_sql>) for de anvendte BQuery udtryk. Betragt

eksempelvis følgende BQuery forespørgsel, der finder et ”Person”

objekt på dets Id:

/Person[ObjectId='53E7D656-F449-469A-857C-5FCFC719BF56’]

Denne resulterer i SQL forespørgslen vist i Figur 13. SQL

forespørgslen udføres via kald af sp_executesql, som er en indbygget

stored procedure i MS SQL Server. sp_executesql muliggør brug af

parametre, hvilket sparer databasen for at kompilere SQL

forespørgslen ved gentagne udførsler af den samme forespørgsel. I

vores tilfælde er dette en fordel, hvis brugeren udfører den samme

BQuery flere gange for forskellige parametre.

exec sp_executesql N'select o.Id, av.AttributeType, av.String,

av.Reference, av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id = av.ObjectId

where o.ObjectType = @ObjectType

and av.AttributeType not in (''ObjectId'', ''ObjectType'')

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId

and av2.AttributeType = @AttribName0

and av2.Reference = @AttribValue0))

order by o.Id',

N'@ObjectType nvarchar(6),@AttribName0 nvarchar(8),@AttribValue0

uniqueidentifier',

@ObjectType=N'Person',@AttribName0=N'ObjectId',@AttribValue0='53

E7D656-F449-469A-857C-5FCFC719BF56'

Figur 13 – BQuery omsat til SQL

Resultatet af den viste SQL forespørgsel er vist i Figur 14.

Page 28: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

27

Figur 14 – Resultat af BQuery SQL forespørgsel

Til afprøvning af forespørgsels API’et udarbejdes en Windows

baseret applikation, som er vist i Figur 15. Applikationen muliggør,

at der kan indtastes en BQuery forespørgsel for oven, hvor efter

resultatet vises for neden i skærmbilledet. For hvert objekt, der

indgår i resultatet, listes de tilhørende attributværdier.

Figur 15 – BQuery Tool

De centrale dele af forespørgsels API’et er medtaget i appendiks 2.

Page 29: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

28

4.6 Måling og tuning af forespørgsler Med API’et på plads ønsker vi nu at måle og tune udførslen af

BQuery forespørgsler. Med henblik på at kunne dette, oprettes

indledningsvis en række testdata i systemet.

4.6.1 Import forberedelser

Med henblik på masseoprettelse af objekter i den dynamiske

datamodel, anvender vi en (anden) til rådighed værende database

med en stor mængde person-testdata. I databasen findes tabellen

tblPerson_Generated med skemaet vist i Figur 16. Hver række i

tabellen, der indeholder nok data til, at vi kan afprøve vores

arkitektoniske målsætninger, repræsenterer oplysninger om en

person. Tabellen har kolonnen ImportDuration, som vi anvender til

at registrere, hvor lang tid det tog at oprette en person i den

dynamiske datamodel. Indledningsvis er kolonnen null i alle rækker.

Et udsnit af tabellens data er vist i Figur 17.

CREATE TABLE [dbo].[tblPerson_Generated](

[ID] [nvarchar](50) NOT NULL,

[FName] [nvarchar](50) NOT NULL,

[LName] [nvarchar](50) NOT NULL,

[Initials] [nvarchar](50) NOT NULL,

[JobTitle] [nvarchar](50) NOT NULL,

[ManagerID] [nvarchar](50) NULL,

[Department] [nvarchar](50) NOT NULL,

[ValidFromOffset] [int] NOT NULL,

[ValidToOffset] [int] NOT NULL,

[GenID] [int] IDENTITY(1,1) NOT NULL,

[ImportTime] [datetime] NULL,

[ImportDuration] [int] NULL,

[YearEmployed] [int] NULL,

[IsSpecial] [smallint] NULL,

CONSTRAINT [PK_tblPerson_Generated] PRIMARY KEY CLUSTERED

(

[ID] ASC

))

Figur 16 – Skema for tabel med person testdata

Page 30: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

29

Figur 17 – Udsnit af person testdata

Page 31: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

30

Med henblik på at importere persondataene til den dynamiske

datamodel, oprettes objekttypen ”Person” og tilhørende attributter

vha. koden vist i Figur 18. SchemaController klassen tilbyder en

bekvem måde at oprette disse objekter, frem for at bruge

ObjectController klassen direkte. Dette skyldes, at SchemaController

muliggør oprettelsen af de indbyggede objekter med et enkelt

metodekald, i modsætning til hvad vi gjorde i Figur 9.

SchemaController schemaController = new SchemaController(…);

schemaController.CreateObjectType("Person");

schemaController.CreateAttributeType("PersonID",

AttributeDataType.String, true);

schemaController.CreateAttributeType("FirstName",

AttributeDataType.String, true);

schemaController.CreateAttributeType("LastName",

AttributeDataType.String, true);

schemaController.CreateAttributeType("Initials",

AttributeDataType.String);

schemaController.CreateAttributeType("JobTitle",

AttributeDataType.String);

schemaController.CreateAttributeType("Department",

AttributeDataType.String);

schemaController.CreateAttributeType("YearEmployed",

AttributeDataType.Integer);

schemaController.CreateAttributeType("IsManager",

AttributeDataType.Boolean);

schemaController.CreateAttributeType("Manager",

AttributeDataType.Reference);

schemaController.CreateAttributeBinding("Person", "DisplayName");

schemaController.CreateAttributeBinding("Person", "PersonID");

schemaController.CreateAttributeBinding("Person", "FirstName");

schemaController.CreateAttributeBinding("Person", "LastName");

schemaController.CreateAttributeBinding("Person", "Initials");

schemaController.CreateAttributeBinding("Person", "JobTitle");

schemaController.CreateAttributeBinding("Person", "Department");

schemaController.CreateAttributeBinding("Person", "YearEmployed");

schemaController.CreateAttributeBinding("Person", "IsManager");

schemaController.CreateAttributeBinding("Person", "Manager");

Figur 18 – Oprettelse af objekttype og attributter til testdata

Således forberedt er vi nu klar til at importere data.

4.6.2 Import af data

Vi importerer først 100.000 objekter, før vi påbegynder analysen af

forespørgslerne. Det er bekvemt, at der er en vis mængde data i

databasen, når forespørgslerne analyseres, da man dermed helt

enkelt kan fornemme, når en forespørgsel ikke kører optimalt. Det

Page 32: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

31

giver sig simpelthen udtryk ved, at en forespørgsel nemt tager flere

sekunder at udføre, hvilket den helst ikke skal, når vi er færdige med

optimeringerne.

Mængden af data i databasen kan også have en effekt på, hvilken

udførselsplan optimizeren vælger til en SQL forespørgsel. Ved små

datamængder kan optimizeren eksempelvis vælge at udføre en

Table Scan på en tabel, selvom der findes et passende indeks,

simpelthen fordi det er mere effektivt ved de pågældende

datamængder.

Efter importen trækkes en rapport over databasens pladsforbrug (se

Figur 19Error! Reference source not found.). Af denne fremgår det

at data indledningsvis fylder 118 MB.

Figur 19 – Rapport over databasens pladsforbrug

Efter importen opdaterer vi databasens statistik mht.

datadistribution, for at sikre, at query optimizeren har de rigtige tal

at arbejde med:

exec sp_updatestats

I det følgende eksperimenterer vi med de forskellige forespørgsler,

med henblik på at bygge indeks. Indledningsvis er der ingen indeks

for AttributeValue tabellen.

4.6.3 Forespørgsler

På baggrund af referenceforespørgslerne beskrevet i afsnit 2.2

udarbejder vi en serie konkrete forespørgsler, som vi ønsker at

afprøve i databasen med de nu 100.000 importerede objekter.

Page 33: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

32

Planen er senere at udføre de samme forespørgsler ved endnu

større datamængder. For at opnå sammenlignelige resultater, er

forespørgslerne (og de anvendte testdata) derfor konstrueret

således, at de returnerer de samme rækker uanset hvor mange

testdata, der er oprettet i systemet. Forespørgslerne, som er vist i

Figur 20, er udstyret med oplysning om, hvor mange objekter de

returnerer. Det angivne antal vil altså være det samme, uanset hvor

mange data der er importeret.

Nr. Forespørgsel og tilsvarende BQuery Antal

returnered

e objekter

1 ”Find en person med et specifikt Id” /Person[ObjectId='08D48E54-1511-4CC3-B4CA-

195B01F1C180']

1

2 ”Find de personer der hedder Song Lan”

/Person[FirstName='Song' and LastName='Lan']

1

3 ”Find alle personer der hedder enten Tian eller Wing til efternavn” /Person[LastName='Tian' or LastName='Wing']

3

4 ”Find alle personer oprettet d. 17 april 2012”

/Person[CreatedTime >= 2012-04-17 and CreatedTime

< 2012-04-18]

10

5 ”Find de personer der er specialoprettede”

/Person[IsSpecial = true]

4

6 ”Find de personer der er ansat før 2009”

/Person[YearEmployed < 2009]

4

7 ”Find alle medarbejdere hvor ’Dept’ indgår i jobtitlen”

/Person[JobTitle contains 'Dept']

4

Figur 20 – BQuery forespørgsler og de antal objekter de returnerer

De syv BQuery forespørgsler oversættes til SQL forespørgsler af C#

API’et. De respektive SQL forespørgsler er vist i Figur 21. Alle

forespørgsler udføres via kald af sp_executesql, som anvendes af

ADO.NET når der udføres en SQL forespørgsel med parametre.

Ad forespørgsel 4)

BQuery sproget understøtter kun hele datoer, som API’et fortolker

som værende ved midnat den pågældende dato. BQuery’en skal

Page 34: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

33

derfor skrives på den angivne måde, for at fange de personer, der er

oprettede den pågældende dato.

Nr. SQL forespørgsler dannet på baggrund af BQuery

forespørgsler

Antal

returnerede

rækker

F1 exec sp_executesql N'select o.Id,

av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id =

av.ObjectId where o.ObjectType =

@ObjectType

and av.AttributeType not in (''ObjectId'',

''ObjectType'')

and (exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.Reference = @AttribValue0))

order by o.Id',

N'@ObjectType varchar(6),@AttribName0

varchar(8),@AttribValue0

uniqueidentifier',@ObjectType='Person',@At

tribName0='ObjectId',@AttribValue0='08D48E

54-1511-4CC3-B4CA-195B01F1C180'

11

F2 exec sp_executesql N'select o.Id,

av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id =

av.ObjectId

where o.ObjectType = @ObjectType

and av.AttributeType not in (''ObjectId'',

''ObjectType'')

and (exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.String = @AttribValue0)

and exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName1 and

av2.String = @AttribValue1))

order by o.Id',

N'@ObjectType varchar(6),@AttribName0

varchar(9),@AttribValue0

nvarchar(4),@AttribName1

varchar(8),@AttribValue1

nvarchar(3)',@ObjectType='Person',@AttribN

ame0='FirstName',@AttribValue0=N'Song',@At

tribName1='LastName',@AttribValue1=N'Lan'

12

F3 exec sp_executesql N'select o.Id,

av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id =

av.ObjectId

where o.ObjectType = @ObjectType

36

Page 35: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

34

and av.AttributeType not in (''ObjectId'',

''ObjectType'')

and (exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.String = @AttribValue0)

or exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName1 and

av2.String = @AttribValue1))

order by o.Id',

N'@ObjectType varchar(6),@AttribName0

varchar(8),@AttribValue0

nvarchar(4),@AttribName1

varchar(8),@AttribValue1

nvarchar(4)',@ObjectType='Person',@AttribN

ame0='LastName',@AttribValue0=N'Tian',@Att

ribName1='LastName',@AttribValue1=N'Wing'

F4 exec sp_executesql N'select o.Id,

av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id =

av.ObjectId

where o.ObjectType = @ObjectType

and av.AttributeType not in (''ObjectId'',

''ObjectType'')

and (exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.DateTime >= @AttribValue0)

and exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName1 and

av2.DateTime < @AttribValue1))

order by o.Id',

N'@ObjectType varchar(6),@AttribName0

varchar(11),@AttribValue0

datetime,@AttribName1

varchar(11),@AttribValue1

datetime',@ObjectType='Person',@AttribName

0='CreatedTime',@AttribValue0='2012-04-17

00:00:00',@AttribName1='CreatedTime',@Attr

ibValue1='2012-04-18 00:00:00'

120

F5 exec sp_executesql N'select o.Id,

av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id =

av.ObjectId

where o.ObjectType = @ObjectType

and av.AttributeType not in (''ObjectId'',

''ObjectType'')

and (exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.Boolean = @AttribValue0))

order by o.Id',

N'@ObjectType varchar(6),@AttribName0

varchar(9),@AttribValue0

bit',@ObjectType='Person',@AttribName0='Is

Special',@AttribValue0=1

48

Page 36: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

35

F6 exec sp_executesql N'select o.Id,

av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id =

av.ObjectId

where o.ObjectType = @ObjectType

and av.AttributeType not in (''ObjectId'',

''ObjectType'')

and (exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.Integer < @AttribValue0))

order by o.Id',

N'@ObjectType varchar(6),@AttribName0

varchar(12),@AttribValue0

int',@ObjectType='Person',@AttribName0='Ye

arEmployed',@AttribValue0=2009

48

F7 exec sp_executesql N'select o.Id,

av.AttributeType, av.String, av.Reference,

av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id =

av.ObjectId

where o.ObjectType = @ObjectType and

av.AttributeType not in (''ObjectId'',

''ObjectType'')

and (exists (select 1 from AttributeValue

av2 where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.String like @AttribValue0))

order by o.Id',N'@ObjectType

varchar(6),@AttribName0

varchar(8),@AttribValue0

nvarchar(6)',@ObjectType='Person',@AttribN

ame0='JobTitle',@AttribValue0=N'%Dept%'

48

Figur 21 – SQL forespørgsler dannet på baggrund af BQuery forespørgsler, samt det antal rækker de returnerer

Page 37: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

36

4.6.4 Analyse af første forespørgsel

Vi udfører SQL forespørgsel 1 (se Figur 22), der er dannet på

baggrund af følgende BQuery forespørgsel:

/Person[ObjectId='08D48E54-1511-4CC3-B4CA-195B01F1C180']

SQL forespørgslen er dannet af API’et, som beskrevet i afsnit 4.5.2.

exec sp_executesql N'select o.Id, av.AttributeType, av.String,

av.Reference, av.Integer, av.Boolean, av.DateTime

from Object o

join AttributeValue av on o.Id = av.ObjectId

where o.ObjectType = @ObjectType

and av.AttributeType not in (''ObjectId'', ''ObjectType'')

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId

and av2.AttributeType = @AttribName0

and av2.Reference = @AttribValue0))

order by o.Id',

N'@ObjectType nvarchar(6),@AttribName0 nvarchar(8),@AttribValue0

uniqueidentifier',@ObjectType=N'Person',@AttribName0=N'ObjectId'

,@AttribValue0='08D48E54-1511-4CC3-B4CA-195B01F1C180'

Figur 22 – Forespørgsel F1

Vi bemærker indledningsvis, at der i SQL forespørgslen sorteres på

Object.Id, hvilket reelt er overflødigt i det konkrete tilfælde, da

forespørgslen maksimalt kan returnere ét objekt (eftersom

forespørgslen leder efter et objekt med et bestemt Id). Sorteringen

er (normalt) til for at de rækker, der indgår i et objekt, ligger

sammen i forespørgselsresultatet, så API’ets indlæsning af objekter

fra resultatet er nem. API’et forventer således, at objekternes

attributter optræder i rækkefølge i de data det modtager. Vi noterer

os at API’et skal forbedres således, at det kan udelade sorteringen,

hvis det kan afgøres, at en forespørgsel maksimalt kan returnere ét

objekt.

SQL forespørgslen ekskluderer attributterne ”ObjectId” og

”ObjectType”. Dette sker i alle forespørgsler, der genereres af

API’et. Attributten ”ObjectId” ekskluderes, da forespørgslen

selekterer værdien fra Object.Id kolonnen (oplysningen er

opbevaret redundant i databasen). ”ObjectType” ekskluderes, da en

BQuery altid er for én angivet objekttype, hvorfor denne oplysning

er kendt og derfor kan udelades af resultatet.

SQL forespørgslen resulterer i udførelsesplanen vist i Figur 23. Vha.

SQL Server Profiler (se Figur 24) kan vi konstatere, at CPU tiden er

1125 millisekunder og at der udføres 28299 læsninger.

Page 38: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

37

Udførelsesplanen kan findes i rapportens bilag 1 i filen q1_1.sqlplan.

Figur 23 – Indledende udførelsesplan (forespørgsel F1) – ingen indeks oprettet

Figur 24 – Forespørgsel F1 – SQL Server Profiler

De målte tal er høje og indikerer, at der formentlig mangler et

indeks. Udførelsesplanen viser da også, at der udføres to Table Scan

operationer, der sammenlagt udgør 82% af omkostningen. Den

første Table Scan skyldes et manglende indeks på

AttributeValue.Reference og AttributeValue.AttributeType

kolonnerne (se Figur 25). SQL Server foreslår efter udførslen

følgende indeks, som vi opretter:

CREATE NONCLUSTERED INDEX IX_AttributeValue_Reference

ON AttributeValue (Reference,AttributeType)

Page 39: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

38

Figur 25 – Table scan #1 (forespørgsel F1)

En ny generering af rapporten over databasens pladsforbrug

fortæller, at pladsforbruget nu er på 173 MB (mod tidligere 118

MB). Indekset (IX_AttributeValue_Reference) optager således 55

MB.

Den anden Table Scan skyldes, at der ledes (probes) efter rækker i

AttributeValue tabellen på deres ObjectId (se Figur 26). SQL Server

foreslår selv følgende indeks, som vi også opretter:

CREATE NONCLUSTERED INDEX IX_AttributeValue_ObjectId

ON AttributeValue (ObjectId)

INCLUDE(AttributeType,String,Integer,DateTime,Boolean,Reference)

Figur 26 – Table scan #2 (forespørgsel F1)

Page 40: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

39

Indeks oprettes generelt med henblik på at opnå hurtigere

forespørgsler. Prisen er imidlertid et større pladsforbrug samt

langsommere indsættelser, opdateringer og sletninger. Jo større

indeks, jo højere er prisen. En væsentlig overvejelse, i forbindelse

med oprettelse af indeks, er derfor, hvad det forventede forhold er

mellem læsninger og opdateringer. I vores tilfælde er det beskrevet i

kravene (se afsnit 2.2), forholdet vil være ca. er 5:1. Vi vurderer

derfor, at det er fornuftigt at oprette førnævnte indeks.

Vi bemærker, at udførelsesplanen indeholder parallelitet, hvilket

betyder at den udføres på flere samtidige tråde. Databasen vælger

kun en parallel plan ved forespørgsler der har en vis (IO)

omkostning. Paralleliteten bevirker i visse tilfælde bedre udnyttelse

af databasens ressourcer, men medfører også øgede omkostninger i

form af trådoprettelse og efterfølgende synkronisering med

hovedtråden. De øgede omkostninger resulterer potentielt i en

længere samlet udførselstid for forespørgslen.

Et nærmere studie af den første Table Scan (se Figur 25) afslører, at

der udføres en CONVERT_IMPLICIT. Dette skyldes, at værdien for

AttributeValue.AttributeType i forespørgslen er angivet som en

nvarchar. I tabellen er den imidlertid defineret som en varchar,

hvilket medfører den implicitte konvertering. Den forkerte datatype

angivelse viser sig at være forårsaget af en mindre fejl i API’et, som

fejlagtigt angav den forkerte datatype ved udførelsen af

forespørgslen.

Vi er nu klar til at udføre forespørgslen igen og nulstiller derfor

databasens cache, så vi er sikre på at sammenligne på et ens

grundlag:

DBCC FREEPROCCACHE

Dernæst udfører vi forespørgsel 1 igen. CPU tiden er nu 31

millisekunder og antallet af læsninger er 32. Altså med andre ord en

markant forbedring. Antallet af læsninger er nu så lavt, at det

umiddelbart ikke kan betale sig at forsøge at forbedre den

yderligere. Udførelsesplanen ser nu ud som i Figur 27. Vi bemærker

at de to Table Scan operationer er erstattet af Index Seek. Ligeledes

bemærker vi, at der ikke længere er parallelitet, hvilket skyldes at

forespørgslens omkostning nu er langt lavere end tidligere.

Udførelsesplanen kan findes i rapportens bilag 1 i filen q1_2.sqlplan.

Page 41: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

40

Figur 27 – Udførelsesplan (forespørgsel F1) efter oprettelse to indeks og ændring af datatyper

Indekset (IX_AttributeValue_ObjectId) inkluderer hovedparten af

tabellens kolonner, hvilket betyder at det er temmelig

pladskrævende. Pladsforbruget er således nu på 283 MB (mod

tidligere 173 MB). Indekset optager således 110 MB – altså ca. det

samme som selve dataene og præcis det dobbelte af det første

indeks.

Indekset IX_AttributeValue_ObjectId betegnes som et

såkaldt dækkende indeks, da forespørgslen kan nøjes med at slå op i

indekset og dermed ikke behøver at lave opslag i selve tabellen

[ExecPlans2008], hvilket bevirker øget hastighed.

Tabellen har på nuværende tidspunkt ikke noget klyngeindeks (af

hvilke en tabel kun kan have ét). Et klyngeindeks ligger fysisk

sammen med tabellens data og er derfor per definition altid

dækkende. Som følge deraf, er klyngeindeks normalt lidt

langsommere end almindelige indeks. Dette dog kun såfremt det

almindelige indekset ikke er dækkende og databasen som

konsekvens må slå op i selve tabellen, efter at lave lokaliseret de

relevante rækker i indekset.

Vi erstatter forsøgsvis indekset (IX_AttributeValue_ObjectId)

med et klyngeindeks, for at undersøge pladsforbrug og ydelse:

DROP INDEX IX_AttributeValue_ObjectId ON AttributeValue

CREATE CLUSTERED INDEX IX_AttributeValue_ObjectId_Clustered

ON AttributeValue (ObjectId)

Dernæst udfører vi forespørgsel 1 igen. Udførselstiden er nu 31

millisekunder og antallet af læsninger er 31, altså stort set identisk

som med det tidligere indeks (IX_AttributeValue_ObjectId).

Pladsforbruget er imidlertid nu kun 191 MB, altså 92 MB mindre end

da vi anvendte IX_AttributeValue_ObjectId. Det mindre

Page 42: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

41

pladsforbrug skyldes, at et klyngeindeks fysisk ligger sammen med

tabellens data.

Forespørgslens udførelsesplan kan ses i Figur 28. Til forskel fra

tidligere, udføres der nu et Clustered Index Seek (i

IX_AttributeValue_ObjectId_Clustered). Vi bemærker endvidere, at

der ikke længere udføres en RID Lookup i AttributeValue tabellen.

Dette skyldes klyngeindekset, som medfører, at databasen har

direkte adgang til rækkernes data fra indekset.

Udførelsesplanen kan findes i rapportens bilag 1 i filen q1_3.sqlplan.

Figur 28 – Udførelsesplan (forespørgsel F1) efter oprettelse af klyngeindeks

Page 43: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

42

4.6.5 Måling og tuning af øvrige forespørgsler

Vi gentager nu fremgangsmåden for de øvrige seks BQuery

forespørgsler. For hver BQuery forespørgsel måler vi performance

på udførsel af de af API’et genererede SQL forespørgsler. Dernæst

oprettes eventuelle indeks, som foreslås af databasen. Efterfølgende

måles performance igen.

Det bemærkes, at fremgangsmåden med at basere os på databasens

forslag til indeks er ikke optimal. Databasen fremkommer med sine

forslag alene på baggrund af den specifikke SQL forespørgsel vi

udfører. Der er således en risiko for, at vores syv reference

forespørgsler ikke er reelt repræsentative for de faktiske

brugsmønstre og at vi derfor overser et eller flere indeks, som burde

blive oprettet. Et andet potentielt problem er, at databasen ikke

forholder sig til hvor ofte forespørgsler udføres. Vi risikerer derfor,

at den foreslår et omkostningsfuldt indeks, som forbedrer en

forespørgsel, der eksempelvis reelt kun udføres en gang om

måneden. I definitionen af vores referenceforespørgsler (se afsnit

2.2) har vi imidlertid ikke forholdt os til hvor ofte de udføres, så vi

kan med rimelighed antage, at de alle udføres lige ofte.

BQuery forespørgslerne er listet i Figur 20. I Figur 21 er en oversigt

over alle BQuery forespørgslernes tilsvarende SQL forespørgsler.

SQL forespørgslerne er udført og målt i rækkefølge. De enkelte SQL forespørgsler drager derfor i visse tilfælde fordel af indeks oprettet på baggrund af analyse af tidligere forespørgsler.

Alle forespørgslernes CPU forbrug og antal læsninger, hhv. før og

efter oprettelse af indeks, er listet i Figur 29.

De oprettede indeks er listet i Figur 30.

Forespørgsel CPU (før) CPU (efter) Læsninger (før) Læsninger (efter)

F1 1125 31 28299 31

F2 594 125 14232 42

F3 79 79 54 54

F4 343 172 14309 121

F5 47 47 92 49

F6 406 47 14603 49

F7 671 32 (*) 6461 60 (*)

Figur 29 – Forespørgslernes CPU forbrug (i millisekunder) og antal læsninger hhv. før og efter oprettelse af indeks

(*) Forbedring skyldes ikke oprettelse af indeks – men ændring af

forespørgsel.

Indeks Foranlediget af Indeks DDL

Page 44: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

43

forespørgsel

I1 F1 CREATE NONCLUSTERED INDEX

IX_AttributeValue_Reference

ON AttributeValue (Reference,

AttributeType)

I2 F1 CREATE CLUSTERED INDEX

IX_AttributeValue_ObjectId_Clustered

ON AttributeValue (ObjectId)

I3 F2 CREATE NONCLUSTERED INDEX

IX_AttributeValue_String

ON AttributeValue (String, AttributeType)

INCLUDE (ObjectId)

I4 F4 CREATE NONCLUSTERED INDEX

IX_AttributeValue_AttributeType_DateTime

ON AttributeValue (AttributeType, DateTime)

INCLUDE (ObjectId)

I5 F5 CREATE NONCLUSTERED INDEX

IX_AttributeValue_Boolean_AttributeType

ON AttributeValue (Boolean, AttributeType)

INCLUDE (ObjectId)

I6 F6 CREATE NONCLUSTERED INDEX

IX_AttributeValue_AttributeType_Integer

ON AttributeValue (AttributeType, Integer)

INCLUDE (ObjectId)

Figur 30 – Oversigt over oprettede indeks

Ad forespørgsel F4)

Forespørgsels API’et danner generelt en ”where exists” for hvert

udtryk, der indgår i en BQuery forespørgsel. Forespørgsel F4

indeholder imidlertid to udtryk, som begge er for den samme

attribut. Det er derfor overflødigt at danne to ”where exists” da

følgende nuværende SQL:

and (exists (select 1 from AttributeValue av2 where o.Id =

av2.ObjectId and av2.AttributeType = @AttribName0 and

av2.DateTime >= @AttribValue0)

and exists (select 1 from AttributeValue av2 where o.Id =

av2.ObjectId and av2.AttributeType = @AttribName1 and

av2.DateTime < @AttribValue1))

.. i stedet kan udtrykkes mere effektivt med:

… and (exists (select 1 from AttributeValue av2 where o.Id =

av2.ObjectId and av2.AttributeType = @AttribName0 and

av2.DateTime >= @AttribValue0 and av2.DateTime < @AttribValue1)

På grund af dette planlægger vi at forbedre API’et.

Ad forespørgsel F7)

Page 45: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

44

Forespørgsel F7 er indledningsvis dyr (CPU 671 og 6461 læsninger),

hvilket ikke er overraskende, da der søges med både højre- og

venstre-wildcard (… and AttriuteValue.String like ‘%Dept%’).

Venstre-wildcard’et burde resultere i et indeks scan, hvilket

forespørgselsplanen imidlertid ikke afslører (se Figur 31). Denne

melder at der foretages en indekssøgning, men detaljerne for den

(se Figur 32) afslører at der er tale om en ”range scan”, hvilket giver

god mening. Imidlertid er det overraskende, at omkostningen

overvejende ligger i sortering af data (43%) og ikke i ”range scan”

operationen (13%).

Forsøgsvis udfører vi forespørgslen uden venstre-wildcard’et (… and

AttriuteValue.String like ‘Dept%’). Dette resulterer i en væsentlig

billigere forespørgsel (CPU 32 og 60 læsninger). Til gengæld ser

udførelsesplanen ud som før (se Figur 33) hvilket kan undre.

På baggrund af den væsentligt forbedrede performance, beslutter

vi, at kravene til systemet skal ændres, således at contains

operatoren i BQuery sproget i praksis fortolkes som ”starter med” i

stedet for ”indeholder”.

Vi overvejer om vi i stedet for contains kan anvende et fuldtekst

indeks. Vi er imidlertid ikke interesserede i den forsinkelse, der er på

vedligeholdelsen af et sådant indeks. Desuden ønsker vi eksakte

sammenligner, der tager hensyn til store og små bogstaver m.v. På

grund af dette fravælges anvendelsen af et fuldtekst indeks.

Figur 31 - Udførelsesplan (forespørgsel F7)

Page 46: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

45

Figur 32 - Detaljer for indekssøgning (forespørgsel F7)

Figur 33 - Udførelsesplan (forespørgsel F7) når venstre-wildcard udelades

Page 47: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

46

4.6.6 Måling af oprettelse, opdatering og sletning

Vi måler nu oprettelse, opdatering og sletning af objekter. For hver

operation måler vi performance på udførsel af de af API’et

genererede SQL operationer.

I Figur 34 er en oversigt over DML operationer for oprettelse,

opdatering og sletning af objekter. Operationerne er forholdsvis

trivielle.

Nr. DML operation

Oprettelse exec sp_executesql N'insert into Object (Id, ObjectType)

values (@newId, @objectType)',N'@newId

uniqueidentifier,@objectType

nvarchar(6)',@newId='E8FCFA6D-0FD5-4132-8E3B-

C12FDC7E9297',@objectType=N'Person'

Tilsvarende udføres én gang for hver attributværdi:

exec sp_executesql N'insert into AttributeValue (ObjectId,

AttributeType, String, Integer, DateTime, Boolean,

Reference)

values (@ObjectId, @AttributeType, @String, @Integer,

@DateTime, @Boolean, @Reference)'

,N'@ObjectId uniqueidentifier,@AttributeType

nvarchar(8),@String nvarchar(4000),@Integer

nvarchar(4000),@DateTime nvarchar(4000),@Boolean

nvarchar(4000)

,@Reference uniqueidentifier',@ObjectId='E8FCFA6D-0FD5-

4132-8E3B-

C12FDC7E9297',@AttributeType=N'ObjectId',@String=NULL,@Int

eger=NULL,@DateTime=NULL,@Boolean=NULL,@Reference='E8FCFA6

D-0FD5-4132-8E3B-C12FDC7E9297'

Opdatering exec sp_executesql N'select ObjectType from Object where

Id = @ObjectId',N'@ObjectId

uniqueidentifier',@ObjectId='1B7F3503-819A-4D13-9298-

0004C392FF10'

exec sp_executesql N'update AttributeValue set String =

@AttributeValue where ObjectId = @ObjectId and

AttributeType = @AttributeType',N'@AttributeValue

nvarchar(32),@ObjectId uniqueidentifier,@AttributeType

varchar(11)',@AttributeValue=N'Assigned on: 28-05-2012

13:19:04',@ObjectId='1B7F3503-819A-4D13-9298-

0004C392FF10',@AttributeType='DisplayName'

Sletning exec sp_executesql N'select ObjectType from Object where

Id = @ObjectId',N'@ObjectId

uniqueidentifier',@ObjectId='B45A3606-04F3-4789-AC03-

00038AFED44A'

exec sp_executesql N'select System from Object where Id =

@ObjectId',N'@ObjectId

uniqueidentifier',@ObjectId='B45A3606-04F3-4789-AC03-

00038AFED44A'

exec sp_executesql N'delete from Object where Id =

@Id',N'@Id uniqueidentifier',@Id='B45A3606-04F3-4789-AC03-

00038AFED44A'

Figur 34 – DML operationer dannet på baggrund af API’et

Page 48: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

47

DML operation

CPU (MS) Læsninger Skrivninger Varighed (MS)

Oprettelse 0 404 71 670

Opdatering 0 35 1 96

Sletning 15 419 21 865

Figur 35 – DML operationernes CPU forbrug (i millisekunder), antal læsninger, skrivninger og varighed (i millisekunder)

Oprettelse af objekter

Oprettelsen af et objekt medfører én indsættelse i Object tabellen

samt én indsættelse i AttributeValue tabellen for hver attributværdi

som objektet har (se Figur 36). For dataene anvendt i vores måling

er der ca. 15 attributværdier for hvert objekt. Oprettelse af et objekt

resulterer således i 16 indsættelser. Dette, sammenholdt med, at

der er oprettet i alt seks indeks for AttributeValue tabellen, er

årsagen til, at den arkitektoniske målsætning (se afsnit 2.2) om

maks. 0,3 sekunder per oprettelse ikke er opfyldt.

Vi har tidligere identificeret, at det er unødigt at indsætte rækker i

AttributeValue tabellen for ObjectId og ObjectType attributterne, da

disse oplysninger også er repræsenteret på anden vis i databasen.

Dette vil bringe antallet af indsættelser i AttributeValue tabellen lidt

ned, men ikke tilstrækkeligt til at nå det arkitektoniske mål. Vi

overvejer derfor en alternativ implementering, som gennemgås

senere i afsnit 0.

Figur 36 – Operationer udført i forbindelse med oprettelse af et objekt

Page 49: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

48

Opdatering af objekter

API’ets metode til opdatering af et objekt muliggør at én attribut på

ét objekt kan gives en ny værdi. Udførslen af en opdatering

resulterer i to SQL operationer, som vist i Figur 34.

Den første operation er et opslag på Object tabellens primærnøgle

og resulterer i en Clustered Index Seek. Det er muligt at forbedre

dette en smule med et dedikeret indeks på ObjectId og ObjectType,

men vi vurderer at fordelen er for lille i forhold til den plads- og

tidsmæssige omkostning ved indekset.

Den anden operation er selve opdateringen i AttributeValue

tabellen. Hovedparten af tiden (86%) der medgår til denne er

opdateringen af klyngeindekset. Det er svært at forbedre dette

synderligt, med mindre det fravælges at anvende et klyngeindeks.

Sammenlagt udføres de to operationer på 96 millisekunder, hvilket

er under det opstillede arkitektoniske krav op 0,2 sekunder.

Udførelsesplanen kan findes i rapportens bilag 1 i filen

object_update.sqlplan.

Sletning af objekter

Udførslen af en sletning resulterer i tre SQL operationer, som vist i

Figur 34.

Den to første operationer er opslag på Object tabellens primærnøgle

og resulterer i en Clustered Index Seek. Som nævnt tidligere, er det

muligt at forbedre dette en smule med et dedikeret indeks på

ObjectId og ObjectType, men vi vurderer stadig at fordelen er for lille

i forhold til den plads- og tidsmæssige omkostning ved indekset.

De to opslag kan dog med fordel kombineres i ét, med en lille

besparelse til følge, hvilket vi planlægger at gøre.

For selve sletningens vedkommende (i Object tabellen) medgår

hovedparten af tiden (75%) med en Clustered Index Delete.

I Figur 37 er operationerne vist i SQL Server Profiler, hvor af det

fremgår, at den tredje operation (sletningen) er klart den dyreste.

Udførelsesplanen kan findes i rapportens bilag 1 i filen

object_delete.sqlplan.

Page 50: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

49

Figur 37 - Operationer udført i forbindelse med sletning af et objekt

I alt tager sletningen i Object tabellen 838 millisekunder. Sletningen

medfører kaskade sletninger i AttributeValue tabellen, hvilket vi

antager udgør en stor del af omkostningen.

Forsøgsvis udfører vi en sletning igen. Denne gang sletter vi først

rækker i AttributeValue tabellen, før vi sletter i Object tabellen:

exec sp_executesql N'delete from AttributeValue where ObjectId

= @Id',N'@Id uniqueidentifier',@Id='6B04823B-F0E8-47A3-869F-

000F9CF9D586'

exec sp_executesql N'delete from Object where Id = @Id',N'@Id

uniqueidentifier',@Id='6B04823B-F0E8-47A3-869F-000F9CF9D586'

Et nyt kig i SQL Server Profiler (Figur 38) viser at selve sletningen fra

Object nu udføres væsentlig hurtigere (29 millisekunder). Til

gengæld tager det 604 millisekunder at udføre sletningen i

AttributeValue tabellen. Sammenlagt viser forsøget, at det er noget

mere effektivt at udføre sletningerne i AttributeValue tabellen

manuelt (varighed på 633 MS mod 865 MS). Dette skyldes

formentlig, at kaskade-sletningen bevirker at AttributeValue

rækkerne slettes én for én, i modsætning til den nye

fremgangsmåde hvor de slettes i én omgang.

Page 51: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

50

Figur 38 - Operationer udført i forbindelse med sletning af et objekt. Nu med individuel sletning fra AttributeValue tabellen.

4.6.7 Diskussion efter indledende analyse

Vi bemærker, at hovedparten af forespørgslerne (efter

forbedringerne) har i omegnen af 50 læsninger (se Figur 29). Dette

vurderes som værende tilfredsstillende, datamængderne taget i

betragtning, til at opfylde de arkitektoniske krav opstillet i afsnit 2.2.

Analysen viste, at anvendelsen af venstre-wildcard, i forb. med

BQuery contains operatoren, medfører for dyre forespørgsler. Vi

beslutter os derfor til, at ændre de arkitektoniske krav, således at

contains operatoren i BQuery sproget fremadrettet skal fortolkes

som ”begynder med” i stedet for ”indeholder”. Man vil dermed kun

kunne fremsøge et objekt med contains operatoren, hvis man

kender den første del af en attributværdi. Dette vil medføre en

forringelse af brugernes udfoldelsesmuligheder, men vi betragter

det som et acceptabelt trade-off i forhold til den forbedrede

performance.

Vi har ved inspektion af de genererede SQL forespørgsler

konstateret, at det i visse tilfælde er unødigt at sortere resultatet.

Dette gælder når vi på forhånd kan udlede, at en BQuery maksimalt

kan resultere i ét objekt. Effekten af ændringen er givet vis ikke

specielt stor, da antallet af rækker der sorteres er ret lille. Imidlertid

planlægger vi at indføre det i API’et.

Vi har konstateret, at det store antal indsættelser, der foretages i

AttributeValue tabellen, i forbindelse med oprettelse af et objekt,

gør det svært at indfri de arkitektoniske krav. Vi planlægger derfor

at ændre designet, hvilket gennemgås i afsnit 0.

Page 52: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

51

I relation til dette, har vi bemærket, at filtreringen af attributterne

”ObjectId” og ”ObjectType”, som foretages i alle forespørgslerne, er

forholds dyr. Filtreringen foretages med ”… and av.AttributeType

not in ('ObjectId', 'ObjectType')”. Vi vil derfor i samme

ombæring undersøge, om vi helt kan undgå at foretage filtreringen.

Vi har i relation til forespørgsel F4 erfaret, at forespørgsels API’et i

visse sammenhænge med fordel kan kombinere to eller flere

BQuery udtryk i den samme ”where exists”.

I forbindelse med sletning har vi konstateret, at det bedre kan

betale sig, at slette rækkerne i AttributeValue tabellen manuelt,

frem for at lade databasen gøre det som led i en kaskade sletning.

Generelt vil de faktiske udførselstider for SQL forespørgslerne være

bedre end de beskrevne. Dette skyldes at, at alle forsøg indledes

med at nulstille databasens cache. I praksis vil cachen imidlertid tage

effekt med nedsatte udførselstider til følge.

Page 53: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

52

4.7 Alternativt XML baseret design af

databasemodel Vi har tidligere konstateret, at det store antal rækker i

AttributeValue tabellen gør det svært at indfri de arkitektoniske

krav vedr. performance.

Som konsekvens af dette forsøger vi os nu med et alternativt design,

der involverer brugen af XML data i databasen. Anvendelsen af XML

data er ikke behandlet i [EAV2007]. Vi finder imidlertid

fremgangsmåden interessant, da den muliggør, at vi kan udlæse alle

data om et objekt fra én enkelt tabel. Samtid bibeholder vi, takket

være SQL Schema, et stærkt type begreb.

Det alternative design indebærer en ny kolonne af datatypen xml i

Object tabellen. Designet er lidt anderledes end det tidligere

introducerede (se afsnit 0), der placerede xml kolonnen i

AttributeValue tabellen. Vi vurderer, at vi, ved at placere kolonnen i

Object tabellen, kan opnå en bedre performance, da vi derved

potentielt helt kan undgå at joine AttributeValue tabellen.

XML format og database ændringer

Vi definerer et XML format til repræsentation af objektdata. Med

dette ser et objekt ud som vist i Figur 39.

<object>

<attributes>

<attribute name="ObjectType">

<value s_value="Person" />

</attribute>

<attribute name="ObjectId">

<value r_value="8B604CA5-2E48-458F-9ED5-4D9085A33251" />

</attribute>

<attribute name="CreatedTime">

<value dt_value="2012-06-10T18:56:12.4430158Z" />

</attribute>

<attribute name="DisplayName">

<value s_value="Ah lam Cai" />

</attribute>

<attribute name="PersonID">

<value s_value="ALCN_2378" />

</attribute>

<attribute name="FirstName">

<value s_value="Ah lam" />

</attribute>

<attribute name="LastName">

<value s_value="Cai" />

Page 54: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

53

</attribute>

<attribute name="Initials">

<value s_value="ALCN_2378" />

</attribute>

<attribute name="JobTitle">

<value s_value="HR Clerk" />

</attribute>

<attribute name="Department">

<value s_value="CN_Shanghai_8_Administration" />

</attribute>

<attribute name="YearEmployed">

<value i_value="2010" />

</attribute>

<attribute name="IsManager">

<value b_value="false" />

</attribute>

<attribute name="ChangedTime">

<value dt_value="2012-06-10T18:56:12.4430158Z" />

</attribute>

</attributes>

</object>

Figur 39 – Et objekts data repræsenteret som XML

Vi definerer nu et XML Schema og opretter det i databasen, som vist

i Figur 40.

CREATE XML SCHEMA COLLECTION ObjectDataSchema AS

'<?xml version="1.0" encoding="UTF-8"?>

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="value">

<xs:complexType>

<xs:attribute name="b_value" type="xs:boolean" use="optional"/>

<xs:attribute name="dt_value" type="xs:dateTime" use="optional"/>

<xs:attribute name="i_value" type="xs:integer" use="optional"/>

<xs:attribute name="s_value" type="xs:string" use="optional"/>

<xs:attribute name="r_value" type="xs:string" use="optional"/>

</xs:complexType>

</xs:element>

<xs:element name="object">

<xs:complexType>

<xs:sequence>

<xs:element ref="attributes"/>

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element name="attributes">

<xs:complexType>

Page 55: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

54

<xs:sequence>

<xs:element ref="attribute" maxOccurs="unbounded"/>

</xs:sequence>

</xs:complexType>

</xs:element>

<xs:element name="attribute">

<xs:complexType>

<xs:sequence>

<xs:element ref="value"/>

</xs:sequence>

<xs:attribute name="name" type="xs:string" use="required"/>

<xs:attribute name="dataType" type="dataType" use="optional"/>

</xs:complexType>

</xs:element>

<xs:simpleType name="dataType">

<xs:restriction base="xs:string">

<xs:enumeration value="Boolean"/>

<xs:enumeration value="DateTime"/>

<xs:enumeration value="Integer"/>

<xs:enumeration value="String"/>

<xs:enumeration value="Reference"/>

</xs:restriction>

</xs:simpleType>

</xs:schema>'

Figur 40 – Oprettelse af XML Schema i databasen

Vi tilføjer nu en ny kolonne til Object tabellen med datatype xml,

som anvender det nyoprettede XML Schema:

ALTER TABLE dbo.Object ADD Data xml (CONTENT

dbo.ObjectDataSchema) NULL

I Data kolonnen gemmes XML repræsentation af et objekts

attributter. Kolonnen skal således udfyldes når et objekt oprettes og

opdateres når dets attributter opdateres.

Ved hjælp et særligt opdateringsprogram (indeholdt i bilag 2),

opdaterer vi nu samtlige 100.000 objekter i databasen, så de får en

XML repræsentation i Object tabellen. Dernæst ændres den nye Data

kolonne til ikke længere at acceptere NULL værdier.

Hvad med AttriuteValue tabellen?

Den nye Object.Data kolonne bevirker, at vi i princippet ikke længere

behøver AttributeValue tabellen, da dens oplysninger nu er

redundante. Vi vælger dog at bevare den, da vi ikke kan søge lige så

hurtigt i XML data, som vi kan i konventionelle kolonner, hvilket

Page 56: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

55

skyldes, at XML data ikke kan indekseres lige så effektivt. Bevarelsen

af tabellen betyder imidlertid, at vi accepterer dataredundans,

hvilket bevirker et større pladsforbrug samt en risiko for

inkonsistens.

”Indekserede” attributter

I praksis er det imidlertid ikke lige interessant for os at søge i alle

attributter. Vi udstyrer derfor AttributeType objekter med en ny

attribut kaldet ”Indexed”, der indikerer, om værdierne for en

attribut indekseres i databasen (dvs. om der oprettes rækker i

AttributeValue tabellen for den). Såfremt det er angivet på en

attribut, at den skal indekseres, opretter vi rækker i AttributeValue

tabellen for den. Vi angiver nu på en række attributter, at de skal

indekseres. Dernæst sletter vi alle rækker i AttributeValue tabellen

for de attributter der ikke er indekserede, med det resultat, at

antallet af rækker i tabellen omtrentligt halveres. Vi forventer på

baggrund af dette, at opnå en bedre ”create” performance

(simpelthen fordi der nu skal udføres færre indsættelser i

AttributeValue tabellen), hvilket de følgende forsøg vil afdække om

er rigtigt.

Vi ændrer nu API’et til at anvende de nye xml data. API’et ændres til

at understøtte både forespørgsler mod indekserede og ikke-

indekserede attributter. For sidstnævntes vedkommende, ledes der i

XML dataene i Object tabellen.

Fordele ved nyt design

Det nye design har andre fordele for os end hurtigere oprettelse af

objekter:

1. I SQL forespørgslerne kan vi nu undlade at joine

AttributeValue tabellen.

2. Ligeledes kan vi i forespørgslerne undgå at sortere på Object.Id

3. Designet muliggør desuden nem og billig pivotering

Ad 1) Ved forespørgsler kan vi undlade at joine AttributeValue

tabellen, hvilket burde bevirke hurtigere performance. Endvidere

muliggør det, at der kan laves en ”select top” på Object tabellen,

hvilket kan være interessant, hvis man ønsker de første x objekter

der opfylder en given forespørgsel. Årsagen til at vi ikke kan lave

”select top” for nuværende er, at de forskellige objekter har et

varierende antal attributværdier. Vi risikerer derfor at ”klippe”

relevante rækker af ved anvendelsen af ”select top”.

Ad 2) For nuværende sorteres der på Object.Id i SQL

forespørgslerne. Dette skyldes, at vi ønsker at gruppere

Page 57: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

56

AttributeValue rækkerne på det objekt de hører til, for at gøre

indlæsningen til objekter nemmere. Med anvendelsen af xml data

på Object tabellen behøver vi ikke længere at joine AttributeValue

tabellen. Dermed er et objekt altid repræsenteret af netop én række

og dermed er sortering unødvendig. Besparelsen ved ikke at sortere

på Object.Id er imidlertid begrænset, da tabellen har et

klyngeindeks på kolonnen.

Ad 3)

Det nye design bevirker, at det nu er nemt at pivotere data. Vi

opretter et view i databasen, som pivoterer

”CreatedTime”,”ChangedTime” og ”DisplayName” attributterne:

CREATE VIEW viewObject

AS

SELECT Id, ObjectType,

Data.value('(/object/attributes/attribute[@name=''CreatedTime'']

/value/@dt_value)[1]', 'datetime') AS CreatedTime,

Data.value('(/object/attributes/attribute[@name=''ChangedTime'']

/value/@dt_value)[1]', 'datetime') AS ChangedTime,

Data.value('(/object/attributes/attribute[@name=''DisplayName'']

/value/@s_value)[1]', 'nvarchar(100)') AS DisplayName, Data

FROM Object

Figur 41 – Oprettelse af pivoterings-view

Med det nye view kan vi selektere attributterne som konventionelle

kolonner:

select top 10 Id, ObjectType, CreatedTime, ChangedTime,

DisplayName

from viewObject

Resultatet er:

Figur 42 – Selektion fra viewObject

Ud over anvendelsen af XML data, foretages desuden en række

generelle forbedringer af design og implementering, svarende til

punkterne beskrevet i 4.6.7.

Pladsforbrug

Page 58: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

57

På nuværende tidspunkt optager databasen samlet set godt 500

MB. Udviklingen i pladsforbrug kan ses nedenfor.

Hvornår? Pladsforbrug (MB)

Initielt 0

Efter import af 100.000 objekter 118

Efter oprettelse af indeks på AttriuteValue tabellen 435

Efter oprettelse af Object.Data XML kolonne 507

4.8 Måling og tuning af ændrede forespørgsler Da vi nu har foretaget en række større ændringer i design og API,

herunder at vi nu anvender XML data i databasen, foretager vi en

række nye målinger.

4.8.1 Ændrede SQL forespørgsler

Vi udfører de syv BQuery forespørgsler igen. Som følge af

introduktionen af XML data, samt generelle forbedringer af API’et,

er de resulterende SQL forespørgsler anderledes i forhold til

tidligere.

De nye SQL forespørgsler kan ses i Figur 43.

Nr. SQL forespørgsler dannet på baggrund af BQuery forespørgsler Antal returnerede rækker

F1 exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

and (o.Id = @AttribValue0)',

N'@ObjectType varchar(6),@AttribValue0

uniqueidentifier',@ObjectType='Person',@Attrib

Value0='A8EAE616-C7CB-4404-837F-000559632B11'

1

F2 exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.String = @AttribValue0)

and exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName1 and

av2.String = @AttribValue1))',

N'@ObjectType varchar(6),@AttribName0

varchar(9),@AttribValue0

nvarchar(4),@AttribName1

varchar(8),@AttribValue1

nvarchar(3)',@ObjectType='Person',@AttribName0

='FirstName',@AttribValue0=N'Song',@AttribName

1='LastName',@AttribValue1=N'Lan'

1

F3 exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

3

Page 59: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

58

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.String = @AttribValue0)

or exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName1 and

av2.String = @AttribValue1))',

N'@ObjectType varchar(6),@AttribName0

varchar(8),@AttribValue0

nvarchar(4),@AttribName1

varchar(8),@AttribValue1

nvarchar(4)',@ObjectType='Person',@AttribName0

='LastName',@AttribValue0=N'Tian',@AttribName1

='LastName',@AttribValue1=N'Wing'

F4 exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0

and av2.DateTime >= @AttribValue0 and

av2.DateTime < @AttribValue1))'

,N'@ObjectType varchar(6),@AttribName0

varchar(11),@AttribValue0

datetime,@AttribValue1

datetime',@ObjectType='Person',@AttribName0='C

reatedTime',@AttribValue0='2012-04-17

00:00:00',@AttribValue1='2012-04-18 00:00:00'

10

F5 exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.Boolean = @AttribValue0))',

N'@ObjectType varchar(6),@AttribName0

varchar(9),@AttribValue0

bit',@ObjectType='Person',@AttribName0='IsSpec

ial',@AttribValue0=1

4

F6 exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.Integer < @AttribValue0))',

N'@ObjectType varchar(6),@AttribName0

varchar(12),@AttribValue0

int',@ObjectType='Person',@AttribName0='YearEm

ployed',@AttribValue0=2009

4

F7 exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

and (exists (select 1 from AttributeValue av2

where o.Id = av2.ObjectId and

av2.AttributeType = @AttribName0 and

av2.String like @AttribValue0))',

N'@ObjectType varchar(6),@AttribName0

varchar(8),@AttribValue0

4

Page 60: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

59

nvarchar(5)',@ObjectType='Person',@AttribName0

='JobTitle',@AttribValue0=N'Dept%'

Figur 43 – Nye SQL forespørgsler dannet på baggrund af BQuery forespørgsler

Alle de nye forespørgslers CPU forbrug og antal læsninger er listet i

Figur 44 sammen med de tilsvarende tal for det oprindelige design.

CPU forbruget er generelt forbedret med mere end 50%. Antallet af

læsninger er også forbedret (reduceret) men med mindre

overbevisende tal (ca. 25% forbedring). Forespørgsel F1 og F4 er

forbedret mest markant, men det skyldes i højere grad at selve SQL

forespørgslerne er ændrede, end det skyldes det nye XML koncept.

Forespørgsel CPU (opr. design)

CPU (XML design)

Læsninger (opr. design)

Læsninger (XML design)

F1 31 0 (100%) 31 7 (77%)

F2 125 46 (63%) 42 41 (2%)

F3 79 31 (61%) 54 47 (13%)

F4 172 14 (92%) 121 60 (50%)

F5 47 0 (100%) 49 37 (24%)

F6 47 16 (66%) 49 35 (29%)

F7 32 15 (53%) 60 46 (23%)

Figur 44 – Oprindelige og nye forespørgslers CPU forbrug (i millisekunder) og antal læsninger. Procentsatserne i parentes er forbedringen i forhold i det oprindelige design.

Analyse af indeks

Vi undersøger nu anvendelsen af de tidligere oprettede indeks på

AttributeValue tabellen (se Figur 30). De fleste af indeksene (I3, I4,

I5 og I6) anvendes stadig i udførelsesplanerne. Dette er ikke så

overraskende, da selektionskriterierne i forespørgslerne overordnet

set er de samme.

Indeks I1 anvendes ikke mere, men det skyldes at forespørgsel F1,

som tidligere anvendte det og som leder efter et objekt med et

specifikt ID, ikke længere resulterer i en ”where exists” på

AttributeValue tabellen. I stedet slås der nu direkte op på

primærnøglen Object.Id. Udførelsesplanen kan ses i Figur 45.

Figur 45 - Udførelsesplan (forespørgsel F1) – XML udgave

Page 61: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

60

Vi vælger dog at beholde indeks I1 af hensyn til andre BQuery

forespørgsler, der slår op på andre attributter med datatype

”Reference” end ObjectId, selv om en sådan ikke for nuværende er

blandt vores eksempel forespørgsler.

Indeks I2, der er et klyngeindeks (se Figur 30), har vi imidlertid ikke

længere behov for til forespørgslerne. Indekset fandt anvendelse

før, hvor alle kolonnerne i AttributeValue tabellen var medtaget i

SQL forespørgslerne. De er de imidlertid ikke længere pga.

anvendelsen af XML data. Vi har dog stadig brug for et indeks på

ObjectId og AttributeType til opdateringer og sletninger af objekter,

så vi nedlægger klyngeindekset og opretter i stedet et nyt mere

velegnet indeks:

DROP INDEX IX_AttributeValue_ObjectId_Clustered ON

AttributeValue

CREATE NONCLUSTERED INDEX

IX_AttributeValue_ObjectIdAndAttributeType

ON AttributeValue (ObjectId, AttributeType)

De nye forespørgsler medfører ikke umiddelbart et behov for nye

indeks og databasen foreslår ikke selv nogen ved udførsel af

forespørgslerne.

Sammenligning af udførselsplaner

Den primære forskel på udførelsesplanerne (samlet betragtet)

sammenlignet med tidligere er, at der ikke længere udføres en

Clustered Index Seek i indeks I2

(IX_AttributeValue_ObjectId_Clustered). Dette skyldes at vi ikke

længere selekterer kolonner fra AttributeValue tabellen og at

forespørgslerne ikke længere indeholder ”and av.AttributeType not

in ('ObjectId', 'ObjectType')”. Dette kan ses i Figur 46 og Figur 47

som viser hhv. den nye og oprindelige udførelsesplan for

forespørgsel F2.

Figur 46 – Ny udførelsesplan for forespørgsel F2

Page 62: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

61

Figur 47 – Oprindelig udførelsesplan for forespørgsel F2. Den indrammede operation indgår ikke i den nye udførelsesplan.

Page 63: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

62

4.8.2 Forsøg med ikke-indekserede attributter

Ind til nu har vi kun udført forespørgsler, der anvender indekserede

attributter. Vi vil nu forsøgsvis prøve med en forespørgsel, der

anvender en ikke indekseret attribut, hvilket bevirker at API’et

genererer en SQL forespørgsel der slår op i Object tabellens XML

data.

Oprettelse af XML indeks

Før vi udfører forespørgslen, opretter vi et primært XML indeks på

den nye kolonne:

CREATE PRIMARY XML INDEX XML_IX_Object ON dbo.Object(Data)

Dernæst opretter vi tre sekundære XML indeks af typerne Property,

Value og Path [XmlIndexes2012]:

CREATE XML INDEX XML_IX_Object_Property ON dbo.Object(Data)

USING XML INDEX XML_IX_Object FOR PROPERTY

CREATE XML INDEX XML_IX_Object_Value ON dbo.Object(Data)

USING XML INDEX XML_IX_Object FOR VALUE

CREATE XML INDEX XML_IX_Object_Path ON dbo.Object(Data)

USING XML INDEX XML_IX_Object FOR PATH

Vi overvåger databasens pladsforbrug efter oprettelsen af hvert

indeks og bemærker at de nye indeks er temmelig pladskrævende,

som det fremgår af Figur 48.

Indeks Pladsforbrug (MB)

XML_IX_Object 400

XML_IX_Object_Property 700

XML_IX_Object_Value 300

XML_IX_Object_Path 300

Total 1700

Figur 48 – XML indeksenes pladsforbrug

Forespørgsel på ikke-indekseret attribut

Vi udfører nu følgende BQuery forespørgsel:

/Person[Initials='ALCN_235']

Page 64: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

63

Attributten ”Initials”, som der filtreres på, er ikke indekseret. API’et

genererer derfor SQL forespørgslen vist i Figur 49, som anvender

exist operationen på XML kolonnen.

exec sp_executesql N'select o.Id, o.Data

from Object o

where o.ObjectType = @ObjectType

and (o.Data.exist(''/object/attributes/attribute[@name =

sql:variable("@AttribName0")]/value[@s_value =

sql:variable("@AttribValue0")]'') <> 0)'

,N'@ObjectType varchar(6),@AttribName0 varchar(8),@AttribValue0

nvarchar(8)',@ObjectType='Person',@AttribName0='Initials',@Attri

bValue0=N'ALCN_235'

Figur 49 – SQL forespørgsel for BQuery med ikke-indekseret attribut

SQL forespørgslen resulterer i udførelsesplanen vist i Figur 50. Vha.

SQL Server Profiler (se Figur 51) kan vi konstatere, at CPU forbruget

er 218 og at der udføres 185 læsninger. Disse tal er ringere end for

de øvrige forespørgsler, hvilket er i overensstemmelse med

forventningerne.

Udførelsesplanen kan findes i rapportens bilag 1 i filen xml_query on

unindexed attribute.sqlplan.

Figur 50 – XML forespørgsel – SQL Server Profiler

Page 65: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

64

Figur 51 - Udførelsesplan for XML forespørgsel

Vi kan af udførelsesplanen se, at Path og Property indeksene

anvendes, mens Value indekset ikke anvendes. Det sidste giver god

mening, da vi angiver en fuld XPath sti til de elementer vi er

interesserede i [XmlIndexes2012].

Alternativt XML format

På baggrund af udførelsesplanen får vi den indskydelse, at vi

muligvis kunne opnå en bedre performance med et alternativt XML

format; I det nuværende XML format repræsenteres alle objekter af

<object> elementer og alle attributter af <attribute> elementer.

Dette bevirker at XPath udtrykket ser sådan ud:

… o.Data.exist(''/object/attributes/attribute[@name =

sql:variable("@AttribName0")]/value[@s_value =

sql:variable("@AttribValue0")]'') <> 0

Hvis vi i stedet anvendte et andet XML format, hvor alle objekttyper

og attributter repræsenteres af individuelle XML elementer, kunne

XPath ydtrykket i stedet udtrykkes noget i stil med:

… o.Data.exist(''/person/attributes/initials/value[@s_value =

sql:variable("@AttribValue0")]'') <> 0

Denne fremgangsmåde vil give hurtigere søgninger, da XML

indekseringen vil være mere effektiv. Dette skyldes, at databasen

allerede på den første node-test i udtrykket fra frasortere alle

objekter, der ikke er af typen ”Person”. Fremgangsmåden vil

imidlertid forhindre os i, at anvende et XML Schema i Object.Data

kolonnen. Dette skyldes, at præmissen for systemet er, at

objekttyperne og attributterne ikke er kendte på forhånd, hvorfor

de ikke kan opremses i et XML Schema. På grund af denne

begrænsning, fravælger vi at forfølge fremgangsmåden yderligere.

Page 66: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

65

4.8.3 Nye målinger af oprettelse, opdatering og

sletning

Vi måler nu igen oprettelse, opdatering og sletning af objekter. For

hver operation måler vi performance på udførsel af de SQL

forespørgsler, som API’et genererer.

I Figur 52 er en oversigt over DML operationer for oprettelse,

opdatering og sletning af objekter.

Nr. DML operation

Oprettelse exec sp_executesql N'insert into Object (Id, ObjectType,

Data) values (@newId, @objectType, @data)',N'@newId

uniqueidentifier,@objectType nvarchar(6),@data

nvarchar(737)',@newId='D8AAED8C-B216-466B-9EE2-

4CC532E96AE8',@objectType=N'Person',@data=N'<object><attri

butes><attribute name="ObjectType"><value s_value="Person"

/></attribute><attribute name="DisplayName"><value

s_value="Ah lam Cai" /></attribute><attribute

name="PersonID"><value s_value="ALCN_23568"

/></attribute><attribute name="FirstName"><value

s_value="Ah lam " /></attribute><attribute

name="LastName"><value s_value="Cai"

/></attribute><attribute name="Initials"><value

s_value="ALCN_23568" /></attribute><attribute

name="JobTitle"><value s_value="HR Clerk"

/></attribute><attribute name="Department"><value

s_value="CN_Shanghai_8_Administration"

/></attribute><attribute name="YearEmployed"><value

i_value="2010" /></attribute><attribute

name="IsManager"><value b_value="false"

/></attribute></attributes></object>'

Tilsvarende udføres én gang for hver indekseret attributværdi:

exec sp_executesql N'insert into AttributeValue (ObjectId,

AttributeType, String, Integer, DateTime, Boolean,

Reference) values (@ObjectId, @AttributeType, @String,

@Integer, @DateTime, @Boolean, @Reference)'

,N'@ObjectId uniqueidentifier,@AttributeType

nvarchar(11),@String nvarchar(4000),@Integer

nvarchar(4000),@DateTime datetime,@Boolean

nvarchar(4000),@Reference

nvarchar(4000)',@ObjectId='D8AAED8C-B216-466B-9EE2-

4CC532E96AE8',@AttributeType=N'CreatedTime',@String=NULL,@

Integer=NULL,@DateTime='2012-06-10

17:26:03.083',@Boolean=NULL,@Reference=NULL

Opdatering exec sp_executesql N'select ObjectType from Object where

Id = @ObjectId',N'@ObjectId

uniqueidentifier',@ObjectId='0960BB40-A00E-45E5-A701-

5F5303996DD5'

exec sp_executesql N'select

Data.exist(''/object/attributes/attribute[@name =

sql:variable("@AttributeName")]/value'') from Object where

Id = @ObjectId',N'@AttributeName nvarchar(11),@ObjectId

uniqueidentifier',@AttributeName=N'DisplayName',@ObjectId=

'0960BB40-A00E-45E5-A701-5F5303996DD5'

exec sp_executesql N'update Object set

Data.modify(''replace value of

(/object/attributes/attribute[@name=sql:variable("@Attribu

teType")]/value/@s_value)[1] with

sql:variable("@AttributeValue")'') where Id =

@ObjectId',N'@AttributeType nvarchar(11),@AttributeValue

nvarchar(32),@ObjectId

uniqueidentifier',@AttributeType=N'DisplayName',@Attribute

Value=N'Assigned on: 10-06-2012

18:15:37',@ObjectId='0960BB40-A00E-45E5-A701-5F5303996DD5'

exec sp_executesql N'update AttributeValue set String =

Page 67: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

66

@AttributeValue where ObjectId = @ObjectId and

AttributeType = @AttributeType',N'@AttributeValue

nvarchar(32),@ObjectId uniqueidentifier,@AttributeType

varchar(11)',@AttributeValue=N'Assigned on: 10-06-2012

18:15:37',@ObjectId='0960BB40-A00E-45E5-A701-

5F5303996DD5',@AttributeType='DisplayName'

Sletning exec sp_executesql N'select ObjectType, System from Object

where Id = @ObjectId',N'@ObjectId

uniqueidentifier',@ObjectId='6E03AB13-A11A-41C6-BC07-

5085CC503990'

exec sp_executesql N'delete from AttributeValue where

ObjectId = @Id',N'@Id uniqueidentifier',@Id='6E03AB13-

A11A-41C6-BC07-5085CC503990'

exec sp_executesql N'delete from Object where Id =

@Id',N'@Id uniqueidentifier',@Id='6E03AB13-A11A-41C6-BC07-

5085CC503990'

Figur 52 – DML operationer dannet på baggrund af API’et

I Figur 53 er en oversigt over de nye målinger. De oprindelige

målinger er anført i parentes.

DML operation

CPU (MS) Læsninger Skrivninger Varighed (MS)

Oprettelse 0 (0) 297 (404) 55 (71) 70 (670)

Opdatering 79 (0) 294 (35) 3 (1) 154 (96)

Sletning 0 (15) 285 (419) 45 (21) 560 (865)

Figur 53 – DML operationernes CPU forbrug (i millisekunder), antal læsninger, skrivninger og varighed (i millisekunder). De oprindelige målinger er anført i parentes.

Oprettelse af objekter

Til forskel fra tidligere, indsættes der nu XML data i Object tabellen,

når et objekt oprettes. Desuden indsættes der nu kun 8 rækker i

AttributeValue tabellen, mod tidligere 15. Dette skyldes dels, at der

nu aldrig indsættes rækker for ”ObjectType” og ”ObjectId”

attributterne. Derudover skyldes det introduktionen af

attributindeks-konceptet; Vi har angivet, at en række attributter ikke

skal være indekserede, da de ikke anvendes i nogen af vores BQuery

referenceforespørgsler.

Tidligere voldte oprettelses-operationen størst kvaler, da

udførelsestiden (670 MS) oversteg den arkitektoniske målsætning

på 500 MS. Efter at have udført de beskrevne ændringer, måler vi på

ny (se Figur 54Error! Reference source not found.) og udførselsiden

er nu på 70 MS, hvilket er både tilfredsstillende og overraskende

lavt.

Page 68: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

67

Figur 54 - Operationer udført i forbindelse med oprettelse af et objekt

Opdatering af objekter

Ved ændring af et objekt, skal de nye XML data i Object tabellen

opdateres ligesom AttributeValue tabellen (som tidligere) skal

opdateres. Sidstnævnte skal dog nu kun opdateres såfremt der er

tale om en indekseret attribut.

Opdateringen af XML dataene sker med to SQL kald (se Figur 52);

først undersøges om der allerede er en værdi for attributten i XML

dataene. I så fald opdateres den og hvis ikke indsættes der en værdi.

(Indsættelsen er ikke vist i Figur 52 da der i vores tilfælde var en

værdi for attributten på det pågældende objekt).

De to XML relaterede SQL kald er en ekstra omkostning i forhold til

tidligere. Samlet set er opdateringsoperationen da også blevet

dyrere (nu 154 MS mod tidligere 96 MS). Vi ligger dog stadig under

det arkitektoniske krav på 200 MS, så vi vurderer det som værende

tilfredsstillende.

Sletning af objekter

Sletning af et objekt er ændret på en række områder i forhold til

tidligere:

- Opslag af ObjectType og System er nu samlet i ét SQL kald

(tidligere var der to)

- AttributeValue rækker slettes nu ”manuelt” før der slettes i

Object tabellen. Tidligere blev AttributeValue rækkerne

slettet vha. en kaskadesletning.

Page 69: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

68

- Der er nu generelt færre rækker i AttributeValue tabellen og

derfor færre at slette.

Alle de nævnte ting medvirker til en bedre performance ved

sletning. De ændrede operationer kan ses i Figur 52.

Vi måler nu udførelsestiden for en sletning til 560 MS (mod tidligere

865 MS). Vi er stadig over det arkitektoniske mål på 500 MS, men vi

er så tæt på, at vi vurderer det til at være godt nok.

4.8.4 Diskussion efter opfølgende analyse

Vi har indført en ny kolonne på Object tabellen af datatypen xml,

som rummer en repræsentation af et objekternes attributværdier.

Ændringen bevirker, at vi nu kan udføre ca. 25% billigere

forespørgsler, primært fordi vi ikke længere joiner med

AttributeValue tabellen. Til gengæld optages der mere plads i

databasen, da attributværdierne nu opbevares redundant. Dette gør

imidlertid ikke så meget, da stigningen i pladsforbruget ikke er

voldsom. Desuden er diskplads billig i vore dage og vi vurderer

derfor, at performance vægter højere end pladsforbrug.

Omkostningen ved oprettelse og sletning af objekter er samlet set

faldet, mens omkostningen ved opdateringer samlet set er steget.

Vi har forsøgsvis udført en BQuery forespørgsel, der filtrerer på en

ikke-indekseret attribut. Dette bevirker en SQL forespørgsel, der slår

op i Object tabellens xml kolonne. På baggrund af forsøget kan vi

konstatere, at antallet af læsninger for forespørgslen er mere end (i

gennemsnit) 4 gange så højt som referenceforespørgslerne (se Figur

44). Den udførte BQuery forespørgsel har imidlertid et lavere

kompleksistetsniveau end hovedparten af vores

referenceforespørgsler.

Incitamentet til at slå op i xml kolonnen var indledningsvis, at

reducere antallet af indsættelser i AttributeValue tabellen. De

oprettede XML indeks optager imidlertid samlet ca. 1700 MB (se

Figur 48), hvilket skal ses i forhold til, at databasen før oprettelse af

indeks optog samlet set ca. 500 MB. Pladsforbruget synes meget

voldsomt og vi vurderer derfor, at det ikke står mål med gevinsten

ved de færre indsættelser. Vi vælger derfor at fjerne de oprettede

XML indeks igen og gå bort fra konceptet med ikke-indekserede

attributter og deraf følgende SQL forespørgsler med XML søgninger.

Page 70: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

69

4.9 Performancemåling Efter at API og database nu er blevet ændret, ønsker vi at måle den

dynamiske datamodels performance ved forskellige datamængder.

Specifikt ønsker vi at måle performance på de syv

referenceforespørgsler ved følgende datamængder:

- 100.000 objekter (nuværende datamængde)

- 500.000 objekter

- 1.000.000 objekter

- 1.500.000 objekter

Vi indlæser de respektive datamængder efter samme

fremgangsmåde som de første 100.000 objekter (beskrevet i afsnit

4.6.2). Dernæst udfører vi de forskellige forespørgsler og måler

ydelsen vha. SQL Server Profiler.

Antallet af læsninger, der foretages af de syv forespørgsler, ved

forskellige datamængder, kan ses i Figur 55.

Udførselstiden for de samme syv forespørgsler, ved forskellige

datamængder, kan ses i Figur 56.

Udviklingen i antal læsninger er fornuftig. Der er maksimalt tale om

godt en fordobling i springet fra 100.000 til 1.500.000 objekter,

hvilket indikerer en lineær skalering.

Udviklingen i udførelsestiden forekommer lidt pudsig, da flere af

målingerne falder(!) mod de 1.500.000 objekter. Dette kan

eventuelt skyldes, at database serveren har været belastet af andre

ting ved de tidligere målinger.

Alt i alt konkluderer vi, at systemet skalerer tilfredsstillende.

Figur 55 – Antal læsninger for de syv reference forespørgsler ved forskellige datamængder

0

20

40

60

80

100

120

140

160

180

100.000 500.000 1.000.000 1.500.000

An

tal l

æsn

inge

r

Antal objekter i databasen

F1

F2

F3

F4

F5

F6

F7

Page 71: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

70

Figur 56 – Udførelsestid (millisekunder) for de syv reference forespørgsler ved forskellige datamængder

0

100

200

300

400

500

600

100.000 500.000 1.000.000 1.500.000

Ud

føre

lse

stid

(M

S)

Antal objekter i databasen

F1

F2

F3

F4

F5

F6

F7

Page 72: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

71

5 Konklussion Rapporten beskæftiger sig med design og evaluering af en dynamisk

datamodel, med det formål at opfylde en række opstillede krav til

funktion og arkitektonisk kvalitet.

De opstillede funktionelle krav er som følger:

1. Den dynamiske datamodel skal understøtte

oprettelsen af brugerdefinerede objekttyper og

attributtyper

2. En attributtype skal kunne tildeles én af følgende

datatyper: String, Integer, DateTime, Boolean

eller Reference

3. En attributtype skal endvidere enten kunne

tildeles en enkelt værdi eller multiple værdier

4. Der skal være et API, som understøtter

oprettelse, læsning, opdatering og sletning af

objekter

5. API’et skal desuden understøtte et

forespørgselssprog

6. Objekter skal kunne have referencer til hinanden

og dermed indgå i en objektgraf

7. Objekter skal opbevares i en relationel database

De opstillede arkitektoniske kvaliteter er:

1. Performance og skalérbarhed

2. Designets kompleksitet

3. Pladsforbrug

Der er blevet udarbejdet et design og en arkitektonisk prototype,

som er anvendt til at afprøve opfyldelsen af de funktionelle såvel

som arkitektoniske krav.

Designet læner sig op ad de retningslinier, der gives i [EAV2007]. Der

er desuden afprøvet en design variant, som baserer sig på XML data

i databasen. Erfaringerne med sidstnævnte har været positive, da

det har medvirket til en forbedret performance samt enkelhed i de

udarbejdede SQL forespørgsler.

Den udarbejdede prototype vurderes at opfylde de opstillede

funktionelle krav, samt de arkitektoniske krav til performance.

Det arkitektoniske krav om, at designet ikke har en unødig høj

kompleksitet, betragtes ligeledes som opfyldt. Vi lægger til grund, at

det udarbejdede databasedesign har få tabeller. Ligeledes at de SQL

Page 73: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

72

forespørgsler, der genereres af det udarbejdede API, er forholdsvis

simple.

Ved hjælp af prototypen, har vi kunnet konstatere, at en database

med 100.000 objekter, der hver har 15 attributværdier tilknyttet,

optager ca. 500 MB diskplads. Vi betragter dette som acceptabelt,

særligt når man tager nutidens priser på harddiske I betragtning.

Den overordnede konklussion er således, at det har været muligt at

opfylde de opstillede krav.

Page 74: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

73

6 Referencer [EAV2007] Dinu, Nadkarni, Guidelines for the Effective Use of Entity-

Attribute-Value Modeling for Biomedical Databases, 2007

[OIS2012] Omada, Omada Identity Suite, 2012

http://www.omada.net/Solutions-145.aspx

[Silberschatz2011] Silberschatz et al., Database System Concepts,

2011

[ExecPlans2008] Fritchey, SQL Server Execution Plans, 2008. ISBN:

978-1-906434-04-5

[XmlIndexes2012] Microsoft, Secondary XML Indexes, 2012

http://msdn.microsoft.com/en-us/library/bb522562(v=sql.105).aspx

Page 75: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

74

7 Bilag

Bilag 1 – Forespørgselsplaner

De i rapporten analyserede forespørgselsplaner er medtaget i bilag

1. Filerne kan åbnes i MS SQL Server 2008 Management Studio eller

læses med en XML editor.

Bilag 2 – Kildekode til API og testprogram

Det beskrevne API samt testprogram til udførsel af BQuery

forespørgsler m.v. er medtaget i bilag 2 i form af et Visual Studio

2010 projekt.

Bilag 3 – Database scripts

Bilaget indeholder SQL DDL script til oprettelse af databasens

tabeller, indexes og stored procedures.

Bilag 4 – Bootstrap data

Bilaget indeholder SQL scriptet til oprettelse af de såkaldte

bootstrap data. Disse data udgøres af et antal objekttyper,

attributtyper og bindinger, som altid skal være til stede i databasen.

Page 76: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

75

8 Appendiks Afsnittet rummer appendiks til rapporten.

Appendiks 1 – Database create script

Appendikset indeholder SQL DDL script til oprettelse af databasens

tabeller, fremmednøgler og indeks.

Appendiks 2 – QueryController

Appendikset indeholder kildekode til QueryController klassen.

8.1 Appendiks 1 – Database create script Appendikset indeholder SQL DDL script til oprettelse af databasens

tabeller, fremmednøgler og indeks. Scriptet svarer til den

indledende udgave af databasen, før XML data tages i anvendelse.

Scripts til oprettelse af den endelige udgave af databasen kan findes

i bilag 3.

Oprettelse af tabeller

CREATE TABLE [dbo].[DataType](

[DataType] [varchar](10) NOT NULL,

CONSTRAINT [PK_DataType] PRIMARY KEY CLUSTERED

(

[DataType] ASC

))

CREATE TABLE [dbo].[ObjectType](

[Name] [varchar](50) NOT NULL,

CONSTRAINT [PK_ObjectType] PRIMARY KEY CLUSTERED

(

[Name] ASC

))

CREATE TABLE [dbo].[Object](

[Id] [uniqueidentifier] NOT NULL,

[ObjectType] [varchar](50) NOT NULL,

CONSTRAINT [PK_Object] PRIMARY KEY CLUSTERED

(

[Id] ASC

))

CREATE TABLE [dbo].[AttributeType](

[Name] [varchar](50) NOT NULL,

[DataType] [varchar](10) NOT NULL,

[MultiValued] [bit] NOT NULL,

[Indexed] [bit] NOT NULL,

Page 77: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

76

[AlwaysBind] [bit] NOT NULL,

CONSTRAINT [PK_AttributeType] PRIMARY KEY CLUSTERED

(

[Name] ASC

))

CREATE TABLE [dbo].[AttributeValue](

[ObjectId] [uniqueidentifier] NOT NULL,

[AttributeType] [varchar](50) NOT NULL,

[String] [nvarchar](400) NULL,

[Integer] [int] NULL,

[DateTime] [datetime] NULL,

[Boolean] [bit] NULL,

[Reference] [uniqueidentifier] NULL

)

CREATE TABLE [dbo].[AttributeBinding](

[ObjectType] [varchar](50) NOT NULL,

[AttributeType] [varchar](50) NOT NULL,

[RequiresValue] [bit] NOT NULL,

CONSTRAINT [PK_AttributeBinding] PRIMARY KEY CLUSTERED

(

[ObjectType] ASC,

[AttributeType] ASC

))

Oprettelse af fremmednøgler

ALTER TABLE [dbo].[AttributeBinding] WITH CHECK ADD CONSTRAINT

[FK_AttributeBinding_AttributeType]

FOREIGN KEY([AttributeType])

REFERENCES [dbo].[AttributeType] ([Name])

ON UPDATE CASCADE

ON DELETE CASCADE

ALTER TABLE [dbo].[AttributeBinding] WITH CHECK ADD CONSTRAINT

[FK_AttributeBinding_ObjectType]

FOREIGN KEY([ObjectType])

REFERENCES [dbo].[ObjectType] ([Name])

ON UPDATE CASCADE

ON DELETE CASCADE

ALTER TABLE [dbo].[AttributeType] WITH CHECK ADD CONSTRAINT

[FK_AttributeType_DataType]

FOREIGN KEY([DataType])

REFERENCES [dbo].[DataType] ([DataType])

ON UPDATE CASCADE

Page 78: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

77

ALTER TABLE [dbo].[AttributeValue] WITH CHECK ADD CONSTRAINT

[FK_AttributeValue_AttributeType]

FOREIGN KEY([AttributeType])

REFERENCES [dbo].[AttributeType] ([Name])

ON UPDATE CASCADE

ALTER TABLE [dbo].[AttributeValue] WITH CHECK ADD CONSTRAINT

[FK_AttributeValue_Object]

FOREIGN KEY([ObjectId])

REFERENCES [dbo].[Object] ([Id])

ON UPDATE CASCADE

ON DELETE CASCADE

ALTER TABLE [dbo].[AttributeValue] WITH CHECK ADD CONSTRAINT

[FK_AttributeValue_Reference]

FOREIGN KEY([Reference])

REFERENCES [dbo].[Object] ([Id])

ALTER TABLE [dbo].[Object] WITH CHECK ADD CONSTRAINT

[FK_Object_ObjectType]

FOREIGN KEY([ObjectType])

REFERENCES [dbo].[ObjectType] ([Name])

Oprettelse af indeks

CREATE NONCLUSTERED INDEX [IX_AttributeValue_Boolean2] ON

[dbo].[AttributeValue]

(

[Boolean] ASC,

[AttributeType] ASC

)

INCLUDE ( [ObjectId])

CREATE NONCLUSTERED INDEX [IX_AttributeValue_String] ON

[dbo].[AttributeValue]

( [String] ASC)

CREATE NONCLUSTERED INDEX [IX_AttributeValue_Integer] ON

[dbo].[AttributeValue]

( [Integer] ASC)

CREATE NONCLUSTERED INDEX [IX_AttributeValue_DateTime] ON

[dbo].[AttributeValue]

( [DateTime] ASC )

CREATE NONCLUSTERED INDEX [IX_AttributeValue_Reference] ON

[dbo].[AttributeValue]

( [Reference] ASC )

Page 79: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

78

8.2 Appendiks 2 – Forespørgsels API Appendikset indeholder kildekoden til en central del af

forespørgsels API’et. QueryController klassen muliggør udførsel af

BQuery forespørgsler, som den omdanner til SQL forespørgsler og

udfører mod databasen. Den viste kildekode svarer til den endelige

version af API’et (hvor der anvendes XML data). Den fulde kildekode

til API’et er medtaget i bilag 2.

using System;

using System.Collections.Generic;

using System.Data;

using System.Data.SqlClient;

using System.Xml;

using DynamicDB.Model;

using DynamicDB.Model.Exceptions;

namespace DynamicDB.AppLogic

{

/// <summary>

/// QueryController allows for execution of BQuery queries against the dynamic data store.

/// The primary methods of QueryController are also (for convenience) surfaced in ObjectController.

/// </summary>

public class QueryController : ControllerBase

{

public QueryController(string connectionString)

: base(connectionString)

{

}

public QueryController(

SqlConnection dbConnection,

SqlTransaction dbTransaction,

int dbCommandTimeout = 0)

: base(dbConnection, dbTransaction, dbCommandTimeout)

{

}

public QueryController(ControllerBase assignFrom)

: base(assignFrom)

{

}

/// <summary>

/// Returns the (Attribute) DataType of either the left- or the right-side value of an expression.

/// </summary>

/// <param name="expressionValue"></param>

/// <param name="objectType"></param>

/// <returns></returns>

private AttributeDataType GetExpressionSideDataType(

object expressionValue,

ObjectType objectType)

{

if (QueryExpression.ExpressionValueIsAttributeName(expressionValue))

{

AttributeType attributeType =

objectType.GetBoundAttribute((string)expressionValue, true);

return attributeType.DataType;

}

else

{

return QueryExpression.GetExpressionValueDataType(expressionValue);

}

}

/// <summary>

/// Converts an expression inner operator to a sql operator.

/// </summary>

/// <param name="op"></param>

/// <returns></returns>

private string InnerOperatorToSqlOperator(InnerOperator op)

{

Page 80: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

79

switch (op)

{

case InnerOperator.Equals:

return "=";

case InnerOperator.NotEquals:

return "<>";

case InnerOperator.LessThan:

return "<";

case InnerOperator.GreaterThan:

return ">";

case InnerOperator.LessThanEquals:

return "<=";

case InnerOperator.GreaterThanEquals:

return ">=";

case InnerOperator.Contains:

return "like";

default:

throw new ArgumentException("Unknown operator: " + op, "op");

}

}

private SqlCommand BuildCommand(ObjectQuery objectQuery, ObjectType objectType)

{

if (objectQuery == null)

throw new ArgumentNullException("objectQuery");

if (objectType == null)

throw new ArgumentNullException("objectType");

this.ValidateOuterOperators(objectQuery);

string top = objectQuery.ResultSize > 0 ? " top " + objectQuery.ResultSize : String.Empty;

string cmdText = "select" + top + " o.Id, o.Data"

+ " from Object o"

+ " where o.ObjectType = @ObjectType";

Dictionary<string, object> queryParameters = new Dictionary<string, object>();

queryParameters.Add("@ObjectType", objectQuery.ObjectType);

string expressionSql = String.Empty;

List<QueryExpression> treatedExpressions = new List<QueryExpression>();

for (int idx = 0; idx < objectQuery.Expressions.Count; idx++)

{

QueryExpression expr = objectQuery.Expressions[idx];

if (!treatedExpressions.Contains(expr))

{

if (idx > 0)

{

expressionSql += (expr.OuterOperator == OuterOperator.And ? " and " : " or ");

}

// Check if we can merge the expression with the following expression(s) with the aim of

producing a cheaper sql query

List<QueryExpression> collectiveEvalExpressions =

this.GetCollectiveEvaluationExpressions(expr, idx, objectQuery, objectType);

string whereClause = this.GetExpressionWhereClause(expr, collectiveEvalExpressions,

objectQuery, objectType, queryParameters);

expressionSql += whereClause;

treatedExpressions.AddRange(collectiveEvalExpressions);

}

}

if (!String.IsNullOrEmpty(expressionSql))

cmdText += " and (" + expressionSql + ")";

// Note: we utilize SQL servers handling of and/or precedence.

SqlCommand cmd = this.CreateCommand(cmdText);

foreach (string param in queryParameters.Keys)

{

if (param == "@ObjectType" || param.StartsWith("@AttribName"))

{

// Important: we add ObjectType and AttribName like this (and not using AddWithValue)

// since AddWithValue will treat them as NVarChar,

Page 81: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

80

// which causes a CONVERT_IMPLICIT in the query plan.

cmd.Parameters.Add(param, SqlDbType.VarChar);

cmd.Parameters[param].Value = queryParameters[param];

}

else

{

cmd.Parameters.AddWithValue(param, queryParameters[param]);

}

}

return cmd;

}

/// <summary>

/// Get expressions to be handled collectively in the evaluation.

/// </summary>

/// <param name="expr">

/// The expression that we want to find similar/mergable expressions for.

/// </param>

/// <param name="idx">

/// The index of the expression we want to find similar/mergable expressions for.

/// </param>

/// <param name="objectQuery"></param>

/// <param name="objectType"></param>

/// <returns>

/// At least the expression itself is returned.

/// </returns>

private List<QueryExpression> GetCollectiveEvaluationExpressions(QueryExpression expr, int idx,

ObjectQuery objectQuery, ObjectType objectType)

{

List<QueryExpression> result = new List<QueryExpression>();

// We always add the expression itself

result.Add(expr);

// Note: expressions for "ObjectId" are skipped because they are is handled specially in

GetExpressionWhereClause

if (expr.InvolvesSingleAttribute && expr.SingleInvolvedAttribute != "ObjectId")

{

AttributeType attributeType = objectType.GetBoundAttribute(expr.SingleInvolvedAttribute, true);

if (attributeType.Indexed)

{

for (int remainIdx = idx + 1; remainIdx < objectQuery.Expressions.Count; remainIdx++)

{

QueryExpression nextExpr = objectQuery.Expressions[remainIdx];

if (expr.SingleInvolvedAttribute == nextExpr.SingleInvolvedAttribute &&

nextExpr.OuterOperator == OuterOperator.And)

{

result.Add(nextExpr);

}

else

{

break;

}

}

}

}

return result;

}

private string GetExpressionWhereClause(

QueryExpression expr,

List<QueryExpression> collectiveEvalExpressions,

ObjectQuery objectQuery,

ObjectType objectType,

Dictionary<string, object> queryParameters)

{

if (!collectiveEvalExpressions.Contains(expr))

throw new ArgumentException("Must at least contain the expression itself",

"collectiveEvalExpressions");

AttributeDataType leftDataType = this.GetExpressionSideDataType(expr.Left, objectType);

AttributeDataType rightDataType = this.GetExpressionSideDataType(expr.Right, objectType);

if (leftDataType != rightDataType)

throw new ArgumentException(

"Left- and right-side must have same data type: '" + expr.ToString() + "'",

Page 82: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

81

"objectQuery");

if (!QueryExpression.OperatorValidForDataType(expr.InnerOperator, leftDataType))

throw new ArgumentException(

String.Format(

"Expression's inner operator ({0}) is not valid for the datatype: {1}",

expr.InnerOperator,

leftDataType),

"objectQuery");

if (expr.LeftIsAttribute && expr.RightIsAttribute)

{

// Validate against a query such as: /Person[ObjectId = CreatedBy]

// The reason is that ObjectId is not stored in the AttributeValue table.

if ((string)expr.Left == "ObjectId" || (string)expr.Right == "ObjectId")

throw new ArgumentException(

String.Format(

"It is currently not allowed to use an expression with attributes on both sides where one

of them is ObjectId: {0}",

objectQuery.ToString()),

"objectQuery");

AttributeType leftAttributeType = objectType.GetBoundAttribute((string)expr.Left, true);

AttributeType rightAttributeType = objectType.GetBoundAttribute((string)expr.Right, true);

if (!leftAttributeType.Indexed || !rightAttributeType.Indexed)

throw new ArgumentException(

String.Format(

"Currently an expression with attributes on both sides requires that both attributes are

indexed: {0}",

objectQuery.ToString()),

"objectQuery");

string colName = EnumHelper.GetAttributeValueColumnName(leftDataType);

string result = "exists (select 1 from AttributeValue av2"

+ " join AttributeValue av3 on o.Id = av3.ObjectId"

+ " and av3.AttributeType = @RightAttribName" + expr.Identifier

+ " where o.Id = av2.ObjectId and av2.AttributeType = @LeftAttribName" + expr.Identifier

+ " and av2." + colName + " "

+ this.InnerOperatorToSqlOperator(expr.InnerOperator) + " av3." + colName

+ ")";

queryParameters.Add("@LeftAttribName" + expr.Identifier, (string)expr.Left);

queryParameters.Add("@RightAttribName" + expr.Identifier, (string)expr.Right);

return result;

}

else if (!expr.LeftIsAttribute && !expr.RightIsAttribute)

{

throw new ArgumentException("Query currently doesn't support constant expressions!");

}

else

{

// One attribute and one value

string colName = EnumHelper.GetAttributeValueColumnName(leftDataType);

string expressionAttribute = expr.LeftIsAttribute ? (string)expr.Left : (string)expr.Right;

if (expressionAttribute == "ObjectId")

{

// We deal with ObjectId in a special way.

// Partly because this is more efficient,

// partly because it isn't present in the AttributeValue table.

string result = " o.Id " + this.InnerOperatorToSqlOperator(expr.InnerOperator) + " " +

this.GetAttributeValueParameterName(expr);

this.AddExpressionAttributeValueParameter(queryParameters, expr, expr.LeftIsAttribute);

return result;

}

else

{

AttributeType attributeType = objectType.GetBoundAttribute(expressionAttribute, true);

if (attributeType.Indexed)

{

string result = "exists (select 1 from AttributeValue av2"

+ " where o.Id = av2.ObjectId and av2.AttributeType = " +

this.GetAttributeNameParameterName(expr);

Page 83: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

82

this.AddExpressionAttributeNameParameter(queryParameters, expr, expr.LeftIsAttribute);

foreach (QueryExpression collectiveEvalExpression in collectiveEvalExpressions)

{

if (collectiveEvalExpression.LeftIsAttribute)

{

result += " and av2." + colName + " "

+ this.InnerOperatorToSqlOperator(collectiveEvalExpression.InnerOperator) + " " +

this.GetAttributeValueParameterName(collectiveEvalExpression);

}

else

{

result += " and " + this.GetAttributeValueParameterName(collectiveEvalExpression) + " "

+ this.InnerOperatorToSqlOperator(collectiveEvalExpression.InnerOperator) + " av2." +

colName;

}

this.AddExpressionAttributeValueParameter(queryParameters, collectiveEvalExpression,

collectiveEvalExpression.LeftIsAttribute);

}

result += ")";

return result;

}

else // attribute is not "indexed" so we look in the xml

{

string result = String.Format(

@"o.Data.exist('/object/attributes/attribute[@name = sql:variable(""{0}"")]/value[@{1} =

sql:variable(""{2}"")]') <> 0",

this.GetAttributeNameParameterName(expr),

EnumHelper.GetAttributeValueXmlAttributeName(attributeType.DataType),

this.GetAttributeValueParameterName(expr));

this.AddExpressionAttributeAndValueParameters(queryParameters, expr, expr.LeftIsAttribute);

return result;

}

}

}

}

private string GetAttributeNameParameterName(QueryExpression expr)

{

return "@AttribName" + expr.Identifier;

}

private string GetAttributeValueParameterName(QueryExpression expr)

{

return "@AttribValue" + expr.Identifier;

}

private void ValidateOuterOperators(ObjectQuery objectQuery)

{

int idx = 0;

foreach (QueryExpression expr in objectQuery.Expressions)

{

if (idx > 0)

{

if (expr.OuterOperator == OuterOperator.Undefined)

throw new ArgumentException(

"Non-first expression must have an outer operator: '" + expr.ToString() + "'",

"objectQuery");

}

else

{

if (expr.OuterOperator != OuterOperator.Undefined)

throw new ArgumentException(

"First expression can't have an outer operator: '" + expr.ToString() + "'",

"objectQuery");

}

idx++;

}

}

// Helper method for BuildCommand()

Page 84: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

83

private void AddExpressionAttributeAndValueParameters(

Dictionary<string, object> queryParameters,

QueryExpression expr,

bool leftRight,

bool skipAttributeNameParam = false)

{

if (!skipAttributeNameParam)

this.AddExpressionAttributeNameParameter(queryParameters, expr, leftRight);

this.AddExpressionAttributeValueParameter(queryParameters, expr, leftRight);

}

// Helper method for BuildCommand()

private void AddExpressionAttributeNameParameter(

Dictionary<string, object> queryParameters,

QueryExpression expr,

bool leftRight)

{

string attribute = leftRight ? (string)expr.Left : (string)expr.Right;

queryParameters.Add(this.GetAttributeNameParameterName(expr), attribute);

}

// Helper method for BuildCommand()

private void AddExpressionAttributeValueParameter(

Dictionary<string, object> queryParameters,

QueryExpression expr,

bool leftRight)

{

string attribute = leftRight ? (string)expr.Left : (string)expr.Right;

object exprValue = leftRight ? expr.RightUnquoted : expr.LeftUnquoted;

if (expr.InnerOperator == InnerOperator.Contains)

exprValue = /* "%" + */ (string)exprValue + "%";

queryParameters.Add(this.GetAttributeValueParameterName(expr), exprValue);

}

/// <summary>

/// QueryObjects allows for executing a BQuery against the object store.

/// </summary>

/// <param name="query">

/// A BQuery query.

/// Example: /Person[FirstName='Thomas']

/// </param>

/// <param name="resultSize">

/// Maximum number of objects to be returned by the query.

/// If resultSize is zero all objects that meet the query are returned.

/// </param>

/// <param name="orderAttribute">

/// Name of an AttributeType.

/// The query result will be ordered (ascending) by this attribute.

/// orderAttribute is ignored if null.

/// </param>

/// <returns></returns>

public List<DataObject> QueryObjects(string query, int resultSize = 0,

string orderAttribute = null)

{

if (String.IsNullOrEmpty(query))

throw new ArgumentNullOrEmptyException("query");

if (resultSize < 0)

throw new ArgumentException("Can't be negative", "resultSize");

// TODO: implement orderAttribute

if (orderAttribute != null)

throw new ArgumentException("This options is not yet supported", "orderAttribute");

return this.DoInConnection<List<DataObject>>(

delegate

{

ObjectQuery objectQuery = new ObjectQuery(query);

objectQuery.ResultSize = resultSize;

objectQuery.OrderAttribute = orderAttribute;

// Load the ObjectType which the query is for

SchemaController schemaController = new SchemaController(this);

ObjectType objectType = schemaController.GetObjectType(objectQuery.ObjectType);

// Check that we're ordering on an attribute that exists on the object type

if (!String.IsNullOrEmpty(orderAttribute))

Page 85: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

84

if (!objectType.HasBindingForAttribute(orderAttribute))

throw new ArgumentException("Can't order on '" + orderAttribute + "' as the ObjectType

has no binding for it", "orderAttribute");

List<DataObject> result = new List<DataObject>();

using (SqlCommand cmd = this.BuildCommand(objectQuery, objectType))

{

DataObject dataObject = null;

using (SqlDataReader dataReader = cmd.ExecuteReader())

while (dataReader.Read())

{

Guid objectId = (Guid)dataReader["Id"];

if (dataObject == null || dataObject.Id != objectId)

{

dataObject = new DataObject(objectId, objectQuery.ObjectType);

result.Add(dataObject);

}

string xml = (string)dataReader["Data"];

this.ParseXml(xml, dataObject, objectType);

}

}

return result;

});

}

private void ParseXml(string xml, DataObject dataObject, ObjectType objectType)

{

XmlDocument xmlDoc = new XmlDocument();

xmlDoc.LoadXml(xml);

foreach (XmlNode node in xmlDoc.SelectNodes("/object/attributes/attribute[@name != 'ObjectType'

and @name != 'ObjectId']"))

{

string attributeName = node.Attributes["name"].Value;

AttributeType attributeType = objectType.GetBoundAttribute(attributeName, false);

if (attributeType != null)

{

if (attributeType.MultiValued)

{

List<object> values = new List<object>(); // !!! kan man det?

foreach (XmlNode valueNode in node.ChildNodes)

{

object value = this.GetValue(valueNode, attributeType);

values.Add(value);

}

dataObject.Attributes.Add(attributeType.Name, values);

}

else

{

object value = this.GetValue(node.ChildNodes[0], attributeType);

dataObject.Attributes.Add(attributeType.Name, value);

}

}

}

}

private object GetValue(XmlNode valueNode, AttributeType attributeType)

{

switch (attributeType.DataType)

{

case AttributeDataType.Boolean:

return XmlConvert.ToBoolean(valueNode.Attributes["b_value"].Value);

case AttributeDataType.Integer:

return XmlConvert.ToInt32(valueNode.Attributes["i_value"].Value);

case AttributeDataType.DateTime:

return XmlConvert.ToDateTime(valueNode.Attributes["dt_value"].Value,

XmlDateTimeSerializationMode.Utc);

case AttributeDataType.String:

return valueNode.Attributes["s_value"].Value;

case AttributeDataType.Reference:

return new Guid(valueNode.Attributes["r_value"].Value);

default:

throw new Exception("Unknown: " + attributeType.DataType);

}

}

Page 86: Hovedopgave - cs.au.dkcs.au.dk/fileadmin/site_files/cs/AA_pdf/Hovedopgave_Thomas_Boel... · 4.4 Bootstrap data ... = ”/”  ”[”  ”]

85

}

}