Catatan
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba masuk atau mengubah direktori.
Akses ke halaman ini memerlukan otorisasi. Anda dapat mencoba mengubah direktori.
Pertimbangkan kode yang mendefinisikan IQueryable atau IQueryable(Of T) terhadap sumber data:
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)
Setiap kali Anda menjalankan kode ini, kueri yang sama persis akan dijalankan. Ini sering tidak terlalu berguna, karena Anda mungkin ingin kode Anda menjalankan kueri yang berbeda tergantung pada kondisi pada waktu proses. Artikel ini menjelaskan cara menjalankan kueri yang berbeda berdasarkan status runtime.
IQueryable / IQueryable(Of T) dan pohon ekspresi
IQueryable pada dasarnya memiliki dua komponen:
- Expression—representasi bahasa dan sumber data-agnostik dari komponen kueri saat ini, dalam bentuk pohon ekspresi.
- Provider—sebuah contoh penyedia LINQ, yang memahami cara mewujudkan kueri yang sedang diproses menjadi nilai atau serangkaian nilai.
Dalam konteks kueri dinamis, penyedia biasanya akan tetap sama; pohon ekspresi kueri akan berbeda dari kueri ke kueri.
Pohon ekspresi tidak dapat diubah; jika Anda menginginkan pohon ekspresi yang berbeda—dan dengan demikian kueri yang berbeda—Anda harus menerjemahkan pohon ekspresi yang ada ke yang baru, dan dengan demikian ke yang baru IQueryable.
Bagian berikut ini menjelaskan teknik khusus untuk mengkueri secara berbeda sebagai respons terhadap status runtime:
- Gunakan status runtime di dalam pohon ekspresi
- Memanggil metode LINQ tambahan
- Memvariasikan pohon ekspresi yang diteruskan ke metode LINQ
- Bangun pohon ekspresi Ekspresi (Dari TDelegate) dengan menggunakan metode pabrik di Expression
- Menambahkan node pemanggilan metode ke pohon ekspresi IQueryable
- Buat string, dan gunakan pustaka Dynamic LINQ
Gunakan status runtime di dalam pohon ekspresi
Dengan asumsi penyedia LINQ mendukungnya, cara paling sederhana untuk mengkueri secara dinamis adalah dengan mereferensikan status runtime langsung dalam kueri melalui variabel tertutup, seperti length dalam contoh kode berikut:
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
Pohon ekspresi internal—dan dengan demikian kueri—belum dimodifikasi; kueri mengembalikan nilai yang berbeda hanya karena nilai length telah diubah.
Memanggil metode LINQ tambahan
Umumnya, metode LINQ bawaan melakukan Queryable dua langkah:
- Bungkus pohon ekspresi saat ini dalam MethodCallExpression yang mewakili panggilan metode.
- Teruskan pohon ekspresi yang dibungkus kembali ke penyedia, baik untuk mengembalikan nilai melalui metode IQueryProvider.Execute penyedia, atau untuk mengembalikan objek kueri yang telah diterjemahkan melalui metode IQueryProvider.CreateQuery.
Anda dapat mengganti kueri asli dengan hasil dari metode yang mengembalikan IQueryable(Of T), untuk mendapatkan kueri baru. Anda dapat melakukan ini secara kondisional berdasarkan status runtime, seperti dalam contoh berikut:
' Dim sortByLength As Boolean = ...
Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)
Memvariasikan pohon ekspresi yang diteruskan ke metode LINQ
Anda dapat meneruskan ekspresi yang berbeda ke metode LINQ, tergantung pada status 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)
Anda mungkin juga ingin menyusun berbagai subekspresi menggunakan pustaka pihak ketiga seperti PredicateBuilderLinqKit:
' 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)
Membuat pohon ekspresi dan kueri menggunakan metode fabrikasi
Dalam semua contoh hingga saat ini, kita telah mengetahui jenis elemen pada waktu kompilasi—String—dan dengan demikian jenis kueri—IQueryable(Of String). Anda mungkin perlu menambahkan komponen ke kueri jenis elemen apa pun, atau untuk menambahkan komponen yang berbeda tergantung pada jenis elemen. Anda dapat membuat pohon ekspresi dari awal, menggunakan metode bawaan di System.Linq.Expressions.Expression, dan dengan demikian menyesuaikan ekspresi saat runtime dengan jenis elemen tertentu.
Membangun Ekspresi (Dari TDelegate)
Saat Anda membuat ekspresi untuk meneruskan ke salah satu metode LINQ, Anda benar-benar membuat instans Ekspresi (Dari TDelegate), di mana TDelegate adalah beberapa jenis delegasi seperti Func(Of String, Boolean), , Actionatau jenis delegasi kustom.
Ekspresi(Dari TDelegate) mewarisi dari LambdaExpression, yang mewakili ekspresi lambda lengkap seperti berikut ini:
Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")
A LambdaExpression memiliki dua komponen:
- Daftar parameter—
(x As String)—diwakili oleh Parameters properti . - Sebuah elemen—
x.StartsWith("a")—diwakili oleh properti Body.
Langkah-langkah dasar dalam membangun Ekspresi (Dari TDelegate) adalah sebagai berikut:
Tentukan objek ParameterExpression untuk setiap parameter (jika ada) dalam ekspresi lambda, dengan menggunakan metode pabrikasi Parameter.
Dim x As ParameterExpression = Parameter(GetType(String), "x")Buat kerangka LambdaExpression Anda, menggunakan ParameterExpression(s) yang telah Anda tentukan, dan metode pabrik di Expression. Misalnya, ekspresi yang mewakili
x.StartsWith("a")dapat dibangun seperti ini:Dim body As Expression = [Call]( x, GetType(String).GetMethod("StartsWith", {GetType(String)}), Constant("a") )Bungkus parameter dan isi dalam Expression(Of TDelegate) yang bertipe waktu kompilasi, menggunakan kelebihan beban metode pabrik yang sesuai Lambda.
Dim expr As Expression(Of Func(Of String, Boolean)) = Lambda(Of Func(Of String, Boolean))(body, x)
Bagian berikut menjelaskan skenario di mana Anda mungkin ingin membuat Ekspresi (Dari TDelegate) untuk diteruskan ke metode LINQ, dan memberikan contoh lengkap tentang cara melakukannya menggunakan metode pabrik.
Skenario
Katakanlah Anda memiliki beberapa jenis entitas:
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
Untuk salah satu jenis entitas ini, Anda ingin memfilter dan mengembalikan hanya entitas yang memiliki teks tertentu di dalam salah satu bidangnya string . Untuk Person, Anda ingin mencari properti FirstName dan LastName.
' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))
Tetapi untuk Car, Anda hanya ingin mencari properti Model.
' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
Where(Function(x) x.Model.Contains(term))
Meskipun Anda dapat menulis satu fungsi kustom untuk IQueryable(Of Person) dan fungsi lainnya untuk IQueryable(Of Car), fungsi berikut menambahkan pemfilteran ini ke kueri yang ada, terlepas dari jenis elemen tertentu.
Contoh
' 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
TextFilter Karena fungsi ini mengambil dan mengembalikan IQueryable(Of T) (dan bukan hanya IQueryable), Anda dapat menambahkan elemen kueri yang bertipe waktu kompilasi lebih lanjut setelah filter teks.
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)
Tambahkan node panggilan metode ke IQueryable pohon ekspresi
Jika Anda memiliki IQueryable alih-alih IQueryable(Of T), Anda tidak dapat langsung memanggil metode LINQ generik. Salah satu alternatifnya adalah membangun pohon ekspresi dalam seperti di atas, dan menggunakan pantulan untuk memanggil metode LINQ yang sesuai saat melewati pohon ekspresi.
Anda juga dapat menduplikasi fungsionalitas metode LINQ, dengan membungkus seluruh pohon dalam MethodCallExpression yang mewakili panggilan ke metode 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
Dalam hal ini, Anda tidak memiliki tempat penampung generik waktu kompilasi T, sehingga Anda akan menggunakan Lambda overload yang tidak memerlukan informasi jenis waktu kompilasi, dan yang menghasilkan LambdaExpression, bukan Ekspresi(Of TDelegate).
Pustaka Dynamic LINQ
Membangun pohon ekspresi menggunakan metode pabrik relatif kompleks; lebih mudah untuk membuat string. Pustaka Dynamic LINQ menyediakan serangkaian metode ekstensi yang sesuai dengan metode LINQ standar dan menerima string dalam IQueryable di Queryable alih-alih pohon ekspresi. Pustaka menghasilkan pohon ekspresi yang sesuai dari string, dan dapat mengembalikan hasil yang diterjemahkan IQueryable.
Misalnya, contoh sebelumnya (termasuk konstruksi pohon ekspresi) dapat ditulis ulang sebagai berikut:
' 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