» tagged pages
» logout

sorted by: recent | see : popular
Content Tagged with NHibernate + castle

Hent data med Castle ActiveRecord

This post is in danish and is a second part in an introduction to Castle ActiveRecord, if you would like to see this content in english let me know

Introduktion

I sidste post kiggede vi på hvordan vi kan specificere en sammenhæng mellem vores klasser og vores database med Castle ActiveRecord. Nu er det på tide at finde dataene frem igen og se lidt på hvordan Castle ActiveRecord egentlig håndterer de komplekse situationer. F.eks de relationer vi specificerede sidst mellem vores klasser.

Forudsætninger

Som nævnt i sidste post er Castle ActiveRecord et abstraktionsniveau over nHibernate, dvs. det pakker nHibernate ind på en pæn måde der gør det lettere at bruge, dog vil vi se at det ofte er nødvendigt at bruge nHibernate funktionalitet for at få fuld fleksibilitet. Det vender jeg tilbage til senere i denne post.

Lad os først se på den mest simple måde at hente data med de ActiveRecord klasser vi har konstrueret. Men før vi går igang med det skal vi tilføje en reference til NHibernate.dll til vores projekt da funktionalitet herfra bliver brugt i nogle af de metoder vi skal til at kalde.

Simpel forespørgsel

Når vi har fået det indledende på plads kan vi eksekvere nedenstående kode:

Post[] posts = Post.FindAll();

Det vil returnere en liste med de instancer af Post der er gemt i databasen, hvis vi kigger på hvilke SQL sætninger dette generer kan man blive lidt overrasket, Post er relateret til alle vores objekter da en post tilhører en Blog der er ejet af en Blogger og udover det har en Post en række Tag's tilknyttet, da vi ikke har fortalt ActiveRecord andet tror den at vi ønsker at få alt data med det samme, så følgende SQL bliver eksekveret:

-- Query 1
SELECT this_.id       AS id0_2_,
       this_.title    AS title0_2_,
       this_.TEXT     AS text0_2_,
       this_.blog     AS blog0_2_,
       blog2_.id      AS id2_0_,
       blog2_.name    AS name2_0_,
       blog2_.owner   AS owner2_0_,
       blogger3_.id   AS id3_1_,
       blogger3_.name AS name3_1_
FROM   post this_
       LEFT OUTER JOIN blogs blog2_
         ON this_.blog = blog2_.id
       LEFT OUTER JOIN blogger blogger3_
         ON blog2_.owner = blogger3_.id

-- Query 2
SELECT tags0_.postid AS postid__1_,
       tags0_.tagid  AS tagid1_,
       tag1_.id      AS id4_0_,
       tag1_.name    AS name4_0_,
       tag1_.TEXT    AS text4_0_
FROM   posttags tags0_
       LEFT OUTER JOIN tag tag1_
         ON tags0_.tagid = tag1_.id
WHERE  tags0_.postid = @p0;

@p0 = '1'

Lazy loading af collections

Vi kan udfra ovenstående SQL tydeligt se at ActiveRecord forsøger at hente alt data. Der laves joins i vores første query for at få fat i hhv. information om Blog instansen bundet til Post samt Blogger instansen bundet til Blog instansen. ActiveRecord (eller rettere nHibernate) er smart nok til at tage dette flade resultatsæt og omforme til vores objektgraf. Efterfølgende kan vi se i Query 2 at vores mange-til-mange relation bliver forespurgt, her henter ActiveRecord all de tags ud der er tilknyttet den ene post jeg har i min database og henter hvert enkelt tag ud. Faktisk har jeg ALT data i min applikation hevet op vha ovenstående query, det kan vi hurtigt blive enige om ikke er smart, så derfor skal vi tilbage og rette på nogle ting i vores relations-attributter. Hvis vi ønsker data ikke skal indlæses med det samme men ved første adgang til det kan vi specificere lazy attributten, så lad os sige jeg ikke ønsker at hente alle Posts op når jeg henter en blog specificerer jeg følgende i min HasMany attribut:

[HasMany(Lazy = true)]
public IList<post> Posts{...}

Det vil medføre at vores Posts collection ikke vil blive hentet op når vi henter en instans af Blog klassen op.

Databaseforbindelser ved Lazy loading

Det er vigtigt at forstå i denne sammenhæng at ActiveRecord nu skal bruge en databaseforbindelse til at indlæse dataene når du tilgår dem, dvs. nu er det ikke længere "hit-and-run"-dataadgang, nu kan du risikere at dit objekt skal snakke med databasen når du tilgår en liste. På grund af dette skal proxien have adgang til en databaseforbindelse, for at den har det skal den være i et såkaldt SessionScope. Et SessionScope er ActiveRecords "wrapper" omkring en databaseforbindelse (hvis det skal forklares enkelt, reelt set er det noget mere end det men mere om det en anden gang). Når vi tilgår data der benytter lazy loading skal vi åbne et SessionScope i den tid vi arbejder med objektet, det kan vi gøre sådan her:

using(new SessionScope()){
	Blog b = Blog.Find(1);
	int count = b.Posts.Count; // Her indlæses dataene først
}

Lazy loading af relaterede objekter

Nu har vi løst problemet for vores collections, men vi har stadig ikke reduceret hvor mange relaterede objekter der hentes ud vha. join'et i den første SQL sætning vi så tidligere. Hvis vi vil reducere dette kan vi markere enkelte klasser som værende Lazy loadet, f.eks. kan vi ved at specificere Lazy parametren til vores ActiveRecord attribut på Blog klassen specificere at denne ikke skal hentes op:

[ActiveRecord(Lazy = true)]
public class Blog : ActiveRecordBase<Blog>{..}

Hvordan gør ActiveRecord det?

Men hov, hvordan håndterer ActiveRecord dette, det vil jo kræve at den kan finde ud af hvornår vi tilgår egenskaben Blog på vores Post instancer. Det er helt korrekt og for at finde ud af dette bliver vores Blog property på Post klassen skiftet ud med et såkaldt proxy objekt. Dette objekt fortæller ActiveRecord når det bliver tilgået og indlæser dataene fra databasen og returnerer dem. Det er mildest talt smart(til at udføre dette bruger ActiveRecord et andet projekt fra Castle nemlig DynamicProxy). Der er dog en ting der er værd at være opmærksom på, for at ActiveRecord kan lave en Proxy af vores type nedarver den fra typen, og for at den kan overskrive vores egenskaber og metoder skal disse være markeret med virtual keywordet. Så husk det, hvis det bliver glemt får vi følgende exception:

NHibernate.InvalidProxyTypeException : The following types may not be used as proxies:
Intellect.BlogWebsite.Model.Blogger: method set_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Name should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Name should be virtual

Lazy loading kan hurtigt blive uoverskueligt at få specificeret korrekt så det passer i alle scenarier. I nogle scenarier i applikationen vil man måske gerne have noget data lazyloadet og i andre noget helt andet. Men bare rolig, det er der også en løsning på, den vender jeg tilbage til om lidt.

Filtrering ved hjælp af kriterier

I ovenstående så vi også hvordan vi finder en enkelt instans frem udfra dens primærnøgle nemlig med Find metoden på klassen.

Lad os se hvordan vi kan filtrere på data udfra andre egenskaber end primærnøglen. Vi starter med et lille eksempel, jeg vil finde alle Blogger instanser hvor Name egenskaben indeholder en bestemt streng

Blogger.FindAll(Expression.Like("Name", "Jakob", MatchMode.Anywhere));

Igen bruger jeg FindAll metoden men denne gang sender jeg en parameter med, og her gør jeg brug af NHibernate. Expression er hvad man kan kalde en factory klasse, den bruges til at generere implementationer af ICriterion interfacet. I ovenstående tilfælde beder jeg om at få et Like expression der sammenligner egenskaben "Name" med strengen "Jakob" og specificerer en MatchMode, ovenstående resulterer i følgende SQL:

SELECT this_.ID as ID4_0_, this_.Name as Name4_0_ FROM Blogger this_ WHERE this_.Name like @p0; @p0 = '%Jakob%'

Som vi kan se indsætter nHibernate selv wildcards i min parameter baseret på den MatchMode jeg har valgt. Jeg kan tilføje flere forskellige ICriterion til min FindAll metode(adskilt med kommaer takket være params keyword på parametren) for at sammensætte komplekse søgninger.

Oversigt over filtreringsmuligheder

Her er en liste med en kort beskrivelse af nogle af de ICriterion Expression factoryen kan genere for os:

And, Or og Not

Expression.And(ICriterion, ICriterion)
Eksempel: Find alle Blogger instanser hvor navnet indeholder både "streng1" og "streng2"
Expression.And(Expression.Like("Name", "streng1", MatchMode.Anywhere), Expression.Like("Name", "streng2", MatchMode.AnyWhere));
Tilsvarende findes
Expression.Or(ICriterion, ICriterion)
Expression.Not(ICriterion)

Between

Expression.Between(string, object, object)

Eksempel: Find alle Post instanser med ID imellem 1 og 10
Expression.Between("ID", 1, 10);

Sammenligning

Expression.Eq(string, object)
Eksempel: Find en Post med Titlen "Introduktion til ActiveRecord"
Expression.Eq("Title", "Introduktion til ActiveRecord");
Tilsvarende findes Expression.Ge(>=), Expression.Le(<=), Expression.Gt(>), Expression.Lt(<) alle disse har også et modstykke der tager navnet på to egenskaber og sammenligner. De hedder EqProperty, GeProperty osv.

Lister

Expression.In(string, ICollection);
Expression.In(string, object[]);
Eksempel: Find alle Post instanser med id 1,2 og 3
Expression.In("ID", new int[]{1,2,3})

NULL håndtering

Expression.IsNull(string);
Expression.IsNotNull(string);
Begge to har en parameter der skal være et navn på en egenskab og den tilsvarende kolonne i databasen vil blive checket om den er/er ikke NULL.

Sortering

Vi kan nu lave noget ret avancerede filtrering i vores data vi kan også sortere ved at sende en instans af Order med til FindAll og derefter specificere filtreringer. Order er en factory ligesom Expression så den bruges sådan her:

Blogger.FindAll(Order.Desc("Name"), Expression.Eq(....));

Lazy loading på forespørgselsniveau

Vi har set hvor let vi kan arbejde med vores data og filtrere hvad vi hiver ud fra databasen, og det er ret simpelt at arbejde med. Men på et tidspunkt rammer vi en mur med denne forespørgselsmekanisme. Som nævnt tidligere kan det være en fordel at specificere hvad der skal lazy loades i det øjeblik vi udfører en forespørgsel. Det kan f.eks. være jeg i et bestemt tilfælde kun ønsker at hive mine Post instanser op men ikke ønsker nogle relaterede instanser loadet med det samme. For at gøre dette skal vi have fat i et koncept fra nHibernate, nemlig en ICriteria query. Nærmere bestemt en DetachedCriteria query:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy);
using (new SessionScope())
{
        Post[] posts = Post.FindAll(postQuery);
	//...
}

Som vi kan se deklarerer vi forespørgslen og sender den derefter til vores FindAll metode. I kaldet til SetFetchMode kan vi specificere hvordan enkelte relationer på Post bliver hentet. Når ovenstående DetachedCriteria eksekveres producerer det følgende SQL:

SELECT this_.ID as ID2_0_, this_.Title as Title2_0_, this_.Text as Text2_0_, this_.Blog as Blog2_0_ FROM Post this_

Kombiner SetFechMode, kriterier og sortering

DetachedCriteria giver os en mulighed for at bruge det vi allerede har lært, ICriterion kan også knyttes på et DetachedCriteria:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy)
                .Add(Expression.Like("Title", "Introduktion til ActiveRecord", MatchMode.Anywhere));

Og vi kan på samme måde tilføje vores sortering vha. AddOrder metoden. FetchMode kan specificeres til enten Lazy, Eager, Join, Select.

Kriterier på tværs af relationer

Udover at specificere lazy loading i vores forespørgsel har vi også mulighed for at lave mere komplekse forespørgsler på tværs af vore relationer ved at tilføje aliaser, f.eks. hvis vi ønsker at hente samtlige Post instanser hvor titlen på den relaterede Blog instans indeholder "streng1":

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .CreateAlias("Blog", "b")
                .Add(Expression.Like("b.Name", "streng1", MatchMode.Anywhere));

Vi kan tilføje et alias til en relationsti og bruge den i vore ICriterion forespørgsler, vi kan også specificere når vi opretter aliaset hvordan data skal hentes ud, ved at angive en jointype som en 3 parameter til CreateAlias metoden. Ovenstående Criteria vil ved eksekvering udføre følgende SQL mod databasen:

SELECT this_.id    AS id2_1_,
       this_.title AS title2_1_,
       this_.TEXT  AS text2_1_,
       this_.blog  AS blog2_1_,
       b1_.id      AS id3_0_,
       b1_.name    AS name3_0_,
       b1_.owner   AS owner3_0_
FROM   post this_
       INNER JOIN blogs b1_
         ON this_.blog = b1_.id
WHERE  b1_.name LIKE @p0;

@p0 = '%streng1%'

Der er mange kombinationsmuligheder, og vi kan med DetachedCriteria have fuld kontrol over hvad data hentes og hvornår, og vi kan forespørge ved at bruge vores relationer ganske let.

Hent en enkelt instans udfra kriterier

Vi har set hvordan vi kan hente en enkelt instans udfra dens identifier og hvordan vi kan hente flere poster med FindAll, en sidste nyttig ting er hvis vi skal hente en enkelt instans op baseret på kriterier, der kan vi bruge FindOne metoden som også kan tage en liste ICriterion eller et DetachedCriteria.

Post p = Post.FindFirst(Expression.Eq("Title", "Introduktion til ActiveRecord"));

Ovenstående vil hente præcis en instans ud så det kan benyttes hvis man er sikkert på at ens forespørgsel returnerer kun et resultatet eller hvis man kun ønsker den første instans i et større resultatetsæt.

Hent en delmængde af et resultatsæt

Ofte ønsker man at præsentere data i sider, så man viser f.eks. 10 resultater af gangen og giver mulighed for at bladre imellem siderne, ActiveRecord gør dette rigtig let. Der findes en udgave af FindAll der kun resulterer en delmængde, den hedder SlicedFindAll, så hvis jeg vil vise f.eks. posterne fra 10 til 20 så kan jeg gøre det sådan her:

Post.SlicedFindAll(10, 20);

Også her har jeg mulighed for vha. overloads af SlicedFindAll at sende hhv. ICriteria, Order og DetachedCriteria med og få returneret en delmængde af disse.

Næste gang

DetachedCriteria har en lang række andre muligheder og jeg kan kun opfordre til at udforske metoderne. I næste post vil jeg se lidt på nogle avancerede features i ActiveRecord bl.a. hvordan vi kan introducere validering af data i vores egenskaber og hvordan vi kan undgå at skulle nedarvre fra ActiveRecordBase<T> hvis man ønsker det.

tag4sree: Hibernate - Objects

Hent data med Castle ActiveRecord

This post is in danish and is a second part in an introduction to Castle ActiveRecord, if you would like to see this content in english let me know

Introduktion

I sidste post kiggede vi på hvordan vi kan specificere en sammenhæng mellem vores klasser og vores database med Castle ActiveRecord. Nu er det på tide at finde dataene frem igen og se lidt på hvordan Castle ActiveRecord egentlig håndterer de komplekse situationer. F.eks de relationer vi specificerede sidst mellem vores klasser.

Forudsætninger

Som nævnt i sidste post er Castle ActiveRecord et abstraktionsniveau over nHibernate, dvs. det pakker nHibernate ind på en pæn måde der gør det lettere at bruge, dog vil vi se at det ofte er nødvendigt at bruge nHibernate funktionalitet for at få fuld fleksibilitet. Det vender jeg tilbage til senere i denne post.

Lad os først se på den mest simple måde at hente data med de ActiveRecord klasser vi har konstrueret. Men før vi går igang med det skal vi tilføje en reference til NHibernate.dll til vores projekt da funktionalitet herfra bliver brugt i nogle af de metoder vi skal til at kalde.

Simpel forespørgsel

Når vi har fået det indledende på plads kan vi eksekvere nedenstående kode:

Post[] posts = Post.FindAll();

Det vil returnere en liste med de instancer af Post der er gemt i databasen, hvis vi kigger på hvilke SQL sætninger dette generer kan man blive lidt overrasket, Post er relateret til alle vores objekter da en post tilhører en Blog der er ejet af en Blogger og udover det har en Post en række Tag's tilknyttet, da vi ikke har fortalt ActiveRecord andet tror den at vi ønsker at få alt data med det samme, så følgende SQL bliver eksekveret:

-- Query 1
SELECT this_.id       AS id0_2_,
       this_.title    AS title0_2_,
       this_.TEXT     AS text0_2_,
       this_.blog     AS blog0_2_,
       blog2_.id      AS id2_0_,
       blog2_.name    AS name2_0_,
       blog2_.owner   AS owner2_0_,
       blogger3_.id   AS id3_1_,
       blogger3_.name AS name3_1_
FROM   post this_
       LEFT OUTER JOIN blogs blog2_
         ON this_.blog = blog2_.id
       LEFT OUTER JOIN blogger blogger3_
         ON blog2_.owner = blogger3_.id

-- Query 2
SELECT tags0_.postid AS postid__1_,
       tags0_.tagid  AS tagid1_,
       tag1_.id      AS id4_0_,
       tag1_.name    AS name4_0_,
       tag1_.TEXT    AS text4_0_
FROM   posttags tags0_
       LEFT OUTER JOIN tag tag1_
         ON tags0_.tagid = tag1_.id
WHERE  tags0_.postid = @p0;

@p0 = '1'

Lazy loading af collections

Vi kan udfra ovenstående SQL tydeligt se at ActiveRecord forsøger at hente alt data. Der laves joins i vores første query for at få fat i hhv. information om Blog instansen bundet til Post samt Blogger instansen bundet til Blog instansen. ActiveRecord (eller rettere nHibernate) er smart nok til at tage dette flade resultatsæt og omforme til vores objektgraf. Efterfølgende kan vi se i Query 2 at vores mange-til-mange relation bliver forespurgt, her henter ActiveRecord all de tags ud der er tilknyttet den ene post jeg har i min database og henter hvert enkelt tag ud. Faktisk har jeg ALT data i min applikation hevet op vha ovenstående query, det kan vi hurtigt blive enige om ikke er smart, så derfor skal vi tilbage og rette på nogle ting i vores relations-attributter. Hvis vi ønsker data ikke skal indlæses med det samme men ved første adgang til det kan vi specificere lazy attributten, så lad os sige jeg ikke ønsker at hente alle Posts op når jeg henter en blog specificerer jeg følgende i min HasMany attribut:

[HasMany(Lazy = true)]
public IList<post> Posts{...}

Det vil medføre at vores Posts collection ikke vil blive hentet op når vi henter en instans af Blog klassen op.

Databaseforbindelser ved Lazy loading

Det er vigtigt at forstå i denne sammenhæng at ActiveRecord nu skal bruge en databaseforbindelse til at indlæse dataene når du tilgår dem, dvs. nu er det ikke længere "hit-and-run"-dataadgang, nu kan du risikere at dit objekt skal snakke med databasen når du tilgår en liste. På grund af dette skal proxien have adgang til en databaseforbindelse, for at den har det skal den være i et såkaldt SessionScope. Et SessionScope er ActiveRecords "wrapper" omkring en databaseforbindelse (hvis det skal forklares enkelt, reelt set er det noget mere end det men mere om det en anden gang). Når vi tilgår data der benytter lazy loading skal vi åbne et SessionScope i den tid vi arbejder med objektet, det kan vi gøre sådan her:

using(new SessionScope()){
	Blog b = Blog.Find(1);
	int count = b.Posts.Count; // Her indlæses dataene først
}

Lazy loading af relaterede objekter

Nu har vi løst problemet for vores collections, men vi har stadig ikke reduceret hvor mange relaterede objekter der hentes ud vha. join'et i den første SQL sætning vi så tidligere. Hvis vi vil reducere dette kan vi markere enkelte klasser som værende Lazy loadet, f.eks. kan vi ved at specificere Lazy parametren til vores ActiveRecord attribut på Blog klassen specificere at denne ikke skal hentes op:

[ActiveRecord(Lazy = true)]
public class Blog : ActiveRecordBase<Blog>{..}

Hvordan gør ActiveRecord det?

Men hov, hvordan håndterer ActiveRecord dette, det vil jo kræve at den kan finde ud af hvornår vi tilgår egenskaben Blog på vores Post instancer. Det er helt korrekt og for at finde ud af dette bliver vores Blog property på Post klassen skiftet ud med et såkaldt proxy objekt. Dette objekt fortæller ActiveRecord når det bliver tilgået og indlæser dataene fra databasen og returnerer dem. Det er mildest talt smart(til at udføre dette bruger ActiveRecord et andet projekt fra Castle nemlig DynamicProxy). Der er dog en ting der er værd at være opmærksom på, for at ActiveRecord kan lave en Proxy af vores type nedarver den fra typen, og for at den kan overskrive vores egenskaber og metoder skal disse være markeret med virtual keywordet. Så husk det, hvis det bliver glemt får vi følgende exception:

NHibernate.InvalidProxyTypeException : The following types may not be used as proxies:
Intellect.BlogWebsite.Model.Blogger: method set_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Name should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Name should be virtual

Lazy loading kan hurtigt blive uoverskueligt at få specificeret korrekt så det passer i alle scenarier. I nogle scenarier i applikationen vil man måske gerne have noget data lazyloadet og i andre noget helt andet. Men bare rolig, det er der også en løsning på, den vender jeg tilbage til om lidt.

Filtrering ved hjælp af kriterier

I ovenstående så vi også hvordan vi finder en enkelt instans frem udfra dens primærnøgle nemlig med Find metoden på klassen.

Lad os se hvordan vi kan filtrere på data udfra andre egenskaber end primærnøglen. Vi starter med et lille eksempel, jeg vil finde alle Blogger instanser hvor Name egenskaben indeholder en bestemt streng

Blogger.FindAll(Expression.Like("Name", "Jakob", MatchMode.Anywhere));

Igen bruger jeg FindAll metoden men denne gang sender jeg en parameter med, og her gør jeg brug af NHibernate. Expression er hvad man kan kalde en factory klasse, den bruges til at generere implementationer af ICriterion interfacet. I ovenstående tilfælde beder jeg om at få et Like expression der sammenligner egenskaben "Name" med strengen "Jakob" og specificerer en MatchMode, ovenstående resulterer i følgende SQL:

SELECT this_.ID as ID4_0_, this_.Name as Name4_0_ FROM Blogger this_ WHERE this_.Name like @p0; @p0 = '%Jakob%'

Som vi kan se indsætter nHibernate selv wildcards i min parameter baseret på den MatchMode jeg har valgt. Jeg kan tilføje flere forskellige ICriterion til min FindAll metode(adskilt med kommaer takket være params keyword på parametren) for at sammensætte komplekse søgninger.

Oversigt over filtreringsmuligheder

Her er en liste med en kort beskrivelse af nogle af de ICriterion Expression factoryen kan genere for os:

And, Or og Not

Expression.And(ICriterion, ICriterion)
Eksempel: Find alle Blogger instanser hvor navnet indeholder både "streng1" og "streng2"
Expression.And(Expression.Like("Name", "streng1", MatchMode.Anywhere), Expression.Like("Name", "streng2", MatchMode.AnyWhere));
Tilsvarende findes
Expression.Or(ICriterion, ICriterion)
Expression.Not(ICriterion)

Between

Expression.Between(string, object, object)

Eksempel: Find alle Post instanser med ID imellem 1 og 10
Expression.Between("ID", 1, 10);

Sammenligning

Expression.Eq(string, object)
Eksempel: Find en Post med Titlen "Introduktion til ActiveRecord"
Expression.Eq("Title", "Introduktion til ActiveRecord");
Tilsvarende findes Expression.Ge(>=), Expression.Le(<=), Expression.Gt(>), Expression.Lt(<) alle disse har også et modstykke der tager navnet på to egenskaber og sammenligner. De hedder EqProperty, GeProperty osv.

Lister

Expression.In(string, ICollection);
Expression.In(string, object[]);
Eksempel: Find alle Post instanser med id 1,2 og 3
Expression.In("ID", new int[]{1,2,3})

NULL håndtering

Expression.IsNull(string);
Expression.IsNotNull(string);
Begge to har en parameter der skal være et navn på en egenskab og den tilsvarende kolonne i databasen vil blive checket om den er/er ikke NULL.

Sortering

Vi kan nu lave noget ret avancerede filtrering i vores data vi kan også sortere ved at sende en instans af Order med til FindAll og derefter specificere filtreringer. Order er en factory ligesom Expression så den bruges sådan her:

Blogger.FindAll(Order.Desc("Name"), Expression.Eq(....));

Lazy loading på forespørgselsniveau

Vi har set hvor let vi kan arbejde med vores data og filtrere hvad vi hiver ud fra databasen, og det er ret simpelt at arbejde med. Men på et tidspunkt rammer vi en mur med denne forespørgselsmekanisme. Som nævnt tidligere kan det være en fordel at specificere hvad der skal lazy loades i det øjeblik vi udfører en forespørgsel. Det kan f.eks. være jeg i et bestemt tilfælde kun ønsker at hive mine Post instanser op men ikke ønsker nogle relaterede instanser loadet med det samme. For at gøre dette skal vi have fat i et koncept fra nHibernate, nemlig en ICriteria query. Nærmere bestemt en DetachedCriteria query:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy);
using (new SessionScope())
{
        Post[] posts = Post.FindAll(postQuery);
	//...
}

Som vi kan se deklarerer vi forespørgslen og sender den derefter til vores FindAll metode. I kaldet til SetFetchMode kan vi specificere hvordan enkelte relationer på Post bliver hentet. Når ovenstående DetachedCriteria eksekveres producerer det følgende SQL:

SELECT this_.ID as ID2_0_, this_.Title as Title2_0_, this_.Text as Text2_0_, this_.Blog as Blog2_0_ FROM Post this_

Kombiner SetFechMode, kriterier og sortering

DetachedCriteria giver os en mulighed for at bruge det vi allerede har lært, ICriterion kan også knyttes på et DetachedCriteria:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy)
                .Add(Expression.Like("Title", "Introduktion til ActiveRecord", MatchMode.Anywhere));

Og vi kan på samme måde tilføje vores sortering vha. AddOrder metoden. FetchMode kan specificeres til enten Lazy, Eager, Join, Select.

Kriterier på tværs af relationer

Udover at specificere lazy loading i vores forespørgsel har vi også mulighed for at lave mere komplekse forespørgsler på tværs af vore relationer ved at tilføje aliaser, f.eks. hvis vi ønsker at hente samtlige Post instanser hvor titlen på den relaterede Blog instans indeholder "streng1":

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .CreateAlias("Blog", "b")
                .Add(Expression.Like("b.Name", "streng1", MatchMode.Anywhere));

Vi kan tilføje et alias til en relationsti og bruge den i vore ICriterion forespørgsler, vi kan også specificere når vi opretter aliaset hvordan data skal hentes ud, ved at angive en jointype som en 3 parameter til CreateAlias metoden. Ovenstående Criteria vil ved eksekvering udføre følgende SQL mod databasen:

SELECT this_.id    AS id2_1_,
       this_.title AS title2_1_,
       this_.TEXT  AS text2_1_,
       this_.blog  AS blog2_1_,
       b1_.id      AS id3_0_,
       b1_.name    AS name3_0_,
       b1_.owner   AS owner3_0_
FROM   post this_
       INNER JOIN blogs b1_
         ON this_.blog = b1_.id
WHERE  b1_.name LIKE @p0;

@p0 = '%streng1%'

Der er mange kombinationsmuligheder, og vi kan med DetachedCriteria have fuld kontrol over hvad data hentes og hvornår, og vi kan forespørge ved at bruge vores relationer ganske let.

Hent en enkelt instans udfra kriterier

Vi har set hvordan vi kan hente en enkelt instans udfra dens identifier og hvordan vi kan hente flere poster med FindAll, en sidste nyttig ting er hvis vi skal hente en enkelt instans op baseret på kriterier, der kan vi bruge FindOne metoden som også kan tage en liste ICriterion eller et DetachedCriteria.

Post p = Post.FindFirst(Expression.Eq("Title", "Introduktion til ActiveRecord"));

Ovenstående vil hente præcis en instans ud så det kan benyttes hvis man er sikkert på at ens forespørgsel returnerer kun et resultatet eller hvis man kun ønsker den første instans i et større resultatetsæt.

Hent en delmængde af et resultatsæt

Ofte ønsker man at præsentere data i sider, så man viser f.eks. 10 resultater af gangen og giver mulighed for at bladre imellem siderne, ActiveRecord gør dette rigtig let. Der findes en udgave af FindAll der kun resulterer en delmængde, den hedder SlicedFindAll, så hvis jeg vil vise f.eks. posterne fra 10 til 20 så kan jeg gøre det sådan her:

Post.SlicedFindAll(10, 20);

Også her har jeg mulighed for vha. overloads af SlicedFindAll at sende hhv. ICriteria, Order og DetachedCriteria med og få returneret en delmængde af disse.

Næste gang

DetachedCriteria har en lang række andre muligheder og jeg kan kun opfordre til at udforske metoderne. I næste post vil jeg se lidt på nogle avancerede features i ActiveRecord bl.a. hvordan vi kan introducere validering af data i vores egenskaber og hvordan vi kan undgå at skulle nedarvre fra ActiveRecordBase<T> hvis man ønsker det.

tag4sree: responding to change

Hent data med Castle ActiveRecord

This post is in danish and is a second part in an introduction to Castle ActiveRecord, if you would like to see this content in english let me know

Introduktion

I sidste post kiggede vi på hvordan vi kan specificere en sammenhæng mellem vores klasser og vores database med Castle ActiveRecord. Nu er det på tide at finde dataene frem igen og se lidt på hvordan Castle ActiveRecord egentlig håndterer de komplekse situationer. F.eks de relationer vi specificerede sidst mellem vores klasser.

Forudsætninger

Som nævnt i sidste post er Castle ActiveRecord et abstraktionsniveau over nHibernate, dvs. det pakker nHibernate ind på en pæn måde der gør det lettere at bruge, dog vil vi se at det ofte er nødvendigt at bruge nHibernate funktionalitet for at få fuld fleksibilitet. Det vender jeg tilbage til senere i denne post.

Lad os først se på den mest simple måde at hente data med de ActiveRecord klasser vi har konstrueret. Men før vi går igang med det skal vi tilføje en reference til NHibernate.dll til vores projekt da funktionalitet herfra bliver brugt i nogle af de metoder vi skal til at kalde.

Simpel forespørgsel

Når vi har fået det indledende på plads kan vi eksekvere nedenstående kode:

Post[] posts = Post.FindAll();

Det vil returnere en liste med de instancer af Post der er gemt i databasen, hvis vi kigger på hvilke SQL sætninger dette generer kan man blive lidt overrasket, Post er relateret til alle vores objekter da en post tilhører en Blog der er ejet af en Blogger og udover det har en Post en række Tag's tilknyttet, da vi ikke har fortalt ActiveRecord andet tror den at vi ønsker at få alt data med det samme, så følgende SQL bliver eksekveret:

-- Query 1
SELECT this_.id       AS id0_2_,
       this_.title    AS title0_2_,
       this_.TEXT     AS text0_2_,
       this_.blog     AS blog0_2_,
       blog2_.id      AS id2_0_,
       blog2_.name    AS name2_0_,
       blog2_.owner   AS owner2_0_,
       blogger3_.id   AS id3_1_,
       blogger3_.name AS name3_1_
FROM   post this_
       LEFT OUTER JOIN blogs blog2_
         ON this_.blog = blog2_.id
       LEFT OUTER JOIN blogger blogger3_
         ON blog2_.owner = blogger3_.id

-- Query 2
SELECT tags0_.postid AS postid__1_,
       tags0_.tagid  AS tagid1_,
       tag1_.id      AS id4_0_,
       tag1_.name    AS name4_0_,
       tag1_.TEXT    AS text4_0_
FROM   posttags tags0_
       LEFT OUTER JOIN tag tag1_
         ON tags0_.tagid = tag1_.id
WHERE  tags0_.postid = @p0;

@p0 = '1'

Lazy loading af collections

Vi kan udfra ovenstående SQL tydeligt se at ActiveRecord forsøger at hente alt data. Der laves joins i vores første query for at få fat i hhv. information om Blog instansen bundet til Post samt Blogger instansen bundet til Blog instansen. ActiveRecord (eller rettere nHibernate) er smart nok til at tage dette flade resultatsæt og omforme til vores objektgraf. Efterfølgende kan vi se i Query 2 at vores mange-til-mange relation bliver forespurgt, her henter ActiveRecord all de tags ud der er tilknyttet den ene post jeg har i min database og henter hvert enkelt tag ud. Faktisk har jeg ALT data i min applikation hevet op vha ovenstående query, det kan vi hurtigt blive enige om ikke er smart, så derfor skal vi tilbage og rette på nogle ting i vores relations-attributter. Hvis vi ønsker data ikke skal indlæses med det samme men ved første adgang til det kan vi specificere lazy attributten, så lad os sige jeg ikke ønsker at hente alle Posts op når jeg henter en blog specificerer jeg følgende i min HasMany attribut:

[HasMany(Lazy = true)]
public IList<post> Posts{...}

Det vil medføre at vores Posts collection ikke vil blive hentet op når vi henter en instans af Blog klassen op.

Databaseforbindelser ved Lazy loading

Det er vigtigt at forstå i denne sammenhæng at ActiveRecord nu skal bruge en databaseforbindelse til at indlæse dataene når du tilgår dem, dvs. nu er det ikke længere "hit-and-run"-dataadgang, nu kan du risikere at dit objekt skal snakke med databasen når du tilgår en liste. På grund af dette skal proxien have adgang til en databaseforbindelse, for at den har det skal den være i et såkaldt SessionScope. Et SessionScope er ActiveRecords "wrapper" omkring en databaseforbindelse (hvis det skal forklares enkelt, reelt set er det noget mere end det men mere om det en anden gang). Når vi tilgår data der benytter lazy loading skal vi åbne et SessionScope i den tid vi arbejder med objektet, det kan vi gøre sådan her:

using(new SessionScope()){
	Blog b = Blog.Find(1);
	int count = b.Posts.Count; // Her indlæses dataene først
}

Lazy loading af relaterede objekter

Nu har vi løst problemet for vores collections, men vi har stadig ikke reduceret hvor mange relaterede objekter der hentes ud vha. join'et i den første SQL sætning vi så tidligere. Hvis vi vil reducere dette kan vi markere enkelte klasser som værende Lazy loadet, f.eks. kan vi ved at specificere Lazy parametren til vores ActiveRecord attribut på Blog klassen specificere at denne ikke skal hentes op:

[ActiveRecord(Lazy = true)]
public class Blog : ActiveRecordBase<Blog>{..}

Hvordan gør ActiveRecord det?

Men hov, hvordan håndterer ActiveRecord dette, det vil jo kræve at den kan finde ud af hvornår vi tilgår egenskaben Blog på vores Post instancer. Det er helt korrekt og for at finde ud af dette bliver vores Blog property på Post klassen skiftet ud med et såkaldt proxy objekt. Dette objekt fortæller ActiveRecord når det bliver tilgået og indlæser dataene fra databasen og returnerer dem. Det er mildest talt smart(til at udføre dette bruger ActiveRecord et andet projekt fra Castle nemlig DynamicProxy). Der er dog en ting der er værd at være opmærksom på, for at ActiveRecord kan lave en Proxy af vores type nedarver den fra typen, og for at den kan overskrive vores egenskaber og metoder skal disse være markeret med virtual keywordet. Så husk det, hvis det bliver glemt får vi følgende exception:

NHibernate.InvalidProxyTypeException : The following types may not be used as proxies:
Intellect.BlogWebsite.Model.Blogger: method set_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Name should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Name should be virtual

Lazy loading kan hurtigt blive uoverskueligt at få specificeret korrekt så det passer i alle scenarier. I nogle scenarier i applikationen vil man måske gerne have noget data lazyloadet og i andre noget helt andet. Men bare rolig, det er der også en løsning på, den vender jeg tilbage til om lidt.

Filtrering ved hjælp af kriterier

I ovenstående så vi også hvordan vi finder en enkelt instans frem udfra dens primærnøgle nemlig med Find metoden på klassen.

Lad os se hvordan vi kan filtrere på data udfra andre egenskaber end primærnøglen. Vi starter med et lille eksempel, jeg vil finde alle Blogger instanser hvor Name egenskaben indeholder en bestemt streng

Blogger.FindAll(Expression.Like("Name", "Jakob", MatchMode.Anywhere));

Igen bruger jeg FindAll metoden men denne gang sender jeg en parameter med, og her gør jeg brug af NHibernate. Expression er hvad man kan kalde en factory klasse, den bruges til at generere implementationer af ICriterion interfacet. I ovenstående tilfælde beder jeg om at få et Like expression der sammenligner egenskaben "Name" med strengen "Jakob" og specificerer en MatchMode, ovenstående resulterer i følgende SQL:

SELECT this_.ID as ID4_0_, this_.Name as Name4_0_ FROM Blogger this_ WHERE this_.Name like @p0; @p0 = '%Jakob%'

Som vi kan se indsætter nHibernate selv wildcards i min parameter baseret på den MatchMode jeg har valgt. Jeg kan tilføje flere forskellige ICriterion til min FindAll metode(adskilt med kommaer takket være params keyword på parametren) for at sammensætte komplekse søgninger.

Oversigt over filtreringsmuligheder

Her er en liste med en kort beskrivelse af nogle af de ICriterion Expression factoryen kan genere for os:

And, Or og Not

Expression.And(ICriterion, ICriterion)
Eksempel: Find alle Blogger instanser hvor navnet indeholder både "streng1" og "streng2"
Expression.And(Expression.Like("Name", "streng1", MatchMode.Anywhere), Expression.Like("Name", "streng2", MatchMode.AnyWhere));
Tilsvarende findes
Expression.Or(ICriterion, ICriterion)
Expression.Not(ICriterion)

Between

Expression.Between(string, object, object)

Eksempel: Find alle Post instanser med ID imellem 1 og 10
Expression.Between("ID", 1, 10);

Sammenligning

Expression.Eq(string, object)
Eksempel: Find en Post med Titlen "Introduktion til ActiveRecord"
Expression.Eq("Title", "Introduktion til ActiveRecord");
Tilsvarende findes Expression.Ge(>=), Expression.Le(<=), Expression.Gt(>), Expression.Lt(<) alle disse har også et modstykke der tager navnet på to egenskaber og sammenligner. De hedder EqProperty, GeProperty osv.

Lister

Expression.In(string, ICollection);
Expression.In(string, object[]);
Eksempel: Find alle Post instanser med id 1,2 og 3
Expression.In("ID", new int[]{1,2,3})

NULL håndtering

Expression.IsNull(string);
Expression.IsNotNull(string);
Begge to har en parameter der skal være et navn på en egenskab og den tilsvarende kolonne i databasen vil blive checket om den er/er ikke NULL.

Sortering

Vi kan nu lave noget ret avancerede filtrering i vores data vi kan også sortere ved at sende en instans af Order med til FindAll og derefter specificere filtreringer. Order er en factory ligesom Expression så den bruges sådan her:

Blogger.FindAll(Order.Desc("Name"), Expression.Eq(....));

Lazy loading på forespørgselsniveau

Vi har set hvor let vi kan arbejde med vores data og filtrere hvad vi hiver ud fra databasen, og det er ret simpelt at arbejde med. Men på et tidspunkt rammer vi en mur med denne forespørgselsmekanisme. Som nævnt tidligere kan det være en fordel at specificere hvad der skal lazy loades i det øjeblik vi udfører en forespørgsel. Det kan f.eks. være jeg i et bestemt tilfælde kun ønsker at hive mine Post instanser op men ikke ønsker nogle relaterede instanser loadet med det samme. For at gøre dette skal vi have fat i et koncept fra nHibernate, nemlig en ICriteria query. Nærmere bestemt en DetachedCriteria query:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy);
using (new SessionScope())
{
        Post[] posts = Post.FindAll(postQuery);
	//...
}

Som vi kan se deklarerer vi forespørgslen og sender den derefter til vores FindAll metode. I kaldet til SetFetchMode kan vi specificere hvordan enkelte relationer på Post bliver hentet. Når ovenstående DetachedCriteria eksekveres producerer det følgende SQL:

SELECT this_.ID as ID2_0_, this_.Title as Title2_0_, this_.Text as Text2_0_, this_.Blog as Blog2_0_ FROM Post this_

Kombiner SetFechMode, kriterier og sortering

DetachedCriteria giver os en mulighed for at bruge det vi allerede har lært, ICriterion kan også knyttes på et DetachedCriteria:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy)
                .Add(Expression.Like("Title", "Introduktion til ActiveRecord", MatchMode.Anywhere));

Og vi kan på samme måde tilføje vores sortering vha. AddOrder metoden. FetchMode kan specificeres til enten Lazy, Eager, Join, Select.

Kriterier på tværs af relationer

Udover at specificere lazy loading i vores forespørgsel har vi også mulighed for at lave mere komplekse forespørgsler på tværs af vore relationer ved at tilføje aliaser, f.eks. hvis vi ønsker at hente samtlige Post instanser hvor titlen på den relaterede Blog instans indeholder "streng1":

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .CreateAlias("Blog", "b")
                .Add(Expression.Like("b.Name", "streng1", MatchMode.Anywhere));

Vi kan tilføje et alias til en relationsti og bruge den i vore ICriterion forespørgsler, vi kan også specificere når vi opretter aliaset hvordan data skal hentes ud, ved at angive en jointype som en 3 parameter til CreateAlias metoden. Ovenstående Criteria vil ved eksekvering udføre følgende SQL mod databasen:

SELECT this_.id    AS id2_1_,
       this_.title AS title2_1_,
       this_.TEXT  AS text2_1_,
       this_.blog  AS blog2_1_,
       b1_.id      AS id3_0_,
       b1_.name    AS name3_0_,
       b1_.owner   AS owner3_0_
FROM   post this_
       INNER JOIN blogs b1_
         ON this_.blog = b1_.id
WHERE  b1_.name LIKE @p0;

@p0 = '%streng1%'

Der er mange kombinationsmuligheder, og vi kan med DetachedCriteria have fuld kontrol over hvad data hentes og hvornår, og vi kan forespørge ved at bruge vores relationer ganske let.

Hent en enkelt instans udfra kriterier

Vi har set hvordan vi kan hente en enkelt instans udfra dens identifier og hvordan vi kan hente flere poster med FindAll, en sidste nyttig ting er hvis vi skal hente en enkelt instans op baseret på kriterier, der kan vi bruge FindOne metoden som også kan tage en liste ICriterion eller et DetachedCriteria.

Post p = Post.FindFirst(Expression.Eq("Title", "Introduktion til ActiveRecord"));

Ovenstående vil hente præcis en instans ud så det kan benyttes hvis man er sikkert på at ens forespørgsel returnerer kun et resultatet eller hvis man kun ønsker den første instans i et større resultatetsæt.

Hent en delmængde af et resultatsæt

Ofte ønsker man at præsentere data i sider, så man viser f.eks. 10 resultater af gangen og giver mulighed for at bladre imellem siderne, ActiveRecord gør dette rigtig let. Der findes en udgave af FindAll der kun resulterer en delmængde, den hedder SlicedFindAll, så hvis jeg vil vise f.eks. posterne fra 10 til 20 så kan jeg gøre det sådan her:

Post.SlicedFindAll(10, 20);

Også her har jeg mulighed for vha. overloads af SlicedFindAll at sende hhv. ICriteria, Order og DetachedCriteria med og få returneret en delmængde af disse.

Næste gang

DetachedCriteria har en lang række andre muligheder og jeg kan kun opfordre til at udforske metoderne. I næste post vil jeg se lidt på nogle avancerede features i ActiveRecord bl.a. hvordan vi kan introducere validering af data i vores egenskaber og hvordan vi kan undgå at skulle nedarvre fra ActiveRecordBase<T> hvis man ønsker det.

tag4sree: Detached objects in nHibernate and Lazy loading

Hent data med Castle ActiveRecord

This post is in danish and is a second part in an introduction to Castle ActiveRecord, if you would like to see this content in english let me know

Introduktion

I sidste post kiggede vi på hvordan vi kan specificere en sammenhæng mellem vores klasser og vores database med Castle ActiveRecord. Nu er det på tide at finde dataene frem igen og se lidt på hvordan Castle ActiveRecord egentlig håndterer de komplekse situationer. F.eks de relationer vi specificerede sidst mellem vores klasser.

Forudsætninger

Som nævnt i sidste post er Castle ActiveRecord et abstraktionsniveau over nHibernate, dvs. det pakker nHibernate ind på en pæn måde der gør det lettere at bruge, dog vil vi se at det ofte er nødvendigt at bruge nHibernate funktionalitet for at få fuld fleksibilitet. Det vender jeg tilbage til senere i denne post.

Lad os først se på den mest simple måde at hente data med de ActiveRecord klasser vi har konstrueret. Men før vi går igang med det skal vi tilføje en reference til NHibernate.dll til vores projekt da funktionalitet herfra bliver brugt i nogle af de metoder vi skal til at kalde.

Simpel forespørgsel

Når vi har fået det indledende på plads kan vi eksekvere nedenstående kode:

Post[] posts = Post.FindAll();

Det vil returnere en liste med de instancer af Post der er gemt i databasen, hvis vi kigger på hvilke SQL sætninger dette generer kan man blive lidt overrasket, Post er relateret til alle vores objekter da en post tilhører en Blog der er ejet af en Blogger og udover det har en Post en række Tag's tilknyttet, da vi ikke har fortalt ActiveRecord andet tror den at vi ønsker at få alt data med det samme, så følgende SQL bliver eksekveret:

-- Query 1
SELECT this_.id       AS id0_2_,
       this_.title    AS title0_2_,
       this_.TEXT     AS text0_2_,
       this_.blog     AS blog0_2_,
       blog2_.id      AS id2_0_,
       blog2_.name    AS name2_0_,
       blog2_.owner   AS owner2_0_,
       blogger3_.id   AS id3_1_,
       blogger3_.name AS name3_1_
FROM   post this_
       LEFT OUTER JOIN blogs blog2_
         ON this_.blog = blog2_.id
       LEFT OUTER JOIN blogger blogger3_
         ON blog2_.owner = blogger3_.id

-- Query 2
SELECT tags0_.postid AS postid__1_,
       tags0_.tagid  AS tagid1_,
       tag1_.id      AS id4_0_,
       tag1_.name    AS name4_0_,
       tag1_.TEXT    AS text4_0_
FROM   posttags tags0_
       LEFT OUTER JOIN tag tag1_
         ON tags0_.tagid = tag1_.id
WHERE  tags0_.postid = @p0;

@p0 = '1'

Lazy loading af collections

Vi kan udfra ovenstående SQL tydeligt se at ActiveRecord forsøger at hente alt data. Der laves joins i vores første query for at få fat i hhv. information om Blog instansen bundet til Post samt Blogger instansen bundet til Blog instansen. ActiveRecord (eller rettere nHibernate) er smart nok til at tage dette flade resultatsæt og omforme til vores objektgraf. Efterfølgende kan vi se i Query 2 at vores mange-til-mange relation bliver forespurgt, her henter ActiveRecord all de tags ud der er tilknyttet den ene post jeg har i min database og henter hvert enkelt tag ud. Faktisk har jeg ALT data i min applikation hevet op vha ovenstående query, det kan vi hurtigt blive enige om ikke er smart, så derfor skal vi tilbage og rette på nogle ting i vores relations-attributter. Hvis vi ønsker data ikke skal indlæses med det samme men ved første adgang til det kan vi specificere lazy attributten, så lad os sige jeg ikke ønsker at hente alle Posts op når jeg henter en blog specificerer jeg følgende i min HasMany attribut:

[HasMany(Lazy = true)]
public IList<post> Posts{...}

Det vil medføre at vores Posts collection ikke vil blive hentet op når vi henter en instans af Blog klassen op.

Databaseforbindelser ved Lazy loading

Det er vigtigt at forstå i denne sammenhæng at ActiveRecord nu skal bruge en databaseforbindelse til at indlæse dataene når du tilgår dem, dvs. nu er det ikke længere "hit-and-run"-dataadgang, nu kan du risikere at dit objekt skal snakke med databasen når du tilgår en liste. På grund af dette skal proxien have adgang til en databaseforbindelse, for at den har det skal den være i et såkaldt SessionScope. Et SessionScope er ActiveRecords "wrapper" omkring en databaseforbindelse (hvis det skal forklares enkelt, reelt set er det noget mere end det men mere om det en anden gang). Når vi tilgår data der benytter lazy loading skal vi åbne et SessionScope i den tid vi arbejder med objektet, det kan vi gøre sådan her:

using(new SessionScope()){
	Blog b = Blog.Find(1);
	int count = b.Posts.Count; // Her indlæses dataene først
}

Lazy loading af relaterede objekter

Nu har vi løst problemet for vores collections, men vi har stadig ikke reduceret hvor mange relaterede objekter der hentes ud vha. join'et i den første SQL sætning vi så tidligere. Hvis vi vil reducere dette kan vi markere enkelte klasser som værende Lazy loadet, f.eks. kan vi ved at specificere Lazy parametren til vores ActiveRecord attribut på Blog klassen specificere at denne ikke skal hentes op:

[ActiveRecord(Lazy = true)]
public class Blog : ActiveRecordBase<Blog>{..}

Hvordan gør ActiveRecord det?

Men hov, hvordan håndterer ActiveRecord dette, det vil jo kræve at den kan finde ud af hvornår vi tilgår egenskaben Blog på vores Post instancer. Det er helt korrekt og for at finde ud af dette bliver vores Blog property på Post klassen skiftet ud med et såkaldt proxy objekt. Dette objekt fortæller ActiveRecord når det bliver tilgået og indlæser dataene fra databasen og returnerer dem. Det er mildest talt smart(til at udføre dette bruger ActiveRecord et andet projekt fra Castle nemlig DynamicProxy). Der er dog en ting der er værd at være opmærksom på, for at ActiveRecord kan lave en Proxy af vores type nedarver den fra typen, og for at den kan overskrive vores egenskaber og metoder skal disse være markeret med virtual keywordet. Så husk det, hvis det bliver glemt får vi følgende exception:

NHibernate.InvalidProxyTypeException : The following types may not be used as proxies:
Intellect.BlogWebsite.Model.Blogger: method set_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Name should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Name should be virtual

Lazy loading kan hurtigt blive uoverskueligt at få specificeret korrekt så det passer i alle scenarier. I nogle scenarier i applikationen vil man måske gerne have noget data lazyloadet og i andre noget helt andet. Men bare rolig, det er der også en løsning på, den vender jeg tilbage til om lidt.

Filtrering ved hjælp af kriterier

I ovenstående så vi også hvordan vi finder en enkelt instans frem udfra dens primærnøgle nemlig med Find metoden på klassen.

Lad os se hvordan vi kan filtrere på data udfra andre egenskaber end primærnøglen. Vi starter med et lille eksempel, jeg vil finde alle Blogger instanser hvor Name egenskaben indeholder en bestemt streng

Blogger.FindAll(Expression.Like("Name", "Jakob", MatchMode.Anywhere));

Igen bruger jeg FindAll metoden men denne gang sender jeg en parameter med, og her gør jeg brug af NHibernate. Expression er hvad man kan kalde en factory klasse, den bruges til at generere implementationer af ICriterion interfacet. I ovenstående tilfælde beder jeg om at få et Like expression der sammenligner egenskaben "Name" med strengen "Jakob" og specificerer en MatchMode, ovenstående resulterer i følgende SQL:

SELECT this_.ID as ID4_0_, this_.Name as Name4_0_ FROM Blogger this_ WHERE this_.Name like @p0; @p0 = '%Jakob%'

Som vi kan se indsætter nHibernate selv wildcards i min parameter baseret på den MatchMode jeg har valgt. Jeg kan tilføje flere forskellige ICriterion til min FindAll metode(adskilt med kommaer takket være params keyword på parametren) for at sammensætte komplekse søgninger.

Oversigt over filtreringsmuligheder

Her er en liste med en kort beskrivelse af nogle af de ICriterion Expression factoryen kan genere for os:

And, Or og Not

Expression.And(ICriterion, ICriterion)
Eksempel: Find alle Blogger instanser hvor navnet indeholder både "streng1" og "streng2"
Expression.And(Expression.Like("Name", "streng1", MatchMode.Anywhere), Expression.Like("Name", "streng2", MatchMode.AnyWhere));
Tilsvarende findes
Expression.Or(ICriterion, ICriterion)
Expression.Not(ICriterion)

Between

Expression.Between(string, object, object)

Eksempel: Find alle Post instanser med ID imellem 1 og 10
Expression.Between("ID", 1, 10);

Sammenligning

Expression.Eq(string, object)
Eksempel: Find en Post med Titlen "Introduktion til ActiveRecord"
Expression.Eq("Title", "Introduktion til ActiveRecord");
Tilsvarende findes Expression.Ge(>=), Expression.Le(<=), Expression.Gt(>), Expression.Lt(<) alle disse har også et modstykke der tager navnet på to egenskaber og sammenligner. De hedder EqProperty, GeProperty osv.

Lister

Expression.In(string, ICollection);
Expression.In(string, object[]);
Eksempel: Find alle Post instanser med id 1,2 og 3
Expression.In("ID", new int[]{1,2,3})

NULL håndtering

Expression.IsNull(string);
Expression.IsNotNull(string);
Begge to har en parameter der skal være et navn på en egenskab og den tilsvarende kolonne i databasen vil blive checket om den er/er ikke NULL.

Sortering

Vi kan nu lave noget ret avancerede filtrering i vores data vi kan også sortere ved at sende en instans af Order med til FindAll og derefter specificere filtreringer. Order er en factory ligesom Expression så den bruges sådan her:

Blogger.FindAll(Order.Desc("Name"), Expression.Eq(....));

Lazy loading på forespørgselsniveau

Vi har set hvor let vi kan arbejde med vores data og filtrere hvad vi hiver ud fra databasen, og det er ret simpelt at arbejde med. Men på et tidspunkt rammer vi en mur med denne forespørgselsmekanisme. Som nævnt tidligere kan det være en fordel at specificere hvad der skal lazy loades i det øjeblik vi udfører en forespørgsel. Det kan f.eks. være jeg i et bestemt tilfælde kun ønsker at hive mine Post instanser op men ikke ønsker nogle relaterede instanser loadet med det samme. For at gøre dette skal vi have fat i et koncept fra nHibernate, nemlig en ICriteria query. Nærmere bestemt en DetachedCriteria query:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy);
using (new SessionScope())
{
        Post[] posts = Post.FindAll(postQuery);
	//...
}

Som vi kan se deklarerer vi forespørgslen og sender den derefter til vores FindAll metode. I kaldet til SetFetchMode kan vi specificere hvordan enkelte relationer på Post bliver hentet. Når ovenstående DetachedCriteria eksekveres producerer det følgende SQL:

SELECT this_.ID as ID2_0_, this_.Title as Title2_0_, this_.Text as Text2_0_, this_.Blog as Blog2_0_ FROM Post this_

Kombiner SetFechMode, kriterier og sortering

DetachedCriteria giver os en mulighed for at bruge det vi allerede har lært, ICriterion kan også knyttes på et DetachedCriteria:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy)
                .Add(Expression.Like("Title", "Introduktion til ActiveRecord", MatchMode.Anywhere));

Og vi kan på samme måde tilføje vores sortering vha. AddOrder metoden. FetchMode kan specificeres til enten Lazy, Eager, Join, Select.

Kriterier på tværs af relationer

Udover at specificere lazy loading i vores forespørgsel har vi også mulighed for at lave mere komplekse forespørgsler på tværs af vore relationer ved at tilføje aliaser, f.eks. hvis vi ønsker at hente samtlige Post instanser hvor titlen på den relaterede Blog instans indeholder "streng1":

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .CreateAlias("Blog", "b")
                .Add(Expression.Like("b.Name", "streng1", MatchMode.Anywhere));

Vi kan tilføje et alias til en relationsti og bruge den i vore ICriterion forespørgsler, vi kan også specificere når vi opretter aliaset hvordan data skal hentes ud, ved at angive en jointype som en 3 parameter til CreateAlias metoden. Ovenstående Criteria vil ved eksekvering udføre følgende SQL mod databasen:

SELECT this_.id    AS id2_1_,
       this_.title AS title2_1_,
       this_.TEXT  AS text2_1_,
       this_.blog  AS blog2_1_,
       b1_.id      AS id3_0_,
       b1_.name    AS name3_0_,
       b1_.owner   AS owner3_0_
FROM   post this_
       INNER JOIN blogs b1_
         ON this_.blog = b1_.id
WHERE  b1_.name LIKE @p0;

@p0 = '%streng1%'

Der er mange kombinationsmuligheder, og vi kan med DetachedCriteria have fuld kontrol over hvad data hentes og hvornår, og vi kan forespørge ved at bruge vores relationer ganske let.

Hent en enkelt instans udfra kriterier

Vi har set hvordan vi kan hente en enkelt instans udfra dens identifier og hvordan vi kan hente flere poster med FindAll, en sidste nyttig ting er hvis vi skal hente en enkelt instans op baseret på kriterier, der kan vi bruge FindOne metoden som også kan tage en liste ICriterion eller et DetachedCriteria.

Post p = Post.FindFirst(Expression.Eq("Title", "Introduktion til ActiveRecord"));

Ovenstående vil hente præcis en instans ud så det kan benyttes hvis man er sikkert på at ens forespørgsel returnerer kun et resultatet eller hvis man kun ønsker den første instans i et større resultatetsæt.

Hent en delmængde af et resultatsæt

Ofte ønsker man at præsentere data i sider, så man viser f.eks. 10 resultater af gangen og giver mulighed for at bladre imellem siderne, ActiveRecord gør dette rigtig let. Der findes en udgave af FindAll der kun resulterer en delmængde, den hedder SlicedFindAll, så hvis jeg vil vise f.eks. posterne fra 10 til 20 så kan jeg gøre det sådan her:

Post.SlicedFindAll(10, 20);

Også her har jeg mulighed for vha. overloads af SlicedFindAll at sende hhv. ICriteria, Order og DetachedCriteria med og få returneret en delmængde af disse.

Næste gang

DetachedCriteria har en lang række andre muligheder og jeg kan kun opfordre til at udforske metoderne. I næste post vil jeg se lidt på nogle avancerede features i ActiveRecord bl.a. hvordan vi kan introducere validering af data i vores egenskaber og hvordan vi kan undgå at skulle nedarvre fra ActiveRecordBase<T> hvis man ønsker det.

tag4sree: Hibernate - Objects

Hent data med Castle ActiveRecord

This post is in danish and is a second part in an introduction to Castle ActiveRecord, if you would like to see this content in english let me know

Introduktion

I sidste post kiggede vi på hvordan vi kan specificere en sammenhæng mellem vores klasser og vores database med Castle ActiveRecord. Nu er det på tide at finde dataene frem igen og se lidt på hvordan Castle ActiveRecord egentlig håndterer de komplekse situationer. F.eks de relationer vi specificerede sidst mellem vores klasser.

Forudsætninger

Som nævnt i sidste post er Castle ActiveRecord et abstraktionsniveau over nHibernate, dvs. det pakker nHibernate ind på en pæn måde der gør det lettere at bruge, dog vil vi se at det ofte er nødvendigt at bruge nHibernate funktionalitet for at få fuld fleksibilitet. Det vender jeg tilbage til senere i denne post.

Lad os først se på den mest simple måde at hente data med de ActiveRecord klasser vi har konstrueret. Men før vi går igang med det skal vi tilføje en reference til NHibernate.dll til vores projekt da funktionalitet herfra bliver brugt i nogle af de metoder vi skal til at kalde.

Simpel forespørgsel

Når vi har fået det indledende på plads kan vi eksekvere nedenstående kode:

Post[] posts = Post.FindAll();

Det vil returnere en liste med de instancer af Post der er gemt i databasen, hvis vi kigger på hvilke SQL sætninger dette generer kan man blive lidt overrasket, Post er relateret til alle vores objekter da en post tilhører en Blog der er ejet af en Blogger og udover det har en Post en række Tag's tilknyttet, da vi ikke har fortalt ActiveRecord andet tror den at vi ønsker at få alt data med det samme, så følgende SQL bliver eksekveret:

-- Query 1
SELECT this_.id       AS id0_2_,
       this_.title    AS title0_2_,
       this_.TEXT     AS text0_2_,
       this_.blog     AS blog0_2_,
       blog2_.id      AS id2_0_,
       blog2_.name    AS name2_0_,
       blog2_.owner   AS owner2_0_,
       blogger3_.id   AS id3_1_,
       blogger3_.name AS name3_1_
FROM   post this_
       LEFT OUTER JOIN blogs blog2_
         ON this_.blog = blog2_.id
       LEFT OUTER JOIN blogger blogger3_
         ON blog2_.owner = blogger3_.id

-- Query 2
SELECT tags0_.postid AS postid__1_,
       tags0_.tagid  AS tagid1_,
       tag1_.id      AS id4_0_,
       tag1_.name    AS name4_0_,
       tag1_.TEXT    AS text4_0_
FROM   posttags tags0_
       LEFT OUTER JOIN tag tag1_
         ON tags0_.tagid = tag1_.id
WHERE  tags0_.postid = @p0;

@p0 = '1'

Lazy loading af collections

Vi kan udfra ovenstående SQL tydeligt se at ActiveRecord forsøger at hente alt data. Der laves joins i vores første query for at få fat i hhv. information om Blog instansen bundet til Post samt Blogger instansen bundet til Blog instansen. ActiveRecord (eller rettere nHibernate) er smart nok til at tage dette flade resultatsæt og omforme til vores objektgraf. Efterfølgende kan vi se i Query 2 at vores mange-til-mange relation bliver forespurgt, her henter ActiveRecord all de tags ud der er tilknyttet den ene post jeg har i min database og henter hvert enkelt tag ud. Faktisk har jeg ALT data i min applikation hevet op vha ovenstående query, det kan vi hurtigt blive enige om ikke er smart, så derfor skal vi tilbage og rette på nogle ting i vores relations-attributter. Hvis vi ønsker data ikke skal indlæses med det samme men ved første adgang til det kan vi specificere lazy attributten, så lad os sige jeg ikke ønsker at hente alle Posts op når jeg henter en blog specificerer jeg følgende i min HasMany attribut:

[HasMany(Lazy = true)]
public IList<post> Posts{...}

Det vil medføre at vores Posts collection ikke vil blive hentet op når vi henter en instans af Blog klassen op.

Databaseforbindelser ved Lazy loading

Det er vigtigt at forstå i denne sammenhæng at ActiveRecord nu skal bruge en databaseforbindelse til at indlæse dataene når du tilgår dem, dvs. nu er det ikke længere "hit-and-run"-dataadgang, nu kan du risikere at dit objekt skal snakke med databasen når du tilgår en liste. På grund af dette skal proxien have adgang til en databaseforbindelse, for at den har det skal den være i et såkaldt SessionScope. Et SessionScope er ActiveRecords "wrapper" omkring en databaseforbindelse (hvis det skal forklares enkelt, reelt set er det noget mere end det men mere om det en anden gang). Når vi tilgår data der benytter lazy loading skal vi åbne et SessionScope i den tid vi arbejder med objektet, det kan vi gøre sådan her:

using(new SessionScope()){
	Blog b = Blog.Find(1);
	int count = b.Posts.Count; // Her indlæses dataene først
}

Lazy loading af relaterede objekter

Nu har vi løst problemet for vores collections, men vi har stadig ikke reduceret hvor mange relaterede objekter der hentes ud vha. join'et i den første SQL sætning vi så tidligere. Hvis vi vil reducere dette kan vi markere enkelte klasser som værende Lazy loadet, f.eks. kan vi ved at specificere Lazy parametren til vores ActiveRecord attribut på Blog klassen specificere at denne ikke skal hentes op:

[ActiveRecord(Lazy = true)]
public class Blog : ActiveRecordBase<Blog>{..}

Hvordan gør ActiveRecord det?

Men hov, hvordan håndterer ActiveRecord dette, det vil jo kræve at den kan finde ud af hvornår vi tilgår egenskaben Blog på vores Post instancer. Det er helt korrekt og for at finde ud af dette bliver vores Blog property på Post klassen skiftet ud med et såkaldt proxy objekt. Dette objekt fortæller ActiveRecord når det bliver tilgået og indlæser dataene fra databasen og returnerer dem. Det er mildest talt smart(til at udføre dette bruger ActiveRecord et andet projekt fra Castle nemlig DynamicProxy). Der er dog en ting der er værd at være opmærksom på, for at ActiveRecord kan lave en Proxy af vores type nedarver den fra typen, og for at den kan overskrive vores egenskaber og metoder skal disse være markeret med virtual keywordet. Så husk det, hvis det bliver glemt får vi følgende exception:

NHibernate.InvalidProxyTypeException : The following types may not be used as proxies:
Intellect.BlogWebsite.Model.Blogger: method set_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Name should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Name should be virtual

Lazy loading kan hurtigt blive uoverskueligt at få specificeret korrekt så det passer i alle scenarier. I nogle scenarier i applikationen vil man måske gerne have noget data lazyloadet og i andre noget helt andet. Men bare rolig, det er der også en løsning på, den vender jeg tilbage til om lidt.

Filtrering ved hjælp af kriterier

I ovenstående så vi også hvordan vi finder en enkelt instans frem udfra dens primærnøgle nemlig med Find metoden på klassen.

Lad os se hvordan vi kan filtrere på data udfra andre egenskaber end primærnøglen. Vi starter med et lille eksempel, jeg vil finde alle Blogger instanser hvor Name egenskaben indeholder en bestemt streng

Blogger.FindAll(Expression.Like("Name", "Jakob", MatchMode.Anywhere));

Igen bruger jeg FindAll metoden men denne gang sender jeg en parameter med, og her gør jeg brug af NHibernate. Expression er hvad man kan kalde en factory klasse, den bruges til at generere implementationer af ICriterion interfacet. I ovenstående tilfælde beder jeg om at få et Like expression der sammenligner egenskaben "Name" med strengen "Jakob" og specificerer en MatchMode, ovenstående resulterer i følgende SQL:

SELECT this_.ID as ID4_0_, this_.Name as Name4_0_ FROM Blogger this_ WHERE this_.Name like @p0; @p0 = '%Jakob%'

Som vi kan se indsætter nHibernate selv wildcards i min parameter baseret på den MatchMode jeg har valgt. Jeg kan tilføje flere forskellige ICriterion til min FindAll metode(adskilt med kommaer takket være params keyword på parametren) for at sammensætte komplekse søgninger.

Oversigt over filtreringsmuligheder

Her er en liste med en kort beskrivelse af nogle af de ICriterion Expression factoryen kan genere for os:

And, Or og Not

Expression.And(ICriterion, ICriterion)
Eksempel: Find alle Blogger instanser hvor navnet indeholder både "streng1" og "streng2"
Expression.And(Expression.Like("Name", "streng1", MatchMode.Anywhere), Expression.Like("Name", "streng2", MatchMode.AnyWhere));
Tilsvarende findes
Expression.Or(ICriterion, ICriterion)
Expression.Not(ICriterion)

Between

Expression.Between(string, object, object)

Eksempel: Find alle Post instanser med ID imellem 1 og 10
Expression.Between("ID", 1, 10);

Sammenligning

Expression.Eq(string, object)
Eksempel: Find en Post med Titlen "Introduktion til ActiveRecord"
Expression.Eq("Title", "Introduktion til ActiveRecord");
Tilsvarende findes Expression.Ge(>=), Expression.Le(<=), Expression.Gt(>), Expression.Lt(<) alle disse har også et modstykke der tager navnet på to egenskaber og sammenligner. De hedder EqProperty, GeProperty osv.

Lister

Expression.In(string, ICollection);
Expression.In(string, object[]);
Eksempel: Find alle Post instanser med id 1,2 og 3
Expression.In("ID", new int[]{1,2,3})

NULL håndtering

Expression.IsNull(string);
Expression.IsNotNull(string);
Begge to har en parameter der skal være et navn på en egenskab og den tilsvarende kolonne i databasen vil blive checket om den er/er ikke NULL.

Sortering

Vi kan nu lave noget ret avancerede filtrering i vores data vi kan også sortere ved at sende en instans af Order med til FindAll og derefter specificere filtreringer. Order er en factory ligesom Expression så den bruges sådan her:

Blogger.FindAll(Order.Desc("Name"), Expression.Eq(....));

Lazy loading på forespørgselsniveau

Vi har set hvor let vi kan arbejde med vores data og filtrere hvad vi hiver ud fra databasen, og det er ret simpelt at arbejde med. Men på et tidspunkt rammer vi en mur med denne forespørgselsmekanisme. Som nævnt tidligere kan det være en fordel at specificere hvad der skal lazy loades i det øjeblik vi udfører en forespørgsel. Det kan f.eks. være jeg i et bestemt tilfælde kun ønsker at hive mine Post instanser op men ikke ønsker nogle relaterede instanser loadet med det samme. For at gøre dette skal vi have fat i et koncept fra nHibernate, nemlig en ICriteria query. Nærmere bestemt en DetachedCriteria query:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy);
using (new SessionScope())
{
        Post[] posts = Post.FindAll(postQuery);
	//...
}

Som vi kan se deklarerer vi forespørgslen og sender den derefter til vores FindAll metode. I kaldet til SetFetchMode kan vi specificere hvordan enkelte relationer på Post bliver hentet. Når ovenstående DetachedCriteria eksekveres producerer det følgende SQL:

SELECT this_.ID as ID2_0_, this_.Title as Title2_0_, this_.Text as Text2_0_, this_.Blog as Blog2_0_ FROM Post this_

Kombiner SetFechMode, kriterier og sortering

DetachedCriteria giver os en mulighed for at bruge det vi allerede har lært, ICriterion kan også knyttes på et DetachedCriteria:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy)
                .Add(Expression.Like("Title", "Introduktion til ActiveRecord", MatchMode.Anywhere));

Og vi kan på samme måde tilføje vores sortering vha. AddOrder metoden. FetchMode kan specificeres til enten Lazy, Eager, Join, Select.

Kriterier på tværs af relationer

Udover at specificere lazy loading i vores forespørgsel har vi også mulighed for at lave mere komplekse forespørgsler på tværs af vore relationer ved at tilføje aliaser, f.eks. hvis vi ønsker at hente samtlige Post instanser hvor titlen på den relaterede Blog instans indeholder "streng1":

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .CreateAlias("Blog", "b")
                .Add(Expression.Like("b.Name", "streng1", MatchMode.Anywhere));

Vi kan tilføje et alias til en relationsti og bruge den i vore ICriterion forespørgsler, vi kan også specificere når vi opretter aliaset hvordan data skal hentes ud, ved at angive en jointype som en 3 parameter til CreateAlias metoden. Ovenstående Criteria vil ved eksekvering udføre følgende SQL mod databasen:

SELECT this_.id    AS id2_1_,
       this_.title AS title2_1_,
       this_.TEXT  AS text2_1_,
       this_.blog  AS blog2_1_,
       b1_.id      AS id3_0_,
       b1_.name    AS name3_0_,
       b1_.owner   AS owner3_0_
FROM   post this_
       INNER JOIN blogs b1_
         ON this_.blog = b1_.id
WHERE  b1_.name LIKE @p0;

@p0 = '%streng1%'

Der er mange kombinationsmuligheder, og vi kan med DetachedCriteria have fuld kontrol over hvad data hentes og hvornår, og vi kan forespørge ved at bruge vores relationer ganske let.

Hent en enkelt instans udfra kriterier

Vi har set hvordan vi kan hente en enkelt instans udfra dens identifier og hvordan vi kan hente flere poster med FindAll, en sidste nyttig ting er hvis vi skal hente en enkelt instans op baseret på kriterier, der kan vi bruge FindOne metoden som også kan tage en liste ICriterion eller et DetachedCriteria.

Post p = Post.FindFirst(Expression.Eq("Title", "Introduktion til ActiveRecord"));

Ovenstående vil hente præcis en instans ud så det kan benyttes hvis man er sikkert på at ens forespørgsel returnerer kun et resultatet eller hvis man kun ønsker den første instans i et større resultatetsæt.

Hent en delmængde af et resultatsæt

Ofte ønsker man at præsentere data i sider, så man viser f.eks. 10 resultater af gangen og giver mulighed for at bladre imellem siderne, ActiveRecord gør dette rigtig let. Der findes en udgave af FindAll der kun resulterer en delmængde, den hedder SlicedFindAll, så hvis jeg vil vise f.eks. posterne fra 10 til 20 så kan jeg gøre det sådan her:

Post.SlicedFindAll(10, 20);

Også her har jeg mulighed for vha. overloads af SlicedFindAll at sende hhv. ICriteria, Order og DetachedCriteria med og få returneret en delmængde af disse.

Næste gang

DetachedCriteria har en lang række andre muligheder og jeg kan kun opfordre til at udforske metoderne. I næste post vil jeg se lidt på nogle avancerede features i ActiveRecord bl.a. hvordan vi kan introducere validering af data i vores egenskaber og hvordan vi kan undgå at skulle nedarvre fra ActiveRecordBase<T> hvis man ønsker det.

tag4sree: responding to change

Hent data med Castle ActiveRecord

This post is in danish and is a second part in an introduction to Castle ActiveRecord, if you would like to see this content in english let me know

Introduktion

I sidste post kiggede vi på hvordan vi kan specificere en sammenhæng mellem vores klasser og vores database med Castle ActiveRecord. Nu er det på tide at finde dataene frem igen og se lidt på hvordan Castle ActiveRecord egentlig håndterer de komplekse situationer. F.eks de relationer vi specificerede sidst mellem vores klasser.

Forudsætninger

Som nævnt i sidste post er Castle ActiveRecord et abstraktionsniveau over nHibernate, dvs. det pakker nHibernate ind på en pæn måde der gør det lettere at bruge, dog vil vi se at det ofte er nødvendigt at bruge nHibernate funktionalitet for at få fuld fleksibilitet. Det vender jeg tilbage til senere i denne post.

Lad os først se på den mest simple måde at hente data med de ActiveRecord klasser vi har konstrueret. Men før vi går igang med det skal vi tilføje en reference til NHibernate.dll til vores projekt da funktionalitet herfra bliver brugt i nogle af de metoder vi skal til at kalde.

Simpel forespørgsel

Når vi har fået det indledende på plads kan vi eksekvere nedenstående kode:

Post[] posts = Post.FindAll();

Det vil returnere en liste med de instancer af Post der er gemt i databasen, hvis vi kigger på hvilke SQL sætninger dette generer kan man blive lidt overrasket, Post er relateret til alle vores objekter da en post tilhører en Blog der er ejet af en Blogger og udover det har en Post en række Tag's tilknyttet, da vi ikke har fortalt ActiveRecord andet tror den at vi ønsker at få alt data med det samme, så følgende SQL bliver eksekveret:

-- Query 1
SELECT this_.id       AS id0_2_,
       this_.title    AS title0_2_,
       this_.TEXT     AS text0_2_,
       this_.blog     AS blog0_2_,
       blog2_.id      AS id2_0_,
       blog2_.name    AS name2_0_,
       blog2_.owner   AS owner2_0_,
       blogger3_.id   AS id3_1_,
       blogger3_.name AS name3_1_
FROM   post this_
       LEFT OUTER JOIN blogs blog2_
         ON this_.blog = blog2_.id
       LEFT OUTER JOIN blogger blogger3_
         ON blog2_.owner = blogger3_.id

-- Query 2
SELECT tags0_.postid AS postid__1_,
       tags0_.tagid  AS tagid1_,
       tag1_.id      AS id4_0_,
       tag1_.name    AS name4_0_,
       tag1_.TEXT    AS text4_0_
FROM   posttags tags0_
       LEFT OUTER JOIN tag tag1_
         ON tags0_.tagid = tag1_.id
WHERE  tags0_.postid = @p0;

@p0 = '1'

Lazy loading af collections

Vi kan udfra ovenstående SQL tydeligt se at ActiveRecord forsøger at hente alt data. Der laves joins i vores første query for at få fat i hhv. information om Blog instansen bundet til Post samt Blogger instansen bundet til Blog instansen. ActiveRecord (eller rettere nHibernate) er smart nok til at tage dette flade resultatsæt og omforme til vores objektgraf. Efterfølgende kan vi se i Query 2 at vores mange-til-mange relation bliver forespurgt, her henter ActiveRecord all de tags ud der er tilknyttet den ene post jeg har i min database og henter hvert enkelt tag ud. Faktisk har jeg ALT data i min applikation hevet op vha ovenstående query, det kan vi hurtigt blive enige om ikke er smart, så derfor skal vi tilbage og rette på nogle ting i vores relations-attributter. Hvis vi ønsker data ikke skal indlæses med det samme men ved første adgang til det kan vi specificere lazy attributten, så lad os sige jeg ikke ønsker at hente alle Posts op når jeg henter en blog specificerer jeg følgende i min HasMany attribut:

[HasMany(Lazy = true)]
public IList<post> Posts{...}

Det vil medføre at vores Posts collection ikke vil blive hentet op når vi henter en instans af Blog klassen op.

Databaseforbindelser ved Lazy loading

Det er vigtigt at forstå i denne sammenhæng at ActiveRecord nu skal bruge en databaseforbindelse til at indlæse dataene når du tilgår dem, dvs. nu er det ikke længere "hit-and-run"-dataadgang, nu kan du risikere at dit objekt skal snakke med databasen når du tilgår en liste. På grund af dette skal proxien have adgang til en databaseforbindelse, for at den har det skal den være i et såkaldt SessionScope. Et SessionScope er ActiveRecords "wrapper" omkring en databaseforbindelse (hvis det skal forklares enkelt, reelt set er det noget mere end det men mere om det en anden gang). Når vi tilgår data der benytter lazy loading skal vi åbne et SessionScope i den tid vi arbejder med objektet, det kan vi gøre sådan her:

using(new SessionScope()){
	Blog b = Blog.Find(1);
	int count = b.Posts.Count; // Her indlæses dataene først
}

Lazy loading af relaterede objekter

Nu har vi løst problemet for vores collections, men vi har stadig ikke reduceret hvor mange relaterede objekter der hentes ud vha. join'et i den første SQL sætning vi så tidligere. Hvis vi vil reducere dette kan vi markere enkelte klasser som værende Lazy loadet, f.eks. kan vi ved at specificere Lazy parametren til vores ActiveRecord attribut på Blog klassen specificere at denne ikke skal hentes op:

[ActiveRecord(Lazy = true)]
public class Blog : ActiveRecordBase<Blog>{..}

Hvordan gør ActiveRecord det?

Men hov, hvordan håndterer ActiveRecord dette, det vil jo kræve at den kan finde ud af hvornår vi tilgår egenskaben Blog på vores Post instancer. Det er helt korrekt og for at finde ud af dette bliver vores Blog property på Post klassen skiftet ud med et såkaldt proxy objekt. Dette objekt fortæller ActiveRecord når det bliver tilgået og indlæser dataene fra databasen og returnerer dem. Det er mildest talt smart(til at udføre dette bruger ActiveRecord et andet projekt fra Castle nemlig DynamicProxy). Der er dog en ting der er værd at være opmærksom på, for at ActiveRecord kan lave en Proxy af vores type nedarver den fra typen, og for at den kan overskrive vores egenskaber og metoder skal disse være markeret med virtual keywordet. Så husk det, hvis det bliver glemt får vi følgende exception:

NHibernate.InvalidProxyTypeException : The following types may not be used as proxies:
Intellect.BlogWebsite.Model.Blogger: method set_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Name should be virtual
Intellect.BlogWebsite.Model.Blogger: method set_Blogs should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_ID should be virtual
Intellect.BlogWebsite.Model.Blogger: method get_Name should be virtual

Lazy loading kan hurtigt blive uoverskueligt at få specificeret korrekt så det passer i alle scenarier. I nogle scenarier i applikationen vil man måske gerne have noget data lazyloadet og i andre noget helt andet. Men bare rolig, det er der også en løsning på, den vender jeg tilbage til om lidt.

Filtrering ved hjælp af kriterier

I ovenstående så vi også hvordan vi finder en enkelt instans frem udfra dens primærnøgle nemlig med Find metoden på klassen.

Lad os se hvordan vi kan filtrere på data udfra andre egenskaber end primærnøglen. Vi starter med et lille eksempel, jeg vil finde alle Blogger instanser hvor Name egenskaben indeholder en bestemt streng

Blogger.FindAll(Expression.Like("Name", "Jakob", MatchMode.Anywhere));

Igen bruger jeg FindAll metoden men denne gang sender jeg en parameter med, og her gør jeg brug af NHibernate. Expression er hvad man kan kalde en factory klasse, den bruges til at generere implementationer af ICriterion interfacet. I ovenstående tilfælde beder jeg om at få et Like expression der sammenligner egenskaben "Name" med strengen "Jakob" og specificerer en MatchMode, ovenstående resulterer i følgende SQL:

SELECT this_.ID as ID4_0_, this_.Name as Name4_0_ FROM Blogger this_ WHERE this_.Name like @p0; @p0 = '%Jakob%'

Som vi kan se indsætter nHibernate selv wildcards i min parameter baseret på den MatchMode jeg har valgt. Jeg kan tilføje flere forskellige ICriterion til min FindAll metode(adskilt med kommaer takket være params keyword på parametren) for at sammensætte komplekse søgninger.

Oversigt over filtreringsmuligheder

Her er en liste med en kort beskrivelse af nogle af de ICriterion Expression factoryen kan genere for os:

And, Or og Not

Expression.And(ICriterion, ICriterion)
Eksempel: Find alle Blogger instanser hvor navnet indeholder både "streng1" og "streng2"
Expression.And(Expression.Like("Name", "streng1", MatchMode.Anywhere), Expression.Like("Name", "streng2", MatchMode.AnyWhere));
Tilsvarende findes
Expression.Or(ICriterion, ICriterion)
Expression.Not(ICriterion)

Between

Expression.Between(string, object, object)

Eksempel: Find alle Post instanser med ID imellem 1 og 10
Expression.Between("ID", 1, 10);

Sammenligning

Expression.Eq(string, object)
Eksempel: Find en Post med Titlen "Introduktion til ActiveRecord"
Expression.Eq("Title", "Introduktion til ActiveRecord");
Tilsvarende findes Expression.Ge(>=), Expression.Le(<=), Expression.Gt(>), Expression.Lt(<) alle disse har også et modstykke der tager navnet på to egenskaber og sammenligner. De hedder EqProperty, GeProperty osv.

Lister

Expression.In(string, ICollection);
Expression.In(string, object[]);
Eksempel: Find alle Post instanser med id 1,2 og 3
Expression.In("ID", new int[]{1,2,3})

NULL håndtering

Expression.IsNull(string);
Expression.IsNotNull(string);
Begge to har en parameter der skal være et navn på en egenskab og den tilsvarende kolonne i databasen vil blive checket om den er/er ikke NULL.

Sortering

Vi kan nu lave noget ret avancerede filtrering i vores data vi kan også sortere ved at sende en instans af Order med til FindAll og derefter specificere filtreringer. Order er en factory ligesom Expression så den bruges sådan her:

Blogger.FindAll(Order.Desc("Name"), Expression.Eq(....));

Lazy loading på forespørgselsniveau

Vi har set hvor let vi kan arbejde med vores data og filtrere hvad vi hiver ud fra databasen, og det er ret simpelt at arbejde med. Men på et tidspunkt rammer vi en mur med denne forespørgselsmekanisme. Som nævnt tidligere kan det være en fordel at specificere hvad der skal lazy loades i det øjeblik vi udfører en forespørgsel. Det kan f.eks. være jeg i et bestemt tilfælde kun ønsker at hive mine Post instanser op men ikke ønsker nogle relaterede instanser loadet med det samme. For at gøre dette skal vi have fat i et koncept fra nHibernate, nemlig en ICriteria query. Nærmere bestemt en DetachedCriteria query:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy);
using (new SessionScope())
{
        Post[] posts = Post.FindAll(postQuery);
	//...
}

Som vi kan se deklarerer vi forespørgslen og sender den derefter til vores FindAll metode. I kaldet til SetFetchMode kan vi specificere hvordan enkelte relationer på Post bliver hentet. Når ovenstående DetachedCriteria eksekveres producerer det følgende SQL:

SELECT this_.ID as ID2_0_, this_.Title as Title2_0_, this_.Text as Text2_0_, this_.Blog as Blog2_0_ FROM Post this_

Kombiner SetFechMode, kriterier og sortering

DetachedCriteria giver os en mulighed for at bruge det vi allerede har lært, ICriterion kan også knyttes på et DetachedCriteria:

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .SetFetchMode("Blog", FetchMode.Lazy)
                .SetFetchMode("Tags", FetchMode.Lazy)
                .Add(Expression.Like("Title", "Introduktion til ActiveRecord", MatchMode.Anywhere));

Og vi kan på samme måde tilføje vores sortering vha. AddOrder metoden. FetchMode kan specificeres til enten Lazy, Eager, Join, Select.

Kriterier på tværs af relationer

Udover at specificere lazy loading i vores forespørgsel har vi også mulighed for at lave mere komplekse forespørgsler på tværs af vore relationer ved at tilføje aliaser, f.eks. hvis vi ønsker at hente samtlige Post instanser hvor titlen på den relaterede Blog instans indeholder "streng1":

DetachedCriteria postQuery = DetachedCriteria.For<Post>()
                .CreateAlias("Blog", "b")
                .Add(Expression.Like("b.Name", "streng1", MatchMode.Anywhere));

Vi kan tilføje et alias til en relationsti og bruge den i vore ICriterion forespørgsler, vi kan også specificere når vi opretter aliaset hvordan data skal hentes ud, ved at angive en jointype som en 3 parameter til CreateAlias metoden. Ovenstående Criteria vil ved eksekvering udføre følgende SQL mod databasen:

SELECT this_.id    AS id2_1_,
       this_.title AS title2_1_,
       this_.TEXT  AS text2_1_,
       this_.blog  AS blog2_1_,
       b1_.id      AS id3_0_,
       b1_.name    AS name3_0_,
       b1_.owner   AS owner3_0_
FROM   post this_
       INNER JOIN blogs b1_
         ON this_.blog = b1_.id
WHERE  b1_.name LIKE @p0;

@p0 = '%streng1%'

Der er mange kombinationsmuligheder, og vi kan med DetachedCriteria have fuld kontrol over hvad data hentes og hvornår, og vi kan forespørge ved at bruge vores relationer ganske let.

Hent en enkelt instans udfra kriterier

Vi har set hvordan vi kan hente en enkelt instans udfra dens identifier og hvordan vi kan hente flere poster med FindAll, en sidste nyttig ting er hvis vi skal hente en enkelt instans op baseret på kriterier, der kan vi bruge FindOne metoden som også kan tage en liste ICriterion eller et DetachedCriteria.

Post p = Post.FindFirst(Expression.Eq("Title", "Introduktion til ActiveRecord"));

Ovenstående vil hente præcis en instans ud så det kan benyttes hvis man er sikkert på at ens forespørgsel returnerer kun et resultatet eller hvis man kun ønsker den første instans i et større resultatetsæt.

Hent en delmængde af et resultatsæt

Ofte ønsker man at præsentere data i sider, så man viser f.eks. 10 resultater af gangen og giver mulighed for at bladre imellem siderne, ActiveRecord gør dette rigtig let. Der findes en udgave af FindAll der kun resulterer en delmængde, den hedder SlicedFindAll, så hvis jeg vil vise f.eks. posterne fra 10 til 20 så kan jeg gøre det sådan her:

Post.SlicedFindAll(10, 20);

Også her har jeg mulighed for vha. overloads af SlicedFindAll at sende hhv. ICriteria, Order og DetachedCriteria med og få returneret en delmængde af disse.

Næste gang

DetachedCriteria har en lang række andre muligheder og jeg kan kun opfordre til at udforske metoderne. I næste post vil jeg se lidt på nogle avancerede features i ActiveRecord bl.a. hvordan vi kan introducere validering af data i vores egenskaber og hvordan vi kan undgå at skulle nedarvre fra ActiveRecordBase<T> hvis man ønsker det.

tag4sree: Detached objects in nHibernate and Lazy loading

Today's errors running on the trunk

I did some work today with MonoRail and some of the tools from Ayende's repository (which contains a lot of tools/code snippets/extensions etc he calls Rhino Tools). I build both things from trunk as well as nHibernate from trunk because I tried to figure out a lot of internal stuff, however this can give some problems from time to time so i thought i post the errors i encountered so someone in the same problems as me could google them.

Problem 1

The first problem was when starting a MonoRail site and it tried to render a View, it gave me an MonoRailException with the following text:

"Error processing MonoRail request. Action Index on controller Home"

And the inner exception gave me "Error compiling Brail code" and "Internal compiler error: Value cannot be null. Parameter name: ns."

I realized that I had used assemblies from two different sources the Castle trunk and the Rhino-Tools trunk. Apparently the Brail view engine wasn't to happy with the version of Boo.Lang.Interpreter.dll from the rhino tools trunk, i haven't dug any deeper into why, but it seems a bit strange.

Problem 2

This time using ActiveRecord i got this ActiveRecordException:

"Error adding information from class My.NamespaceName.ClassName to NHibernate."

Again a look at the Inner exceptions gave some more information from nHibernate itself: "Could not compile the mapping document: (string)" (MappingException) and  last but not least i got:

"The given key was not present in the dictionary."

Which i guess we all have seen at some times, but this time from the core of nHibernate. It happened in NHibernate.Dialect.Dialect.GetDialect() method, its pretty short so lets take a look:

public static Dialect GetDialect(IDictionary<string  , string> props)
{
	if (props == null)
		throw new ArgumentNullException("props");
	string dialectName = props[Environment.Dialect];
	if (dialectName == null)
	{
		return GetDialect();
	}

	return InstantiateDialect(dialectName);
}

It gets the properties from the configuration of nHibernate as an argument, and then tries to look up the dialect in these values but somehow it couldn't find my configuration of my Dialect, i checked it for misspellings but no luck, then I realized my properties was on the format "hibernate.dialect" but the Environment.Dialect constant was only "dialect". Apparently this was changed a few weeks back, but had not cought my attention. Take a look at this diff if you wont take my word for it.

tag4sree: Hibernate - Objects

Today's errors running on the trunk

I did some work today with MonoRail and some of the tools from Ayende's repository (which contains a lot of tools/code snippets/extensions etc he calls Rhino Tools). I build both things from trunk as well as nHibernate from trunk because I tried to figure out a lot of internal stuff, however this can give some problems from time to time so i thought i post the errors i encountered so someone in the same problems as me could google them.

Problem 1

The first problem was when starting a MonoRail site and it tried to render a View, it gave me an MonoRailException with the following text:

"Error processing MonoRail request. Action Index on controller Home"

And the inner exception gave me "Error compiling Brail code" and "Internal compiler error: Value cannot be null. Parameter name: ns."

I realized that I had used assemblies from two different sources the Castle trunk and the Rhino-Tools trunk. Apparently the Brail view engine wasn't to happy with the version of Boo.Lang.Interpreter.dll from the rhino tools trunk, i haven't dug any deeper into why, but it seems a bit strange.

Problem 2

This time using ActiveRecord i got this ActiveRecordException:

"Error adding information from class My.NamespaceName.ClassName to NHibernate."

Again a look at the Inner exceptions gave some more information from nHibernate itself: "Could not compile the mapping document: (string)" (MappingException) and  last but not least i got:

"The given key was not present in the dictionary."

Which i guess we all have seen at some times, but this time from the core of nHibernate. It happened in NHibernate.Dialect.Dialect.GetDialect() method, its pretty short so lets take a look:

public static Dialect GetDialect(IDictionary<string  , string> props)
{
	if (props == null)
		throw new ArgumentNullException("props");
	string dialectName = props[Environment.Dialect];
	if (dialectName == null)
	{
		return GetDialect();
	}

	return InstantiateDialect(dialectName);
}

It gets the properties from the configuration of nHibernate as an argument, and then tries to look up the dialect in these values but somehow it couldn't find my configuration of my Dialect, i checked it for misspellings but no luck, then I realized my properties was on the format "hibernate.dialect" but the Environment.Dialect constant was only "dialect". Apparently this was changed a few weeks back, but had not cought my attention. Take a look at this diff if you wont take my word for it.

tag4sree: responding to change