Utvärdering av ADO.NET Entity Framework TOMAS PETTERSSON Examensarbete Stockholm, Sverige 2009
Utvärdering av ADO.NET Entity Framework TOMAS PETTERSSON Examensarbete i datalogi om 30 högskolepoäng vid Programmet för datateknik Kungliga Tekniska Högskolan år 2009 Handledare på CSC var Kjell Lindqvist Examinator var Stefan Arnborg TRITA-CSC-E 2009:106 ISRN-KTH/CSC/E--09/106--SE ISSN-1653-5715 Kungliga tekniska högskolan Skolan för datavetenskap och kommunikation KTH CSC 100 44 Stockholm URL: www.csc.kth.se
Utvärdering av ADO.NET Entity Framework Sammanfattning ADO.NET Entity Framework är Microsofts senaste försök att överbrygga glappet mellan applikation och databas. Att manuellt försöka överbrygga glappet är en tidskrävande uppgift och utvecklare kan ägna så mycket som 40 % av utvecklingstiden åt denna uppgift. På grund av detta så kan det vara av intresse att se hur det nya ramverket lyckas med uppgiften. Entity Framework är snabbt, lättanvänt och första versionen har just släppts ut på marknaden. I den här rapporten kommer jag att visa att trots de många fördelarna så är Entity Framework inte redo att användas i större kommersiella projekt där man inte är säker på att man enbart kommer att använda MS SQL Server. Denna slutsats baserar jag på att cachningen inte är fullt utbyggd, det finns inget inbyggt stöd för lazy loading, stora databaser (+120 tabeller) stödjs inte och den enda databas man i dagsläget kan använda med Entity Framework är MS SQL Server. Evaluation of ADO.NET Entity Framework Abstract ADO.NET Entity Framework is Microsoft s latest attempt to bridge the gap between databases and applications. To manually bridge the gap takes time, application developers can spend as much as 40 % of the total project time on this task alone. It is therefore of interest to see how good Microsoft s new framework can handle the task. Entity Framework is fast, easy to use and the first version has just been released to the manufacturers. In this report I will show that although Entity framework has many advantages it is too early to use it in larger commercial projects where one is not sure that the database to be used is MS SQL Server. I base this conclusion upon that caching is largely missing, there is no built in support for lazy loading, it does not support large databases (+120 tables) and at the moment the only supported database is MS SQL Server.
Förord Under våren, sommaren och hösten 2008 har jag gjort mitt examensarbete på ÅF:s Nynäshamnskontor. Arbetet gick ut på att utvärdera Microsofts ADO.NET Entity Framework speciellt med avseende på NHibernate. Handledare på företaget var Björn Svedman. På de ställen i rapporten där jag använder ordet man avser jag person eller personer som arbetar med programmering mot databaser.
Innehåll 1 BAKGRUND OCH GRUNDSTENARNA...1 1.1 Bakgrund...1 1.2 Upplägg...2 1.3 Objekt/Relations-mappning...2 1.4 Hur utförs en O/R-M i praktiken?...3 1.5 LINQ...4 1.6 NHibernate...6 1.7 ADO.NET Entity Framework...7 1.7.1 Arkitekturen hos ADO.NET EF...7 1.7.2 Entitetsdatamodellen...8 1.7.3 EDM...8 1.7.4 Typer...8 1.7.5 Instanser...9 1.7.6 esql...9 1.7.7 EFs frågeprocess...10 2 ATT ANVÄNDA ENTITY FRAMEWORK...11 2.1 Att visa upp data i en DataGrid...11 2.2 Parametiserade frågor...11 2.3 Lagrade procedurer...12 2.4 Den konceptuella modellen...14 2.5 Låshantering...15 2.6 Cachning...16 2.7 Entity frameworks stöd för övriga databaser...17 2.8 Användarvänlighet EF vs NHibernate...17 2.8.1 Installation...18 2.8.2 Att knyta en databas till applikationen...18 2.8.3 När man gör fel...19 2.8.4 Användarsupport...21 2.8.5 Dokumentation...21 3 BELASTNINGSTEST...22 4 KONVERTERING AV DATAACCESS...31 5 KRITIK MOT ENTITY FRAMEWORK...35 6 FRAMTIDEN...38 7 SLUTSATS...39 8 LITTERATURFÖRTECKNING...40 9 BILAGOR...44 9.1 Bilaga 1...45 9.2 Bilaga 2...47 9.3 Bilaga 3...53 9.4 Bilaga 4: Ordlista...55
1 Bakgrund och Grundstenarna I det här kapitlet kommer jag att skriva om bakgrunden till arbetet, dess upplägg samt beröra LINQ, NHibernate och ADO.NET Entity Framework. 1.1 Bakgrund När man programmerar applikationer som arbetar mot databaser riskerar man att man hamnar i den sitsen att man får skriva stora mängder med kod för att överbrygga glappet mellan de olika datarepresentationerna. I början på 90-talet kunde så mycket som 20-30 % av utvecklingstiden för ett projekt kan gå åt till detta (Keller, Jensen, & Agarwal, 1993), ungefär ett decennium senare så hade uppskattningen av tidsåtgången växt till 30-40 % (Keene, 2004). Att tidsåtgången växt så pass mycket under elva år visar på hur viktigt det är att ägna problemet med glappet mellan databas och applikation tid. Utvecklar man i C# så använder man med största sannolikhet ADO.NET vilket har sina rötter i ADO (ActiveX Data Objects) som är ett API för dataåtkomst ur olika datakällor utvecklat av Microsoft. Första versionen av ADO lanserades 1996. ADO skulle ersätta Microsofts tidigare objektlager för dataåtkomst: RDO (Remote Data Objects) och DAO (Data Access Objects). ADO.NET har hängt med sedan.net 1.0. Trots att ADO.NET har sina rötter i ADO så har man genomfört så många ändringar att ADO.NET kan ses som en helt egen produkt. Men att använda ADO.NET löser inte helt problemet med tidsåtgången, för att snabba upp utvecklingen kan man använda sig av O/R-M-bibliotek/verktyg. O/R-M står här för Object/Relational- Mapping, vilket är ett samlingsnamn för tekniker att gå mellan relationsdatabaser och objektorienterade applikationer. Men varför använda ett extern bibliotek/verktyg om man med språket kan få erforderliga verktyg? Microsofts första steg med att överbrygga glappet mellan applikation och databas är introduktionen av LINQ for SQL och för att fullständigt överbrygga glappet har Microsoft ADO.NET Entity Framework under utveckling. Nu är Microsoft inte ensamma om att arbeta med att ta fram verktyg för att överbrygga tidigare nämnda glapp, vi har även det professionella verktyget LLBLGen Pro och Open Source alternativet NHibernate, plus en hel del andra projekt av varierande storlek. År 2006 hade man på OOPSLA (Object-Oriented Programming, Systems, Languages, and Applications) en paneldiskussion rörande objekt och databaser. I panelen satt representanter från sju större företag (Versant, BEA, Microsoft, Progress Software, Sun Microsystems, GemStoneSystems och db4o) som alla har intressen på databasområdet. Diskussionen andas tillförsikt men samtidigt var de flesta av debattörerna överens om att det fortfarande fanns (och fortfarande finns) saker att göra (Panel at OOPSLA 2006, 2006). LINQ dök upp i och med.net 3.5 och RTM 1 -versionen av Entity Framework släpptes i och med SP1 till VS 2008 (11/8-2008) och även om det inte är en färdig produkt som kommit ut så är det förhoppningsvis ännu ett steg i rätt riktning. ÅF gjorde under våren 2008 snabb utvärdering av Entity Framework inför ett större projekt på Karlstadskontoret och de kom då fram till att EF inte var rätt produkt för jobbet utan valde NHibernate istället. NHibernate är ett O/R-M-verktyg för.net-platformen som har sina rötter i Hibernate för Java. 1 Release to Manufacturing 1
Min uppgift var nu att gör en mer grundlig utvärdering av Entity Framework speciellt med avseende på NHibernate där jag tittar närmare på ett antal punkter: Vad är skillnaderna? Hur är användarvänligheten? Hur lätt är det att sätta sig in i hur det fungerar? Kompatibilitet mellan olika databaser Framtida planer Prestanda Dessa punkter föreslogs av min uppdragsgivare. Att jag gjorde utvärderingen med avseende på NHibernate berodde på att detta är ett av de populäraste verktygen på marknaden och att det dessutom är fritt att använda. 1.2 Upplägg Rapporten börjar med ett avsnitt om Objekt/Relations-mappning och några rader om hur man går tillväga när man utför en sådan. Därefter följer en översikt över LINQ, NHibernate och ADO.NET Entity Framework. Efter denna översikt kommer ett kapitel om hur man använder ADO.NET Entity Framework i praktiken med vissa jämförelser med NHibernate. Därefter jämför jag användarvänligheten mellan de två O/R-M verktygen och efter detta kommer ett belastningstest där jag utför sju stycken test för att se vilket verktyg som är snabbast. Efter detta följer ett avsnitt om att konvertera dataaccessen i en befintlig applikation till ADO.NET Entity Framework, ett avsnitt om kritik mot Entity Framework och slutligen något om ramverkets framtid och min slutsats. Alla kodexempel som förekommer är skrivna i C#. 1.3 Objekt/Relations mappning Att göra mappningar mellan objekt och relationer är inte så enkelt som det först verkar. Eller för att vara exakt; ju komplexare databas desto svårare blir mappningen. Ted Neward går till och med så långt att han i sin blogg Ted Neward s Technical Blog skriver att Object/Relational Mapping is the Vietnam of Computer Science (Neward, 2006). Hans poäng med liknelsen är i korta drag att under Vietnamnkriget drabbades USA av en dödlig form av avtagande avkastning, de tidiga framgångarna i kriget gjorde att de inte insåg när det var dags att sluta. Samma sak menar Neward råder när man ägnar sig åt objekt/relations-mappning, det vill säga de tidiga framgångarna med O/R-M gör att man inte ser var man inte bör försöka använda tekniken ifråga. Neward anser att det finns sex stycken vägar man kan välja när man står inför ett O/R-M problem: 1. Övergivandet. Utvecklaren struntar helt i att använda objekt och faller tillbaka på en programmeringsmodell som inte skapar glapp mellan objekt och relationer. 2. Helhjärtat accepterande. Utvecklaren överger relationsdatabasen till förmån för en objektorienterad databas. 3. Manuell mappning. Utvecklaren accepterar att problemet inte är så svårt att lösa manuellt och skriver accesskoden till databasen för hand. Denna kod kan även genereras av ett verktyg som undersöker databasen. 4. Acceptera O/R-M:s begränsningar. Utvecklaren accepterar att O/R-M inte löser alla problem utan kanske enbart 80 % av dem. Man använder O/R-M där det lönar sig och hittar andra vägar där det inte lönar sig. 5. Integration av relationskoncept i språket. Utvecklaren accepterar att det här är ett problem som ska lösas av språket och inte med bibliotek eller ramverk. (LINQ är ett exempel på detta.) 6. Integration av relationskoncept i ramverk. Utvecklaren accepterar att problemet är lösbart, men endast genom att man byter perspektiv. Istället för att förlita sig på språk eller bibliotek för att lösa problemet så ändrar man synen på objekt till en som ser mer relationsnatur i dem. De två första punkterna kan vi bortse från eftersom man på det sättet går miste om i ena fallet fördelarna med objektorientering (modularitet, arv och polymorfism) och i det andra fallet fördelarna med relationsdatabaser (effektiv lagring av data). Men en liten reservation är på sin plats: om man inte 2
har några som helst problem med att hantera alla sina data som objekt och hastighet är av stor betydelse så tjänar man på att använda sig av en objektorienterad databas istället för ett O/R-M verktyg. I en jämförande undersökning av DB4O och NHibernate visade det sig att tidsvinsten att använda den objektorienterade databasen var avsevärd (Zyl, et.al., 2006). Mjukvaruutvecklaren Mark Fussell menar att O/R-M är mer komplicerat än det behöver vara på grund av att relationsdatabaser inte implementerar kärnkoncepten hos relationsteorin samt att varje programmeringsspråk har sin variant av objektmodellering. Vidare menar han att de två olika modellerna har så olika angelägenheter att de i själva verket är väldigt kompatibla; relationsmodellen behandlar kunskap och objektmodellen behandlar beteende (Fussell, 1997). Mappningsproblemet blir då att bestämma hur de två olika världarna kan referera till varandra. Även Fussell presenterar en lista på olika integrationsgrader, men till skillnad från Newards lista så är Fussells bara på fem punkter: 1. Ren relationsmodell. Klientapplikationens användarinterface är centrerat runt rader och tabeller. 2. Lätt objektmappning. Det finns några objekt på klientsidan och man har försökt isolera det mesta av applikationen från SQL-satserna. Rader i databasen konverteras till objekt hos klienten. Hårdkodade SQL-satser inkapslas i särskilda klasser/metoder. 3. Mellanhård objektmappning. Klientapplikationen är skriven i huvudsak för objekt och den kanske även måste hantera transaktioner kring dessa objekt. Klienten kan svara på enkla identitetsbaserade hämta-frågor utan att ta kontakt med databasen. 4. Full objektmappning. Klientapplikationen är helt skriven för objekt. Denna nivå är lik föregående nivå men tillåter mycket generella format för att lagra klassdata, hantera skillnader mellan objektrymderna hos server och klient samt försöker i möjligaste mån att köra frågor lokalt istället för att skicka dessa till databasen. 5. Objektserver. Här använder sig även servern av objekt för att organisera både information och objekt. Även här är de två ytterligheterna (punkt 1 och 5) ointressanta eftersom man då förlorar fördelarna med någon av de två modellerna. 1.4 Hur utförs en O RM i praktiken? Det enklaste sättet att överbrygga klyftan mellan objektorientering och relationsdatabaser är att använda ett automatiserat verktyg som t.ex. Nhibernate (vilket jag kommer att återkomma till längre fram i rapporten). Ett annat lite svårare sätt är att med hjälp av JDBC (Java Database Connectivity) eller ADO.NET handkoda mappningen. Det tredje sättet att lösa problemet på är att helt enkelt låta databasens lagringsmodell stå modell för hur objekten ska utformas. Det sista sättet är den traditionella lösningen på att komma över glappet. Man har så att säga effektivt löst problemet genom att ignorera den ena halvan (Neward, 2006). Denna lösning medför dock att det blir omöjligt att utnyttja objekten fullt ut. Vi är nu fast i en pseudo-objekt-relations mappning; förhoppningsvis en mappning som automatiserat så mycket som möjligt så att man kan ägna sig åt domänmodellen och applikationsutvecklingen istället för detaljerna hos objekt-tabell mappning. Använder man den sistnämnda lösningen så riskerar man att råka ut för problem. Det första problemet (och också det som lättast identifieras) är frågan hur man ska mappa klasser till tabeller. (En liten parantes är dock på sin plats; om man utvecklar i C# så är den naturligaste vägen att använda sig av DataSet för att hantera databaser.) Först kan det verka som ett relativt rättframt förfarande: tabeller mappas till typer och kolumner till fält. Men problemen börjar hopa sig när man vill försöka få med arv, något som inte existerar i relationsvärlden. För att försöka komma runt det problemet kan man välja en av tre vägar: 1. Mappa en tabell per klass, 2. Mappa en tabell per konkret klass (en konkret klass är en klass som kan instansieras) 3. Mappa en tabell per klassfamilj. Dessvärre är ingen av dessa lösningar fri från komplikationer. Den första vägen är kanske den som är lättast att förstå eftersom den strävar efter att minimera avståndet mellan objektmodellen och relationsmodellen, varje klass i arvshierarkin får sin egen 3
relationstabell och objekt av härledda typer sys ihop via join-frågor. Nackdelen är att det ofta blir många join-frågor för att sammanställa data till ett objekt. Om man betänker att join är en av de mest tidskrävande operationerna i relationsdatabassystem så inser man att detta förfarande inte är en bra lösning. På grund av detta så vänder sig utvecklarna typiskt till någon av de andra två lösningarna. De är mer komplexa men också mer effektiva då det gäller att handskas med relationsdata. Den andra vägen är att använda en konkret klass för varje tabell och sedan avnormalisera databasen, den klass man väljer för en specifik tabell är typiskt den mest härledda klassen. Till sist har vi den tredje vägen som innebär att man skapar en enda tabell för hela hierarkin och samtidigt skapar en extra kolumn där man anger vilken klass varje rad tillhör. (hybrider av dessa tre tillvägagångssätt finns, men resultaten av dessa är inte signifikant bättre än de renodlade varianterna). De största nackdelarna med metod två är att avnormaliseringen kan ta lång tid och att man löper en överhängande risk att få många tomma kolumner och att alla kolumner måste tillåta nullvärden. Arv och mappning är inte de enda problemen. Hur ska man ange associationer mellan objekt? Relationer av 1:n eller m:n kardinallitet finns det gott om i relationsdatabaser. Efter denna utläggning kan man mycket väl förstå att det sista sättet oftast inte är något lyckat sätt att lösa problemet på. Vill man nu inte avsvära sig det objektorienterade synsättet utan hellre välja den andra vägen så presenterar Wolfgang Keller en hel uppsjö med designmönster som man kan använda för att skapa ett lyckat accesslager mellan relationer och objekt (Keller, 1997), (Keller, 1998). Men för en tidspressad utvecklare känns det inte som något bra alternativ (vi talar här om över 40 olika designmönster som ska tillämpas på de olika stegen i processen). Nu inser man att inte heller den andra vägen har så stort praktiskt värde i utvecklarnas dagliga liv. Det är därför inte så konstigt att man ofta faller tillbaka på den första vägen det vill säga att använda ett automatiserande verktyg för att skapa den kod man behöver. 1.5 LINQ LINQ står för Language Integrated Query och är en komponent hos Microsofts.Net-ramverk från och med version 3.5. Eric Meijer en mjukvaruutvecklare hos Microsoft anser att det idag finns tre förhärskande datamodeller som alla kommer med sitt eget frågespråk (Meijer, 2006). De tre modellerna han syftar på är relationsmodellen med sina tupler och SELECT-FROM-WHERE frågor, XML-modellen med sina ordnade mängder och FROM-LET-WHERE-ORDERBY-RETURN frågor och OO-modellen med sin stora mängd olika samlingar och ännu fler sätt att hantera dessa. Det dessa tre synsätt har gemensamt är idén om samlingar som kan filtreras, transformeras och jämnas till. Samlingstypen tillsammans med transformationen bildar en monad eller vad som med LINQ-terminologi kallas Standard Query Operators. Monad kommer ifrån den funktionella programmeringen där den är en form av abstrakt datatyp som representerar beräkningar 2. Den stora fördelen med det monadiska synsättet är att det tillåter att man (utvecklarna av LINQ) lägger till syntaktisk godis (d.v.s. för användaren förenklande kod) över de monadiska operationerna så att man erhåller något som kan liknas vid en övergång mellan SQL och relationsalgebra. Skillnaden är dock den att detta syntaktiska godis fungerar för alla datamodeller som stödjer monadmönstret och inte bara för SQL. Tanken med LINQ är alltså att man ska ha ett enda frågespråk som är oberoende av källan och att frågespråket ska vara integrerat i programspråket så att man kan åtnjuta fördelarna med att få frågorna kontrollerade av kompilatorn. Figur 1 visar en schematisk bild av LINQ. De frågeoperatorer LINQ är baserat på fungerar i huvudsak på objekt som implementerar IEnumerable<T>. IEnumerable<T> är ett interface i.net som gör objekt uppräkningsbara: har man en samling av objekt av en typ som implementerar IEnumerable<T> så kan man med hjälp av foreach stega igenom dem. Frågespråket kan utökas till att fungera med andra datakällor än de som det ursprungligen stödjer (Pialorsi & Russo, 2007). 2 Se http://en.wikipedia.org/wiki/monad_(functional_programming) för utförligare förklaring 4
Figur 1: Schematisk bild av LINQ För att kunna hantera många olika typer av datakällor är LINQ uppdelad i fem delar: LINQ to SQL: Denna del av LINQ används för att ställa frågor mot MS SQL Server-databaser och gick tidigare under namnet DLinq. Det har diskuterats ivrigt på nätet om denna variant av LINQ kommer att stödja fler databaser än SQL Server och inte ens Microsoft tycks veta vad de vill (OakLeaf Systems, 2007). Själv misstänker jag att de gått in så hårt för att optimera LINQ to SQL för just MS SQL Server så att en utbyggd kompabilitet inte låter sig göras på ett enkelt sätt. Men detta hindrar inte att andra kan utveckla sina egna LINQ varianter för just den databas de utvecklar/använder. En av de största nackdelarna med den första versionen av LINQ to SQL var att den inte hade något färdigt stöd för multilager applikationer (OakLeaf Systems, 2007). LINQ to XML: Denna del av LINQ används för att hantera XML-filer. Tidigare gick denna variant av LINQ under namnet XLinq. Microsoft har här lagt till en del funktionalitet som de tyckte saknades i standard XML DOM (Document Object Model). LINQ to DataSet: Om man har en databas som inte är av typ MS SQL Server, eller om man har existerande kod som förlitar sig på DataSet så använder man denna del av LINQ. LINQ to Objects: Denna del av LINQ är till för tillfällen då man vill använda LINQ mot vilken IEnumerable- eller IEnumerable<T>-samling som helst. LINQ to Entities: Alternativt LINQ API som används för att kommunicera med databaser. Den skiljer entitetsobjektmodellen från den fysiska databasen genom att skapa en logisk mappning mellan de två. Om man vill ha mer flexibilitet än vad LINQ to SQL erbjuder eller om man arbetar med ADO.NET Entity Framework så är det denna LINQ variant man ska begagna sig av. Flera delar är under utarbetande av folk som inte är Microsoft-anslutna som t.ex. LINQ to NHibernate. När man i traditionella fall vill ställa en fråga från en applikation till en databas så representerar man frågan som en textsträng. Detta innebär att man så gott som helt saknar felkontroll (och om man utvecklar i Visual Studio har man inte heller tillgång till Microsofts IntelliSense), vilket givetvis är problematiskt och ju komplexare fråga man vill ställa desto större är risken att det blir fel. De största fördelarna med att använda LINQ när man vill behandla data är att: Frågorna ställs på samma sätt 5
oavsett datakälla, frågespråket är bekant, frågorna är starkt typade, lokalt tillstånd är omedelbart tillgängligt för frågan och starkt IDE support är möjligt (Torgersen, 2007). Med att frågorna är starkt typade menas att typerna de använder är definierade i programmeringsspråket och att resultatet av en fråga måste vara av en sådan typ. Att kunna ställa starkt typade frågor till databasen direkt i språket man programmerar i ger ett mycket bättre skydd mot fel än att representera frågorna som vanliga textsträngar, stödjs det dessutom av utvecklingsmiljön så kan man också dra nytta av IntelliSense för att skriva sina frågor. Förutom detta så kan objekt som är länkade till relationsdata behandlas precis som vilka andra objekt som helst. En enkel LINQ fråga ser ut så här: IQueryable<Order_Details> orderdetq2 = (from od in dbcontext.order_details select od); Vad den gör är att den hämtar alla element från tabellen Order_Details och stoppar in dessa i orderdetq2 som är av typen IQueryable. Observera att frågan inte körs mot databasen förrän man vill komma åt frågans innehåll. T.ex. så skulle följande kodrad få applikationen att anropa databasservern: List<Order_Details> orderlist = orderdetq2.tolist<order_details>(); Man kan också stega igenom IQueryable direkt, utan att göra om den till en list: foreach(order_details od in orderdetq2) { //Lämplig accesskod } En trevlig funktionalitet i LINQ är att man kan använda lambdauttryck i frågorna. De kan ses som ett mer koncis sätt att skriva anonyma metoder på och dessutom känns de igen från den funktionella programmeringen 3. I LINQ är de användbara eftersom de tillhandahåller ett kompakt och typsäkert sätt att uttrycka funktioner som kan skickas som argument till utvärderingsmetoder (Gu, 2007). Vill man ha alla orderdetaljer som hör till order id 10300 skulle frågan formuleras så här: var orderdetwq2a = dbcontext.order_details.where(x => x.orderid == 10300); Det är inte nödvändigt att använda var när man skickar tillbaka hela poster utan man kan istället specificera att det är något av typen Order_Details man vill ha tillbaka, då måste man dock skicka in resultatet i en lista, detta är logiskt eftersom det kan vara en mängd man får tillbaka och inte ett enskilt element. En annan trevlig funktionalitet i LINQ är kompilerade frågor. Har man frågor som läser från databasen många gånger kan man tjäna mycket tid på att kompilera dem i förväg. Raja Nadar skriver i ett blogginlägg att han uppmätt prestandaökningar på upp till 220 % när han gått från vanliga LINQfrågor till kompilerade diton (Nadar, 2008). Om LINQ-frågan för select * här ovan skulle skrivas om som en kompilerad fråga (ett inte helt realistiskt scenario), så skulle den se ut så här: Func<LastEntities, IQueryable<Order_Details>> orderdetcomp2 = CompiledQuery.Compile((LastEntities le) => (from od in le.order_details select od)); Ja, syntaxen blir krångligare och det blir mer att skriva, men använder man frågan ofta så belönas det extra kodskrivandet av snabbare körtid. Trots att LINQ släpptes i och med.net 3.5 är det ännu ej en färdig produkt och det händer att man försöker använda metoder som ännu ej implementerats som t.ex. last-metoden som ska skicka tillbaka sista posten i en samling. 1.6 NHibernate NHibernate är ett O-RM verktyg som är en.net anpassning av Hibernate för Java. Version 1.2.1 av NHibernate kom 26/11 2007 och innehåller flera av Hibernate 3:s features (Hibernate 3 släpptes 23/9 2007). NHibernate använder sig av POCO (Plain Old CLR Objects) för mappningen. NHibernate är en open source-produkt som har rönt en viss popularitet bland utvecklare av databasapplikationer i.net miljö. Nackdelarna med NHibernate är dels att det inte stödjer LINQ, men det finns ett sidoprojekt på gång (LINQ to NHibernate) som kanske kan ändra på det och dels att man måste skapa 3 För mer om lambdauttryck se: http://en.wikipedia.org/wiki/lambda_expression 6
mappningskoden själv, eller via något tredjepartsprogram; se kapitlet om användarvänlighet för en utläggning om kodgenereringsverktygen. När man arbetar med NHibernate och vill ställa frågor mot databasen kan man gå tillväga på tre olika sätt. Det första sättet är att använda sig av kriterier för att få fram den data man vill ha. En typisk kriteriefråga kan se ut så här: ICriteria criteria = session.createcriteria(typeof(subject)); IList subjectlist = criteria.list(); Denna fråga skapar en lista över alla posterna i tabellen Subject. Den andra metoden att ställa frågor till databasen via NHibernate är att använda NHibernates egna frågespråk HQL (Hibernate Query Language). En HQL-fråga ser ut så här: IQuery subjectquery = session.createquery("select ALL FROM Subject"); IList subjectlist = subjectquery.list(); Denna fråga gör precis samma sak som den förra frågan, det vill säga väljer ut allt från Subjecttabellen. Som synes så har man ingen som helst kompileringskontroll av frågan. Det tredje sättet som man kan ställa frågor på är att använda vanlig SQL: IQuery subjectquery = session.createsqlquery("select {subjects.*} FROM Subject {subjects}","subjects",typeof(subject)); IList<Subject> subjectlist = subjectquery.list<subject>(); Som synes är detta det omständligaste sättet att arbeta mot databasen. I första hand bör man använda sig av HQL när man vill ställa frågor till databasen eftersom man då slipper ta hänsyn till egenheter hos databasens SQL-dialekt. Om man däremot vill använda sig av något special SQL-kommando som databasen stödjer men som inte finns i HQL eller om man arbetar med lagrade procedurer så kan man använda sig av vanlig SQL. 1.7 ADO.NET Entity Framework ADO.NET Entity Framework är ett ramverk som med ADO.NET i grunden ska förenkla kommunikationen mellan applikation och datakälla. Vad man vill åstadkomma är att höja abstraktionsnivån för att på så sätt minimera vad som i programmeringskretsar kallas the objectrelational impedance mismatch (Vastro, et.al., 2007). I och med entitetsdatamodellen (EDM) vill man även införa en ny datamodell. Man hoppas med andra ord att kunna åstadkomma något mer än att bara skapa ännu ett O/R-M verktyg. Alla är naturligtvis inte lika övertygade om att EF är något mer än bara ännu ett O/R-M verktyg. Frans Bouma som har lyssnat till avsnitt 369 av.net Rocks! (Simmons & Stephen, 2008) kommenterar det hela på sin blogg (Bouma, 2008). Se kapitlet om kritik för mer om detta. ADO.NET EF skiljer sig från andra system på fyra större sätt (Adya, et.al., 2007): 1. EF definierar en rik, värdebaserad konceptuell datamodell (EDM) och ett nytt frågespråk (Entity SQL) som opererar på denna modell. 2. Modellen görs konkret genom att Entity Framework inkluderar en egen mappningsmotor som stödjer tvåvägs (EDM - relationsmodellen) mappningar, frågor och uppdateringar. 3. Applikationer och tjänster kan arbeta direkt mot det konceptuella lagret. EDM är tänkt att vara en mer flexibel bas för att dela data bland applikationer och datacentrerade tjänster än objekt. 4. EF fungerar med Microsofts Language Integrated Query så att man på det sättet kan ställa frågor mot EDM vars syntaktiska riktighet kan kontrolleras vid kompilering. 1.7.1 Arkitekturen hos ADO.NET EF För att kunna hantera något så omfattande som Microsoft hoppas att Entity Framework ska bli så krävs det att ramverket är väl strukturerat och indelat i väl definierade delar där varje del har sitt ansvar. Man kan säga att ADO.NET EF är indelat i sex större delar, i (Adya, et.al. 2007) anges dessa som: 1. Datatillhandahållare (Data source providers). EF bygger på ADO.NETs datatillhandahållarmodell. Här sköts kommunikationen med själva databasen. Det finns specifika datatillhandahållare för en uppsjö av källor, till exempel relationsdatabaser. 7
2. Entitetsklienttillhandahållare (EntityClient provider). Den representerar ett konkret konceptuellt programmeringslager. Data nås via EDM:s entiteter och associationer och den utfrågas/uppdateras via en entitetsbaserad variant av SQL (esql). 3. Objekttjänster och programmeringslager (Object Services and other Programming Layers). Objekttjänsterna hos EF tillåter en rik dataabstraktion över entiteterna och en stor mängd tjänster ovan dessa tillåter applikationer att använda välbekanta programkonstruktioner. 4. Metadatatjänster (Metadata services). Denna del tillhandahåller metadata för alla ramverkets behov. All metadata associerad med EDM-koncept och mappningskoncept exponeras via metadatainterface. 5. Design- och metadataverktyg (Design and Metadata Tools). EF kan integreras med domändesignare för att på så sätt möjliggöra modelldriven applikationsutveckling. Till verktygen hör EDM designverktyg, mappningsverktyg samt kodgenereringsverktyg. 6. Tjänster (Services). Hit hör externa datacentriska tjänster så som t.ex. rapportering och synkronisering. 1.7.2 Entitetsdatamodellen Centralt i ramverket är Entitetsdatamodellen (Entity Data Model) EDM som är en typ av entitetsrelations-datamodell. Entitets-relations-datamodellen föreslogs första gången 1976 av Peter Pin-Shan Chen och har används framgångsrikt sedan dess (Microsoft, 2006). ER-modellen tar hänsyn till att den verkliga världen både består av entiteter och relationer (Chen, 1976). För att hantera lagringen av data använder sig ramverket av Storage Entity Data Model, vilken beskrivs av det XML-baserade språket SSDL (Storage Schema Definition Language). 1.7.3 EDM EDM-konceptet kan delas in i två kategorier: en som definierar typer och en som hanterar instanser av dessa typer. För att beskriva EDM scheman används ett XML-baserat datadefinitionsspråk som erhållit namnet Conceptual Schema Definition Language (CSDL). I specifikationen för EDM 1.0 gick språket enbart under namnet SDL (Microsoft, 2006). 1.7.4 Typer Typsystemet som hör till EDM är strukturbaserat istället för beteendebaserat och det märks på tre sätt: de är inte inkapslade, de har inga associerade metoder och de stödjer strukturellt arv men inte beteendebaserat arv. Strukturellt arv innebär att värdena ärvs medan beteendebaserat arv innebär att implementationen ärvs. Entitetstyp (EntityType): Definierar det dataobjekt för vilket man måste hantera information, som t.ex. platser och personer. En entitet är en instans av denna typ. Entitetstypen har bland annat unik identitet. Entitetstypen modellerar konceptet den översta nivån i en datamodell så som t.ex. Kund eller Order (exempel från ett affärssystem). En entitetstyp kan antingen vara abstrakt eller konkret. Är den abstrakt så kan den inte ha instanser. En entitetstyp kan ha en eller flera egenskaper (property) av enkel typ, komplex typ eller radtyp. Egenskaper kan antingen ha ett eller flera värden. Alla instanser av en entitetstyp är unikt identifierbara genom värdet hos deras identifierande egenskaper. Precis som hos relationsdatabaser kan nyckeln utgöras av en eller flera typer. Typerna måste dock vara enkla. Den/de egenskap/egenskaper som identifierar entiteten kallas entitetsnyckel EntityKey() Enkel typ (SimpleType): En enkel typ är atomär och kan vara av primitiv typ (PrimitiveType) dvs. String, bool, Int16, osv. eller uppräknings typ (EnumerationType) dvs. uppräkningar av saker. Komplex typ (ComplexType): En mängd av relaterad information. Den består precis som entitetstypen av en eller flera egenskaper av enkel typ, komplex typ eller radtyp. Den komplexa typen är dock inte associerad med någon nyckel. 8
Radtyp (RowType): En anonym typ som strukturellt liknar den komplexa typen men den kan inte delta i typarv. Två radtyper är jämförbara om de har samma antal, sekvens och typ av egenskaper. Radtypen är den enda anonyma typen i EDM. CSDL i version 1 av EF stödjer inte deklaration av radtyper. Relationstyp (RelationshipType): Om man skulle säga att entitetstypen är datamodellens substantiv så skulle relationstypen vara dess verb. En relationstyp beskrivs över en eller flera deltagande entitetstyper. Det finns två typer av relationstyper i EDM: association (Association) och inneslutning (Containment). Inneslutning är inte implementerad fullt ut i EF v1. Alla EDM-typer tillhör någon namnrymd. Schemakonceptet definierar en namnrymd som beskriver omfånget hos EDM-typerna. I första versionen av Entity Framework är inte EnumerationType implementerad 4 utan de enkla typerna utgörs enbart av primitiva typer. De primitiva typer som det från början är tänkt att EF ska stödja är: Binary, Boolean, DateTime, Decimal, Float, Double, Guid, SByte, Int16, Int32, Int64, Byte, UInt16, UInt32, UInt64, String, Money och Xml (Microsoft, 2006). I RTM-versionen har inte alla av dessa implementerats och några nya har tillkommit. De primitiva typer man i dagsläget kan använda är: Binary, Boolean, Byte, DateTime, DateTimeOffset, Decimal, Double, Guid, Int16, Int32, Int64, SByte, Single, String och Time (Simple Types (EDM), 2008). Betydelsen av de flesta primitiva typer är rättfram, de två vars betydelse är mer obskyr är Guid och DateTimeOffset. Den första typen är ett 128 bitars unikt id-värde som mappar till samma datatyp i SQL Server. Den andra typen (DateTimeOffset) representerar ett intervall i millisekunder som kan adderas till eller subtraheras från en DateTime. Som synes är inte Money eller Xml implementerade än. Vissa egenskaper hos typer har även facetter. En facett kan t.ex. vara nullable som anger att denna typ kan ha värdet null, eller multiplicity som anger kardinaliteten hos egenskapen. 1.7.5 Instanser Förutom att EDM tillhandahåller koncept för att definiera typer så tillhandahålls även koncept för att hantera instanser av dessa typer. Dessa instanser kan sedan samlas i olika typer av mängder beroende på vad för typ de är instanser av. Enligt specifikationen för EDM finns det följande mängder: Entitetsmängd (EntitySet): I denna mängd samlar man instanser av entitetstyp. En entitetstyp tillhör som mest en entitetsmängd. Man kan samla flera entiteter i en och samma entitetsmängd förutsatt att de uppfyller följande tre krav: 1. Instansen har samma typ som entitetsmängden eller samma undertyp. 2. Nyckeln hos instansen har ett inom entitetsmängden unikt värde. 3. Instansen tillhör inte någon annan entitetsmängd. Relationsmängd (RelationshipSet): I denna mängd samlar man relationer som är av samma relationstyp. Inneslutningsmäng (ContainmentSet): I denna mängd samlar man relationsinstanser av inneslutningstyp. Denna mängd är inte helt implementerad i första versionen av EF, mycket på grund av att själva idén om inneslutning inte är standardiserad. Entitetscontainers (Entity Containers): Alla EDM-instansbaserade koncept så som entitetsmänger och relationsmängder definieras inom en Entitetscontainer. Man kan ha en eller flera instanser av Entitetscontainers och en Entitetscontainer kan referera till ett eller flera scheman. 1.7.6 esql Förutom EDM hör ett frågespråk esql till ramverket. esql står för Entity SQL (ej att förväxla med extended SQL) och är en vidareutveckling av Microsofts SQL variant Transact-SQL. Än så länge stödjer esql enbart SELECT-frågor, man kan alltså inte köra UPDATE-, INSERT-, eller DELETE- 4 http://forums.microsoft.com/msdn/showpost.aspx?postid=2726986&siteid=1 (22/1 2008) 9
frågor på detta sätt. Enligt en forumpost från Daniel Simmons 2006 5 finns det dock planer på att utöka esql så att man även kan ställa andra frågor än SELECT-frågor. Det skulle antagligen inte ske till första versionen av Entity Framework (vilket jag nu vet stämmer). 1.7.7 EFs frågeprocess En typisk esql- eller LINQ-fråga som ställs via EF går igenom 11 steg innan man erhåller svaret (Adya et al., 2007): Syntaktisk och semantisk analys. En fråga ställd i esql går först igenom tolken och analyseras semantiskt med hjälp av information från metadata servicen. LINQ-frågor tolkas och analyseras med hjälp av kompilatorn för språket de är skrivna i. Omvandling till ett kanoniskt kommandoträd (canonical command tree). Oavsett om frågan ursprungligen uttrycktes i esql eller LINQ så skrivs den nu om till ett kanoniskt kommandoträd och valideras. Utveckling av mappningsvyer (Mapping View Unfolding). Frågor som ställs i EM är riktade mot den konceptuella modellen och måste därför översättas så att den istället refererar till den underliggande databasens tabeller och vyer. Mappningen mellan EDM-schemat och databasen kompileras till fråge- och uppdaterings-vyer. Frågevyn utvecklas sedan till användarens fråga och på så sätt så riktas den ställda frågan mot databasen. Strukturerad typeliminering (Structured Type Elimination). Alla referenser till strukturerade typer tas bort från frågan och flyttas över till en uppsamlingsplats för data som ska användas när svaret på frågan ska sättas ihop. Till strukturerade typer räknas bl.a. typkonstruktorer. Projektionsbeskärning (Projection pruning). Frågan analyseras och orefererade uttryck elimineras från frågan. Nästlingsbegränsning (Nest Pull-up). Alla nästlingsoperationer i frågan tas upp till ytan och man erhåller här en grundläggande relations substrat. Nu kan också komplexa frågor delas upp i mindre och enklare frågor. Transformationer (Transformations). Ett antal heuristiska transformationer tillämpas på frågan för att förenkla den. Här sker dock ingen kostnadsbaserad optimering. Översättning till tillhandahållarspecifika kommandon (Provider-Specific Commands). Kommandoträdet skickas över till tillhandahållaren (providern) för att där omvandlas till ett tillhandahållarspecifikt kommando i rätt SQL-dialekt. Kommandoexekvering (Command Execution). Det/de ovan skapade kommandot/kommandona körs hos databasen. Resultatihopsättning (Result assembly). Resultaten (DataReaders) från tillhandahållarna omformas till rätt form med hjälp av informationen som man sparade undan tidigare och skickas tillbaka i form av en enda DataReader. Objektmaterialisering (Object Materialization). För frågor som skickades via objekttjänster (Object Services) så materialiseras resultatet i rätt programspråk. I punktlistan ovan nämner jag kanoniska kommandoträd. Ett sådant träd är en trädstruktur som kan respresentera alla frågor som kan ställas i EF. De kan representera alla typer av frågor och även anrop av lagrade procedurer. 5 http://forums.microsoft.com/msdn/showpost.aspx?postid=724998&siteid=1 (14/9 2006) 10
2 Att använda Entity Framework I det här kapitlet kommer jag att titta lite närmare på hur man använder EF samt göra vissa jämförelser med NHibernate. Innan man börjar använda EF måste man ställa sig frågan varför man skulle vilja använda EF när man har LINQ to SQL? LINQ to SQL är först och främst avsett som ett RAD-verktyg när man arbetar med MS SQL Server-databaser. RAD står för Rapid Application Development vilket är en metodik för applikationsutveckling. Har man en annan typ av databas eller arbetar med en företagsapplikation så är det EF som gäller. Dessvärre så stödjer EF i dagsläget inga andra databaser än MS SQL Server, för framtida planer se avsnitt 2.7 Entity Frameworks stöd för övriga databaser. För att se hur EF fungerar i praktiken har jag utvecklat en liten applikation kring Microsofts testdatabas Northwind. Vad jag testar är att hämta data till datagrids, ändra, ta bort och lägga till data i databasen och använda lagrade procedurer. Applikationen är inte ett fullständigt program utan snarare bara ett demo som visar hur man kan använda EF och LINQ to Entities i praktiken. 2.1 Att visa upp data i en DataGrid Det första problemet som uppstod var hur jag skulle få innehållet till valfri tabell att visas i en DataGrid. När jag kikade runt lite i EFs metoder hittade jag metoden GetSchemaTable() som var kopplad till EntityDataReader. Den verkade ge mig det jag ville ha, problemet var bara att den ännu ej var implementerad. Efter att ha letat på nätet hittade jag ett sätt att fylla en datatable med data från valfri tabell i databasen (James, 2007). Hade GetSchemaTable-metoden varit implementerad så skulle det ha sett ut såhär: DataTable dt = new DataTable(); EntityConnection conn = new EntityConnection(dbContext.Connection.ConnectionString); conn.open(); string querystr =@"SELECT VALUE t FROM ExampleEntities."+this.comboBox1.SelectedItem.ToString()+" as t"; EntityCommand cmd = new EntityCommand(queryStr, conn); EntityDataReader reader2 = cmd.executereader(commandbehavior.sequentialaccess); dt=reader2.getschematable(); conn.close(); datagridview1.datasource = dt; Tabellen som man vill visa upp i datagriden väljer man från en combobox i GUI:t. 2.2 Parametiserade frågor En parametiserad fråga är en fråga som förutom frågenyckelord även tar parametrar för det man frågar efter. Om man i esql skriver sin fråga som en vanlig textsträng och bara slänger in de värden man får från användaren med hjälp av +-operatorn så blir frågan känslig för SQL-injektioner. Denna frågesträng illustrerar problemet: string querystr = "select VALUE row (c.contactid, c.companyname, c.contactname, c.contacttitle, c.address, c.city, c.region, c.postalcode, c.country, c.phone) from ExampleEntities.Contacts AS c where c.contacttype="+param; var custq = new ObjectQuery<DbDataRecord>(querystr, dbcontext); En illvillig användare kan här slänga in en egen esql-fråga i param-variablen och på så sätt ställa till elände. Om man nu istället skulle använda en parametiserad fråga så skulle det i esql se ut så här: 11
string querystr = "select VALUE row (c.contactid, c.companyname, c.contactname, c.contacttitle, c.address, c.city, c.region, c.postalcode, c.country, c.phone) from ExampleEntities.Contacts AS c where c.contacttype=@param"; var custq = new ObjectQuery<DbDataRecord>(querystr, dbcontext); custq.parameters.add(new ObjectParameter("param", "Customer")); I den parametiserade frågan ovan så sätts param-variabeln till Customer och då får vi ut all information om kunder från Contacts-tabellen utan att riskera att råka ut för SQL-injektioner. Man kan även roa sig med att skriva parametiserade LINQ-frågor. En sådan skulle se ut såhär: var empq2 = dbcontext.contacts.where("it.contacttype=@param", new ObjectParameter("param", "Employee")).Select(x => new { x.contactid, x.contactname, x.contacttitle, x.address, x.city, x.region, x.postalcode, x.country, x.phone, x.extension }); I frågan ovan sätts param-variabeln till Employee. Varför man skulle vilja göra så är ett mysterium för mig eftersom man i och med LINQ kommit bort från SQLs sårbarhet. Ovanstående fråga skulle i ren LINQ se ut så här: var empq2 = dbcontext.contacts.where(p => p.contacttype == param).select(x => new { x.contactid, x.contactname, x.contacttitle, x.address, x.city, x.region, x.postalcode, x.country, x.phone, x.extension }); Om man har behov av att I sammanhanget använda en kompilerad parametiserad LINQ-fråga så får man något som ser mer komplext ut: var compiledquery = CompiledQuery.Compile((ExampleEntities ee, string selection)=> from c in ee.contacts where c.contacttype==selection select new { c.contactid, c.companyname, c.phone }); var shipqc = compiledquery.invoke(dbcontext, "Shipper"); Frågan ovan plockar ut alla kontakter som är klassade som Shipper från kontakttabellen. 2.3 Lagrade procedurer Lagrade procedurer är någonting som förekommer ofta i databasvärlden, därför är det av vikt att EF kan hantera dessa på ett bra sätt. Vill man använda sig av lagrade procedurer måste man först och främst ha valt att dessa ska tas med när man skapar sin EDM utifrån databasen. Därefter måste man skapa en funktionsimport (FunctionImport) för dem. I figur 2 har den lagrade proceduren CustOrderHist markerats och vid högerklick med musen så kan man som första menyalternativ se valet man ska göra för att skapa en funktionsimport. Figur 3 visar dialogrutan för att skapa en funktionsimport, man fyller i namn för den och väljer returtyp. Observeras i sammanhanget bör att än så länge så stödjer EF enbart att man skickar tillbaka befintliga entiteter från lagrade procedurer. Om man av det ena eller andra skälet får tillbaka något annat så får man problem. Ett praktiskt exempel är att när jag ville få upp de tio dyraste varorna så skickade den lagrade proceduren tillbaka två kolumner; en kolumn med varans namn och en med dess pris och denna struktur finns det inte någon motsvarande entitet för. Jag tänkte först att detta löser jag genom att skapa en korresponderande entitet i EDM-editorn, men det visade sig inte gå eftersom EF ännu inte stödjer att man mappar samma databaskolumn till flera entiteter, 12
Figur 2: Menyval för att skapa funktionsimporten Figur 3: Dialogrutan som visas när man skapar en funktionsimport dataintegriteten hotas eftersom det inte finns något sätt för EF att ange att en entitet enbart får läsas från och inte skrivas till. Det finns tre sätt att gå runt detta problem: 1. Första sättet är att skapa en vy på databassidan för det man vill ha tillbaka, det kanske är den enklaste lösningen iallafall om man har tillgång till databasen och inte räds att den ska bli översållad med vyer. 2. Den andra metoden är att gå in i den automatiskt genererade SSDL-filen (eller korresponderande avsnitt i EDMX-filen om man bara har en fil) och skapa vyn där. 3. Den tredje metoden är att man går in och skapar sin vy i MSL-filen, denna metod är den säkraste och också den enklaste i de fall där man inte kan gå in i databasen och ändra. 13
En annan sak som man måste tänka på är att namnen på kolumner som returneras av den lagrade proceduren måste vara samma som namnet på kolumnerna i databasen. Noam Ben-Ami och Daniel Simmons lovar att detta ska bli bättre till version 2 av EF 6. När man väl har skapat funktionsimporten för sin lagrade procedur så använder man den på samma sätt som vilken metod som helst: ObjectResult<ProductAndPrice> expensiveproducts = dbcontext.tenmostexpensiveproducts(); Notera här att ProductAndPrice är min specialskapade databasvy. Skulle man vilja använda EntityCommand för att anropa den lagrade proceduren så skulle det bli avsevärt mer med kod: using (EntityConnection conn = new EntityConnection(dbContext.Connection.ConnectionString)) { conn.open(); using (EntityCommand cmd = conn.createcommand()) { cmd.commandtext = "RTS_ConfigEntities.SelGroupTags"; cmd.commandtype = CommandType.StoredProcedure; EntityParameter param = new EntityParameter(); param.value = id; param.parametername = Constants.groupId; cmd.parameters.add(param); param = new EntityParameter(); param.value = node; param.parametername = Constants.groupNode; cmd.parameters.add(param); using (EntityDataReader edr = cmd.executereader(commandbehavior.sequentialaccess)) { //Här körs lämplig accesskod mot det den lagrade //Proceduren returnerar } } Koden ovan är hämtad från mitt försök att konvertera databasaccessen i en befintlig applikation till EF och den har inget att göra med tidigare exempel. Koden kan naturligtvis minskas något genom att man skapar sin EntityConnection och sitt EntityCommand tidigare, då slipper man using-blocken 2.4 Den konceptuella modellen Den konceptuella modellen är en grafisk representation av de entiteter man har. Man bör dock observera att det inte syns på dem om de representerar tabeller eller vyer. Tanken att man i Visual Studio ska kunna komma åt den konceptuella modellen är god men den har sina brister, t.ex. ska alla entiteter samsas på en sida så den blir lätt oöverskådlig och om man gör några ändringar i den underliggande XML-filen så finns det risk att schemat inte kan ritas upp igen. Det är tänkt att man i framtiden ska kunna rita upp en modell direkt i The Entity Data Model Designer och utifrån den automatiskt generera både databasen och applikationskoden, men frågan är om vi kommer att se den funktionaliteten ens i version 2. När man skapar sin EDM från databasen måste man tänka på att alla tabeller måste ha en primärnyckel. Har de inte det så kommer EF att försöka härleda en sådan och om detta går vägen så kommer en definierande fråga att skapas i EDMX-filens SSDL-del och entiteten blir endast läsbar. Vill man att den skapade entiteten även ska vara uppdateringsbar så måste man först verifiera att den härledda primärnyckeln är korrekt samt därefter radera den definierade frågan från XML-filen. 6 http://forums.microsoft.com/msdn/showpost.aspx?postid=2629634&siteid=1 (4/1 2008) 14
Entitetsmängden (EntitySet) för vyn Category Sales for 1997 i exempelapplikationen innehåller en definierande fråga och ser ut såhär: <EntitySet Name="Category Sales for 1997" EntityType="LastModel.Store.Category Sales for 1997" store:type="views" store:schema="dbo" store:name="category Sales for 1997"> <DefiningQuery>SELECT [Category Sales for 1997].[CategoryName] AS [CategoryName], [Category Sales for 1997].[CategorySales] AS [CategorySales] FROM [dbo].[category Sales for 1997] AS [Category Sales for 1997] </DefiningQuery> </EntitySet> Tabeller i databasen som representerar många-till-många-relationer kommer inte att få någon entitet i den konceptuella modellen. När EDM-verktyget stöter på en tabell som enbart utgörs av två kolumner med främmande nycklar så kommer den att skapa en många-till-många-relation mellan berörda entiteter istället för en ny entitet. I figur 4 ses en enkel konceptuell modell som hör till EFs kommaigångexempel 7. Notera att entiteterna här har blivit omflyttade för att göra bilden så tydlig som möjligt. Vad vi ser i bilden är fyra entiteter som motsvarar tabeller på serversidan, tre ett-till-mångarelationer och en många-till-många-relation. Främmande nycklar syns inte i entiteterna eftersom de är inbakade i navigationsegenskaperna. Figur 4: Modellen för school-databasen 2.5 Låshantering Har man flera applikationer som samtidigt arbetar mot samma databas så finns det en risk att två klienter försöker ändra på samma data mer eller mindre samtidigt. EF hanterar detta genom att tillämpa något som kallas optimistisk låshantering (optimistic concurrency), men och detta är ett stort men som standard så kontrolleras inte om de data man behandlar samtidigt behandlas av någon annan (Microsoft, 2008b). Optimistisk låshantering innebär att de data man behandlar inte låses för editering på servern och om en konflikt inträffar så kastas ett OptimisticConcurrencyException. När 7 http://msdn.microsoft.com/en-us/library/bb399731.aspx 15