In de data access laag van m'n projecten werd het nu toch wel een drukke boel met verschillende LINQ to SQL queries. Aangezien je namelijk niet goed dynamische LINQ-queries kunt maken, moet je eigenlijk voor iedere 'uitzondering' een aparte query maken. Je krijgt dan zoiets als dit:
if (criteria.PeriodeVan != null && criteria.PeriodeTot != null) { var query = (from post in dataContext.GetTable() where post.Item.CreatieDD >= criteria.PeriodeVan && post.Item.CreatieDD <= criteria.PeriodeTot orderby post.Item.CreatieDD descending select post).ToArray(); if (criteria.TopList > 0) { return query.Take(criteria.TopList); } else { return query; } } else { var query = from post in dataContext.GetTable() orderby post.Item.CreatieDD descending select post; if (criteria.TopList > 0) { return query.Take(criteria.TopList); } else { return query; } }
Dit is een hele lap code voor eigenlijk maar 2 acties, namelijk een periode kiezen en een selectie van de bovenste items. Voor mijn relatief kleine projecten is dat wel te doen, maar bij grotere projecten zeker niet. Zelfs dit vind ik al lastig qua onderhoud. Nu ben ik vandaag op zoek gegaan naar een goed alternatief. Als vrij snel kwam ik op een Chris Rock z'n weblog waar hij dit probleem ook beschrijft in de post die hier is te vinden: http://www.rocksthoughts.com/blog/archive/2008/01/24/linq-to-sql-dynamic-queries.aspx Eerst komt hij met het voorstel om de Dynamic LINQ Query Library te gebruiken. Deze had ik zelf ook al een keer gezien, maar dat is eigenlijk iets wat je totaal niet wilt gaan gebruiken. Je krijgt dan gewoon tekst queries in je code welke niet meer door je compiler worden gecontroleerd. Dit haalt Chris ook al aan. Daarna begint hij over de LINQ Predicate Builder. Dit is eigenlijk precies wat je wilt gebruiken, of in ieder geval wat mij momenteel goed lijkt. Nu vind ik het ietwat overbodig om de hele LINQKit te downloaden en in m'n projecten te stoppen, dus ben ik zelf maar aan de slag gegaan om m'n eigen implementatie hier aan te geven. Copy-pasten vond ik ook een beetje doelloos, aangezien ik het toch iets anders wil dan dat ze op de LINQ Predicate Builder pagina beschrijven ( http://www.albahari.com/nutshell/predicatebuilder.html ). Omdat Chris Rock z'n voorbeeld in VB.Net was heb ik het zelf nog moeten omschrijven naar een correcte C# implementatie. Waar ik nu zelf op uit ben gekomen is het volgende:
var query = dataContext.GetTable() as IQueryable; query = query.OrderByDescending(p => p.Item.CreatieDD); if (criteria.PeriodeVan != null && criteria.PeriodeTot != null) { query = query.Where(p => p.Item.CreatieDD >= criteria.PeriodeVan && p.Item.CreatieDD <= criteria.PeriodeTot); } if (criteria.CategorieID > 0 ) { query = query.Where(p => p.CategorieID == criteria.CategorieID); } if (criteria.TopList > 0) { query = query.Take(criteria.TopList); } return query.ToArray();
Op zich nog een redelijke lap code, maar een stuk beter onderhoudbaar als wat ik eerst had. Eerst definieer je de query variabele als een IQueryable. Daarna zorg je dat de nodige filters aan worden gekoppeld. Pas op het einde doe ik een query.ToArray() om er voor te zorgen dat de LINQ-query gelijk wordt uitgevoerd. Hopelijk lever ik hierdoor niets (of weinig) in op de performance, maar dat zal moeten blijken in de toekomst.

Vandaag ben ik veel interessante artikelen tegen gekomen op het internet. Een daarvan is een LINQ to SQL performance test. Blijkbaar had Derik Whittaker wat tijd over en heeft een performance test gedaan met verschillende manieren van de data-access op te bouwen. Het volledige artikel is op z'n weblog te lezen: http://devlicio.us/blogs/derik_whittaker/archive/2008/03/14/playing-with-linq2sql-and-various-performance-tests.aspx Z'n conclusie is dat wanneer je een generieke DataContext gebruikt in je code, je enorm veel performance verlies hebt. Het hoe en waarom is hij nog niet achter, maar de uitvoer is ongeveer 7x langzamer dan wanneer je een 'goede' DataContext gebruikt. In dit bovenste voorbeeld wordt een generieke DataContext gebruikt, de tweede een specifieke welke alleen voor de betreffende tabel kan worden gebruikt.
# DataContext db = new DataContext(new SqlConnection(connectionString)); # IEnumerable sports = from s in db.GetTable() # select s;
# SportsDemoDataContext db = new SportsDemoDataContext(new SqlConnection(connectionString)); # IEnumerable sports = from s in db.Sports # select s;
Na 10.000 keer de query uit te voeren blijkt dat het 2e voorbeeld met een gemiddelde van 13022 miliseconden wordt uitgevoerd en het 1e voorbeeld met een gemiddelde van 78042 miliseconden wordt uitgevoerd. Een behoorlijk verschil dus. Zelf maak ik ook gebruik van zo'n generieke DataContext uit het eerste voorbeeld. Dit omdat je dat mooi in je Base.cs kunt opnemen. In de eerstvolgende performance-tuning update zal ik dit aanpassen zodat iedere klasse z'n eigen, juiste, DataContext krijgt. Het is toch mooi dat sommige mensen hier de tijd voor nemen om te testen. Zelf zou ik dat namelijk nooit doen, maar ik doe er wel weer m'n voordeel mee.

Vandaag kwam ik een website met een vette applicatie tegen, namelijk LINQpad. Zoals de naam misschien wel doet vermoeden, kun je met deze 'pad' zelf LINQ queries maken en uitvoeren. Deze applicatie ondersteunt LINQ to SQL, LINQ to XML en LINQ to Objects. Door het uitvoeren van verschillende queries (op je eigen data) kijken of het resultaat dat je krijgt gewenst is. Zo kun je dus mooi oefenen om de LINQ-syntax te beheersen. De website is te bereiken op http://www.linqpad.net/

Afgelopen week kwam ik in m'n RSS-feeds een mooi artikel tegen met daarin enkele richtlijnen hoe en wanneer je LINQ kunt gebruiken. Om eerlijk te zijn heb ik het alleen maar even snel door gescanned, omdat ik nogal druk ben de laatste tijd, maar het lijkt me een interessant stukje om te lezen. De link van het artikel is http://blogs.msdn.com/mirceat/archive/2008/03/13/linq-framework-design-guidelines.aspx. Zodra ik zelf tijd heb zal ik het ook doorlezen, maar dat zit er vandaag waarschijnlijk niet meer in.

Onlangs heb ik iets 'uitgevonden' bij het maken van LINQ-queries. Deze queries worden namelijk uitgevoerd zodra je een item zoekt in de verzameling van objecten, dus eigenlijk pas in een foreach-loop. Ik had een 'kleine' fout gemaakt tijdens het ontwikkelen van dit weblog, waardoor het voor kon komen dat de database meer dan 7000x in een kwartier kon worden aangeroepen als er 2 gebruikers 20 pagina's bekeken. Dat is natuurlijk niet echt bevorderlijk voor de performance, waardoor je soms wel tussen de 10 en 30 seconden moest wachten voordat er een pagina zichtbaar was. Nadat ik dit design issue er uit had gewerkt, waardoor de database een stuk minder wordt aangeroepen en de performance dus ook is verhoogd, ben ik op zoek gegaan hoe ik LINQ-queries direct kan uitvoeren. Dit blijkt niet zo enorm lastig te zijn. Je kunt de query gelijk in een array zetten, waardoor alle objecten gelijk worden opgehaald, dus de database wordt aangeroepen. Nadat de objecten in de array staan zal de database niet nogmaals te hoeven worden aangeroepen. Bij deze een voorbeeld query van zo'n cast.
var query = (from collectie in dataContext.GetTable() orderby collectie.Item.CreatieDD ascending select collectie).ToArray();
Een voordeel hiervan is dat alle database acties in de DAL blijven. Nog wel belangrijk om te weten is dat er nog steeds een IEnumerable wordt teruggegeven. Je hoeft dus verder niets in je code te wijzigen.