跟踪 Linq 表达式评估

Bor*_* B. 5 c# linq

我想知道是否有可能为 IQueryable 编写一个“直通”扩展方法,它会在评估可查询时编写调试字符串,换句话说,调试打印应该是评估的副作用。

就像是:

var qr = SomeSource.Where(...).OrderBy(...).Trace("SomeSource evaluated at {0}", DateTime.Now)
var qr2 = qr.Where(...);
Run Code Online (Sandbox Code Playgroud)

当我构造一个 linq 查询并将其作为数据源传递给某个对象时,我想知道该对象何时以及多久评估一次我的查询。我想它可以通过其他方式实现,例如包装 IEnumerable.GetEnumerator,但我想对任何 linq 查询通用。

Blu*_*kMN 5

我做了类似的事情,但更复杂(因为它在处理表达式时也会操纵表达式)。为了实现它,我创建了一个实现 IQueryable 的包装类,并包含对我实际想要查询的内容的引用。我让它将所有接口成员传递给引用的对象,但 Provider 属性除外,该属性返回对我创建的从 IQueryProvider 继承的另一个类的引用。IQueryProvider 具有在构造或执行查询时调用的方法。因此,如果您不介意被迫始终查询包装器对象而不是原始对象,则可以执行类似的操作。

您还应该注意,如果您使用 LINQ-to-SQL,则 DataContext 上有一个 Log 属性,您可以使用它来将大量调试信息路由到您想要的任何位置。

示例代码:

制作您自己的 IQueryable 来控制返回的 QueryProvider。

Public Class MyQueryable(Of TableType)
   Implements IQueryable(Of TableType)

   Private innerQueryable As IQueryable(Of TableType)
   Private myProvider As MyQueryProvider = Nothing

   Public Sub New(ByVal innerQueryable As IQueryable(Of TableType))
      Me.innerQueryable = innerQueryable
   End Sub

   Public Function GetEnumerator() As System.Collections.Generic.IEnumerator(Of TableType) Implements System.Collections.Generic.IEnumerable(Of TableType).GetEnumerator
      Return innerQueryable.GetEnumerator()
   End Function

   Public Function GetEnumerator1() As System.Collections.IEnumerator Implements System.Collections.IEnumerable.GetEnumerator
      Return innerQueryable.GetEnumerator()
   End Function

   Public ReadOnly Property ElementType() As System.Type Implements System.Linq.IQueryable.ElementType
      Get
         Return innerQueryable.ElementType
      End Get
   End Property

   Public ReadOnly Property Expression() As System.Linq.Expressions.Expression Implements System.Linq.IQueryable.Expression
      Get
         Return innerQueryable.Expression
      End Get
   End Property

   Public ReadOnly Property Provider() As System.Linq.IQueryProvider Implements System.Linq.IQueryable.Provider
      Get
         If myProvider Is Nothing Then myProvider = New MyQueryProvider(innerQueryable.Provider)
         Return myProvider
      End Get
   End Property

   Friend ReadOnly Property innerTable() As System.Data.Linq.ITable
      Get
         If TypeOf innerQueryable Is System.Data.Linq.ITable Then
            Return DirectCast(innerQueryable, System.Data.Linq.ITable)
         End If
         Throw New InvalidOperationException("Attempted to treat a MyQueryable as a table that is not a table")
      End Get
   End Property
End Class
Run Code Online (Sandbox Code Playgroud)

创建自定义查询提供程序来控制生成的表达式树。

Public Class MyQueryProvider
   Implements IQueryProvider

   Private innerProvider As IQueryProvider

   Public Sub New(ByVal innerProvider As IQueryProvider)
      Me.innerProvider = innerProvider
   End Sub
   Public Function CreateQuery(ByVal expression As System.Linq.Expressions.Expression) As System.Linq.IQueryable Implements System.Linq.IQueryProvider.CreateQuery
      Return innerProvider.CreateQuery(expression)
   End Function

   Public Function CreateQuery1(Of TElement)(ByVal expression As System.Linq.Expressions.Expression) As System.Linq.IQueryable(Of TElement) Implements System.Linq.IQueryProvider.CreateQuery
      Dim newQuery = innerProvider.CreateQuery(Of TElement)(ConvertExpression(expression))
      If TypeOf newQuery Is IOrderedQueryable(Of TElement) Then
         Return New MyOrderedQueryable(Of TElement)(DirectCast(newQuery, IOrderedQueryable(Of TElement)))
      Else
         Return New MyQueryable(Of TElement)(newQuery)
      End If
   End Function

   Public Shared Function ConvertExpression(ByVal expression As Expression) As Expression
      If TypeOf expression Is MethodCallExpression Then
         Dim mexp = DirectCast(expression, MethodCallExpression)
         Return Expressions.MethodCallExpression.Call(ConvertExpression(mexp.Object), _
            mexp.Method, (From row In mexp.Arguments Select ConvertExpression(row)).ToArray())
      ElseIf TypeOf expression Is BinaryExpression Then
         Dim bexp As BinaryExpression = DirectCast(expression, BinaryExpression)
         Dim memberInfo As NestedMember = Nothing
         Dim constExp As Expression = Nothing
         Dim memberOnLeft As Boolean
         Dim doConvert = True
         '' [etc... lots of code to generate a manipulated expression tree
      ElseIf TypeOf expression Is LambdaExpression Then
         Dim lexp = DirectCast(expression, LambdaExpression)
         Return LambdaExpression.Lambda( _
            ConvertExpression(lexp.Body), (From row In lexp.Parameters Select _
            DirectCast(ConvertExpression(row), ParameterExpression)).ToArray())
      ElseIf TypeOf expression Is ConditionalExpression Then
         Dim cexp = DirectCast(expression, ConditionalExpression)
         Return ConditionalExpression.Condition(ConvertExpression(cexp.Test), _
                                                ConvertExpression(cexp.IfTrue), _
                                                ConvertExpression(cexp.IfFalse))
      ElseIf TypeOf expression Is InvocationExpression Then
         Dim iexp = DirectCast(expression, InvocationExpression)
         Return InvocationExpression.Invoke( _
            ConvertExpression(iexp.Expression), (From row In iexp.Arguments _
            Select ConvertExpression(row)).ToArray())
      ElseIf TypeOf expression Is MemberExpression Then
         '' [etc... lots of code to generate a manipulated expression tree
      ElseIf TypeOf expression Is UnaryExpression Then
         '' [etc... lots of code to generate a manipulated expression tree
      Else
         Return expression
      End If
   End Function

   Public Function Execute(ByVal expression As System.Linq.Expressions.Expression) As Object Implements System.Linq.IQueryProvider.Execute
      Return innerProvider.Execute(expression)
   End Function

   Public Function Execute1(Of TResult)(ByVal expression As System.Linq.Expressions.Expression) As TResult Implements System.Linq.IQueryProvider.Execute
      Return innerProvider.Execute(Of TResult)(ConvertExpression(expression))
   End Function
End Class
Run Code Online (Sandbox Code Playgroud)

然后通过提供包装的可查询来扩展派生的 DataContext:

Partial Public Class MyDataContext
  Private myQueries As Dictionary(Of System.Type, Object) = New Dictionary(Of System.Type, Object)
  Public ReadOnly Property My_AccountCategories() As MyQueryable(Of AccountCategory)
     Get
        Dim result As Object = Nothing
        If (Me.myQueries.TryGetValue(GetType(AccountCategory), result) = false) Then
           result = New MyQueryable(Of AccountCategory)(Me.AccountCategories)
           Me.myQueries(GetType(AccountCategory)) = result
        End If
        Return CType(result,MyQueryable(Of AccountCategory))
     End Get
  End Property
  Public ReadOnly Property My_AccountSegmentations() As MyQueryable(Of AccountSegmentation)
     Get
        Dim result As Object = Nothing
        If (Me.myQueries.TryGetValue(GetType(AccountSegmentation), result) = false) Then
           result = New MyQueryable(Of AccountSegmentation)(Me.AccountSegmentations)
           Me.myQueries(GetType(AccountSegmentation)) = result
        End If
        Return CType(result,MyQueryable(Of AccountSegmentation))
     End Get
  End Property
End Class
Run Code Online (Sandbox Code Playgroud)

  • 我也在做的事情就是将东西注入到表达式树中。但我认为除了使用包装器之外没有其他方法,因为您还需要将代码“注入”到队列中的某个地方,并且如果没有任何钩子来扩展现有过程,您别无选择但要坚持在前端,以确保你的代码有一定的控制权。 (2认同)