Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Considere o código que define um IQueryable ou um IQueryable(Of T) em relação a uma fonte de dados:
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)
Sempre que você executar esse código, a mesma consulta exata será executada. Isso geralmente não é muito útil, pois talvez você queira que seu código execute consultas diferentes, dependendo das condições em tempo de execução. Este artigo descreve como você pode executar uma consulta diferente com base no estado de runtime.
IQueryable/IQueryable(Of T) e árvores de expressão
Fundamentalmente, um IQueryable tem dois componentes:
- Expression— uma representação independente da linguagem e da fonte de dados dos componentes da consulta atual, na forma de uma árvore de expressão.
- Provider— uma instância de um provedor LINQ, que sabe como materializar a consulta atual em um valor ou conjunto de valores.
No contexto da consulta dinâmica, o provedor geralmente permanecerá o mesmo; a árvore de expressão da consulta será diferente da consulta para a consulta.
Árvores de expressão são imutáveis; se você quiser uma árvore de expressão diferente e, portanto, uma consulta diferente, será necessário traduzir a árvore de expressão existente para uma nova e, portanto, para uma nova IQueryable.
As seções a seguir descrevem técnicas específicas para consultar de forma diferente em resposta ao estado de runtime:
- Usar o estado de runtime de dentro da árvore de expressão
- Chamar métodos LINQ adicionais
- Variar a árvore de expressão passada para os métodos LINQ
- Construir uma árvore de expressão Expression(Of TDelegate) usando os métodos de fábrica em Expression
- Adicionar nós de chamada de método a uma árvore de expressão de IQueryable
- Construir cadeias de caracteres e usar a biblioteca LINQ Dinâmica
Usar o estado de runtime de dentro da árvore de expressão
Supondo que o provedor LINQ dê suporte a ele, a maneira mais simples de consultar dinamicamente é referenciar o estado de runtime diretamente na consulta por meio de uma variável fechada, como length no exemplo de código a seguir:
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
A árvore de expressão interna — e, portanto, a consulta — não foi modificada; a consulta retorna valores diferentes apenas porque o valor foi length alterado.
Chamar métodos LINQ adicionais
Em geral, os métodos LINQ embutidos executam Queryable duas etapas:
- Empacote a árvore de expressão atual em uma MethodCallExpression representando a chamada de método.
- Passe a árvore de expressão encapsulada de volta para o provedor, seja para retornar um valor por meio do método do IQueryProvider.Execute provedor; ou para retornar um objeto de consulta traduzido por meio do IQueryProvider.CreateQuery método.
Você pode substituir a consulta original pelo resultado de um método IQueryable(Of T)-returning, para obter uma nova consulta. Você pode fazer isso condicionalmente com base no estado de runtime, como no exemplo a seguir:
' Dim sortByLength As Boolean = ...
Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)
Variar a árvore de expressão passada para os métodos LINQ
Você pode passar expressões diferentes para os métodos LINQ, dependendo do estado de runtime:
' 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)
Talvez você também queira compor as várias subexpressões usando uma biblioteca de terceiros, como o PredicateBuilder do 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)
Construir árvores de expressão e consultas usando métodos de fábrica
Em todos os exemplos até este ponto, conhecemos o tipo de elemento em tempo de compilação —String e, portanto, o tipo da consulta—IQueryable(Of String). Talvez seja necessário adicionar componentes a uma consulta de qualquer tipo de elemento ou adicionar componentes diferentes dependendo do tipo de elemento. Você pode criar árvores de expressão do zero, usando os métodos de fábrica em System.Linq.Expressions.Expression, e assim adaptar a expressão em tempo de execução a um tipo de elemento específico.
Construindo uma Expression(Of TDelegate)
Quando você constrói uma expressão para passar para um dos métodos LINQ, você está realmente construindo uma instância de Expression(Of TDelegate), onde TDelegate há algum tipo de delegado, como Func(Of String, Boolean), Actionou um tipo de delegado personalizado.
Expression(Of TDelegate) herda de LambdaExpression, que representa uma expressão lambda completa como a seguinte:
Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")
A LambdaExpression tem dois componentes:
- Uma lista de parâmetros —
(x As String)representada pela Parameters propriedade. - Um corpo —
x.StartsWith("a")— representado pela propriedade Body.
As etapas básicas na construção de uma Expressão(De TDelegate) são as seguintes:
Defina ParameterExpression objetos para cada um dos parâmetros (se houver) na expressão lambda, usando o Parameter método de fábrica.
Dim x As ParameterExpression = Parameter(GetType(String), "x")Construa o corpo do seu LambdaExpression, usando os ParameterExpression que você definiu e os métodos de fábrica em Expression. Por exemplo, uma expressão que
x.StartsWith("a")representa poderia ser construída desta forma:Dim body As Expression = [Call]( x, GetType(String).GetMethod("StartsWith", {GetType(String)}), Constant("a") )Empacote os parâmetros e o corpo em um Expression(Of TDelegate) do tipo tempo de compilação, usando a sobrecarga do método de fábrica Lambda apropriada:
Dim expr As Expression(Of Func(Of String, Boolean)) = Lambda(Of Func(Of String, Boolean))(body, x)
As seções a seguir descrevem um cenário no qual você pode querer construir uma Expressão(Of TDelegate) para passar para um método LINQ e fornecer um exemplo completo de como fazer isso usando os métodos de fábrica.
Cenário
Digamos que você tenha vários tipos de entidade:
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
Para qualquer um desses tipos de entidade, você deseja filtrar e retornar somente as entidades que têm um determinado texto dentro de um de seus string campos. Para Person, você deve pesquisar as propriedades FirstName e LastName.
' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))
Já para Car, você deve pesquisar apenas a propriedade Model:
' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
Where(Function(x) x.Model.Contains(term))
Embora você possa escrever uma função personalizada para IQueryable(Of Person) e outra para IQueryable(Of Car), a função a seguir adiciona essa filtragem a qualquer consulta existente, independentemente do tipo de elemento específico.
Exemplo
' 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
Como a TextFilter função usa e retorna um IQueryable(Of T) (e não apenas um IQueryable), você pode adicionar mais elementos de consulta com tipo de tempo de compilação após o filtro de texto.
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)
Adicionar nós de chamada de método à árvore de expressão do IQueryable
Se você tiver um IQueryable em vez de um IQueryable(Of T), não poderá chamar diretamente os métodos LINQ genéricos. Uma alternativa é criar a árvore de expressão interna conforme acima e usar a reflexão para invocar o método LINQ apropriado e passar como parâmetro a árvore de expressão.
Você também pode duplicar a funcionalidade do método LINQ encapsulando a árvore inteira em uma MethodCallExpression que representa uma chamada para o método 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
Nesse caso, você não tem um espaço reservado genérico T em tempo de compilação, portanto, você usará a sobrecarga Lambda que não requer informações do tipo tempo de compilação e que produz um LambdaExpression em vez de um Expression(Of TDelegate).
A biblioteca LINQ dinâmica
Construir árvores de expressão usando métodos de fábrica é relativamente complexo; é mais fácil compor cadeias de caracteres. A biblioteca LINQ Dinâmica expõe um conjunto de métodos de extensão em IQueryable correspondentes aos métodos LINQ padrão em Queryable, e que aceitam cadeias de caracteres em uma sintaxe especial em vez de árvores de expressão. A biblioteca gera a árvore de expressão apropriada da cadeia de caracteres e pode retornar o resultado traduzido IQueryable.
Por exemplo, o exemplo anterior (incluindo a construção da árvore de expressão) pode ser reescrito da seguinte maneira:
' 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
Consulte também
- árvores de expressão (Visual Basic)
- Como executar árvores de expressão (Visual Basic)