[英]Tracing Linq expression evaluation
我想知道是否有可能為IQueryable編寫一個“passthrough”擴展方法,它會在評估可查詢時編寫調試字符串,換句話說,調試打印應該是評估的副作用。
就像是:
var qr = SomeSource.Where(...).OrderBy(...).Trace("SomeSource evaluated at {0}", DateTime.Now)
var qr2 = qr.Where(...);
當我構造一個linq查詢並將其作為數據源傳遞給某個對象時,我想知道該對象何時以及多久評估一次我的查詢。 我想它可以通過其他方式實現,例如包裝IEnumerable.GetEnumerator,但我想對任何linq查詢一般這樣做。
我做了類似的事情,但更復雜(因為它在處理表達時也操縱表達式)。 為了實現它,我創建了一個實現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
然后通過提供包裝的可查詢來擴展派生的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
定義一個新的擴展方法:
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
委托替換format
和data
,因此您可以執行任何任務(與集合無關),而不僅僅是跟蹤。
編輯:我覺得這可能僅適用於linq到對象。 對於IQueryable<T>
,您必須調整您無權訪問的表達式樹解析器。 對不起: - /
我相信你可以堅持使用LINQ to SQL的標准跟蹤功能。 您不僅可以知道查詢執行的時間,還可以了解查詢,這可以很方便。
為此, DataContext
具有Log
屬性,允許您跟蹤其SQL輸出。
在LINQ to Entities中, ObjectQuery<T>
公開了ToTraceString
方法, 您可以使用該方法以您描述的方式進行跟蹤 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.