onsdag, juni 11, 2008

Lazy Load och Eager Load med LINQ to SQL

Lazy Load

LINQ to SQL implementerar i grunden ett pattern som heter Lazy Load. Lazy Load innebär att man, som i fallet med LINQ to SQL har att göra med en databas, bara hämtar de rader som man behöver när man faktiskt behöver dem. Inom LINQ kallar man det för Deferred Query Execution vilket betyder att man kan definiera en fråga i ett läge men att kopplingen mot databasen sker först när man börjar iterera resultatet.

När man deklarerar en fråga för att t ex hämta alla kunder som finns i en stad som börjar på bokstaven M:

            var customers = from c in db.Customers

                            where c.City.StartsWith("M")

                            select c;


så kommer det att skapas en representation av frågan i form av ett Query Expression Tree. Om man då sätter break point och debuggar kan man se vilken SQL som kommer att exekveras. Men det är först när man använder någon information från resultatet:

            foreach (var customer in customers)

            {

                Console.WriteLine(customer.ContactName);

            }


som raderna faktiskt hämtas. Så frågan ovan genererar en server-round trip som returnerar 13 rader (om man använder sig av Northwinds databas).

Om man sedan går vidare på varje kund och skriver ut dess ordrar enligt följande:

            foreach (var customer in customers)

            {

                Console.WriteLine(customer.ContactName);

                foreach (var order in customer.Orders)

                {

                    Console.WriteLine(" - " + order.OrderDate.ToString());

                }

            }


så kommer man att för varje kund-objekt generera en ny SQL och hämta just den kundens ordrar. Det innebär att iterationen över kundernas ordrar kommer att generera 14 server-round trips.

Det här kan vara väldigt bra i vissa lägen men mindre bra i andra lägen. Tänk att man har ett presenteras en trädvy där alla kund-objekt visas och sedan väljer användaren att bara expandera en av dessa kunder för att visa kundens alla ordrar. Ja då kanske det är bättre att i bara hämta just den kundens ordrar istället för att skicka över alla kunders ordrar.

Eager Load

Men om man nu har en sådan situation där man vet att man vill ladda alla kunder och alla dess ordrar, dvs man vill använda sig av Eager Load istället för Lazy Load, ja då finns det förstås en lösning och det är DataLoadOptions.

För att lyckas med Eager Load i LINQ så skapar man ett objekt av typen DataLoadOptions och anropar en metod som heter LoadWith. I LoadWith anger man vilka objekt man ska ladda samtidigt som det huvudobjekt man hämtar. Detta gör man med hjälp av ett lambdauttryck. DataLoadOptions-objektet lägger man sedan till i DataContext-objektet. Här nedan har jag använt mig av en object initializer för att sätta propertyn samtidigt som jag skapar objektet.

            var dlo = new DataLoadOptions();

            dlo.LoadWith<Customer>(p => p.Orders);

            var db = new NorthwindDataContext() { LoadOptions = dlo };


Den frågan som jag ställde i början av det här inlägget gällde att hämta alla kunder som bor i en stad som börjar på bokstaven M. När man då skapar ett DataLoadOptions-objekt så är det ett objekt av typen Customer som man anger som typ till den generiska metoden LoadWith och det här objektet är det som kallas grundobjektet. I lambdauttrycket anger man sedan vilket typ av objekt som man vill ladda samtidigt som det grundobjekt man planerar att hämta. Detta innebär att man med en server-round trip kommer att hämta kund-objekten samt dessa kunders alla ordrar.

DataLoadOptions-objektet kan man sedan fylla på med flera relaterade objekt av andra typer som man vill ladda samtidigt som huvudobjektet.

            var dlo = new DataLoadOptions();

            dlo.LoadWith<Customer>(p => p.Orders);

            dlo.LoadWith<Order>(o => o.Order_Details);


Lazy Load är ett fantastiskt pattern men det är väldigt viktigt att man känner till vilka implikationer det kan få att använda det. Lazy Load innebär alltid ett mer chatty interface vilket som sagt kan vara bra ibland men helt förödande i andra lägen.

3 kommentarer:

Löwendahl sa...

eager och eager. Lite att ta i kanske, less lazy skulle jag säga ;)

kolla det här: http://www.lowendahl.net/showShout.aspx?id=190

Björn Eriksen sa...

Det är ju sant! Jag skulle kunna ta gift på att jag fick det att funka igår med flera nivåer men så var nog inte fallet... Men less lazy är ju inte heller helt fel :D

löwendahl sa...

Jag och Mats hade en lång diskussion med Matt Warren om det här och han hade väldigt konstiga försvarstal.

Problemet är vilket som att du får 1 + n queries när du har 2+ nivåer. Jobbar du då med eager laddade aggregat så blir det svårt med LINQ to SQL.

Här är den dikussionen: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=2870127&SiteID=1