Nuta
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zalogować się lub zmienić katalogi.
Dostęp do tej strony wymaga autoryzacji. Możesz spróbować zmienić katalogi.
Rozważ użycie kodu definiującego element IQueryableIQueryable(Of T) względem źródła danych:
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)
Za każdym razem, gdy uruchomisz ten kod, zostanie wykonane dokładnie to samo zapytanie. Często nie jest to bardzo przydatne, ponieważ kod może wykonywać różne zapytania w zależności od warunków w czasie wykonywania. W tym artykule opisano sposób wykonywania innego zapytania na podstawie stanu środowiska uruchomieniowego.
IQueryable / IQueryable(Of T) i drzewa wyrażeń
Zasadniczo element IQueryable ma dwa składniki:
- Expression— niezależna od języka i źródła danych reprezentacja składników bieżącego zapytania w postaci drzewa wyrażeń.
- Provider— wystąpienie dostawcy LINQ, które wie, jak zmaterializować bieżące zapytanie w wartość lub zestaw wartości.
W kontekście dynamicznego wykonywania zapytań dostawca zwykle pozostanie taki sam; drzewo wyrażeń zapytania będzie się różnić od zapytania do zapytania.
Drzewa wyrażeń są niezmienne; jeśli chcesz użyć innego drzewa wyrażeń , a tym samym innego zapytania, musisz przetłumaczyć istniejące drzewo wyrażeń na nowe, a tym samym na nowe IQueryable.
W poniższych sekcjach opisano konkretne techniki wykonywania zapytań inaczej w odpowiedzi na stan środowiska uruchomieniowego:
- Używanie stanu środowiska uruchomieniowego z poziomu drzewa wyrażeń
- Wywoływanie dodatkowych metod LINQ
- Różnicuj drzewo wyrażeń przekazane do metod LINQ
- Konstruowanie drzewa wyrażeń Expression(Of TDelegate) przy użyciu metod fabrycznych w Expression
- Dodaj węzły wywołania metody do drzewa wyrażeń IQueryable
- Konstruowanie ciągów i używanie dynamicznej biblioteki LINQ
Używanie stanu środowiska uruchomieniowego z poziomu drzewa wyrażeń
Przy założeniu, że dostawca LINQ obsługuje go, najprostszym sposobem na dynamiczne wykonywanie zapytań jest odwołanie się do stanu środowiska uruchomieniowego bezpośrednio w zapytaniu za pośrednictwem zmiennej zamkniętej, na length przykład w poniższym przykładzie kodu:
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
Drzewo wyrażeń wewnętrznych — w związku z tym zapytanie — nie zostało zmodyfikowane; zapytanie zwraca różne wartości tylko dlatego, że wartość length została zmieniona.
Wywoływanie dodatkowych metod LINQ
Ogólnie rzecz biorąc, wbudowane metody LINQQueryable wykonują dwa kroki:
- Umieść bieżące drzewo wyrażeń w MethodCallExpression, które reprezentuje wywołanie metody.
- Przekaż opakowane drzewo wyrażeń z powrotem do dostawcy, aby zwrócić wartość za pośrednictwem metody dostawcy IQueryProvider.Execute lub zwrócić przetłumaczony obiekt zapytania za pośrednictwem IQueryProvider.CreateQuery metody .
Możesz zastąpić oryginalne zapytanie wynikiem metody IQueryable(Of T)-returning, aby uzyskać nowe zapytanie. Można to zrobić warunkowo na podstawie stanu środowiska uruchomieniowego, jak w poniższym przykładzie:
' Dim sortByLength As Boolean = ...
Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)
Różnicuj drzewo wyrażeń przekazane do metod LINQ
W zależności od stanu środowiska uruchomieniowego można przekazać różne wyrażenia do metod LINQ:
' 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)
Możesz również utworzyć różne podwyrażenia przy użyciu zewnętrznej biblioteki, takiej jak LinqKit's PredicateBuilder:
' 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)
Konstruowanie drzew wyrażeń i zapytań przy użyciu metod fabrycznych
We wszystkich przykładach do tego momentu znaliśmy typ elementu w czasie kompilacji —String i w związku z tym typ zapytania —IQueryable(Of String) . Może być konieczne dodanie składników do zapytania dowolnego typu elementu lub dodanie różnych składników w zależności od typu elementu. Drzewa wyrażeń można tworzyć od podstaw przy użyciu metod fabrycznych w System.Linq.Expressions.Expression, co pozwala na dostosowanie wyrażenia w czasie wykonywania do konkretnego typu elementu.
Konstruowanie wyrażenia (TDelegate)
Podczas konstruowania wyrażenia, które ma być przekazane do jednej z metod LINQ, faktycznie konstruujesz wystąpienie Expression(Of TDelegate), gdzie TDelegate jest jakimś typem delegata, takim jak Func(Of String, Boolean), Action, lub niestandardowym typem delegata.
Wyrażenie(Of TDelegate) dziedziczy po LambdaExpression, które reprezentuje kompletną funkcję lambda, podobną do następującej:
Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")
Komponent LambdaExpression ma dwa składniki.
- Lista parametrów—
(x As String)—reprezentowana przez właściwość Parameters. - Ciało—
x.StartsWith("a")—reprezentowane przez właściwość Body.
Podstawowe kroki konstruowania Expression(Of TDelegate) są następujące:
Zdefiniuj ParameterExpression obiekty dla każdego z parametrów (jeśli istnieją) w wyrażeniu lambda przy użyciu Parameter metody factory.
Dim x As ParameterExpression = Parameter(GetType(String), "x")Skonstruuj treść swojego LambdaExpression, używając zdefiniowanych ParameterExpression(s) oraz metod fabrycznych znajdujących się w Expression. Na przykład wyrażenie reprezentujące
x.StartsWith("a")może być skonstruowane w następujący sposób:Dim body As Expression = [Call]( x, GetType(String).GetMethod("StartsWith", {GetType(String)}), Constant("a") )Owiń parametry i treść w wyrażeniu typizowanym w czasie kompilacji Expression(Of TDelegate), używając odpowiedniego przeciążenia metody fabrykacyjnej:
Dim expr As Expression(Of Func(Of String, Boolean)) = Lambda(Of Func(Of String, Boolean))(body, x)
W poniższych sekcjach opisano scenariusz, w którym można utworzyć wyrażenie (Of TDelegate) w celu przekazania do metody LINQ i przedstawić pełny przykład tego, jak to zrobić przy użyciu metod fabrycznych.
Scenariusz
Załóżmy, że masz wiele typów jednostek:
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
W przypadku dowolnego z tych typów jednostek chcesz filtrować i zwracać tylko te jednostki, które mają dany tekst w jednym z pól string . W przypadku Person, należy wyszukać właściwości FirstName i LastName.
' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))
Jednak w przypadku Car chciałbyś wyszukać tylko właściwość Model:
' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
Where(Function(x) x.Model.Contains(term))
Chociaż można napisać jedną funkcję niestandardową dla IQueryable(Of Person) i drugą dla IQueryable(Of Car), następująca funkcja dodaje to filtrowanie do dowolnego istniejącego zapytania, niezależnie od określonego typu elementu.
Przykład
' 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
Ponieważ funkcja przyjmuje i zwraca TextFilter (a nie tylko ), możesz dodać kolejne elementy zapytania typowane w czasie kompilacji po filtrze tekstu.
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)
Dodawanie węzłów wywołania metody do IQueryabledrzewa wyrażeń
Jeśli masz IQueryable zamiast IQueryable(Of T), nie możesz bezpośrednio wywołać ogólnych metod LINQ. Jedną z alternatyw jest utworzenie drzewa wyrażeń wewnętrznych, jak powyżej, i użycie odbicia w celu wywołania odpowiedniej metody LINQ podczas przekazywania drzewa wyrażeń.
Można również zduplikować funkcjonalność metody LINQ, opakowując całe drzewo w obiekcie MethodCallExpression reprezentującym wywołanie metody LINQ:
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
W tym przypadku nie masz uniwersalnego zastępnika czasu kompilacji T, dlatego użyjesz przeciążenia Lambda, które nie wymaga informacji o typie czasu kompilacji i które generuje LambdaExpression zamiast wyrażenia (TDelegate).
Dynamiczna biblioteka LINQ
Konstruowanie drzew wyrażeń przy użyciu metod fabrycznych jest stosunkowo złożone; łatwiej jest tworzyć ciągi znaków. Dynamiczna biblioteka LINQ udostępnia zestaw metod rozszerzeń odpowiadających standardowym metodom IQueryable LINQ w Queryable, które akceptują ciągi w specjalnej składni zamiast drzew wyrażeń. Biblioteka generuje odpowiednie drzewo wyrażeń z ciągu znaków oraz może zwrócić rezultat przetłumaczony IQueryable.
Na przykład poprzedni przykład (w tym konstrukcja drzewa wyrażeń) może zostać przepisany w następujący sposób:
' 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