LINQ to SSQL für nterrprisse‐A Appllikattion n En Abstra act: Anhand d eines einfa fachen Enterrprise­Anw wendungssze enarios wird eevaluiert, ob b und inwie eweit LINQ tto SQL sich ffür den Einsatz in solcheen Anwendu ungstypen e eignet. Hierf rfür beleuch hten wir zun nächst die grund dsätzlichen A Anforderun ngen solcheer Architektturen, stelle en dann die Featurres von LIN NQ to SQL vo or und erörttern danach h deren Anw wendung. Die Errgebnisse zeeigen, dass das rigide O Object­Traccking­Mode ell von LINQ Q to SQL L bei prozesssübergreife fendem Dateenaustauscch nicht flexxibel genug ist. Diees verursaccht insbeson ndere bei zu ustandsloseen Anwen ndungsumg gebungen (zz. B. ASP.NE ET, WCF­Serrvices) Prob bleme. Die in n Visuall Studio geb botene Toollunterstützu ung ist noch h nicht auf a alle Design nansätze an nwendbar. In Bezug au uf das reinee O/R­Mapp ping leistet LINQ tto SQL allerrdings gute Dienste. TTill Rebenich Seenior eXpert SDX AG Bo orsigallee 19 6038 88 Frankfurt www w.sdx‐ag.de +49 (69) 24 75 18 ‐ 0 1 Ein nleitung Dieser A Artikel soll an nhand eines eeinfachen An nwendungsszenarios im Enterprise‐U Umfeld die Vor‐ und Nachteile des Einsatzes von LINQ Q to SQL aufzzeigen. Hierffür gehen wirr zunächst au uf die grundleggenden Anfo orderungen ssolcher Appliikationen ein n, stellen dan nn die Features von LINQ Q to SQL im Einzeelnen vor und d setzen diesse im genann nten Kontextt ein. 1.1 Anwendun A ngsszenarrio Enterprise‐Applikatio onen sind geewöhnlich schichtenorien ntiert. Hierfü ür wird jede FFunktionalitäät zunächstt kategorisieert und dann einer von no ormalerweisse drei logischen Schichteen (Layer) zugeordnet: Presenttation‐Layer, Business‐Layer, und Datta‐Layer. In d den meisten Fällen wird man hier Design anwen nden, indem man zunäch hst Contractss in Form von n außerdeem ein Contract‐Driven D Interfacees erstellt un nd danach mittels Objectt‐Factory Insttanzen der SSchnittstellen nimplementierung erzeugt. Diese Vorgeehensweise eermöglicht die parallele EEntwicklung jeder einzelnen Schicht, ohne Architektur isst auch notw wendig, wenn n dass Entwickler sich gegenseitig blockieren. EEine solche A Anwendungen für diie Verteilungg auf mehrerre physikaliscche Schichten (Tiers) entw wickelt werd den. Bei der V Verteilung m müssen die veerwendeten Datenobjektte über Prozeessgrenzen h hinweg zwiscchen den physikalischen Schichten ausgetaauscht werden, was dere en (De‐)Seriaalisierung erfforderlich maacht. müssen vorab Software‐D Designentsch heidungen getroffen werrden (1): Zudem m 1. Business‐Entitiess werden als Data‐Trransfer‐Objeccts eingesetzt, d. h. ssie enthalte en keine nd nur einfacche oder kein ne Assoziatio onen zu andeeren Entities. Businesslogik un den so präp pariert, dass sie über einen e WCF‐SService veröffentlicht 2. Conttracts (Interrfaces) werd werd den können. 3. Die B Businesslogik wird als Traansaction‐Skkript in die Scchnittstellenimplementieerung ausgellagert. Alternativ kann im Business‐Layeer auch ein D Domain Model zum Einsaatz kommen (1). Dann mü üssen Data‐Transfeer‐Objects beereitgestellt u und die Dateen der Domaain‐Objects vor der allerdinggs separate D Verwend dung über W WCF‐Servicescchnittstellen n auf diese ge emappt werd den. 1.2 Objektrela O ationales M Mapping Ein zentrraler Aspekt bei der Konzzeption einer Anwendun ng ist das Perrsistieren von n objektorien ntierten Datenstrrukturen, gew wöhnlich in ein relationaales Datenbaankmanagem mentsystem. Verfolgt man die oben genannte Strattegie, Dateneentitäten mö öglichst einfaach zu gestallten, hat man n damit gewöhnlich del, dessen SStruktur sich nicht ohne w weiteres weniger Probleme als bei einem komplexen Domain Mod uf Tabellen aabbilden lässt. Unabhänggig von der ge ewählten Strrategie findeen fast imme er direkt au entsprecchende Transsformationen statt. Prob bleme treten dann meisteens in folgen nden Situatio onen auf: • • • Kom mplexe Vererbungshierarcchien Valu ue‐Objects, d d. h. Objektee, die nur im m Kontext der sie referen nzierenden O Objekte Sinn n machen und normalerweeise keinen P Primärschlüsssel enthalten n über Collections N:m‐Beziehungeen, meistens abgebildet ü Till Rebeenich Seite 2 von 17 2 LIN NQ to SQL L Mit der n neuesten Veersion (3.5) d des .NET‐Fram meworks wu urde erstmalss LINQ (Langguage Integraated Query) eeingeführt. LINQ selbst isst nichts andeeres als eine e Abfragespraache, die direkt in die verwend dete Program mmiersprache (C#, VB…) integriert istt. Extension Methods, An nonyme Type en, Lambda Expressions und Expresssion Trees bilden die Gru undlage dieseer Erweiterung. Mithilfe von d der Interfaces IQuery yable und I IQueryable e<T> lassen sich außerdem Expression Trees und beliebige Dattenformate d definieren. LINQ to Objeccts wendet d dieses Prinzip p auf Query‐Provider für b erable‐Objeekte an, LINQ Q to XML auff XML‐Daten und LINQ to o SQL auf relationale IEnume Datenbaanken. Der Vorteil dieser Strategie istt, dass der Zu ugriff auf Info ormationen über eine einheitliche Abfragessprache erfo olgt, die unab bhängig vom m Datenformaat ist. Im Fall vvon LINQ to SSQL erzeugt d der Query‐Provider im Hintergrund eein entsprech hendes SELECT‐ Statemeent, das dann n auf der Dattenbank ausggeführt wird. Die Ergebnisse der Abfrrage werden n als IEnume erable‐Objeekt zurückgeegeben. LINQ Q to SQL gehtt noch einen Schritt weitter, denn neb ben der genannten Möglichkkeit, LINQ alss Abfragespraache zu verw wenden, werden folgende Features bereitgeestellt (1): • • • • • Objeektrelationales Mappingg: Entityklassengenerieru ung zur Lau ufzeit als an nonyme Typ pen oder explizites Mapping von Entityklassen übeer O/R‐Desiggner oading (verzö ögertes Nach hladen von Instanzen asssoziierter Entities) Defeerred/Lazy Lo Chan nge Trackingg Conccurrency Cheecks Zenttrales Persisttenzmanagem ment 2.1 Datenkont D text und o objektrela ationales Mapping Die zentrale Kompon nente von LIN NQ to SQL isst der Data C Context. Dieser fungiert aals Zugriffspu unkt auf he Funktionen, die LINQ tto SQL bereittstellt. Listing 1 verdeutlicht diesen A Aspekt. sämtlich DataCon ntext ctx = new Data aContext(“ “Server=(lo ocal);Init tial Catalo og= “ + “ “Adventure eWorks;Inte egrated Se ecurity=Tr rue”); var que ery = (fro om x in ctx x.GetTable e<Customer> >() where x.Sa alesTerrit tory.Countr ryRegionCo ode == “US” ” select x x); h (var cus st in query y) foreach { C Console.Wr riteLine(“I ID = {0}, CountryCode = {1}”, , cust.Cus stomerID, cust t.SalesTer rritory.Cou untryRegio onCode); } Listing 1 1: Zugriff auf relationale Daten mit LINQ Man beaachte, dass d die SELECT‐Anfrage erst b bei der Iterattion in der fo oreach‐Sch hleife auf derr Datenbaank ausgefüh hrt wird, nich ht etwa schon bei der Definition des LLINQ‐Query.. Liegt dem K Kontext kein kon nkretes Typ‐M Mapping vorr, werden ano onyme Typen für die anggeforderten Entities und deren Abhängigkeiten erzeeugt. Da dies erst zur Laufzeit geschie eht, muss maan hier mit dem var‐ Schlüsseelwort arbeiten. Till Rebeenich Seite 3 von 17 Besser isst es, einen D Datenkontexxt mit starkem m Typkonzep pt zu erzeugeen. Dies ist aallerdings nur möglich,, wenn die Datenbankentitäten, überr die Abfrage en erfolgen, im Vorfeld ggegen entsprechende Entityklaassen gemap ppt werden u und eine Spezialisierung vvon DataCo ontext für d die angespro ochene Datenbaank definiert wird. LINQ to SQL unterstü ützt dabei grrundsätzlich SSingle Table Inheritance.. Ist eine Verrerbungshierrarchie über meehrere Tabelllen verteilt, m muss eine Sicht eingesetzt werden. 2.1.1 Attribut­ba A asiertes Ma apping Eine Möglichkeit ist, das Typ‐Mapping direkt im Quellcod de vorzunehm men, indem die Klassenm member mit entsprechenden Attributen vversehen weerden. Listingg 2 liefert hieerfür ein Beisspiel. [Databa ase(Name = "Adventur reWorks")] public class Adv ventureWork ksContext : DataCont text { p private st tatic Mappi ingSource _mappingSource = ne ew Attr ributeMapp pingSource( (); p public Adv ventureWork ksContext( (string connection) : ba ase(connec ction, _map ppingSourc ce) { } public Tab p ble<Custome er> Custom mers { get { this.Ge etTable<Cus stomer>(); } } public Tab p ble<Custome erAddress> > Addresses { get { this.Ge etTable<Cus stomerAddr ress>(); } } } [Table( (Name = "S Sales.Custo omer")] public class Cus stomer { p private in nt _custome erID; p private st tring _acco ountNumber r; p private En ntitySet<Cu ustomerAdd dress> _ad ddresses; [Column(Na [ ame = "Cust tomerID", Storage = "_custome erID", DbT Type = "Int t", CanB BeNull = false, f IsPr rimaryKey = true)] p public int t CustomerI ID { get { return _customerI ID; } set { _custom merID = val lue; } } [Column(Na [ ame = "Acco ountNumber r", Storag ge = "_acco ountNumber r", DbTy ype = "Var rChar(50)") )] p public str ring Accoun ntNumber { get { return _accountNu umber; } set { _accoun ntNumber = value; } } Till Rebeenich Seite 4 von 17 [Associati [ ion(Name = "Customer r_Addresses", Storag ge = "_add dresses", Othe erKey = "_ _customerID D")] p public Ent titySet<Cus stomerAddr ress> Addr resses { get { return _addresses s; } set { _addres sses.Assign n(value); } } p public Cus stomer() { } } (Name = "S Sales.Custo omerAddres ss")] [Table( public class Cus stomerAddre ess { [ [Column(Na ame = "Cust tomerID", Storage = "_custome erID", DbTy ype = "Int t")] p private in nt _custome erID; p private in nt _address sID; p private En ntityRef<Cu ustomer> _customer; _ [Column(Na [ ame = "Addr ressID", Storage S = "_addressI ID", DbTyp pe = "Int", , IsPr rimaryKey = true, Ca anBeNull = false)] p public int t AddressID D { get { return _addressID D; } ssID = valu ue; } set { _addres } [Associati [ ion(Name = "Customer r_Addresses", Storag ge = "_cus stomer", This sKey = "_c customerID" ", IsForei ignKey = tr rue)] p public Cus stomer Cust tomer { get { return _customer. .Entity; } set { _custom mer.Entity = value; } } p public Cus stomerAddre ess() { } } Listing 2 2: Mapping m mithilfe von A Attributen im m Quelltext Der Nach hteil dieses A Ansatzes ist, dass Änderu ungen in der Datenbank eine Neukom mpilierung der gesamteen Solution n notwendig m machen, denn n die Mappin ng‐Informatio onen sind en ng mit dem C Code verwobeen. Bei exten nsiven Änderrungen komm mt es dann schnell zu sch hwer nachvo ollziehbaren Laufzeitffehlern. Till Rebeenich Seite 5 von 17 2.1.2 XML­basier X rtes Mappin ng Alternativ können die für das Maapping benöttigten Metad daten vom C Code getrenn nt gehalten w werden, m Einsatz kommt. Der Daata‐Context eerhält diese Information dann in indem ein XML‐Mapping‐File zum nes XmlMapp pingSourc ce‐Parameteers. Für das in n Listing 2 geezeigte Beisp piel sähe ein solches Form ein Mappingg‐File folgend dermaßen au us: <?xml version="1 v 1.0" encodi ing="utf-8 8"?> <Databa ase Name="AdventureW Works" x xmlns="htt tp://schema as.microso oft.com/linqtosql/ma apping/200 07"> < <Table Nam me="Sales.C Customer" Member="Customers"> > <Typ pe Name="C Customer"> <Column n Name="Cus stomerID" Member="CustomerID" " S Storage="_ _customerID D" DbType= ="INT NOT N NULL" I IsPrimaryK Key="true" CanBeNull l="false" / /> <Column n Name="Acc countNumbe er" Member="AccountN Number" S Storage="_ _accountNum mber" DbTy ype="VarCha ar(50)" /> > <Column n Name="Typ pe" Member r="Type" Storage="_T Type" D DbType="Un niqueIdenti ifier" /> <Associ iation Name e="Custome er_Address" Member=" "Addresses s" S Storage="_ _addresses" " OtherKey y="_custome erID"/> </Ty ype> < </Table> < <Table Nam me="Sales.C CustomerAd ddress" Member="Addr resses"> <Typ pe Name="C CustomerAdd dress"> <Column n Name="Cus stomerID" Member="_ _customerID D" S Storage="_ _customerID D" Dbtype= ="INT NOT N NULL" C CanBeNull= ="false" /> > <Column n Name="Add dressID" Member="Ad M ddressID" S Storage="_ _addressID" " DbType=" "Int" /> <Associ iation Name e="Custome er_Address" Member=" "Customer" " S Storage="_ _customer" ThisKey=" "_customerI ID" I IsForeignK Key="true" /> </Ty ype> < </Table> </Datab base> Listing 3 3: Mapping m mithilfe von X XML Die sepaarate Haltungg von Datenb bindungsmettadaten maccht den Codee zwar lesbarrer, allerdinggs müssen Schemaändeerungen häu ufig an mehreeren Stellen nachgetrageen werden, w was ebenfallss eine erhöhte Fehleranfälligkeit mit sicch bringt. Das hier vorgestelltee manuelle M Mapping von Entityklasse en macht vorr allem dann Sinn, wenn diese hträglich an eeine Datenbaank gebundeen werden so ollen. Im bereits vvorhanden siind und nach umgekeh hrten Fall kö önnen jedoch h sowohl die Entityklasse en als auch das Mapping aus einem vorhandenen Datenb bankschemaa automatisch generiert w werden. Hierrfür stehen zzwei Method den zur ng: Zum eineen bietet sich h die Verwen ndung des O/R‐Designerss von Visual Studio 2008 an Verfügun (siehe Abbildung 1). Über den Seerver‐Exploreer zieht man n zunächst die gewünschtten Tabellen n auf die Anhängigkeitten im Daten nbankschema gepflegt sind, werden Oberfläcche. Wenn reeferentielle A Assoziationen zwisch hen den korrrespondieren nden Entitykklassen autom matisch ersteellt. Im Hinte ergrund nklusive der Mapping‐Atttribute. Alteernativ kann man hierfür auch erzeugt der Designerr den Code in mmandozeilenprogramm „sqlmetal“ vverwenden. Der Aufruf von das Kom sqlmeta al.exe /co ode:Entitie es.cs /map p:Mapping.x xml DataCo ontext.dbml ml Till Rebeenich Seite 6 von 17 auf der V Visual‐Studio o‐Kommondo ozeile erzeuggt den Entityyklassencodee und den Daatenkontext in einer Datei namens „Entities.cs“ und d das XML‐basiierte Mappin ng in „Mappiing.xml“. Alss Grundlage d dient das ml“ definiertee Schema. in „DataContext.dbm Abbild dung 1: O/R‐‐Designer in Visual Studiio 2008 2.2 Verzögerte V es Nachladen Das „Defferred Loading“, oft auch h „Lazy Loading“ genannt, ist ein scho on von andeeren objektreelationalen M Mappern bekkannter Mech hanismus zur Performancesteigerungg. Bei 1:n‐ Beziehun ngen werden n die Elemen nte der n‐Meenge erst dan nn tatsächlich aus der Daatenbank gelladen, wenn siee benötigt werden, d. h. wenn der Geetter des korrrespondiereenden Properties im Container aufgeruffen wird. Diees findet norm malerweise iintern und vom Aufruferr vollkommen unbemerktt statt. LINQ to SQL implemeentiert diesee Funktion üb ber die schon n erwähnte C Collection‐Klasse ySet<T>, üb ber die mehrwertige Frem mdschlüsselb beziehungen n abgebildet werden. Dies ist Entity gleicherm maßen Vor‐ und Nachteiil, denn einerseits ist man so an die V Verwendung dieses Colle ection‐ Typs geb bunden, d. h. man kann kkeine eigeneen Collection‐Klassen einssetzen, andeererseits musss man sich um die bereits in ntegrierte „LLazy Loading“‐Funktionallität als Entw wickler nicht selbst kümm mern. et<T> und Zum ersttgenannten Punkt muss noch ergänzzt werden, daass es sich beei EntitySe Entity yRef<T> um m finale Klasssen handelt, von denen aalso nicht abggeleitet werd den kann. In Bezugg auf das in LListing 2 gezeeigte Beispiel würde also ein Aufruf vvon Address ses der Klassse Custom mer ein Nach hladen der m mit dem Kund den assoziierrten Adressen in die Colleection vom TTyp Entity ySet<Custo omerAddre ess> auslöseen. Intern wird dabei ein SQL‐Kommaando erstellt und Till Rebeenich Seite 7 von 17 ausgefüh hrt, welches die abhängigen Zeilen der Tabelle „C CustomerAddress“ anhan nd des Spaltenn namens des iin OtherKey genannten n Members sselektiert. Alternativ lässt sich d das verzögerrte Nachladeen auch explizit unterbind den und man nuell steuern n: Adventu ureWorksDa ataContext ctx = new w Adventure eWorksData aContext( “ “Server=(l local);Init tial Catal log= “ + “ “Adventure eWorks;Inte egrated Se ecurity=Tr rue”); ctx.Def ferredLoad dingEnabled d = false; DataLoa adOptions options = new DataL LoadOptions s(); options s.LoadWith h<Customer> >(c => c.A Addresses); ; ctx.Loa adOptions = options; ; Listing 4 4: Manuelle SSteuerung des verzögertten Nachlade ens Listing 4 4 zeigt, wie über Lambda‐‐Ausdrücke u und in Verbindung mit eiiner DataLo oadOptions s‐ Instanz eexplizit festgelegt werden kann, welcche assoziierten Entitäten n bei der Erzzeugung des Containeerobjektes direkt geladen n werden sollen. In obige em Beispiel w würden also alle mit dem m Kunden verbundenen Adressobjeekte dann in n die Collectio on geladen, wenn auch d das Kundeno objekt selbst errzeugt wird. 2.3 Zentrales P Persisten nzmanagement Bisher haben wir gessehen, wie D Daten aus der Datenbankk geladen und d in objektorrientierte Strrukturen ungen des Ob bjektes verfo olgen und die ese in übersetzzt werden. LIINQ to SQL kkann jedoch aauch Änderu die Dateenbank zurücckschreiben o oder neue Datensätze in Tabellen ein nfügen. Hierbei fungiert der Datenko ontext wieder als Black Bo ox, d. h. alle Persistenzoperationen w werden zentral über eine e Instanz von Dat taContext angestoßen. 2.3.1 Change Tra C acking Um Änderungen dess Objektzustaandes zu erkennen, werd den alle überr den Datenkkontext gelad denen n verwalteten Table<T> >‐Instanzen zwischengesspeichert. Dies hat folgen nde Entities iin den intern Vorteile:: 1. Der Datenkonteext fungiert als Cache für Entity‐In nstanzen. Errneute Aufru ufe desselbe en LINQ‐ n nicht an die Datenbank weite ergeleitet, sondern die schon im Kontext Queries werden gesp peicherten Instanzen werden w zurrückgegeben n. Dies ist deshalb möglich, weil w jede Objeektinstanz eine Identität hat, die über die gemappten Primärsschlüsselattrribute realisiert ist. 2. Ändeerungen derr zwischengeespeicherten n Objektzustände werdeen zentral deetektiert und d selektiv zurü ückgeschrieben, d. h. es werden nur die Daten derjenigen Objekte zurückgeschrie eben, die auch h tatsächlich geändert wurden. Hierzu ein kleines Beeispiel, welch hes ein Cust tomer‐Objekkt aus der Daatenbank läd dt, es modifizziert und danach w wieder speicchert. Die darauf folgenden Zeilen initiieren die Erstellung ein nes neuen un nd die Löschung eines vorhandenen Kunden. Custome er custome er = (from x in ctx.Customers where x.C CustomerID = 5 s select x). .FirstOrDef fault(); if (cus stomer != null) { c customer.A AccountNumb ber = “081 15”; } Till Rebeenich Seite 8 von 17 Custome er newCust tomer = new w Customer r(); newCust tomer.Acco ountNumber = “4711”; ctx.Cus stomers.In nsertOnSubm mit(newCus stomer); Custome er obsolet teCustomer = (from x in ctx.Cu ustomers where w x.Cus stomerID = 6 s select x). .FirstOrDef fault(); if (obs soleteCust tomer != nu ull) { c ctx.Custom mers.Delete eOnSubmit( (obsoleteCustomer); } ctx.Sub bmitChange es(); Listing 5 5: Zurückschrreiben von Ä Änderungen in die Daten nbank Die Ändeerungsverfollgung bedien nt sich hierbeei zweier untterschiedlich her Mechanissmen. Implemeentieren die Entityklassen die INoti ifyPropert tyChange‐SSchnittstelle, so erwartett der Kontext,, dass nach d der Zuweisun ng eines neueen Wertes an eine der Prroperties dass Proper rtyChanged d‐Ereignis au usgelöst wird d. Andernfalls werden inttern Kopien d der zwischen ngespeichertten Entity‐Zu ustände angeefertigt, die d dann beim A Aufruf von Su ubmitChan nges() mit dem m neuen Zustaand verglichen werden. Über diee von GetCh hangeSet() ) zurückgegeebene Chang geSet‐Instaanz kann man feststellen n, welche Entities ggeändert, geelöscht oder neu eingefügt wurden. V Vorteilhaft isst, dass alle zzunächst unaabhängig von der Datenbank d durchgeführtten Änderun ngen über ein ne einzige M Methode perssistiert werde en anges(). M Möchte man neue Objekttinstanzen peersistieren, sso erstellt maan können: SubmitCha nz der Entitykklasse, weist deren Eigen nschaften diee gewünschten Werte zu und zunächstt eine Instan übergibtt die Instanz danach an I InsertOnSu ubmit() de er entsprechenden Tabl le<T>‐Instan nz. Ähnlich ffunktioniert auch das Löschen vorhandener Entitties, allerdinggs heißt die entsprechen nde Methodee dann Dele eteOnSubm mit(). 2.3.2 Optimistisc O ches Sperre en Die Zwisschenspeicheerung von En ntityinstanzen ermöglichtt es dem Dattenkontext aauch, ein optimisttisches Sperren durchzuführen. Für diesen Zweckk kann im Maapping ein so ogenanntes „„Version‐ Propertyy“ definiert w werden. Gew wöhnlich würrde man hierr wohl einen DateTime‐‐Wert wähle en, der den Zeitpunkt der letzten Änderu ung anzeigt ((Zeitstempell); die Verweendung andeerer Datentyp pen ist ebenfallss möglich. Dieses Attribu ut muss gegeen eine entsp prechende Taabellenspalte gemappt w werden, die bei jeedem UPDATTE mit der akktuellen Zeit überschrieb ben wird. Wird ein ne zuvor aus der Datenbaank geladenee Entityklasse eninstanz vo on zwei Benu utzern gleichzeitig modifizieert, so änderrt sich der W Wert der Verssionsspalte der korrespon ndierenden ZZeile in der Datenbaanktabelle, so obald Benutzzer 1 Submi itChanges( () aufruft. K Kurz darauf w wird Benutze er 2 wahrsch heinlich das ggleiche tun, aallerdings wird diesmal e eine Exceptio on geworfen.. Intern hat d der Datenko ontext nämlicch einen Verrgleich des Veersionswerte es durchgefü ührt und festtgestellt, dasss die Instanz zzwischenzeittlich von Ben nutzer 1 geän ndert wurde.. Die Meth hode Submi itChanges( () liegt in zw wei Überladu ungen vor. W Wird sie parameterlos auffgerufen, so wird b beim Auftretten eines Versionskonflikktes eine Aussnahme erzeeugt. Alternaativ kann man n aber auch ein nen Wert derr Conflict tMode‐Enum meration als P Parameter üb bergeben. Damit gibt maan dem Kontext eine Lösungsstrategie vo or, die dann im Falle von Versionskon nflikten ausggeführt wird. Till Rebeenich Seite 9 von 17 2.3.3 Mapping vo M on Stored P Procedures und Functiions Neben TTabellen und Views können auch Storred Procedurres und Funcctions im Dattenkontext ggemappt werden, und zwar als Methoden der spezialisierten Data aContext‐K Klasse selbstt. Auch hier b bedient man sich h am einfach hsten des O/R R‐Designers und zieht die e Datenbankkobjekte per Drag & Drop p direkt auf die O Oberfläche. B Beim Aufruf dieser Methoden wird dann intern eeine paramettrisierte SQL‐‐Anfrage erstellt u und auf der D Datenbank ausgeführt. R Rückgabewerrte, seien es nun Objektinstanzen von Entityklaassen oder skkalare Wertee, werden eb benfalls unte erstützt. Listing 6 zeigt eine Erweiteru ung des in Listingg 1 gezeigten n Beispiels. D Die Methodee SaveCustomer() maappt die gleicchnamige Sto ored Procedure in der Dattenbank. public class Adv ventureWork ksContext : DataCont text { p private st tatic Mappi ingSource _mappingSource = ne ew Attr ributeMapp pingSource( (); p public Adv ventureWork ksContext( (string connection) : ba ase(connec ction, _map ppingSourc ce) { } / //... [Function( [ (Name = "Sa aveCustome er", IsCom mposable = false)] [ [Parameter r(Name = "c customerID D", DbType = "Int")] [ [Parameter r(Name = "a accountNum mber", DbType = "Var rChar(50)" ")] p public int t SaveCusto omer(ref int? i customerID, str ring accou untNumber) { IExe ecuteResul lt result = ExecuteM MethodCall( (this, (Method dInfo)Metho odInfo.Get tCurrentMethod(), custome erID, accou untNumber) ); retu urn ((int)result.Ret turnValue); } } Listing 6 6: Mapping e einer Stored Procedure im m Datenkon ntext 2.3.4 Steuerung S von schreib benden Dattenbankzug griffen Bisher haben wir unss mit Kontexxten beschäfttigt, in denen n alle Zugrifffe direkt auf die gemapptten n oder Views in der Daten nbank erfolggten. Diese V Vorgehensweeise ist insbessondere bei Tabellen komplexxeren Anwen ndungen nich ht empfehlen nswert. Statttdessen solltten Datenban nkzugriffe ge enerell über Sto ored Procedu ures oder Fun nctions durchgeführt we erden. LINQ tto SQL ermögglicht die De efinition benutzerdefinierter Stored Proceedures für scchreibende, nicht jedoch für lesende Datenbankzzugriffe. nn also grund dsätzlich dass Verhalten d des Datenkon ntextes beim m Aufruf von Man kan Submit tChanges() ) beeinflusseen. Soll z. B. die Method de SaveCust tomer() in obigem Beisspiel (Listingg 6) generell für alle neue en und geänderten Objektin nstanzen aufggerufen werrden, so erste ellt man zweei Methoden im Datenkontext, die als Parameter ein ne Instanz deer zu persistiierenden Enttityklasse erw warten. Inneerhalb dieserr Methodeen ruft man dann die gem mappte Storred Procedurre auf: Till Rebeenich Seite 1 10 von 17 public void Inse ertCustomer r(Customer r instance) ) { i int? custo omerID = ne ew int?(); ; t this.SaveC Customer(re ef custome erID, instance.Accou untNumber); i instance.C CustomerID = custome erID.GetVa alueOrDefau ult(); } public void Upda ateCustomer r(Customer r instance) ) { i int? custo omerID = ne ew int?(in nstance.Cu ustomerID); ; t this.SaveC Customer(re ef custome erID, instance.Accou untNumber); } public void Dele eteCustomer r(Customer r instance) ) { / //... } Löschende Zugriffe kkönnen eben nfalls auf diesse Weise kon nfiguriert weerden. Till Rebeenich Seite 1 11 von 17 3 Praxistest Das in Abschnitt 1.1 erwähnte Anwendungssszenario lässst sich mithilffe der folgen nden Diagram mme (Abbildu ung 2 und Ab bbildung 3) b beschreiben.. Man beachtte hierbei diee Besonderh heiten beim Deploym ment. Auf dem m Presentation Tier verw wenden wir e eine ASP.NETT Webapplikkation, die im m IIS betriebeen wird. Diese greift auf eeinen ebenfaalls im IIS lau ufenden WCFF‐Service zu, der als Busin ness Tier fungiert.. Der Data Laayer wird wiee dargestellt im gleichen Prozess geh hostet und grreift über die e von Microsofft SQL Server bereitgesteellten Protokkolle (z. B. TC CP) auf die Datenbank zu. Nun stellt sich die Frrage: Kann LINQ to SQL in n diesem Anw wendungsko ontext sinnvo oll eingesetztt werden?? Presentation IBusinesssServic e IBusinessServic e Common n B BusinessLogi c «lib rary» «library» Entitie s Util s ITablePro ovider ITablePro ovider DataAccess «flow» «file» Databas e ng 2: Typisch he Komponen ntenarchitekktur einer En nterprise‐An nwendung Abbildun Till Rebeenich Seite 1 12 von 17 Web Serv S er «execution environment» IIS Prese entation H HttpServerPort HttpPort WSPort WSClientPorrt WSSerrverPort Window s Serv er DattabaseServ er «execution environment» IIS DB BServerPort WSPort DBPort Logi c DBClientPort DataAcce ess IBusin nessServic e Abbildun ng 3: Deployyment des Anwendungssszenarios In unserem Anwendungsszenario o haben wir bewusst Pro ozessgrenzen n vorgesehen n, da diese in n den meisten Enterprise‐A Anwendungeen vorkommen. In der Re egel müssen mehrere Ap pplikationen in untersch hiedlichen Au usführungsko ontexten auff gleiche Services zugreiffen können. Werden hierrbei Daten prrozessübergrreifend ausggetauscht, zum Beispiel zw wischen Presentation‐ und Business Tier, dann weerden die übeertragenen EEntities beim m Verlassen d des Client‐Ko ontextes serialisiert und aauf Serviceseite wieder d deserialisiertt oder umgekehrt. Dies isst übrigens u unabhängig vvom verwendeten Protokolll der Fall. Bei den in Abschnitt 2 verwendeeten Beispielen sind wir sstets davon aausgegangen n, dass alle drei Layer m einzigen Prozess laufen n. Es stellen ssich deshalb folgende Deetailfragen: in einem • • Wie können Object O Chang ge Tracking g und Deferrred Loadin ng über Pro ozessgrenzen n hinaus wendet werd den? verw Wie verhält sich LINQ to SQLL bei mehreren DataCon ntext‐Instanzen? 3.1 Änderungs Ä sverfolgung über P Prozessgrrenzen Beim Stu udium der M MSDN‐Dokum mentation erffährt man, dass Entities aautomatisch vom Datenkkontext „abgehängt“ (detach hed) werden, sobald diesse serialisiertt werden (1). Konkret bedeutet dies, dass walteten Refe erenzen ungü ültig macht. LINQ to SQLL geht in eine Serialisierung die im Datenkkontext verw us, dass für jeeden einzeln nen Vorgang eine neue D DataContex xt‐Instanz diesem FFall davon au Till Rebeenich Seite 1 13 von 17 verwend det wird. Anggenommen d die Daten ein nes Kunden N Nr. 5 sollen aauf einer Weebseite darge estellt und danach verändeert werden, d dann wird folgender Ablaauf vorausgeesetzt (2): 1. Aufrruf einer Metthode auf deem Business‐Layer, die w wiederum deen Data‐Layeer kontaktiert, um die Dateen des Kundeen Nr. 5 zu laaden. 2. Der Data‐Layer erzeugt zun nächst eine DataConte ext‐Instanz, selektiert K Kunde Nr. 5 5 aus der Table<Custom mer>‐Collecttion und gibtt diese Instan nz an den Bu usiness‐Layerr zurück. 3. Der Business‐Laayer gibt die erzeugte Customer‐In nstanz an den d Aufrufer zurück, wobei w sie aufggrund der Pro ozessgrenze z. B. als Teil einer SOAP‐‐Message serrialisiert wird d. 4. Auf dem Presenttation‐Layerr werden Datta Objects ve erwendet, um die Instan nz direkt an A ASP.NET‐ m die Änderu ungen über e ein Webform mular abgescchickt wurden, erfolgt Conttrols zu binden. Nachdem der A Aufruf einer entsprechen nden Business‐Methode,, welche die Instanz entggegennimmt. 5. Der Business‐Layer kontaktiert erneut den Data‐Laayer, der eine neue Da ataContext t‐Instanz T tomer>‐Collection bind det. Dies ersteellt und diee deserialisiierte Entitätt an die Table<Cust gescchieht über d die Methode Attach(). 6. Danaach wird Su ubmitChang ges() auf dem Datenkkontext aufggerufen, um m die Änderu ungen zu perssistieren. Attach h() ermögliccht das Bindeen einer außerhalb des P Prozesses verränderten En ntity an den Datenko ontext. Die M Methode hat drei Überlad dungen: Attach h(Customer r entity) nimmt an, d dass die als P Parameter üb bergebene In nstanz außerrhalb des Kontextees nicht veräändert wurdee. Möchte m man, dass derr Kontext die Instanz als ggeändert maarkiert, so muss man stattdeessen Attac ch(Custome er entity y, bool as sModified d) oder h(Customer r entity, Customer r original) verwenden. Erstere V Version nimm mt die Attach Instanz u und einen W Wert entgegen n, der angibtt, ob die Entiity als modifiziert anzuseehen ist. Sie sschreibt allerdinggs zwingend vor, dass das Mapping des Typs Cus stomer ein V Version‐Prop perty enthaltten muss. Diie letztgenan nnte Version erwartet, daass man neb ben der geänderten Instanz auch das Original mitlieferrt. LINQ to SQ QL kann durcch einen Verrgleich der Objektzuständ de dann entsscheiden, ob b eine Änderun ng vorliegt. D Diese Einschrränkungen m macht LINQ to o SQL nicht o ohne Grund: beim nachtrräglichen Anhängeen deserialisierter Entitätten muss nämlich die Än nderungsverffolgung für d diese und alle e von ihr abhängiggen Instanzeen wieder akttiviert werdeen. Leider bietet damit kkeine der oben genannteen Überladun ngen die für Enterprisean nwendungen n man nicht imm mer Einfluss darauf, ob eein Versionsaattribut benötigtte Flexibilitätt (5; 6). Zum einen hat m in der Daatenbank existiert, zum aanderen wird d man normalerweise deen Originalzu ustand einer geladeneen Entität au uf Seite des A Aufrufers unggern zwische enspeichern.. Davon abgeesehen werd den die zwischen n den Servicee‐Endpunkteen ausgetausschten Nachrrichten dadu urch unnötig vergrößert. Man würde siich hier ein intuitiveres V Verhalten des Datenkonttextes wünscchen. Umgeh hen kann man diesen Missstan nd derzeit nu ur, indem maan das Objecct Tracking im m Datenkonttext komplettt deaktiviertt, was diesen jeedoch zum R Read‐Only‐Ko ontext degradiert. Die Su ubmitChang ges()‐Methode steht d dann nicht meehr zur Verfü ügung und alle Persistenzzmechanismen müssen d durch explizitten Aufruf gemapptter Stored Prrocedures od der Function ns angestoße en werden. Till Rebeenich Seite 1 14 von 17 3.2 Verzögerte V es Laden Beim Ein nsatz von LIN NQ to SQL in N‐Tier‐Appliikationsszenaarien ist „Deeferred Loadiing“ nicht sin nnvoll. Der Grun nd hierfür istt, dass diese Funktionalittät von der ständigen Üb berwachung der Entity ySet<T>‐Insstanzen jedes Entitytyps abhängt. Verlässt eine Entityinstanz den Einflusssbereich des Dateenkontextes (nach Serialiisierung), wird das verzögerte Laden nicht mehr ffunktioniere en. Vielmehr hängt es vo on den Data aMember‐Atttributen in d der jeweiligen Entityklassse ab, ob die Instanzeen assoziierteer Entityklasssen zuvor geeladen werde en oder nichtt, denn der D Data Contracct Serializer ruft während der Serialisierung jedes der so gekennzeichneeten Membeer explizit auff und löst dabei deen verzögerteen Ladevorgang aus. Hat man n das Object Tracking auffgrund der in n Abschnitt 3 3.1 genannteen Schwierigkeiten deakttiviert, so steht damit auch dass verzögerte Laden nicht mehr zur Ve erfügung. 3.3 Automatis A ierung Visual Sttudio 2008 bietet einige ssehr nützlich he Automatissierungsfeatures an, die sowohl die Erstellun ng von Entityyklassen als aauch das Mapping auf Daatenbankschemata unterrstützen. Wie e intensiv man von dieesen Werkzeugen Gebrau uch machen kann, hängtt allerdings vvom Vorgehe en beim Anwendungsdesign aab. Beim Bottom‐Up‐Ansatz geht maan von der D Datenbanksch hicht aufwärrts, d. h. man n erstellt das Datenbaankdesign un nd verwendeet den O/R‐Designer, um die Datenbaankobjekte auf ein objektorrientiertes En ntitymodell zzu mappen. Hierbei kann n man den Designer so ko onfigurieren, dass Kontext und Entities in zwei unteerschiedlicheen Namespacces erzeugt w werden. Leid der werden b beide Namespaces dann ab ber in einer eeinzigen Dattei zusammengefasst. Mö öchte man die erzeugten n Klassen auf zwei Dateien verrteilen, musss man dies m manuell mitte els Copy & Paaste tun – un nd dies nach jeder n für alle Meember der En ntityklassen bereits Data aMember‐Atttribute Schemaäänderung. Feerner können generierrt werden, allerdings nich ht selektiv nu ur für bestim mmte Membeer. Beim Top‐Down‐Anssatz geht man dagegen in n umgekehrtter Richtung vor, indem zzunächst Anwendungsfälle, daanach ein Klaassenmodell und dann eiin Datenbankmodell ersttellt wird. In diesem man bereits Entityklassen, bevor man sich überhaupt um derren relationaale Abbildungg Fall hat m Gedankeen gemacht h hat. Der O/R R‐Designer biietet leider kkeine Möglich hkeit an, diese Klassen nachträgglich auf ein relationales Schema zu m mappen, d. h h. dies muss manuell und d ohne jede Automattisierung bew werkstelligt w werden. Till Rebeenich Seite 1 15 von 17 4 Fazit Die Verw wendung von n LINQ to SQ QL für verteiltte Enterprise eanwendunggen macht deerzeit nur eingesch hränkt Sinn. Der von Microsoft in Visu ual Studio 20 008 unterstü ützte Bottom m‐Up‐Ansatz ist in solchen Projekten hääufig nicht reealisierbar. In n diesem Falll muss man dann auf jeggliche erung von Entityklassen u und Datenko ontext in Toolunteerstützung verzichten. Die automatissche Generie untersch hiedlichen Naamespaces, Dateien oder gar Assemb blies ist nicht möglich un nd erschwertt deren Einsatz in verteilten Anwendungen. LINQ to SQL schreibtt die Verwen ndung spezieller Collectio on‐Klassen und Hilfsattrib bute (z. B. Fremdscchlüsselattrib bute) in den Entityklassen vor. Dies isst in vielen Fäällen eine en norme Einschräänkung, wenn n beispielsweise benutzeerdefinierte C Collections eeingesetzt weerden sollen n. Hier würde m man sich eineen schnittstellenbasierten Ansatz wünschen, um die für das D Deferred Loading und Objeect Tracking benötigte Fu unktionalitätt in eigenen Collection‐Klassen impleementieren zzu können, sofern dies gewünscht ist. Beim Austausch von Entities übeer Prozessgreenzen hinweg stellt das rigide Object Tracking ein n nicht unerheb bliches Hindeernis dar. Diee vom Datenkontext gebo otene Funktiionalität ist ffür solche Szenarien derzeit n nicht flexibel genug. Ein D Deaktivieren n dieses Features versetztt den Kontexxt in den schreibggeschützten M Modus und aalle Insert‐, U Update‐ und Delete‐Operationen mü üssen manue ell aufgeruffen werden. Schreibeende und löschende Dateenbankzugrifffe des Konte extes können n über Stored Procedure es konfigurriert werden,, nicht jedoch lesende Zu ugriffe. Der d direkte Zugrifff auf Tabelleen und Sichten ist aber in d der Regel niccht gewünsch ht oder überr Datenbank‐‐Zugriffsrech hte unterbunden. Das bettrifft auch dass verzögerte Laden, denn n hier operieert LINQ to SQ QL ebenfalls direkt auf Taabelleneben ne. Positiv isst die reine O O/R‐Mappingg‐Funktionalität von LINQ Q to SQL hervorzuheben. Die Umwan ndlung von SQL‐‐ auf CLR‐Datentypen un nd umgekehrrt verläuft klaaglos und oh hne Fehler. A Auch die gute e Performance beim D Datenzugriff ffällt positiv aauf. Es bleibtt also abzuwaarten, was die nächste V Version von LLINQ to SQL b bringt. Till Rebeenich Seite 1 16 von 17 Quelle enverzeichnis 1. Fowle er, Martin. Pa atterns of En nterprise App plication Arch hitecture. s.l. : Addison‐W Wesley Profe essional, 2002. 2. Micro osoft Corporaation. Linq to o SQL: Langu uage‐Integrated Query fo or Relational Data. msdn.. [Online] 2008. http:///msdn.microsoft.com/een‐us/library//bb425822.aaspx. 3. —. N‐TTier and Rem mote Applicaations with LINQ to SQL. msdn. [Onlin ne] Microsofft Corporatio on, 2008. http://m msdn.microso oft.com/en‐u us/library/bb b882661.aspx. 4. Kulkarni, Dinesh. Attach() if yo ou have som mething detacched. MSDN N Blogs. [Online] Microsoft Corporattion, 2008. h http://blogs.msdn.com/d dinesh.kulkarrni/archive/2 2007/10/08//attach‐if‐you‐have‐ something‐detached d.aspx. 5. Strahll, Rick. Comp plex Detacheed Entities in LINQ to SQLL ‐ More Nigh htmares. Ricck Strahl's W Web Log. [Online] West Wind Technologiees, 01. October 2007. http p://www.weest‐ m/weblog/posts/162336 6.aspx. wind.com 6. Sieme er, Andrew. LINQ to SQL ‐ Implementting the Repo ository Patteern. Andrew Siemer's Blo og. [Online] 05. Februaryy 2008. http://geekswith hblogs.net/A AndrewSiemeer/archive/2008/02/05/linq‐to‐ sql‐‐‐imp plementing‐tthe‐repository‐pattern.aspx. Till Rebeenich 17 von 17 Seite 1