Thomas Kramer

IT-COW | Webcast-Reviews

“LINQ und Konsorten” Teil 5 & Überblick

By Administrator at April 08, 2011 20:13
Filed Under: 3.5, C#, Webcast-Reviews

Es hat etwas gedauert, aber mittlerweile habe ich auch den fünften Teil der “LINQ und Konsorten”-Webcast-Reihe von Dariusz Parys durchgearbeitet, seines Zeichens Technical Evangelist von Microsoft. Mein Review zum vierten Teil ist hier einsehbar.

 

In diesem Teil wird LINQ to Entities behandelt, im vierten Teil wurde LINQ to XML behandelt, im dritten LINQ to Objects und LINQ to DataSets sowie LINQ to SQL im zweiten Teil. Die Intentionen für LINQ allgemein hat er am Anfang mit einem Rückblick erklärt, wie in ADO.NET 2.0 SQL-Befehle direkt in den Quellcode geschrieben und auf Korrektheit somit erst zur Laufzeit getestet werden konnten.

 

Überblick

 

In ADO.NET gibt es mit Framework 3.5 drei (neue) Möglichkeiten um auf Daten aus Datenbanken zuzugreifen, dazu gehören zwei verschiedene O/R-Mapper – LINQ to Entities und LINQ to SQL – sowie direkter mit LINQ to Objects. Allen drei gemein ist der Vorteil der Syntaxfehler- und Typsicherheitsüberprüfung zur Kompilierzeit, weitere Vorteile siehe MSDN.

 

LINQ to SQL ist eigentlich nur für das DBS MS SQL-Server gedacht. Herr Parys konstatierte im Webcast selbst zu LINQ to SQL, das bei Tabellen-Änderungen auch direkt die Klassen im Quellcode angepasst werden müssen und es nur für Client/Server-Anwendungen gedacht ist (also nicht für Multi-Tier-Anwendungen).

 

Heise Developer schreibt weitergehend folgendes:

 

  • Bei LINQ-to-SQL hingegen blockiert Microsoft die Erstellung neuer Treiber ("No public provider model is available. At this time, LINQ-to-SQL supports SQL Server and SQL Server Compact 3.5 only", heißt es in der LINQ-to-SQL-FAQ). Dies bezieht sich wohlgemerkt nur auf die ORM-Funktionen im engeren Sinne; die Erstellung allgemeiner LINQ-Provider ist wieder dokumentiert, sodass es zahlreiche Treiber für Nicht-Datenbanken gibt. Die Firma Devart liefert zudem LINQ-to-SQL-ähnliche Provider für Oracle, MySQL, PostgreSQL und SQLite.
  • LINQ-to-SQL gestattet fast nur die 1:1-Abbildung zwischen Tabellen und Objekten. Einzige Ausnahme davon ist die Vererbung, bei der es Filtered Mapping anbietet.
  • LINQ-to-SQL arbeitet direkt auf dem Datenbankschema, nicht auf dem konzeptuellen Modell, das bei der Entity-Relationship-Modellierung (ERM) verwendet wird
  • LINQ-to-SQL bietet als Abfragesprache LINQ oder SQL.
  • Dennoch hat in dieser Kategorie LINQ-to-SQL einen Vorteil, denn es unterstützt datenbankspezifisches SQL. Das kann gegenüber einer datenbankneutralen Objektabfragesprache Vorteile beim Optimieren bringen.

 

Zum Thema LINQ to Entities steht in Heise Developer nachfolgendes geschrieben:

 

  • Das Entity Framework ermöglicht mehr Abbildungen. Insbesondere kann es die im relationalen Modell notwendigen Zwischentabellen in n:m-Beziehungen eliminieren und unterstützt bei der Vererbung Vertical Mapping (alias Joined Mapping, Inheritance Table per Class oder Vertical Polymorphism) sowie Horizontal Mapping (alias Table per Subclass, Horizontal Polymorphism).
  • EF bietet Mapping auch auf Tabellenebene (das heißt auch ohne objekt-relationales Mapping). LINQ-to-SQL kennt das nicht.
  • EF kennt LINQ sowie das datenbankneutrale Entity SQL (eSQL). Dieser SQL-Dialekt bietet nur den SELECT-Befehl.

 

Ein anderes Blog bringt die Unterschiede verkürzt auf den Punkt:

 

Die wesentlichen Unterschiede sind, dass LINQ to Entities auch mit Datenbanken anderer Hersteller zusammenarbeiten wird, und dass die Business-Objekte (eben die Entities) nicht exakt den Tabellen/Views der Datenbank entsprechen müssen.

 

In der MSDN steht zu LINQ to Objects (LINQ auf Objekte, die bereits im Arbeitsspeicher sind) folgendes:

 

Der Begriff "LINQ-zu-Objekten" bezieht sich auf die Verwendung von LINQ-Abfragen mit einer beliebigen direkten IEnumerable- oder IEnumerable<T>-Auflistung ohne die Verwendung eines LINQ-Zwischenanbieters oder einer Zwischen-API, wie zum Beispiel LINQ to SQL oder LINQ to XML.

 

Neben dem bereits erwähnten Heise-Developer-Artikel ist dort auch ein sehr guter Artikel mit den Unterschieden im Detail aufgeführt, von Herrn Holger Schwichtenberg: Link. Eine weitere gute Differenzierung ist bei it-visions zu finden. Zum Überblick noch einmal die Links in der MSDN zu LINQ to Entities, LINQ to SQL und LINQ to Objects.

 

Inhalte des Webcasts

 

Der Überblick war nötig, aber nun zu den eigentlichen Inhalten dieses Webcasts. Ab Minute 10 zeigte Herr Parys das Hinzufügen eines ADO.NET Entity Data Model in einer Konsolen-Anwendung, über den Assistenten von Visual Studio. Benutzt wird in seinen Webcasts bislang immer die Northwind-Datenbank von Microsoft.

 

Im Entity Data Model Wizard wählt man die Option Generate from Database aus, von der anderen Option Empty Model rät Herr Parys ab da zu dem damaligen Entwicklungsstand (April 2008) ansonsten mit bestimmten Attributen und Interfaces im Quellcode gearbeitet werden muss und auch Stored Procedures nur unvollständig unterstützt werden.

 

Im nächsten Schritt wählt man dann die zu verwendende Datenbank aus, wobei anders als im Video in meiner Express-Version von VS2008 nur SQL-Server-Verbindungen als Datenbanken zulässig sind. LINQ to Entities ist anders als LINQ to SQL natürlich datenbankunabhängig.

   

Beim Zugriff auf meine Northwind-Datenbank hagelte es wieder eine Fehlermeldung, ähnlich wie bei meinem Review zu Teil 2:

 

Sie verfügen nicht über die Berechtigung, diese Datei zu öffnen. Wenden Sie sich an Ihren Administrator.

 

Das liegt daran das die Datenbank über einen Installer auf die Festplatte geschrieben wurde, dieser Installer mit Admin-Rechten (nach Passwort-Eingabe) gearbeitet und keinen Benutzer “Jeder” mit explizitem Lese-Zugriff hinzugefügt hat. Ich hatte schon einmal dem übergeordneten Verzeichnis händisch meinen Standard-Windows-Benutzer als autorisierten Member hinzugefügt, nun musste ich es auch noch für die Datei direkt tun (wer denkt das sich NTFS-Rechte zwangsläufig vererben irrt sich, das kann im Detail für Ordner, Unterordner und Dateien festgelegt werden).

 

Danach funktionierte es endlich und in Visual Studio wurden alle Entities/Mappings für LINQ to Entities angezeigt. Ähnlich hatte er dann zum Vergleich das LINQ to SQL-Modell der gleichen Datenbank hinzugefügt, wobei dort jede Tabelle einzeln auf den Designer gezogen werden muss.

 

Nachfolgend hatte Herr Parys die Unterschiede zwischen LINQ to Entities und LINQ to SQL innerhalb der GUI-Editoren gezeigt:

 

LINQ to Entities

 

Entities_thumb5

 

LINQ to SQL

 

sqltoentities_thumb2

 

Im LINQ to Entities-GUI-Editor sind zu jeder Tabelle die damit verknüpften – direkt oder indirekt – Tabellen als Navigationseigenschaften aufgelistet, während die eigenen Spalten als Skalareigenschaften aufgelistet sind. Die Relationen zwischen den Tabellen werden hier als Multiplizität des Zuordnungsendes bezeichnet – wer schon einmal von 1:n oder m:n-Beziehungen zwischen Tabellen gehört hat, weiss was damit gemeint ist.

 

Im Kontextmenü bei den Navigationseigenschaften kann man über “Zuordnung auswählen” zu den verknüpften Tabellen-Mappings springen. Auch möglich ist das Springen über das Kontextmenü zu dem Entity-Eintrag im Modellbrowser (und von dort wieder zurück).

 

In der Datei “$Name$.Designer.cs” kann man den generierten Quellcode einsehen. Änderungen an diesen Quellcodedateien kann man vornehmen, werden aber wie bei jedem Codegenerator bei einem erneuten Generieren überschrieben.

 

LINQ to SQL arbeitet dagegen lediglich mit “Assoziationen auf Grundlage des Mappings”, wie Herr Parys es ausdrückte. Der GUI-Editor von LINQ to Entities erstellt Dateien mit der Endung edmx, der von LINQ to SQL mit der Endung dbml.

 

Benutzung

 

Danach zeigte er endlich eine Standard-Query für LINQ to Entities, diese sehen prinzipiell genauso aus wie bei den anderen LINQ-Typen:

 

using (NORTHWNDEntities entities = new NORTHWNDEntities())
{
    var query = from c in entities.Customers
                where c.Country == "Germany"
                select c;

    foreach (var item in query)
    {
        Console.WriteLine(item.CompanyName);
    }

    Console.ReadKey(); 
}

 

Anschließend zeigte er den XML-Quellcode, mit dem der Designer arbeitet (LINQ to Entities). Weiterhin verdeutlichte er, das Visual Studio neben der edmx-Datei auch csdl (Konzept-Modell), msl (Mapping) und ssdl (Storage-Modell)-Dateien erzeugt. In der edmx-XML-Datei gibt es u. a. die Abschnitte

 

<edmx:ConceptualModels>

 

und

 

<edmx:StorageModels>

 

und

 

<edmx:Mappings>

 

Die Abschnitte darunter sind markiert mit

<!-- EF Designer content (DO NOT EDIT MANUALLY BELOW HERE) -->

 

Das Storagemodell beschreibt die Datenbank, das Konzeptmodell den gekapselten Zugriff und das Mapping stellt die Verbindung dazwischen her. Im Storagemodel muss man in der jetzigen Beta-Version gemäß seiner Aussage noch von Hand Änderungen bei Stored Procedures vornehmen.

 

Im Abschnitt Storagemodel sieht man die Beziehungen zwischen den Tabellen mit den <Association> Tags, die im Konzeptmodell nicht vorhanden sind – dafür sind dort die <NavigationProperty>-Tags, die über das RelationShip-Attribut auf diese Assoziationen verweisen.

 

Im Konzeptmodell wird über die Eigenschaft Multiplicity die Art der Beziehungen zwischen den Tabellen festgelgt. 1 zu N wird durch 0..1 bspw. beschrieben.

 

Im Mappingmodell findet die Verknüpfung zwischen Konzeptmodell und Storagemodell statt. Beispiel, in dem Video gab es bei der Northwind-Datenbank u. a. diesen Eintrag im Mappingmodell:

 

<EntitySetMapping Name=”Customers”>

<EntityTypeMapping TypeName=”IsTypeOf(NorthwindModel.Customers)”> (konzeptionelles Modell)

<MappingFragment StoreEntitySet=”Customers”>

<ScalarProperty Name=”CustomerID” ColumnName=”CustomerID” />

 

Links sind die Spalten des konzeptionellen Modells zu sehen, rechts die Spalten des Speichermodells.

 

Danach besprach er den connectionString von LINQ to Entities in der app.config… diese wird gewöhnlich durch den Editor gesetzt (XML-Format). In dieser Datei sind Meta-Daten aufgelistet, das Konzeptmodell (csdl), das Storagemodell (ssdl) sowie die msl-Datei mit dem Mapping.

 

Der Dataprovider wird in der Datei mit angegeben, standardmäßig ist dies System.Data.SqlClient. Von Fremdfirmen seien bereits Alternativen angekündigt - bei einem Umstieg müsse nur das Storagemodell angepasst werden, führte Herr Parys weiter aus.

 

Danach zeigte er den Unterschied beim Zugriff zwischen den verschiedenen LINQ-Varianten im Quellcode:

 

using (NORTHWNDEntities entities = new NORTHWNDEntities())
{
    var query = from c in entities.Customers
                where c.Country == "Germany"
                select c;

    foreach (var item in query)
    {
        Console.WriteLine(item.CompanyName);
    }

    Console.ReadKey();
}

using (DataClasses1DataContext ctx = new DataClasses1DataContext())
{
    var query = from c in ctx.Customers
                where c.Country == "Germany"
                select c;

    foreach (var item in query)
    {
        Console.WriteLine(item.CompanyName);
    }

    Console.ReadKey();
}

 

Der obige Code zeigt die LINQ to Entities-Variante, der untere die Entsprechung für LINQ to SQL. Nachfolgend zeigte er auch den generierten SQL-Code im Profiler von MS SQL Server, welcher in meiner Express-Version leider nicht enthalten ist. In dem Zusammenhang fällt mir ein, das ich vor kurzem den MSDNAA-Account erhalten habe und mir darüber noch die Vollversion beschaffen könnte.

 

Theorethisch könnte alternativ der Anjlab SQL Profiler (Freeware) benutzt werden, den ich bereits im zweiten Teil erwähnt hatte. In meinen früheren Tests mit diesem Profiler wurden aber stets nur DDL-SQL-Befehle geloggt - Befehle die Tabellen-Strukturen verändern - und keine DML-SQL-Befehle.

 

Im SQL Server Profiler zeigt er jedenfalls die Unterschiede bei den generierten SQL-Befehlen auf, dazu loggte er die Events SQL:BatchStarting, SQL:BatchCompleted und RPC: Completed von SQL Server mit. In den Zeilen für SQL:BatchStarting bzw. SQL:BatchCompleted waren die generierten SQL-Befehle für LINQ to Entities zu sehen, in RPC: Completed die SQL-Befehle von LINQ to SQL. Die Syntax glich weitgehend der Standard-SQL-Syntax, auch Unterschiede waren nur geringfügig zu erkennen:

 

LINQ to Entities schien direkt SQL-Befehle zu erzeugen, während LINQ to SQL scheinbar Befehle erzeugt die an die Stored Procedure sp_executesql übergeben werden (könnte aber auch von der Art der Events abhängen, im Video war es jedenfalls so). Bei LINQ to Entities konnte man außerdem sehen das mit Aliasen auf die Tabellen bzw. Spalten gearbeitet wurde, die aber gleichlautend waren.

 

Nachfolgend zeigte er folgende Query, diesmal ausschließlich auf LINQ to Entities:

 

using (NORTHWNDEntities entities = new NORTHWNDEntities())
{
    var query = from c in entities.Customers
                where c.Country == "Germany"
                select c;

    foreach (var item in query)
    {
        Console.WriteLine(item.CompanyName);
    }

    Console.ReadKey();

    var query2 = from c in query
                 where c.CompanyName.StartsWith("A")
                 select c;

    foreach (var item2 in query2)
    {
        Console.WriteLine(item2.CompanyName);
    }
}

 

Mit diesem Code zeigte er, das die Queries bei Linq auch aufeinander aufsetzen dürfen. Ansonsten erwähnte er noch, das LINQ to SQL momentan stärker auf Performance optimiert ist als LINQ to Entities. Anschließend zeigte er wieder einen Überblick über LINQ in seinen Präsentationsfolien, dazu verweise ich lieber auf meine eingangs erwähnten Links.

 

Nachfolgend zeigte er wieder Standard-Querys wie Einfügen und Löschen bei LINQ to Entities, dazu verweise ich auf meine früheren Reviews weil die Syntax zu den anderen LINQ-Varianten eigentlich identisch ist. Diese Query war für mich noch relevant:

 

using (NORTHWNDEntities entities = new NORTHWNDEntities())
{
    Customers c = (from cust in entities.Customers
                   where cust.CustomerID == "PARYS"
                   select cust).Single();

    Console.WriteLine(c);
}

 

Single() liefert den ersten Datensatz zurück… dieser Code lässt sich so kompilieren und die Methode Single() wird auch in Intellisense angezeigt, funktioniert aber nur bei LINQ to SQL, bei LINQ to Entitites wird ein Fehler angezeigt – jedoch nicht bereits beim kompilieren – dort muss man stattdessen First() benutzen. Bei Single() handelt es sich um eine Extension Method, die nicht von jedem Entity-Provider unterstützt wird.

 

Abschließend zeigte er die Benutzung von EntitySQL des Entity-Client über den Quellcode, dazu verweise ich lieber auf dieses Beispiel: Link. It-visions schreibt zum EntityClient folgendes:

 

Erläuterung des Begriffs EntityClient:

EntityClient ist ein ADO.NET-Datenprovider zum Zugriff auf ein konzeptuelles Modell aus dem ADO.NET Entity Framework unter Verwendung von eSQL. Der Zugang ist aber nur lesend.

 

Microsoft führt dazu folgendermaßen aus (Quelle):

 

Die Entity SQL -Sprache ist ein speicherunabhängiger Dialekt von SQL, der direkt mit konzeptionellen Entitätsschemas verwendet werden kann und Entity Data Model-Konzepte wie Vererbung und Beziehungen unterstützt. Die EntityCommand-Klasse wird zur Ausführung eines Entity SQL -Befehls für ein Entitätsmodell verwendet. Bei der Erstellung von EntityCommand-Objekten kann der Name einer gespeicherten Prozedur oder einen Abfragetext angegeben werden. Das Entity Framework arbeitet mit speicherspezifischen Datenanbietern, um generisches Entity SQL in speicherspezifische Abfragen zu übersetzen. Weitere Informationen zum Schreiben von Entity SQL -Abfragen finden Sie unter Entity SQL-Sprache.

 

Abschließend zeigte er noch den Verweis in seinem Blog zu dem Trainingskit zu .NET3.5: Link. Im Microsoft Download Center heisst es im Overview dazu:

 

The .NET Framework 3.5 Enhancements Training Kit includes presentations, hands-on labs, demos, and event materials. This content is designed to help you learn how to utilize the .NET 3.5 Enhancement features including: ASP.NET MVC, ASP.NET Dynamic Data, ASP.NET AJAX History, ASP.NET Routing, ADO.NET Data Services, ADO.NET Entity Framework, WCF 3.5 SP1, and the .NET Framework Client Profile.

 

Vielleicht werde ich mich damit später noch beschäftigen. Insgesamt wieder ein interessanter Webcast. Das Wiedergeben war für mich diesmal trotzdem etwas mühsam; für zukünftige Webcasts werde ich mich vermutlich mehr auf die Teile daraus konzentrieren die für mich relevant (interessant) sind - und das ist die praktische Anwendung. Auf eine 1:1 Wiedergabe werde ich daher vermutlich künftig verzichten – das gilt dann aber für Webcasts allgemein, war nun nicht als Kritik gemeint.

 

Monats-Liste