38.2 Abfragen mit Entity SQL
Entity SQL (kurz: eSQL) ist eine Alternative zu LINQ to Entities. Entity SQL ist ein datenbankunabhängiger SQL-Dialekt, der direkt mit dem konzeptionellen Modell arbeitet. Die Syntax ähnelt der des traditionellen T-SQL und ist auf die Belange der Entitäten hin geprägt. Allerdings wird das einfache Übernehmen einer T-SQL nach Entity SQL zu einem Fehler führen – und natürlich auch in umgekehrter Richtung.
Warum eine weitere Abfragevariante? Zunächst einmal ist festzustellen, dass die Entwicklung von Entity SQL begann, als es LINQ noch nicht gab. Dass man später trotz LINQ Entity SQL weiterverfolgte, hat einen recht einfachen Grund: LINQ ist Teil der Sprache von C# geworden, wird aber von den vielen anderen .NET-fähigen Sprachen unterstützt. Zudem werden in manchen Situationen keine Entitäten (also Objekte) benötigt – beispielsweise wenn nur »nackte« Daten gewünscht werden und die Materialisierung in Entitäten nicht von Interesse ist, ähnlich wie bei den DataReader-Objekten von ADO.NET. Solche Anforderungen können nur mit Entity SQL gelöst werden. Ein weiterer Vorteil von Entity SQL ist, dass eine Abfrage erst zur Laufzeit erzeugt werden kann. Das ist mit LINQ to Entities nicht möglich.
Entity SQL unterstützt nur Abfragen. Es gibt daher in Entity SQL zwar ein SELECT, aber keine Aktualisierungsstatements wie UPDATE, INSERT oder DELETE.
38.2.1 Ein erstes Beispiel mit Entity SQL
Sehen Sie sich noch einmal Abbildung 37.13 im vorhergehenden Kapitel an. Sie können darin erkennen, dass Sie die Object Services des Entity Frameworks ansprechen können oder die Entity-SQL-Abfrage dem EntityClient-Provider übergeben können. Das folgende erste Beispiel einer Entity-SQL-Abfrage soll die Object Services nutzen.
using (NorthwindEntities context = new NorthwindEntities())
{
var query = "SELECT VALUE p FROM NorthwindEntities.Products AS p " +
"WHERE p.UnitPrice > 50";
ObjectQuery<Product> products = context.CreateQuery<Product>(query);
foreach (Product item in products)
Console.WriteLine(item.ProductName);
}
Listing 38.21 Einfache Entity-SQL-Abfrage
Die Variable query beschreibt das Entity-SQL-Statement. Anschließend wird ein Objekt vom Typ ObjectQuery erzeugt. Dazu wird auf dem Objektkontext dessen Methode CreateQuery aufgerufen und das Entity-SQL-Statement übergeben. Sie sehen, das Entity-SQL-Statement sieht anders aus als ein T-SQL-Statement. Das sollte aber auch nicht verwundern, denn Entitäten unterscheiden sich deutlich von relationalen Daten.
In Listing 38.21 wird ein Objekt vom Typ ObjectQuery durch den Aufruf der Methode CreateQuery auf den Objektkontext erzeugt. Sie können alternativ auch den Konstruktor der Klasse ObjectQuery benutzen, dem Sie zuerst die das Entity SQL beschreibende Zeichenfolge und anschließend die Referenz auf den Objektkontext übergeben. Bezogen auf Listing 38.21 sähe die Anweisung wie folgt aus:
ObjectQuery<Product> products = new ObjectQuery<Product>(query, context);
38.2.2 Die fundamentalen Regeln der Entity-SQL-Syntax
Sehen wir uns das Entity-SQL-Statement des Listings 38.21 genauer an und ignorieren dabei die WHERE-Klausel.
SELECT VALUE p FROM NorthwindEntities.Products AS p
In jeder Entity-SQL-Abfrage muss das Entitätenmodell angegeben werden, auf das sich die Abfrage bezieht. In unserem Fall ist das NorthwindEntities.Products. Die Groß- oder Kleinschreibung findet hier keine Berücksichtigung.
Die VALUE-Klausel wird benötigt, wenn die einzelnen Resultate nur einen bestimmten Typ beschreiben. Dabei kann es sich um eine Entität, eine Eigenschaft oder auch eine Liste von Entitäten handeln. Um alle Produktbezeichner abzufragen, wären die folgenden Anweisungen notwendig:
var query = "SELECT VALUE p.ProductName FROM NorthwindEntities.Products AS p";
var products = context.CreateQuery<string>(query);
Ohne VALUE wird das Resultat der Abfrage tabellarisch geliefert. Um die Daten zu erhalten, muss dann durch die Zeilen und Spalten navigiert werden.
Gehören mehrere Elemente zu einem Rückgabeelement, können Sie auf VALUE verzichten. Angenommen, jedes Element der Ergebnismenge soll durch ProductName und UnitPrice beschrieben werden, würde das Entity-SQL-Statement wie folgt lauten:
var query = "SELECT p.ProductName, p.UnitPrice FROM ... AS p";
Etwas Probleme macht nun die Ausgabe an der Konsole, denn wir haben es hier mit einer Projektion zu tun, die unter LINQ zu einem anonymen Typ wird, womit LINQ problemlos umgehen kann. Entity SQL kann das jedoch nicht. Andererseits benötigt die Methode CreateQuery eine genaue Typangabe. Die Lösung ist sehr einfach: Tragen Sie den Typ DbDataRecord ein, der sich im Namespace System.Data.Common befindet. Der Code würde einschließlich des Entity-SQL-Statements wie folgt lauten:
using (NorthwindEntities context = new NorthwindEntities())
{
var query = "SELECT p.ProductName, p.UnitPrice FROM ... AS p";
var products = new ObjectQuery<DbDataRecord>(query, context);
foreach (var item in products)
Console.WriteLine(item.GetValue(0));
}
Listing 38.22 Entity-SQL-Statement
Den Einsatz von VALUE kennen Sie nun. Erwähnt werden muss in diesem Statement noch, dass mit AS eine Variable deklariert wird, die die Ergebnismenge darstellt.
Entity SQL unterstützt nicht das »*«-Zeichen, um alle Spalten an den Aufrufer zurückzuliefern. Das bedeutet, dass alle Spalten, auf die der Aufrufer zugreifen soll, ausdrücklich genannt werden müssen.
38.2.3 Filtern mit Entity SQL
Eine der wichtigsten Operationen einer Abfrage ist das Filtern von Daten. Wie in der klassischen SQL-Syntax verwendet auch Entity SQL zum Filtern von Daten die WHERE-Klausel. Das folgende Listing zeigt, wie diejenigen Produkte in die Ergebnismenge geschrieben werden können, die mit Discontinued=true als Auslaufprodukte gekennzeichnet sind.
using (NorthwindEntities context = new NorthwindEntities())
{
string query = "SELECT VALUE p FROM northwindentities.products AS p " +
"WHERE p.Discontinued = true";
ObjectQuery<Product> products = new ObjectQuery<Product>(query, context);
foreach (var item in products)
Console.WriteLine(item.ProductName);
}
Listing 38.23 Alle Auslaufprodukte mit Entity SQL herausfiltern
Alle Filteroperatoren von Entity SQL aufzuführen würde den Rahmen sprengen. Nichtsdestotrotz sollen hier die wichtigsten erwähnt werden.
Logische Operatoren
Sie können in Entity SQL die üblichen logischen Operatoren NOT, OR und AND benutzen. Sollten mehrere logische Operatoren verwendet werden, spiegelt die Reihenfolge NOT, OR und AND auch die Prioritätsreihenfolge wider. In C# ist es auch möglich, diese Operatoren durch die sprachspezifischen zu ersetzen. Daher können Sie eine Entity-SQL-Abfrage entweder mit
SELECT VALUE p FROM NorthwindEntities.Products AS p
WHERE p.UnitPrice > 20 AND p.UnitsInStock <10
oder mit
SELECT VALUE p FROM NorthwindEntities.Products AS p
WHERE p.UnitPrice > 20 && p.UnitsInStock <10
formulieren.
Vergleichsoperatoren
Zum Vergleich von Werten bietet Entity SQL die üblichen »Verdächtigen« an (siehe Tabelle 38.2):
Operator | Beschreibung |
< |
Kleiner als ... |
> |
Größer als ... |
>= |
Größer als oder gleich ... |
<= |
Kleiner als oder gleich ... |
= |
Gleich. In C# kann für diesen Operator auch »==« benutzt werden. |
<> |
Ungleich. In C# kann für diesen Operator auch »!=« benutzt werden. |
Mustervergleiche mit Zeichenfolgen
Auch hinsichtlich des Mustervergleichs mit LIKE ähnelt Entity SQL dem klassischen SQL. Mit Platzhaltern wird geprüft, ob die Zeichenkette passend zu einer Vorgabe ist.
Die folgende Entity-SQL-Abfrage liefert beispielsweise alle Artikel, die mit dem Buchstaben »C« im Produktnamen beginnen.
SELECT VALUE p FROM ... AS p WHERE p.ProductName LIKE 'C%'
Im folgenden Statement wird der Filter erweitert in der Weise, dass alle Produkte, deren erster Buchstabe des Produktbezeichners im Bereich von »A« bis »D« liegt, zurückgeliefert werden.
SELECT VALUE p FROM ... AS p WHERE p.ProductName LIKE '[A-D]%'
Das »%«-Zeichen dient als Platzhalter für x-beliebig viele Zeichen, mit den eckigen Klammern wird der gewünschte Bereich definiert.
Die Festlegung des Bereichs kann auch negiert werden. Wollen Sie beispielsweise alle Produkte abfragen, die sich nicht im Bereich »A« bis »D« befinden, muss die WHERE-Klausel wie folgt definiert werden:
... WHERE p.ProductName LIKE '[^A-D]%'
Bereiche von Werten prüfen
Um zu prüfen, ob ein Wert sich innerhalb eines bestimmten Bereichs befindet, benutzen Sie den BETWEEN-Operator. Das folgende Statement liefert alle Produkte zurück, deren Preis zwischen einschließlich 10 und 20 liegt.
SELECT VALUE p FROM ... AS p WHERE p.UnitPrice BETWEEN 10 AND 20
Anzumerken sei noch, dass sich der BETWEEN-Operator mit NOT negieren lässt.
Der Operator IS NULL
Tabellenspalten einer Datenbank können den Wert NULL haben. Mit den logischen Operatoren wie »=« oder »<>« ein Feld auf NULL hin zu prüfen, erzeugt zwar keinen Fehler, liefert aber andererseits auch nichts zurück, weil ein Wert weder NULL noch ungleich NULL sein kann. Um gegen NULL zu prüfen, gibt es daher in Entity SQL den Operator IS NULL.
Das nächste Entity-SQL-Statement fragt nach allen Datensätzen, die im Feld CategoryID den Wert NULL haben. Standardmäßig hat das kein Datensatz in der Tabelle Products. Wenn Sie tatsächlich ein Ergebnis sehen wollen, sollten Sie vorher eine Datenzeile hinzufügen, die in dem betreffenden Feld NULL aufweist.
SELECT VALUE p FROM ... AS p WHERE p.CategoryID IS NULL
38.2.4 Parametrisierte Abfragen
Die meisten Filter einer Datenabfrage sind nicht statisch, sondern dynamisch. Mit anderen Worten heißt das, dass die meisten Abfragen parametrisiert sind. Auch in diesen Fällen unterstützt uns das ObjectQuery-Objekt durch die Bereitstellung einer Parameter-Collection, die über die Eigenschaft Parameters angesprochen werden kann. Die Parameter werden, angelehnt an T-SQL, mit einem @-Zeichen eingeleitet und mit der Add-Methode der Parameterliste des ObjectQuery-Objekts hinzugefügt. Dabei muss jedoch beachtet werden, dass beim Hinzufügen das @-Zeichen nicht mit angegeben wird.
using (NorthwindEntities context = new NorthwindEntities())
{
string query = "SELECT VALUE p FROM NorthwindEntities.Products AS p " +
"WHERE p.UnitPrice > @preis";
ObjectQuery<Product> products = context.CreateQuery<Product>(query);
products.Parameters.Add(new ObjectParameter("preis", 50));
foreach (Product item in products)
Console.WriteLine("{0,-35}{1}", item.ProductName, item.UnitPrice);
}
Listing 38.24 Parametrisierte Abfrage mit Entity SQL
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.