2011-02-23 43 views
4

我想知道是否有可能为IQueryable编写一个“passthrough”扩展方法,只要评估queryable就编写一个debugstring,换句话说,调试打印应该是评估的副作用。跟踪Linq表达式评估

喜欢的东西:

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

当我建立一个LINQ查询,并把它作为一个数据源到某个对象,我想知道何时以及如何往往并对象评价我的查询。我想这可以通过其他方式来实现,比如包装IEnumerable.GetEnumerator,但是我希望为任何linq查询做到这一点。

回答

3

我已经做了类似的事情,但更复杂(因为它也处理表达式,因为它处理它们)。为了完成它,我创建了一个包装类,它实现了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 

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

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 

然后通过提供包裹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 
+0

我希望的注入出头,到最表达式树的一种解决方案,恕我直言,你可以完成同样的事情包装IEnumerable.GetEnumerator,我相信最终会在评估查询时调用它,而且我真的很想避免包装。 – 2011-02-23 23:50:31

+2

注入事物到表达式树中也是我所做的。但我认为除了使用包装器以外,还有其他方法,因为您还需要将代码“注入”到某个地方的阵容中,并且如果没有任何钩子来扩展现有的过程,那么您别无选择但要坚持在前端,以确保您的代码有一定的控制权。 – BlueMonkMN 2011-02-24 13:27:44

+0

这很有道理:)你可以发布一些代码,它不需要清理,只是为了看到基本原理,以便我可以适应它。我最感兴趣的是挖掘提供者。 – 2011-02-24 15:07:44

1

定义一个新的扩展方法:

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; 
    } 

这应该打印你迭代它跟踪每次。感谢Jon Skeet的灵感。

就个人而言,我会继续Action代表取代formatdata,这样你就可以执行,而不是简单的跟踪任何任务(到集合无关)。

编辑:我有这样的感觉,这可能只适用于LINQ对象。对于IQueryable<T>,您必须调整您无权访问的表达式树解析器。对不起: -/

0

我相信你可以坚持使用LINQ to SQL的标准跟踪功能。您不仅可以知道查询执行的时间,还可以知道可以同样方便的查询。

为此,DataContext具有Log属性,该属性允许您跟踪其SQL输出。

在LINQ to Entities中,ObjectQuery<T>公开ToTraceString方法,其中you can use for tracing in a fashion you described