Aracılığıyla paylaş


Çalışma zamanı durumuna göre sorgulama (Visual Basic)

Bir veri kaynağına karşı bir IQueryable veya bir IQueryable(Of T) tanımlayan kodu göz önünde bulundurun:

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)

Bu kodu her çalıştırdığınızda tam olarak aynı sorgu yürütülür. Kodunuzun çalışma zamanındaki koşullara bağlı olarak farklı sorgular yürütmesini isteyebileceğiniz için bu genellikle çok kullanışlı değildir. Bu makalede çalışma zamanı durumuna göre farklı bir sorguyu nasıl yürütebileceğiniz açıklanmaktadır.

IQueryable / IQueryable(Of T) ve ifade ağaçları

Temelde, bir IQueryable iki bileşene sahiptir:

  • Expression—geçerli sorgu bileşenlerinin ifade ağacı biçiminde dil ve veri kaynağından bağımsız bir gösterimi.
  • Provider—geçerli sorguyu bir değere veya değer kümesine dönüştürmeyi bilen bir LINQ sağlayıcısı örneği.

Dinamik sorgulama bağlamında sağlayıcı genellikle aynı kalır; sorgunun ifade ağacı sorgudan sorguya farklılık gösterir.

İfade ağaçları sabittir; farklı bir ifade ağacı ve dolayısıyla farklı bir sorgu istiyorsanız, var olan ifade ağacını yeni bir ifade ağacına ve dolayısıyla yeni IQueryablebir ifade ağacına çevirmeniz gerekir.

Aşağıdaki bölümlerde çalışma zamanı durumuna yanıt olarak farklı sorgulamaya yönelik belirli teknikler açıklanmaktadır:

  • İfade ağacının içinden çalışma zamanı durumunu kullanma
  • Ek LINQ yöntemlerini çağırma
  • LINQ yöntemlerine geçirilen ifade ağacını değiştirin
  • konumundaki fabrika yöntemlerini kullanarak bir Expression(Of TDelegate) ifade ağacı oluşturun Expression
  • ' IQueryablenin ifade ağacına yöntem çağrı düğümleri ekleme
  • Dizeler oluşturun ve Dinamik LINQ kitaplığını kullanın

İfade ağacının içinden çalışma zamanı durumunu kullanma

LINQ sağlayıcısının bunu desteklediğini varsayarsak, dinamik olarak sorgulamanın en basit yolu, aşağıdaki kod örneğinde olduğu gibi length kapalı bir değişken aracılığıyla doğrudan sorgudaki çalışma zamanı durumuna başvurmaktır:

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

İç ifade ağacı ve dolayısıyla sorgu değiştirilmemiştir; sorgu, yalnızca değeri değiştirildiğinden farklı değerler length döndürür.

Ek LINQ yöntemlerini çağırma

Genel olarak, içindeki Queryable iki adımda gerçekleştirilir.

  • Geçerli ifade ağacını, yöntem çağrısını temsil eden bir MethodCallExpression içinde sarmalayın.
  • Sarmalanmış ifade ağacını sağlayıcıya geri iletin; sağlayıcının IQueryProvider.Execute yöntemi aracılığıyla bir değer döndürmek veya IQueryProvider.CreateQuery yöntemi aracılığıyla çevrilmiş bir sorgu nesnesi döndürmek için.

Yeni bir sorgu almak için özgün sorguyu IQueryable(Of T) dönüş yönteminin sonucuyla değiştirebilirsiniz. Bunu, aşağıdaki örnekte olduğu gibi çalışma zamanı durumuna göre koşullu olarak yapabilirsiniz:

' Dim sortByLength As Boolean  = ...

Dim qry = companyNamesSource
If sortByLength Then qry = qry.OrderBy(Function(x) x.Length)

LINQ yöntemlerine geçirilen ifade ağacını değiştirin

Çalışma zamanı durumuna bağlı olarak LINQ yöntemlerine farklı ifadeler geçirebilirsiniz:

' 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)

LinqKit'inPredicateBuilder'ı gibi bir üçüncü taraf kitaplığı kullanarak çeşitli alt ifadeleri de oluşturmak isteyebilirsiniz:

' 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)

Fabrika yöntemlerini kullanarak ifade ağaçları ve sorguları oluşturma

Bu noktaya kadar olan tüm örneklerde, derleme zamanındaString öğe türünü ve dolayısıyla sorgununIQueryable(Of String) türünü biliyoruz. Herhangi bir öğe türündeki bir sorguya bileşen eklemeniz veya öğe türüne bağlı olarak farklı bileşenler eklemeniz gerekebilir. Fabrika yöntemlerini System.Linq.Expressions.Expression konumunda kullanarak ifade ağaçlarını sıfırdan oluşturabilir ve ifadeyi çalışma zamanı sırasında belirli bir öğe türüne uyarlayabilirsiniz.

İfade Oluşturma(TDelegate'in)

LINQ yöntemlerinden birine geçirmek için bir ifade oluşturduğunuzda, aslında bir expression (Of TDelegate) örneği oluşturursunuz; burada TDelegate , Func(Of String, Boolean)veya özel bir temsilci türü gibi Actionbir temsilci türüdür.

Expression(Of TDelegate), aşağıdaki gibi eksiksiz bir lambda ifadesini temsil eden LambdaExpression öğesinden devralır:

Dim expr As Expression(Of Func(Of String, Boolean)) = Function(x As String) x.StartsWith("a")

A'nın LambdaExpression iki bileşeni vardır:

  • (x As String) özelliği tarafından temsil edilen bir parametre listesi—Parameters.
  • Bir gövdex.StartsWith("a"), Body özelliğiyle temsil edilir.

İfade (Of TDelegate) oluşturmanın temel adımları aşağıdaki gibidir:

  • Factory yöntemini kullanarak ParameterExpression lambda ifadesindeki parametrelerin (varsa) her biri için nesneleri tanımlayınParameter.

    Dim x As ParameterExpression = Parameter(GetType(String), "x")
    
  • Tanımladığınız LambdaExpression öğelerini ve ParameterExpression'deki fabrika yöntemlerini kullanarak Expression'nin gövdesini oluşturun. Örneğin, x.StartsWith("a") ifadesini temsil eden bir yapı şu şekilde oluşturulabilir:

    Dim body As Expression = [Call](
        x,
        GetType(String).GetMethod("StartsWith", {GetType(String)}),
        Constant("a")
    )
    
  • Parametreleri ve gövdeyi, uygun fabrika metot overload'ını kullanarak derleme zamanı tipinde bir Expression(Of TDelegate) içine sarın.

    Dim expr As Expression(Of Func(Of String, Boolean)) =
        Lambda(Of Func(Of String, Boolean))(body, x)
    

Aşağıdaki bölümlerde, bir LINQ yöntemine geçirmek için bir expression (Of TDelegate) oluşturmak isteyebileceğiniz bir senaryo açıklanmaktadır ve fabrika yöntemlerini kullanarak bunun nasıl yapıldığını gösteren eksiksiz bir örnek sağlanmaktadır.

Senaryo

Birden çok varlık türünüz olduğunu varsayalım:

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

Bu varlık türlerinden herhangi biri için, yalnızca alanlarından birinde string belirli bir metni olan varlıkları filtrelemek ve döndürmek istiyorsunuz. Person için, FirstName ve LastName özelliklerinde arama yapmak istersiniz:

' Dim term = ...
Dim personsQry = (New List(Of Person)).AsQueryable.
    Where(Function(x) x.FirstName.Contains(term) OrElse x.LastName.Contains(term))

Ancak Car için, yalnızca Model özelliğini aramak istersiniz:

' Dim term = ...
Dim carsQry = (New List(Of Car)).AsQueryable.
    Where(Function(x) x.Model.Contains(term))

için IQueryable(Of Person) bir özel işlev ve için IQueryable(Of Car)başka bir özel işlev yazabilirsiniz ancak aşağıdaki işlev, belirli öğe türünden bağımsız olarak bu filtrelemeyi mevcut sorgulara ekler.

Örnek

' 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 işlevi bir IQueryable(Of T) aldığından ve döndürdüğünden (yalnızca bir IQueryable değil), metin filtresinden sonra derleme zamanında türüne göre daha fazla sorgu öğesi ekleyebilirsiniz.

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)

IQueryable'nin ifade ağacına yöntem çağrı düğümleri ekleyin

IQueryable yerine bir varsa, genel LINQ yöntemlerini doğrudan çağıramazsınız. Bir alternatif, yukarıdaki gibi iç ifade ağacını oluşturmak ve ifade ağacını geçirirken uygun LINQ yöntemini çağırmak için yansımayı kullanmaktır.

LINQ yönteminin işlevselliğini, LINQ yöntemine yapılan çağrıyı temsil eden bir MethodCallExpression içine tüm ağacı alarak da çoğaltabilirsiniz.

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

Bu durumda derleme zamanı T genel yer tutucunuz olmadığından, derleme zamanı tür bilgisi gerektirmeyen ve bir Lambda yerine LambdaExpression üreten aşırı yüklemeyi kullanırsınız.

Dinamik LINQ kitaplığı

fabrika yöntemlerini kullanarak ifade ağaçları oluşturmak nispeten karmaşıktır; dize oluşturmak daha kolaydır. Dinamik LINQ kitaplığı, üzerindeki IQueryable standart LINQ yöntemlerine karşılık gelen ve dizeleri ifade ağaçları yerine Queryable kabul eden bir uzantı yöntemleri kümesini kullanıma sunar. Kitaplık, dizeden uygun ifade ağacını oluşturur ve dönüştürülmüş sonucu IQueryable döndürebilir.

Örneğin, önceki örnek (ifade ağacı yapısı dahil) aşağıdaki gibi yeniden yazılabilir:

' 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

Ayrıca bakınız