Notes
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de vous connecter ou de modifier des répertoires.
L’accès à cette page nécessite une autorisation. Vous pouvez essayer de modifier des répertoires.
Considérez le code qui définit un IQueryable ou un IQueryable(Of T) contre une source de données :
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)
Chaque fois que vous exécutez ce code, la même requête exacte est exécutée. Cela n’est fréquemment pas très utile, car vous souhaiterez peut-être que votre code exécute différentes requêtes en fonction des conditions au moment de l’exécution. Cet article explique comment exécuter une requête différente en fonction de l’état d’exécution.
IQueryable / IQueryable(Of T) et arborescences d’expressions
Fondamentalement, un IQueryable se compose de deux éléments.
- Expression— représentation indépendante du langage et de la source de données des composants de la requête actuelle, sous la forme d’une arborescence d’expressions.
- Provider— instance d’un fournisseur LINQ, qui sait comment matérialiser la requête actuelle dans une valeur ou un ensemble de valeurs.
Dans le contexte de l’interrogation dynamique, le fournisseur reste généralement le même ; l’arborescence d’expressions de la requête diffère de la requête à la requête.
Les arborescences d’expressions sont immuables ; si vous souhaitez une arborescence d’expressions différente, et donc une autre requête, vous devez traduire l’arborescence d’expressions existante en une nouvelle, et ainsi en une nouvelle IQueryable.
Les sections suivantes décrivent des techniques spécifiques pour interroger différemment en réponse à l’état d’exécution :
- Utiliser l’état d’exécution à partir de l’arborescence d’expressions
- Appeler des méthodes LINQ supplémentaires
- Changer d’arborescence d’expression passée dans les méthodes LINQ
- Construire une arborescence d’expressions Expression(Of TDelegate) à l’aide des méthodes de fabrique à l’adresse Expression
- Ajouter des nœuds d’appel de méthode à une arborescence d’expressions IQueryable
- Construire des chaînes et utiliser la bibliothèque LINQ dynamique
Utiliser l’état d’exécution à partir de l’arborescence d’expressions
En supposant que le fournisseur LINQ le prend en charge, le moyen le plus simple d’interroger dynamiquement consiste à référencer l’état d’exécution directement dans la requête via une variable fermée, comme length
dans l’exemple de code suivant :
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
L’arborescence d’expressions internes , et donc la requête, n’a pas été modifiée ; la requête retourne des valeurs différentes uniquement parce que la valeur d’a length
été modifiée.
Appeler des méthodes LINQ supplémentaires
En règle générale, les méthodes LINQ intégrées à Queryable effectuent deux étapes :
- Encapsulez l’arborescence d’expressions actuelle dans un MethodCallExpression représentant l’appel de méthode.
- Transmettez l’arborescence d’expressions encapsulée au fournisseur, soit pour renvoyer une valeur via la méthode du IQueryProvider.Execute fournisseur, soit pour renvoyer un objet de requête traduit via la IQueryProvider.CreateQuery méthode.
Vous pouvez remplacer la requête d’origine par le résultat d’une méthode IQueryable(Of T)-returning pour obtenir une nouvelle requête. Vous pouvez effectuer cette opération de manière conditionnelle en fonction de l’état d’exécution, comme dans l’exemple suivant :
' Dim sortByLength As Boolean = ...
Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)
Changer d’arborescence d’expression passée dans les méthodes LINQ
Vous pouvez transmettre différentes expressions aux méthodes LINQ, en fonction de l’état d’exécution :
' 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)
Vous pouvez également composer les différentes sous-expressions à l’aide d’une bibliothèque tierce telle que le PredicateBuilder de 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)
Construire des arborescences d’expression et des requêtes avec des méthodes de fabrique
Dans tous les exemples jusqu’à ce stade, nous avons connu le type d’élément au moment de la compilation,String
et donc le type de la requête.IQueryable(Of String)
Vous devrez peut-être ajouter des composants à une requête de n’importe quel type d’élément ou ajouter différents composants en fonction du type d’élément. Vous pouvez créer des arborescences d’expressions à partir de la base, à l’aide des méthodes de fabrique à l’adresse System.Linq.Expressions.Expression, et ainsi adapter l’expression au moment de l’exécution à un type d’élément spécifique.
Construction d’une expression(Of TDelegate)
Lorsque vous construisez une expression pour passer à l’une des méthodes LINQ, vous construisez en fait une instance d’Expression(Of TDelegate), où TDelegate
se trouve un type délégué tel que Func(Of String, Boolean)
, Action
ou un type délégué personnalisé.
Expression(Of TDelegate) hérite de LambdaExpression, qui représente une expression lambda complète comme suit :
Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")
Un LambdaExpression a deux composants :
- Liste de paramètres ,
(x As String)
représentée par la Parameters propriété. - Un corps—
x.StartsWith("a")
—représenté par la propriété Body.
Les étapes de base de la construction d’une expression(of TDelegate) sont les suivantes :
Définissez des ParameterExpression objets pour chacun des paramètres (si applicable) dans l’expression lambda, en utilisant la méthode de fabrication Parameter.
Dim x As ParameterExpression = Parameter(GetType(String), "x")
Construire le corps de votre LambdaExpression en utilisant la ou les ParameterExpressions que vous avez définies et les méthodes de fabrique sur Expression. Par exemple, une expression représentant
x.StartsWith("a")
peut être construite comme suit :Dim body As Expression = [Call]( x, GetType(String).GetMethod("StartsWith", {GetType(String)}), Constant("a") )
Encapsulez les paramètres et le corps dans une Expression(Of TDelegate) typée au moment de la compilation à l’aide de la surcharge de méthode de fabrique Lambda appropriée :
Dim expr As Expression(Of Func(Of String, Boolean)) = Lambda(Of Func(Of String, Boolean))(body, x)
Les sections suivantes décrivent un scénario dans lequel vous souhaiterez peut-être construire une expression(Of TDelegate) pour passer à une méthode LINQ et fournir un exemple complet de la façon de procéder à l’aide des méthodes de fabrique.
Scénario
Supposons que vous avez plusieurs types d’entités :
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
Pour l’un de ces types d’entités, vous souhaitez filtrer et renvoyer uniquement les entités qui ont un texte donné à l’intérieur de l’un de leurs string
champs. Pour Person
, vous souhaiterez rechercher les propriétés FirstName
et LastName
.
' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))
Mais pour Car
, vous souhaitez rechercher uniquement la Model
propriété :
' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
Where(Function(x) x.Model.Contains(term))
Bien que vous puissiez écrire une fonction personnalisée pour IQueryable(Of Person)
et une autre pour IQueryable(Of Car)
, la fonction suivante ajoute ce filtrage à n’importe quelle requête existante, quel que soit le type d’élément spécifique.
Exemple :
' 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
Étant donné que la fonction TextFilter
prend et retourne un IQueryable(Of T) (et pas seulement un IQueryable), vous pouvez ajouter d'autres éléments de requête typés lors de la compilation après le filtre de texte.
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)
Ajouter des nœuds d’appel de méthode à l’arborescence IQueryabled’expressions
Si vous avez un IQueryable au lieu de IQueryable(Of T), vous ne pouvez pas appeler directement les méthodes génériques de LINQ. Une alternative consiste à créer l’arborescence d’expression interne comme ci-dessus et à utiliser la réflexion pour appeler la méthode LINQ appropriée lors du passage de l’arborescence d’expression.
Vous pouvez également dupliquer la fonctionnalité de la méthode LINQ en encapsulant l’arborescence entière dans un MethodCallExpression appel à la méthode 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
Dans ce cas, vous n’avez pas d’espace réservé générique T
au moment de la compilation. Vous utiliserez donc la surcharge Lambda qui ne nécessite pas d’informations de type temps de compilation et qui produit une LambdaExpression au lieu d’une Expression(Of TDelegate).
Bibliothèque LINQ dynamique
La construction d’arborescences d’expression à l’aide de méthodes de fabrique est relativement complexe, il est plus facile de composer des chaînes. La bibliothèque LINQ dynamique expose un ensemble de méthodes d’extension correspondant IQueryable aux méthodes LINQ standard à l’adresse Queryable, et qui accepte des chaînes dans une syntaxe spéciale au lieu d’arborescences d’expressions. La bibliothèque génère l’arborescence d’expressions appropriée à partir de la chaîne et peut retourner la traduction résultante IQueryable.
Par exemple, l’exemple précédent (y compris la construction de l’arborescence d’expressions) peut être réécrit comme suit :
' 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