English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

LINQ-Ausdrucksbaum

Sie haben im vorherigen Abschnitt die Ausdrücke kennengelernt. Lassen Sie uns nun die Ausdrucksbäume hier verstehen.

Wie der Name schon sagt, ist der Ausdrucksbaum nichts anderes als ein nach Baumstruktur geordneter Ausdruck. Jeder Knoten im Ausdrucksbaum ist ein Ausdruck. Zum Beispiel kann der Ausdrucksbaum verwendet werden, um die mathematische Formel x < y darzustellen, wobei x, < und y als Ausdrücke dargestellt und in einer Baumstruktur angeordnet werden.

Der Ausdrucksbaum ist die Speicherrepräsentation der Lambda-Ausdrücke. Er speichert die tatsächlichen Elemente der Abfrage, nicht das Ergebnis der Abfrage.

Das Ausdrucksbaum macht die Struktur der Lambda-Ausdrücke transparent und explizit. Sie können mit den Daten im Ausdrucksbaum interagieren, genau wie mit anderen Datenstrukturen.

Zum Beispiel, schauen wir uns den Ausdruck isTeenAgerExpr an:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.age > 12 && s.age < 20;

Der Compiler wandelt den obigen Ausdruck in den folgenden Ausdrucksbaum um:

Beispiel: Ausdrucksbaum in C#

Expression.Lambda<Func<Student, bool>>(
                Expression.AndAlso(
                    Expression.GreaterThan(Expression.Property(pe, "Age"), Expression.Constant(12, typeof(int))),
                    Expression.LessThan(Expression.Property(pe, "Age"), Expression.Constant(20, typeof(int)))),
                        new[] { pe });

Sie können auch manuell einen Ausdrucksbaum erstellen. Lassen Sie uns sehen, wie wir für den folgenden einfachen Lambda-Ausdruck einen Ausdrucksbaum erstellen:

Beispiel: C# Delegate-Func:

Func<Student, bool> isAdult = s => s.age >= 18;

Dieser Delegate-Typ Func wird als folgende Methode betrachtet:

 C#:

public bool function(Student s)
{
  return s.Age > 18;
}

Um einen Ausdrucksbaum zu erstellen, erstellen Sie zunächst einen Parameter-Ausdruck, bei dem Student der Typ des Parameters ist, 's' ist der Name des Parameters, wie folgt:

Schritte1In C# erstellen Sie einen Parameter-Ausdruck

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");

Jetzt erstellen Sie den Ausdruck s.Age mit Expression.Property () für s ist der Parameter, Age ist der Attributname von Student. (Expressionist eine abstrakte Klasse, die statische Helper-Methode enthält, um manuell einen Ausdrucksbaum zu erstellen.)

Schritte2In C# erstellen Sie ein Attribut-Ausdruck

MemberExpression me = Expression.Property(pe, "Age");

Jetzt, für18Erstellen Sie einen konstanten Ausdruck:

Schritte3In C# erstellen Sie einen konstanten Ausdruck

ConstantExpression constant = Expression.Constant(18, typeof(int));

Bis jetzt haben wir für s.Age (Mitgliedsausdruck) und18Ein Ausdrucksbaum wurde erstellt (konstante Ausdrücke). Jetzt müssen wir überprüfen, ob der Mitgliedsausdruck größer als der konstante Ausdruck ist. Verwenden Sie dazu den Expression.GreaterThanOrEqual () -Methode und übergeben Sie den Mitgliedsausdruck und den konstanten Ausdruck als Parameter:

Schritte4In C# erstellen Sie einen binären Ausdruck

BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);

Daher geben wir dem Lambda-Ausdruckskörper s.Age> = 18 Erstellen Sie ein Ausdrucksbaum. Jetzt müssen wir den Ausdrucksausdruck und den Körpersausdruck verbinden. Verwenden Sie Expression.Lambda(body, parameters array) Verbinden Sie den Lambda-Ausdruck s => s.age> = 18und die Teile des Körpers (body) und der Parameter:

Schritte5In C# erstellen Sie Lambda-Ausdrücke

var isAdultExprTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });

Dadurch können Sie Ausdrucksbäume für einfache Func-Delegaten mit Lambda-Ausdrücken erstellen.

Beispiel: Ausdrucksbaum in C#

ParameterExpression pe = Expression.Parameter(typeof(Student), "s");
MemberExpression me = Expression.Property(pe, "Age");
ConstantExpression constant = Expression.Constant(18, typeof(int));
BinaryExpression body = Expression.GreaterThanOrEqual(me, constant);
var ExpressionTree = Expression.Lambda<Func<Student, bool>>(body, new[] { pe });
Console.WriteLine("Ausdrucksbaum: {0}", ExpressionTree);
        
Console.WriteLine("Körper des Ausdrucksbaums: {0}", ExpressionTree.Body);
        
Console.WriteLine("Körper des Expression-Baums: {0}", ExpressionTree.Body) 
                                ExpressionTree.Parameters.Count);
        
Console.WriteLine("Parameter im Ausdrucksbaum: {0}", ExpressionTree.Parameters[0]);
Dim pe As ParameterExpression = Expression.Parameter(GetType(Student), "s")
Dim mexp As MemberExpression = Expression.Property(pe, "Age")
Dim constant As ConstantExpression = Expression.Constant(18, GetType(Integer))
Dim body As BinaryExpression = Expression.GreaterThanOrEqual(mexp, constant)
Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) = 
    Dim ExpressionTree As Expression(Of Func(Of Student, Boolean)) =
Expression.Lambda(Of Func(Of Student, Boolean))(body, New ParameterExpression() { pe })
Console.WriteLine("Expression-Baum: {0}", ExpressionTree)
        
Console.WriteLine("Körper des Expression-Baums: {0}", ExpressionTree.Body) 
                                Console.WriteLine("Anzahl der Parameter im Expression-Baum: {0}",
        
Console.WriteLine("Parameter im Expression-Baum: {0}", ExpressionTree.Parameters(0))
Ausgabe:}}
Expression-Baum: s => (s.Age >= 18)
Körper des Expression-Baums: (s.Age >= 18)
Anzahl der Parameter im Expression-Baum: 1
Parameter im Expression-Baum: s

Das folgende Diagramm erläutert den gesamten Prozess der Erstellung des Expression-Baums:

Konstruktion des Expression-Baums

Warum wird der Expression-Baum gewählt?

In dem vorherigen Abschnitt haben wir gesehen, dass der dem Lambda-Ausdruck zugewieseneFunc<T>Wird in ausführbaren Code kompiliert und zugewiesen an Lambda-AusdrückeExpression<TDelegate>Typen werden in Expression-Bäume kompiliert.

Ausführbarer Code wird im gleichen Anwendungsbereich ausgeführt, um in-memory-Sammlungen zu verarbeiten. Die statische Klasse 'Enumerables' enthält Methoden zur ImplementierungIEnumerable<T>Erweiterte Methoden für in-memory-Sammlungen von Schnittstellen, wie List<T>, Dictionary<T> usw. Die erweiterten Methoden der Enumerable-Klasse akzeptierenFuncPrädikatparameter der Typdelegation. Zum BeispielWhereDie erweiterte Methode akzeptiertFunc<TSource, bool>-PrädikatDann wird sie in IL (Mittelsprache) kompiliert, um im Speicher des gleichen AppDomain Sammlungen zu verarbeiten.

Das folgende Diagramm zeigt den Fall, dass die erweiterte Methode 'Where' der Enumerable-Klasse die Delegation als Parameter enthält:

Where-Funktion der Delegation

FuncDelegation ist ursprünglicher ausführbarer Code, daher wird bei der Debugging des Codes festgestelltFuncDelegation wird als undurchsichtiger Code dargestellt. Sie können seine Parameter, Rückgabetyp und das Hauptthema nicht sehen:

Func-Delegat im Debug-Modus

FuncDelegat wird für Sammlungen in der Speicherfläche verwendet, da er im selben AppDomain verarbeitet wird, aber wie LINQ-to-Was ist mit Remote-LINQ-Abfrageanbietern für SQL, EntityFramework oder anderen Drittanbieter-Produkten, die LINQ-Funktionen bereitstellen? Wie werden sie Lambda-Ausdrücke, die bereits in Originalausführungscode kompiliert wurden, analysieren, um Parameter, den Rückgabetyp des Lambda-Ausdrucks und den Aufbau der Laufzeitabfrage zu verstehen, um sie weiter zu verarbeiten? Die Antwort istAusdrucksbaum.

Expression <TDelegate> wird in die Datenstruktur "Ausdrucksbaum" kompiliert.

Wenn Sie den Quellcode debuggen, dann repräsentiert der Ausdruck wie folgt:

Ausdrucksbaum im Debug-Modus

Jetzt können Sie den Unterschied zwischen einem normalen Delegat und einem Ausdruck sehen. Der Ausdrucksbaum ist transparent. Sie können Parameter, Rückgabetyp und Hauptausdrucksinformationen aus dem Ausdruck abrufen, wie folgt:

Expression<Func<Student, bool>> isTeenAgerExpr = s => s.Age > 12 && s.Age < 20;
Console.WriteLine("Expression: {0}", isTeenAgerExpr);
        
Console.WriteLine("Typ des Ausdrucks: {0}", isTeenAgerExpr.NodeType);
var parameters = isTeenAgerExpr.Parameters;
foreach (var param in parameters)
{
    Console.WriteLine("Name des Parameters: {0}", param.Name);
    Console.WriteLine("Typ des Parameters: {0}", param.Type.Name);
}
var bodyExpr = isTeenAgerExpr.Body as BinaryExpression;
Console.WriteLine("Linker Ausdruck des Ausdrucks: {0}", bodyExpr.Left);
Console.WriteLine("Typ des binären Ausdrucks: {0}", bodyExpr.NodeType);
Console.WriteLine("Rechter Ausdruck: {0}", bodyExpr.Right);
Console.WriteLine("Rückgabetyp: {0}", isTeenAgerExpr.ReturnType);
Ausgabe:}}
Ausdruck: s => ((s.Age > 12) AndAlso (s.Age < 20))
Ausdrucks-Typ: Lambda
Parameter-Name: s
Parameter-Typ: Student
Linker Ausdruckskopf: (s.Age > 12)
Binärer Ausdrucks-Typ: AndAlso
Rechter Ausdruckskopf: (s.Age < 20)
Rückgabetyp: System.Boolean

Für LINQ-Aufrufe, die nicht im selben Anwendungsbereich ausgeführt werden-to-LINQ-Abfragen in SQL oder Entity Framework. Zum Beispiel wird die folgende LINQ-Abfrage für Entity Framework niemals im Programm intern tatsächlich ausgeführt:

Beispiel: LINQ-Abfrage in C#
var query = from s in dbContext.Students
            where s.Age >= 18
            select s;

wird zunächst in ein SQL-Statement umgewandelt und dann auf dem Datenbankserver ausgeführt.

Der Code, der in der Abfrageausdrucksfindung gefunden wird, muss in eine SQL-Abfrage umgewandelt werden, die als String an einen anderen Prozess gesendet werden kann. Für LINQ-to-SQL oder Entity Framework, dieser Prozess ist genau der SQL Server-Datenbank. Das Konvertieren von Datenstrukturen (wie Ausdrucksbäumen) in SQL ist viel einfacher als das Konvertieren von ursprünglichem IL oder ausführbarem Code in SQL, da es, wie Sie sehen, einfach ist, Informationen aus dem Ausdruck abzurufen.

Ziel des Erstellens eines Ausdrucksbaums ist es, Code wie den Abfrageausdruck in eine Zeichenkette zu konvertieren, die an einen anderen Prozess übergeben und hier ausgeführt werden kann.

Abfragbare statische Klassen umfassen erweiterte Methoden, die Predicate-Parameter des Typs Expression akzeptieren. Der Predicate-Ausdruck wird in einen Ausdrucksbaum umgewandelt und dann als Datenstruktur an den Remote-LINQ-Anbieter übergeben, damit der Anbieter die Anfrage aus dem Ausdrucksbaum aufbauen und ausführen kann.