Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Si consideri il codice che definisce un oggetto IQueryable o un oggetto IQueryable(Of T) rispetto a un'origine dati:
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)
Ogni volta che si esegue questo codice, verrà eseguita la stessa query esatta. Questo non è spesso molto utile, perché è possibile che il codice esegua query diverse a seconda delle condizioni in fase di esecuzione. Questo articolo descrive come eseguire una query diversa in base allo stato di runtime.
IQueryable/IQueryable(Of T) e alberi delle espressioni
Fondamentalmente, un IQueryable ha due componenti:
- Expression— una rappresentazione indipendente dal linguaggio e dall'origine dati dei componenti della query corrente, sotto forma di albero delle espressioni.
- Provider: un'istanza di un provider LINQ, che sa come convertire la query corrente in un valore o un insieme di valori.
Nel contesto dell'esecuzione dinamica di query, il provider rimarrà in genere lo stesso; L'albero delle espressioni della query sarà diverso dalla query alla query.
Gli alberi delle espressioni sono immutabili; se si desidera un albero delle espressioni diverso, e quindi una query diversa, è necessario convertire l'albero delle espressioni esistente in uno nuovo, e dunque ottenere un nuovo IQueryable.
Le sezioni seguenti descrivono tecniche specifiche per l'esecuzione di query in modo diverso in risposta allo stato di runtime:
- Usare lo stato di runtime dall'interno dell'albero delle espressioni
- Chiamare metodi LINQ aggiuntivi
- Variare l'albero delle espressioni passato nei metodi LINQ
- Costruire un albero dell'espressione Expression(Of TDelegate) usando i metodi factory in Expression
- Aggiungere nodi di chiamata di metodo all'albero delle espressioni di un IQueryable.
- Costruire stringhe e usare la libreria LINQ dinamica
Usare lo stato di runtime dall'interno dell'albero delle espressioni
Supponendo che il provider LINQ lo supporti, il modo più semplice per eseguire query in modo dinamico consiste nel fare riferimento allo stato di runtime direttamente nella query tramite una variabile closed-over, ad esempio length nell'esempio di codice seguente:
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'albero delle espressioni interne, e quindi la query, non sono state modificate; la query restituisce valori diversi solo perché il valore di length è stato modificato.
Chiamare metodi LINQ aggiuntivi
In genere, i metodi LINQ predefiniti vengono Queryable eseguiti in due passaggi:
- Avvolgere l'albero di espressioni corrente in un MethodCallExpression che rappresenta la chiamata al metodo.
- Passare nuovamente l'albero delle espressioni di cui è stato eseguito il wrapping al provider, per restituire un valore tramite il metodo del provider IQueryProvider.Execute, oppure per restituire un oggetto query convertito tramite il metodo IQueryProvider.CreateQuery.
È possibile sostituire la query originale con il risultato di un metodo IQueryable(Of T)-returning, per ottenere una nuova query. È possibile eseguire questa operazione in modo condizionale in base allo stato di runtime, come nell'esempio seguente:
' Dim sortByLength As Boolean = ...
Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)
Variare l'albero delle espressioni passato nei metodi LINQ
È possibile passare espressioni diverse ai metodi LINQ, a seconda dello stato di 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)
È anche possibile comporre le varie sottoespressioni usando una libreria di terze parti, ad esempio PredicateBuilder di 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)
Costruire alberi delle espressioni e query usando i metodi factory
In tutti gli esempi fino a questo punto, è stato noto il tipo di elemento in fase di compilazione,String e quindi il tipo della queryIQueryable(Of String). Potrebbe essere necessario aggiungere componenti a una query di qualsiasi tipo di elemento o aggiungere componenti diversi a seconda del tipo di elemento. È possibile creare alberi delle espressioni da zero, usando i metodi factory in System.Linq.Expressions.Expression e quindi personalizzare l'espressione in fase di esecuzione per adattarla a un tipo di elemento specifico.
Costruzione di un'espressione (di TDelegate)
Quando si costruisce un'espressione da passare a uno dei metodi LINQ, si costruisce effettivamente un'istanza di Expression(Of TDelegate), dove TDelegate è un tipo delegato, ad Func(Of String, Boolean)esempio , Actiono un tipo delegato personalizzato.
Expression(Of TDelegate) eredita da LambdaExpression, che rappresenta un'espressione lambda completa simile alla seguente:
Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")
A LambdaExpression ha due componenti:
- Elenco di parametri,
(x As String)rappresentato dalla Parameters proprietà . - Un corpo—
x.StartsWith("a")—rappresentato dalla proprietà Body.
I passaggi di base per la creazione di un'espressione (Di TDelegate) sono i seguenti:
Definire ParameterExpression gli oggetti per ognuno dei parametri (se presenti) nell'espressione lambda, usando il Parameter metodo factory.
Dim x As ParameterExpression = Parameter(GetType(String), "x")Costruisci il corpo del LambdaExpression, utilizzando i ParameterExpression(s) che hai definito e i metodi di fabbrica in Expression. Ad esempio, un'espressione che rappresenta
x.StartsWith("a")può essere costruita come segue:Dim body As Expression = [Call]( x, GetType(String).GetMethod("StartsWith", {GetType(String)}), Constant("a") )Incorpora i parametri e il corpo in una Expression(Of TDelegate) tipizzata a tempo di compilazione, utilizzando il sovraccarico del metodo factory appropriato Lambda.
Dim expr As Expression(Of Func(Of String, Boolean)) = Lambda(Of Func(Of String, Boolean))(body, x)
Le sezioni seguenti descrivono uno scenario in cui è possibile creare un'espressione (Di TDelegate) da passare a un metodo LINQ e fornire un esempio completo di come eseguire questa operazione usando i metodi factory.
Sceneggiatura
Si supponga di avere più tipi di entità:
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
Per uno di questi tipi di entità, è necessario filtrare e restituire solo le entità con un testo specificato all'interno di uno dei relativi string campi. Per Person, è consigliabile eseguire ricerche nelle FirstName proprietà e LastName :
' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))
Ma per Car, si vuole cercare solo la Model proprietà :
' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
Where(Function(x) x.Model.Contains(term))
Anche se è possibile scrivere una funzione personalizzata per e un'altra per IQueryable(Of Person)IQueryable(Of Car), la funzione seguente aggiunge questo filtro a qualsiasi query esistente, indipendentemente dal tipo di elemento specifico.
Esempio
' 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
Poiché la funzione TextFilter accetta e restituisce un IQueryable(Of T) (e non solo un IQueryable), è possibile aggiungere ulteriori elementi di query tipizzati in fase di compilazione dopo il filtro di testo.
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)
Aggiungere i nodi di chiamata di metodo all'albero delle espressioni di IQueryable
Se si dispone di un oggetto IQueryable invece di IQueryable(Of T), non è possibile chiamare direttamente i metodi LINQ generici. Un'alternativa consiste nel compilare l'albero delle espressioni interne come sopra e usare la reflection per richiamare il metodo LINQ appropriato durante il passaggio dell'albero delle espressioni.
È anche possibile duplicare la funzionalità del metodo LINQ eseguendo il wrapping dell'intero albero in un MethodCallExpression oggetto che rappresenta una chiamata al metodo 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
In questo caso non si dispone di un segnaposto generico T in fase di compilazione, quindi si userà l'overload Lambda che non richiede informazioni sul tipo in fase di compilazione e che produce un oggetto LambdaExpression invece di un oggetto Expression(Of TDelegate).
Libreria LINQ dinamica
La costruzione di alberi delle espressioni utilizzando i metodi delle fabbriche è relativamente complessa; è più facile assemblare stringhe. La libreria LINQ dinamica espone un set di metodi IQueryable di estensione corrispondenti ai metodi LINQ standard in Queryablee che accettano stringhe in una sintassi speciale anziché alberi delle espressioni. La libreria genera l'albero delle espressioni appropriato dalla stringa e può restituire il risultato tradotto IQueryable.
Ad esempio, l'esempio precedente (inclusa la costruzione dell'albero delle espressioni) può essere riscritto come segue:
' 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