Computing a Linq Expression

I am wondering if it is possible to write a Passthrough extension method for IQueryable that will write a debug string whenever a query is requested, in other words, debug printing should be a side effect of the evaluation.

Sort of:

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

When I create a linq query and pass it as a data source for some object, I would like to know when and how often the object evaluates my request. I assume that this can be achieved in other ways, for example, wrapping IEnumerable.GetEnumerator, for example, but I would like to do this in the general case for any linq request.

+4
source share
3 answers

I did something similar, but more complex (because it also manipulates expressions when it processes them). To do this, I created a shell class that implemented IQueryable and contained a link to what I really wanted to request. I passed all the interface elements through a reference object, with the exception of the Provider property, which returned a link to another class I created, inherited from IQueryProvider. IQueryProvider has methods that are called each time a query is built or executed. Thus, you can do something similar if you do not mind that you are always forced to request your wrapper object instead of the original object (s).

You should also know that if you use LINQ-to-SQL, there is a Log property in the DataContext that you can use to route a lot of debugging information wherever you want.

Code example:

Create your own IQueryable to control the returned 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 

Create your own query provider to manage the generated expression generator.

 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 the resulting derived DataContext by providing wrapped queries:

 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 
+3
source

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 every time you iterate over it. Thanks to John Skeet for the inspiration.

Personally, I would replace format and data an Action delegate so that you can perform any task (not related to the collection) instead of just tracing.

Edit: I have the feeling that this can only work for linq-to-objects. For IQueryable<T> you will have to configure expression tree parsers that you do not have access to. Excuse me: -/

+1
source

I believe that you can use the standard trace functions for LINQ to SQL. Not only can you find out when the request is executed, but you also know the request, which can be just as convenient.

To do this, the DataContext has a Log property that allows you to track its SQL output.

In LINQ to Entities, ObjectQuery<T> exposes a ToTraceString method that you can use to track in the ToTraceString that you described .

0
source

Source: https://habr.com/ru/post/1341046/


All Articles