简体   繁体   English

追踪Linq表达式评估

[英]Tracing Linq expression evaluation

I'm wondering if it's possible to write a "passthrough" extension method for IQueryable which would write a debugstring whenever the queryable is evaluated, in other words, the debug print should be a side-effect of evaluation. 我想知道是否有可能为IQueryable编写一个“passthrough”扩展方法,它会在评估可查询时编写调试字符串,换句话说,调试打印应该是评估的副作用。

Something like: 就像是:

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

When I construct a linq query and pass it as a data source to some object, I'd like to know when and how often does the object evaluate my query. 当我构造一个linq查询并将其作为数据源传递给某个对象时,我想知道该对象何时以及多久评估一次我的查询。 I suppose it can be achieved in other ways, like for example wrapping IEnumerable.GetEnumerator, but I'd like to do it generically for any linq query. 我想它可以通过其他方式实现,例如包装IEnumerable.GetEnumerator,但我想对任何linq查询一般这样做。

I've done something similar, but more complex (because it also manipulates the expressions as it processes them). 我做了类似的事情,但更复杂(因为它在处理表达时也操纵表达式)。 In order to accomplish it, I created a wrapper class that implemented IQueryable and contained a reference to the thing I actually wanted to query. 为了实现它,我创建了一个实现IQueryable的包装类,并包含对我实际想要查询的东西的引用。 I made it pass all interface members through to the referenced object except for the Provider property, which returned a reference to another class I created that inherited from IQueryProvider. 我让它将所有接口成员传递给引用的对象,但Provider属性除外,它返回对我创建的继承自IQueryProvider的另一个类的引用。 IQueryProvider has the methods that get called whenever a query is constructed or executed. IQueryProvider具有在构造或执行查询时调用的方法。 So you could do something like this if you don't mind being forced to always query your wrapper object(s) instead of the original object(s). 因此,如果您不介意被迫总是查询您的包装器对象而不是原始对象,那么您可以执行此类操作。

You should also be aware, if you're using LINQ-to-SQL, there's a Log property on the DataContext that you can use to route lots of debug information wherever you want. 您还应该知道,如果您正在使用LINQ-to-SQL,DataContext上有一个Log属性,您可以使用它来在任何地方路由大量调试信息。

Sample code: 示例代码:

Make your own IQueryable to control the QueryProvider that gets returned. 创建自己的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

Make a custom query provider to control the expression tree that gets generated. 创建自定义查询提供程序以控制生成的表达式树。

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

Then extend your derived DataContext by providing wrapped queryables: 然后通过提供包装的可查询来扩展派生的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

Define a new extension method: 定义一个新的扩展方法:

    public static IEnumerable<T> Trace<T>(this IEnumerable<T> input, 
                                            string format, 
                                            params object[] data) 
    {
        if (input == null)
            throw new ArgumentNullException("input");

        return TraceImpl(input, format, data);
    }

    private static IEnumerable<T> TraceImpl<T>(IEnumerable<T> input, 
                                                string format, 
                                                params object[] data) 
    {
        System.Diagnostics.Trace.WriteLine(string.Format(format, data));

        foreach (T element in input)
            yield return element;
    }

This should print a trace everytime you iterate over it. 每次迭代时都应该打印一条跟踪。 Thanks to Jon Skeet for the inspiration. 感谢Jon Skeet的灵感。

Personally, I would replace format and data with an Action delegate, so you could perform any task (not relevant to the collection) instead of simply tracing. 就个人而言,我会用Action委托替换formatdata ,因此您可以执行任何任务(与集合无关),而不仅仅是跟踪。

Edit: I have the feeling that this might only work for linq-to-objects. 编辑:我觉得这可能仅适用于linq到对象。 For IQueryable<T> , you'd have to adjust the expression tree parsers, which you don't have access to. 对于IQueryable<T> ,您必须调整您无权访问的表达式树解析器。 Sorry :-/ 对不起: - /

I believe you can stick with standard tracing features for LINQ to SQL. 我相信你可以坚持使用LINQ to SQL的标准跟踪功能。 Not only you will be able to know when the query executes, but you'll also know the query which can be just as handy. 您不仅可以知道查询执行的时间,还可以了解查询,这可以很方便。

For that, DataContext has Log property which allows you to trace its SQL output. 为此, DataContext具有Log属性,允许您跟踪其SQL输出。

In LINQ to Entities, ObjectQuery<T> exposes ToTraceString method which you can use for tracing in a fashion you described . 在LINQ to Entities中, ObjectQuery<T>公开了ToTraceString方法, 您可以使用该方法以您描述的方式进行跟踪

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM