.
Anmeldung | Registrieren | Hilfe | Posteingang
Suchen
Home Foren News Member Offers Termine Developer Blogs Knowledge Base

Navigation

Navigationslinks überspringen.
Knowledge Base reduzierenKnowledge Base
Tutorials reduzierenTutorials
Webentwicklung
Cliententwicklung
Datenbankentwicklung
IT Professional
Sharepoint
Sprachspezifisch reduzierenSprachspezifisch
C#
Visual Basic
C++
XAML
SQL
JavaScript
Erfahrungsberichte reduzierenErfahrungsberichte
Entwicklersoftware
Bücher
FAQ Grundlagen

Verknüpfungen

  • Knowledge Base durchsuchen
  • Hilfe zur Knowledge Base
  • RSS Feed
  • Twitter

LINQ: Compiled Query

Einleitende Worte

LINQ-Abfragen werden durch Lambda-Ausdrücke angegeben. Diese werden wiederum als Expression-Tree (Ausdrucksbaum) dargestellt und beim Aufruf einer Abfrage wird der Expression-Tree in SQL-Anweisungen übersetzt. Und zwar jedes Mal wenn die Abfrage ausgeführt werden soll.
Hier liegt es nahe die so erzeugten SQL-Anweisungen zwischenzuspeichern und wieder zu verwenden.


Genau an diesem Punkt setzen die Compiled Queries an. Nachfolgend will ich an einem einfachen Beispiel zeigen wie dies ohne viel Aufwand umgesetzt werden kann.

Beispielsanwendung

DB

Es wird eine einfache Datenbank verwendet die nur aus einer Tabelle besteht. Dabei ist die ID-Spalte vom Typ System.Guid und die Bezeichnung ist ein vom Typ System.String. Die Tabelle wird mit beispielhaften Daten gefüllt und die Abfragen der Beispielanwendung filtern die Artikel nach dem Anfangsbuchstaben der Bezeichnung.

Herkömmliche Abfrage

Um die Abfrage auszuführen wird folgender Code verwendet:

using (DataClasses1DataContext db = new DataClasses1DataContext())
{
List<Artikel> q =
db.Artikel.Where(a => a.Bezeichnung.StartsWith(name)).ToList();
}

In der Where-Methode wird eine Lambda-Expression übergeben und diese wird – wie bereits einleitend erwähnt – bei jedem Aufruf in die entsprechenden SQL-Statements übersetzt. Dieses so erzeugte SQL wird an die Datenbank geschickt, dort ausgeführt und das Ergebnis zurück geschickt.

Wird die selbe Abfrage wiederholt aufgerufen so muss der (gleiche) Overhead jedes mal ausgeführt werden.

Compiled Queries

Das Ergebnis der Übersetzung des Lambda-Ausdruck in die SQL-Entsprechung kann mit Compiled Queries gespeichert werden. Damit dies möglich ist – und somit die Leistungssteigerung – muss dieses Ergebnis statisch gespeichert werden. Während der Ausführung werden die statischen Ausdrücke bei der ersten Verwendung übersetzt (geJITet).

Der Abfrageausdruck

Hierzu erstellen wir als ersten Schritt die statische Expression welche die Abfrage enthält:

public static readonly
Expression<Func<DataClasses1DataContext, string, IQueryable<Artikel>>>
GetArtikelQueryExpression =
(db, name) =>
db.Artikel.Where(a => a.Bezeichnung.StartsWith(name));

Da es sich um eine statische Definition handelt muss als Argument auch der aktuelle DataContext übergeben werden. Dies könnte bereits für eine Abfrage verwendet werden indem die Expression kompiliert wird. Während der Kompilation wird das SQL erzeugt und ein Delegat zurückgegeben der für die Abfrage verwendet wird, wie im folgenden Beispiel gezeigt:

public List<Artikel> GetArtikelNotCompiled(
DataClasses1DataContext db, string name)
{
Func<DataClasses1DataContext, string, IQueryable<Artikel>> del =
GetArtikelQueryExpression.Compile();
IQueryable<Artikel> query = del(db, name);
return query.ToList();
}

Ein Geschwindigkeitsvorteil ist hier nicht gegeben da die Expression in jedem Aufruf erneut kompiliert wird – der Delegat ist eine lokale Variable und somit nicht persistent. Die Ausführung dieser Variante entspricht weitestgehend einer herkömmlichen LINQ-Abfrage.

Die Kompilierung

Hier wird die obige Experssion kompiliert und als Resultat wird ein Delegat zurückgegeben. Dieser Delegat kann von IQueryable verarbeitet werden da er bereits nach SQL übersetzt worden ist.

public static readonly
Func<DataClasses1DataContext, string, IQueryable<Artikel>>
GetArtikelCompiled1 =
CompiledQuery.Compile<DataClasses1DataContext, string, IQueryable<Artikel>>
(
GetArtikelQueryExpression
);

Ausführen der Abfrage

Das Ausführen der Abfrage geschieht durch eine Methode.

public List<Artikel> GetArtikel(DataClasses1DataContext db, string name)
{
return GetArtikelCompiled(db, name).ToList();
}

Gesamter Code

Es ist natürlich möglich den Abfrageausdruck und die Kompilierung zusammenzufassen. Nachfolgend wird der komplette Code der partiellen DataContext-Klasse dargestellt:

partial class DataClasses1DataContext
{
public static readonly
Func<DataClasses1DataContext, string, IQueryable<Artikel>>
GetArtikelCompiled =
CompiledQuery.Compile<DataClasses1DataContext, string, IQueryable<Artikel>>
(
(db, name) => db.Artikel.Where(a => a.Bezeichnung.StartsWith(name))
);

//---------------------------------------------------------------------
public List<Artikel> GetArtikel(DataClasses1DataContext db, string name)
{
return GetArtikelCompiled(db, name).ToList();
}
}

Das aufrufende Programm:

static void Main(string[] args)
{
string name = "B";

using (DataClasses1DataContext db = new DataClasses1DataContext())
{
List<Artikel> q = db.GetArtikel(db, name);
}
}

Vergleich mit der herkömmlichen Methode

Für den Vergleich wurde eine Konsoleanwendung mit folgendem Code erstellt und mit dem CLR Profiler analyisiert.

class Program
{
public const int N = 100;
//---------------------------------------------------------------------
static void Main(string[] args)
{
string name = "B";
GetArtikelNotCompiled(name);
GetArtikelCompiled(name);
}
//---------------------------------------------------------------------
private static void GetArtikelNotCompiled(string name)
{
for (int i = 0; i < N; i++)
{
using (DataClasses1DataContext db = new DataClasses1DataContext())
{
List<Artikel> q = db.Artikel.Where(a => a.Bezeichnung.StartsWith(name)).ToList();
}
}
}
//---------------------------------------------------------------------
private static void GetArtikelCompiled(string name)
{
for (int i = 0; i < N; i++)
{
using (DataClasses1DataContext db = new DataClasses1DataContext())
{
List<Artikel> q = db.GetArtikel(db, name);
}
}
}
}

In der Main-Methode werden die Methoden für die Durchführung der Abfragen aufgerufen. Dies ist praktisch da so eine Analyse mit dem CLR Profiler einfacher ist.

Da Bilder mehr als tausend Worte sagen sind die Screenshot der CLR Profiler-Ausgaben abgebildet.

AllocationGraph

CallGraph

In den beiden obigen Abbildungen ist deutlich zu erkennen dass die kompilierte Version performanter ist. Dies sowohl in Hinsicht auf Speicherverbrauch (Heap-Allocation) sowie in der Anzahl der Aufrufe der Methoden.

Schlussbemerkung

Natürlich wäre es auch möglich im SQL Server eine Stored Procedure (SP) zu erstellen. Die Möglichkeit der LINQ Compiled Queries macht Sinn wenn das Erstellen von SPs nicht möglich/gewünscht ist.

Für LINQ-Abfragen die vielfach ausgeführt werden ist es durchaus sinnvoll die Abfrage als Compiled Query zu erstellen da die Vorteile dann nicht von der Hand zu weisen sind.

Anmerkungen

  • Eine Expression könnte mit der Compile-Methode kompiliert werden – dabei entsteht ein Delegat. Würde dieser Delegat für eine LINQ-Abfrage verwendet so kann IQueryable den Delegaten nicht nach SQL übersetzten und deshalb werden alle Daten in den lokalen Speicher übertragen und per LINQ to Object das Ergebnis der Abfrage ermittelt. Aus diesem Grund wird CompiledQuery verwendet da der so erzeugte Delegat auf das übersetzte SQL zeigt.
  • Sind bei der Deklaration der Expression für Func<> mehr als vier Argumente nötig so können diese in einer “Helferklasse” zusammengefasst werden (oder einer in einer struct).
  • Eine Stored Procedure kann in einer Expression nicht verwendet werden. Dies würde in einem Fehler enden (“Gespeicherte Prozeduren können nicht in Abfragen verwendet werden.”).

Durch folgende Anweisung kann dem Visual Studio-Debugger mitgeteilt werden was er wie anzeigen soll. Siehe auch mein Snippet http://dotnet-snippets.de/dns/debugger-attribute-SID1147.aspx

/// <summary>
/// Hier nur für Debugging-Zwecke festgelegt was angezeigt werden soll.
/// </summary>
[DebuggerDisplay("{_Bezeichnung}")]
partial class Artikel { }

von gfoidl, 06.06.2009 zugeordnet zu C# , Cliententwicklung , Datenbankentwicklung .

Weiterführende Artikel

Grundlagen zu LINQ, λ-Expressions und Extension-Methods
http://dotnet-forum.de/KnowledgeBase/articles/2008/07/29/317-grundlagen-zu-linq-lambda-expressions-und-extension-methods.aspx

Kommentare

Der weiterführende Artikel ist die Grundlage für diesen Artikel.
von gfoidl, 06.06.2009.

Eigener Kommentar

Sie müssen angemeldet sein, um ein Kommentar zu erstellen.
  • Schwierigkeit: Fortgeschrittene
  • Views: 2787
  • Zur Druckversion
  • Artikel von gfoidl

Kick it on dotnet-kicks.de

Artikel

Autor

Kick it!

Wenn ihnen dieser Artikel gefällt, bitte "kicken" sie ihn.

WPF Forum | ASP.NET Forum | ASP.NET MVC Forum | Silverlight Forum | Windows Phone 7 Forum | SharePoint Forum | Dotnet Jobs | Dotnet Termine | Developer Blogs | Dotnet News

Das Team | Regeln | Impressum