Notitie
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen u aan te melden of de directory te wijzigen.
Voor toegang tot deze pagina is autorisatie vereist. U kunt proberen de mappen te wijzigen.
Houd rekening met code die een IQueryable of een IQueryable(of T) definieert voor een gegevensbron:
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)
Telkens wanneer u deze code uitvoert, wordt dezelfde exacte query uitgevoerd. Dit is vaak niet erg nuttig, omdat u mogelijk wilt dat uw code verschillende query's uitvoert, afhankelijk van de omstandigheden tijdens de runtime. In dit artikel wordt beschreven hoe u een andere query kunt uitvoeren op basis van runtimestatus.
IQueryable / IQueryable(Of T) en expressiebomen
Een IQueryable heeft fundamenteel twee componenten:
- Expression— een taal- en gegevensbronagnostische weergave van de onderdelen van de huidige query, in de vorm van een expressiestructuur.
- Provider— een exemplaar van een LINQ-provider die weet hoe de huidige query moet worden omgezet in een waarde of waardenset.
In de context van dynamische query's blijft de provider meestal hetzelfde; de expressiestructuur van de query verschilt van query naar query.
Expressiestructuren zijn onveranderbaar; als u een andere expressiestructuur en dus een andere query wilt, moet u de bestaande expressiestructuur vertalen naar een nieuwe, en dus naar een nieuwe IQueryable.
In de volgende secties worden specifieke technieken beschreven voor het opvragen van query's op een andere manier als reactie op runtimestatus:
- Runtimestatus gebruiken vanuit de expressieboom
- Aanvullende LINQ-methoden aanroepen
- De expressiestructuur variëren die is doorgegeven aan de LINQ-methoden
- Een expressiestructuur (van TDelegate) maken met behulp van de factory-methoden op Expression
- Methodeaanroepknooppunten toevoegen aan een IQueryableexpressiestructuur
- Strings construeren en de dynamische LINQ-bibliotheek gebruiken
Runtimestatus gebruiken vanuit de expressieboom
Ervan uitgaande dat de LINQ-provider dit ondersteunt, is de eenvoudigste manier om dynamisch te zoeken naar de runtimestatus rechtstreeks in de query via een gesloten-overvariabele, zoals length in het volgende codevoorbeeld:
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
De interne expressiestructuur en dus de query zijn niet gewijzigd; de query retourneert alleen verschillende waarden omdat de waarde is length gewijzigd.
Aanvullende LINQ-methoden aanroepen
Over het algemeen voeren de ingebouwde LINQ-methodenQueryable twee stappen uit:
- Verpakt de huidige expressiestructuur in een MethodCallExpression weergave van de methode-aanroep.
- Geef de verpakte expressieboom terug aan de provider, hetzij om een waarde te retourneren via de IQueryProvider.Execute methode van de provider, hetzij om een vertaald queryobject te retourneren via de IQueryProvider.CreateQuery methode.
U kunt de oorspronkelijke query vervangen door het resultaat van een IQueryable(of T)-retourmethode om een nieuwe query op te halen. U kunt dit voorwaardelijk doen op basis van de runtimestatus, zoals in het volgende voorbeeld:
' Dim sortByLength As Boolean = ...
Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)
De expressiestructuur variëren die is doorgegeven aan de LINQ-methoden
U kunt verschillende expressies doorgeven aan de LINQ-methoden, afhankelijk van de runtimestatus:
' 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)
U kunt ook de verschillende subexpressies opstellen met behulp van een bibliotheek van derden, zoals PredicateBuilder van LinqKit:
' 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)
Expressiestructuren en query's maken met behulp van factory-methoden
In alle voorbeelden tot nu toe kennen we het elementtype tijdens de compilatie (String en dus het type van de query).IQueryable(Of String) Mogelijk moet u onderdelen toevoegen aan een query van elk elementtype of verschillende onderdelen toevoegen, afhankelijk van het elementtype. U kunt expressiestructuren vanaf de grond maken met behulp van de fabrieksmethoden op System.Linq.Expressions.Expression, en zo de expressie tijdens runtime aanpassen aan een specifiek elementtype.
Een expressie construeren (van TDelegate)
Wanneer u een expressie maakt die moet worden doorgegeven aan een van de LINQ-methoden, maakt u eigenlijk een exemplaar van Expressie (van TDelegate), waarbij TDelegate een bepaald type gedelegeerde is, zoals Func(Of String, Boolean), Actionof een aangepast gemachtigdetype.
Expression(Of TDelegate) erft LambdaExpression van, dat een volledige lambda-expressie vertegenwoordigt zoals de volgende:
Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")
A LambdaExpression heeft twee onderdelen:
- Een lijst met parameters,
(x As String)vertegenwoordigd door de Parameters eigenschap. - Een lichaam—
x.StartsWith("a")—vertegenwoordigd door de Body eigenschap.
De basisstappen voor het maken van een expressie (van TDelegate) zijn als volgt:
Definieer ParameterExpression objecten voor elk van de parameters (indien aanwezig) in de lambda-expressie, met behulp van de Parameter factory-methode.
Dim x As ParameterExpression = Parameter(GetType(String), "x")Bouw het lichaam van uw LambdaExpression op met behulp van de ParameterExpression(s) die u hebt gedefinieerd en de fabrieksmethoden bij Expression. Een expressie die
x.StartsWith("a")vertegenwoordigt, kan bijvoorbeeld als volgt worden samengesteld:Dim body As Expression = [Call]( x, GetType(String).GetMethod("StartsWith", {GetType(String)}), Constant("a") )Omhul de parameters en het lichaam in een 'compile-time'-getypeerde Expression(Of TDelegate), door gebruik te maken van de juiste Lambda overbelasting van de fabrieksmethode.
Dim expr As Expression(Of Func(Of String, Boolean)) = Lambda(Of Func(Of String, Boolean))(body, x)
In de volgende secties wordt een scenario beschreven waarin u een expressie (van TDelegate) wilt maken om door te geven aan een LINQ-methode en een volledig voorbeeld van hoe u dit doet met behulp van de factory-methoden.
Scenariobeschrijving
Stel dat u meerdere entiteitstypen hebt:
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
Voor een van deze entiteitstypen wilt u alleen de entiteiten filteren en retourneren die een bepaalde tekst in een van hun string velden hebben. Voor Person, wilt u zoeken in de FirstName en LastName eigenschappen:
' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))
Maar voor Car, wilt u alleen de Model eigenschap doorzoeken:
' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
Where(Function(x) x.Model.Contains(term))
Hoewel u een aangepaste functie kunt schrijven voor IQueryable(Of Person) en een andere voor IQueryable(Of Car), voegt de volgende functie deze filtering toe aan elke bestaande query, ongeacht het specifieke elementtype.
Voorbeeld
' 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
Omdat de TextFilter functie een IQueryable(Of T) (en niet alleen een IQueryable) gebruikt en retourneert, kunt u na het tekstfilter nog meer compile-time getypeerde query-elementen toevoegen.
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)
Methode-aanroepknooppunten toevoegen aan de expressiestructuur van de IQueryable
Als u een IQueryable hebt in plaats van een IQueryable(Of T), kunt u de generieke LINQ-methoden niet rechtstreeks aanroepen. Een alternatief is om de binnenste expressiestructuur zoals hierboven te bouwen en weerspiegeling te gebruiken om de juiste LINQ-methode aan te roepen terwijl de expressiestructuur wordt doorgegeven.
U kunt ook de functionaliteit van de LINQ-methode dupliceren door de hele structuur te verpakken in een structuur die een MethodCallExpression aanroep naar de LINQ-methode vertegenwoordigt:
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 dit geval hebt u geen generieke tijdelijke aanduiding voor compilatietijd T, dus gebruikt u de Lambda overbelasting die geen informatie over het type tijdens compilatietijd vereist, en die een LambdaExpression produceert in plaats van een Expression(Of TDelegate).
De dynamische LINQ-bibliotheek
Het opbouwen van expressiebomen met behulp van fabrieksmethoden is relatief complex; het is eenvoudiger om tekenreeksen samen te stellen. De dynamische LINQ-bibliotheek bevat een set extensiemethoden die IQueryable overeenkomen met de standaard LINQ-methoden op Queryableen die tekenreeksen in een speciale syntaxis accepteren in plaats van expressiestructuren. De bibliotheek genereert de juiste expressiestructuur van de tekenreeks en kan de resulterende vertaalde IQueryablestructuur retourneren.
Het vorige voorbeeld (inclusief de constructie van de expressiestructuur) kan bijvoorbeeld als volgt worden herschreven:
' 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