Hinweis
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, sich anzumelden oder das Verzeichnis zu wechseln.
Für den Zugriff auf diese Seite ist eine Autorisierung erforderlich. Sie können versuchen, das Verzeichnis zu wechseln.
Betrachten Sie Code, der eine IQueryable oder ein IQueryable(Of T) für eine Datenquelle definiert:
Dim companyNames As String() = {
"Consolidated Messenger", "Alpine Ski House", "Southridge Video",
"City Power & Light", "Coho Winery", "Wide World Importers",
"Graphic Design Institute", "Adventure Works", "Humongous Insurance",
"Woodgrove Bank", "Margie's Travel", "Northwind Traders",
"Blue Yonder Airlines", "Trey Research", "The Phone Company",
"Wingtip Toys", "Lucerne Publishing", "Fourth Coffee"
}
' We're using an in-memory array as the data source, but the IQueryable could have come
' from anywhere -- an ORM backed by a database, a web request, Or any other LINQ provider.
Dim companyNamesSource As IQueryable(Of String) = companyNames.AsQueryable
Dim fixedQry = companyNamesSource.OrderBy(Function(x) x)
Jedes Mal, wenn Sie diesen Code ausführen, wird dieselbe genaue Abfrage ausgeführt. Dies ist häufig nicht sehr nützlich, da Sie möchten, dass Ihr Code unterschiedliche Abfragen abhängig von den Bedingungen zur Laufzeit ausführt. In diesem Artikel wird beschrieben, wie Sie eine andere Abfrage basierend auf dem Laufzeitzustand ausführen können.
IQueryable/IQueryable(Of T) und Ausdrucksbaumstrukturen
Grundsätzlich verfügt ein IQueryable System über zwei Komponenten:
- Expression– eine sprach- und datenquellenunabhängige Darstellung der Komponenten der aktuellen Abfrage in Form einer Ausdrucksstruktur.
- Provider– eine Instanz eines LINQ-Anbieters, der weiß, wie die aktuelle Abfrage in einen Wert oder wertesatz materialisiert wird.
Im Kontext der dynamischen Abfrage bleibt der Anbieter in der Regel gleich; Die Ausdrucksstruktur der Abfrage unterscheidet sich von Abfrage zu Abfrage.
Ausdrucksbäume sind unveränderlich; Wenn Sie eine andere Ausdrucksstruktur und somit eine andere Abfrage benötigen, müssen Sie die vorhandene Ausdrucksstruktur in eine neue struktur und damit in eine neue IQueryableübersetzen.
In den folgenden Abschnitten werden die genauen Verfahren für das unterschiedliche Abfragen in Abhängigkeit vom Laufzeitzustand beschrieben:
- Verwenden des Laufzeitzustands innerhalb der Ausdrucksbaumstruktur
- Aufrufen zusätzlicher LINQ-Methoden
- Variieren der Ausdrucksbaumstruktur, die an die LINQ-Methoden übergeben wird
- Expression(Of TDelegate)-Ausdrucksbaumstruktur mithilfe der Factorymethoden in Expression erstellen
- Hinzufügen von Methodenaufrufknoten zu einer Ausdrucksbaumstruktur von IQueryable
- Erstellen von Zeichenfolgen und Verwenden der dynamischen LINQ-Bibliothek
Verwenden des Laufzeitzustands innerhalb der Ausdrucksbaumstruktur
Vorausgesetzt, der LINQ-Anbieter unterstützt dies, besteht die einfachste Möglichkeit, dynamisch abzufragen darin, direkt in der Abfrage über eine geschlossene Variable auf den Laufzeitzustand zu verweisen, wie z. B. length im folgenden Codebeispiel.
Dim length = 1
Dim qry = companyNamesSource.
Select(Function(x) x.Substring(0, length)).
Distinct
Console.WriteLine(String.Join(", ", qry))
' prints: C, A, S, W, G, H, M, N, B, T, L, F
length = 2
Console.WriteLine(String.Join(", ", qry))
' prints: Co, Al, So, Ci, Wi, Gr, Ad, Hu, Wo, Ma, No, Bl, Tr, Th, Lu, Fo
Die interne Ausdrucksstruktur – und damit die Abfrage – wurde nicht geändert; die Abfrage gibt nur unterschiedliche Werte zurück, weil der Wert length geändert wurde.
Aufrufen zusätzlicher LINQ-Methoden
Im Allgemeinen führen die integrierten LINQ-Methoden am Queryable zwei Schritte aus.
- Umschließen der aktuellen Ausdrucksbaumstruktur in einem MethodCallExpression-Objekt, das den Methodenaufruf darstellt
- Übergeben Sie den umschlossenen Ausdrucksbaum zurück an den Anbieter, entweder um einen Wert mithilfe der IQueryProvider.Execute-Methode des Anbieters zurückzugeben oder um ein übersetztes Abfrageobjekt über die IQueryProvider.CreateQuery-Methode zurückzugeben.
Sie können die ursprüngliche Abfrage durch das Ergebnis einer IQueryable(Of T)-returning-Methode ersetzen, um eine neue Abfrage abzurufen. Sie können dies bedingt basierend auf dem Laufzeitstatus tun, wie im folgenden Beispiel gezeigt:
' Dim sortByLength As Boolean = ...
Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)
Variieren der Ausdrucksbaumstruktur, die an die LINQ-Methoden übergeben wird
Je nach Laufzeitzustand können Sie verschiedene Ausdrücke an die LINQ-Methoden übergeben:
' Dim startsWith As String = ...
' Dim endsWith As String = ...
Dim expr As Expression(Of Func(Of String, Boolean))
If String.IsNullOrEmpty(startsWith) AndAlso String.IsNullOrEmpty(endsWith) Then
expr = Function(x) True
ElseIf String.IsNullOrEmpty(startsWith) Then
expr = Function(x) x.EndsWith(endsWith)
ElseIf String.IsNullOrEmpty(endsWith) Then
expr = Function(x) x.StartsWith(startsWith)
Else
expr = Function(x) x.StartsWith(startsWith) AndAlso x.EndsWith(endsWith)
End If
Dim qry = companyNamesSource.Where(expr)
Möglicherweise möchten Sie auch die verschiedenen Unterausdrücke mithilfe einer Drittanbieterbibliothek wie linqKitsPredicateBuilder verfassen:
' This is functionally equivalent to the previous example.
' Imports LinqKit
' Dim startsWith As String = ...
' Dim endsWith As String = ...
Dim expr As Expression(Of Func(Of String, Boolean)) = PredicateBuilder.[New](Of String)(False)
Dim original = expr
If Not String.IsNullOrEmpty(startsWith) Then expr = expr.Or(Function(x) x.StartsWith(startsWith))
If Not String.IsNullOrEmpty(endsWith) Then expr = expr.Or(Function(x) x.EndsWith(endsWith))
If expr Is original Then expr = Function(x) True
Dim qry = companyNamesSource.Where(expr)
Erstellen von Ausdrucksbaumstrukturen und Abfragen mit Factorymethoden
In allen Beispielen bis zu diesem Punkt haben wir den Elementtyp zur KompilierungszeitString – und somit den Typ der Abfrage –IQueryable(Of String) kennen. Möglicherweise müssen Sie einer Abfrage eines beliebigen Elementtyps Komponenten hinzufügen oder je nach Elementtyp unterschiedliche Komponenten hinzufügen. Sie können Ausdrucksbaumstrukturen mithilfe der Factorymethoden in System.Linq.Expressions.Expression von Grund auf neu erstellen und so den Ausdruck zur Laufzeit auf einen bestimmten Elementtyp zuschneiden.
Erstellen eines Expression(Of TDelegate)-Objekts
Wenn Sie einen Ausdruck erstellen, der an eine der LINQ-Methoden übergeben werden soll, erstellen Sie tatsächlich eine Instanz von Expression(Of TDelegate), wobei TDelegate es sich um einen Delegatentyp wie Func(Of String, Boolean), Actionoder einen benutzerdefinierten Delegattyp handelt.
Expression(Of TDelegate) erbt von LambdaExpression, der einen vollständigen Lambda-Ausdruck wie folgt darstellt:
Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")
A LambdaExpression verfügt über zwei Komponenten:
- Eine Parameterliste,
(x As String), die von der Parameters-Eigenschaft dargestellt wird. - Einen Textkörper,
x.StartsWith("a"), der durch die Body-Eigenschaft dargestellt wird.
Die grundlegenden Schritte beim Erstellen eines Ausdrucks(von TDelegate) sind wie folgt:
Definieren Sie ParameterExpression-Objekte für jeden der Parameter (sofern vorhanden) im Lambdaausdruck mithilfe der Parameter-Factorymethode.
Dim x As ParameterExpression = Parameter(GetType(String), "x")Erstellen Sie den Text von LambdaExpression mit den von Ihnen definierten ParameterExpressions und den Factorymethoden unter Expression. Beispielsweise könnte ein Ausdruck, der
x.StartsWith("a")repräsentiert, wie folgt konstruiert werden:Dim body As Expression = [Call]( x, GetType(String).GetMethod("StartsWith", {GetType(String)}), Constant("a") )Schließen Sie Parameter und Textkörper mithilfe der Überladung der -Factorymethode in ein zur Kompilierzeit typisiertes Lambda-Objekt ein:
Dim expr As Expression(Of Func(Of String, Boolean)) = Lambda(Of Func(Of String, Boolean))(body, x)
In den folgenden Abschnitten wird ein Szenario beschrieben, in dem Sie ein Expression(Of TDelegate)-Objekt erstellen, das an eine LINQ-Methode übergeben werden soll. Außerdem wird ein vollständiges Beispiel für die Erstellung des Objekts mithilfe der Factorymethoden geliefert.
Szenario
Angenommen, Sie haben mehrere Entitätstypen:
Public Class Person
Property LastName As String
Property FirstName As String
Property DateOfBirth As Date
End Class
Public Class Car
Property Model As String
Property Year As Integer
End Class
Für einen dieser Entitätstypen möchten Sie nur die Entitäten filtern und zurückgeben, die einen bestimmten Text in einem ihrer string Felder enthalten. Für Person sollten Sie die Eigenschaften FirstName und LastName durchsuchen:
' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))
Für Car soll jedoch nur die Eigenschaft Model durchsucht werden:
' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
Where(Function(x) x.Model.Contains(term))
Obwohl Sie eine benutzerdefinierte Funktion für IQueryable(Of Person) und eine andere für IQueryable(Of Car) implementieren könnten, fügt die folgende Funktion diese Filterung jeder vorhandenen Abfrage hinzu, unabhängig vom jeweiligen Elementtyp.
Beispiel
' Imports System.Linq.Expressions.Expression
Function TextFilter(Of T)(source As IQueryable(Of T), term As String) As IQueryable(Of T)
If String.IsNullOrEmpty(term) Then Return source
' T is a compile-time placeholder for the element type of the query
Dim elementType = GetType(T)
' Get all the string properties on this specific type
Dim stringProperties As PropertyInfo() =
elementType.GetProperties.
Where(Function(x) x.PropertyType = GetType(String)).
ToArray
If stringProperties.Length = 0 Then Return source
' Get the right overload of String.Contains
Dim containsMethod As MethodInfo =
GetType(String).GetMethod("Contains", {GetType(String)})
' Create the parameter for the expression tree --
' the 'x' in 'Function(x) x.PropertyName.Contains("term")'
' The type of the parameter is the query's element type
Dim prm As ParameterExpression =
Parameter(elementType)
' Generate an expression tree node corresponding to each property
Dim expressions As IEnumerable(Of Expression) =
stringProperties.Select(Of Expression)(Function(prp)
' For each property, we want an expression node like this:
' x.PropertyName.Contains("term")
Return [Call]( ' .Contains(...)
[Property]( ' .PropertyName
prm, ' x
prp
),
containsMethod,
Constant(term) ' "term"
)
End Function)
' Combine the individual nodes into a single expression tree node using OrElse
Dim body As Expression =
expressions.Aggregate(Function(prev, current) [OrElse](prev, current))
' Wrap the expression body in a compile-time-typed lambda expression
Dim lmbd As Expression(Of Func(Of T, Boolean)) =
Lambda(Of Func(Of T, Boolean))(body, prm)
' Because the lambda is compile-time-typed, we can use it with the Where method
Return source.Where(lmbd)
End Function
Da die TextFilter Funktion eine IQueryable(Of T) (und nicht nur eine IQueryable) zurückgibt, können Sie nach dem Textfilter weitere Abfrageelemente mit Kompilierungszeittyp hinzufügen.
Dim qry = TextFilter(
(New List(Of Person)).AsQueryable,
"abcd"
).Where(Function(x) x.DateOfBirth < #1/1/2001#)
Dim qry1 = TextFilter(
(New List(Of Car)).AsQueryable,
"abcd"
).Where(Function(x) x.Year = 2010)
Hinzufügen von Methodenaufrufknoten zur Ausdrucksbaumstruktur von IQueryable
Wenn Sie ein IQueryable anstelle eines IQueryable(Of T) haben, können Sie die generischen LINQ-Methoden nicht direkt aufrufen. Eine Alternative besteht darin, die innere Ausdrucksbaumstruktur wie oben zu erstellen und mithilfe der Reflexion die entsprechenden LINQ-Methode aufzurufen, während die Ausdrucksbaumstruktur übergeben wird.
Sie können die Funktionalität der LINQ-Methode auch duplizieren, indem Sie den gesamten Baum in ein MethodCallExpression umschließen, das einen Aufruf der LINQ-Methode repräsentiert.
Function TextFilter_Untyped(source As IQueryable, term As String) As IQueryable
If String.IsNullOrEmpty(term) Then Return source
Dim elementType = source.ElementType
' The logic for building the ParameterExpression And the LambdaExpression's body is the same as in
' the previous example, but has been refactored into the ConstructBody function.
Dim x As (Expression, ParameterExpression) = ConstructBody(elementType, term)
Dim body As Expression = x.Item1
Dim prm As ParameterExpression = x.Item2
If body Is Nothing Then Return source
Dim filteredTree As Expression = [Call](
GetType(Queryable),
"Where",
{elementType},
source.Expression,
Lambda(body, prm)
)
Return source.Provider.CreateQuery(filteredTree)
End Function
In diesem Fall ist zur Kompilierzeit kein generischer T-Platzhalter verfügbar. Aus diesem Grund verwenden Sie die Lambda-Überladung, die keine Typinformationen zur Kompilierzeit benötigt und LambdaExpression anstelle eines Expression(Of TDelegate)-Objekts erzeugt.
Die dynamische LINQ-Bibliothek
Das Erstellen von Ausdrucksbaumstrukturen mit Factorymethoden ist relativ komplex. Es ist einfacher, Zeichenfolgen zu verfassen. Die Dynamische LINQ-Bibliothek stellt eine Reihe von Erweiterungsmethoden bereit, die den standardmäßigen LINQ-Methoden unter IQueryable entsprechen und Zeichenfolgen in einer Queryable anstelle von Ausdrucksbäumen akzeptieren. Die Bibliothek generiert die entsprechende Ausdrucksbaumstruktur aus der Zeichenfolge und kann die resultierende übersetzte Schnittstelle IQueryable zurückgeben.
Das vorherige Beispiel (einschließlich der Ausdrucksbaumkonstruktion) könnte beispielsweise wie folgt umgeschrieben werden:
' Imports System.Linq.Dynamic.Core
Function TextFilter_Strings(source As IQueryable, term As String) As IQueryable
If String.IsNullOrEmpty(term) Then Return source
Dim elementType = source.ElementType
Dim stringProperties = elementType.GetProperties.
Where(Function(x) x.PropertyType = GetType(String)).
ToArray
If stringProperties.Length = 0 Then Return source
Dim filterExpr = String.Join(
" || ",
stringProperties.Select(Function(prp) $"{prp.Name}.Contains(@0)")
)
Return source.Where(filterExpr, term)
End Function