Implementing string comparer in custom Linq Provid

2019-05-23 02:55发布

问题:

I'm attempting to follow this series of articles. I'm between parts 2 and 3 but am having some issues.

I'm writing the code in VB.Net which has thrown a couple of quirks.

Specifically, when visiting the expression tree, string comparisons aren't working as expected.

This method in the QueryProvider (Part 2)

Protected Overrides Function VisitMethodCall(m As MethodCallExpression) As Expression
    If m.Method.DeclaringType = GetType(Queryable) AndAlso m.Method.Name = "Where" Then
        sb.Append("SELECT * FROM (")
        Me.Visit(m.Arguments(0))
        sb.Append(") AS T WHERE ")
        Dim lambda As LambdaExpression = DirectCast(StripQuotes(m.Arguments(1)), LambdaExpression)
        Me.Visit(lambda.Body)
        Return m
    End If
    Throw New NotSupportedException(String.Format("The method '{0}' is not supported", m.Method.Name))
End Function

Is throwing a NotImplementedException for String comparisons

m.Method.DeclaringType is of type Microsoft.VisualBasic.CompilerServices.Operators and m.Method.Name is CompareString. It looks like the string equality is handled slightly differently by VB and isn't being picked up in the correct way.

I'm using a Query.Where(function(x) x.Content_Type <> "") to test.

Specifically, if I debug the calls to VisitBinary(b As BinaryExpression) (Also Part 2), b is {(CompareString(x.Content_Type, "", False) != 0)}

This then attempts to visit b.Left (CompareString(x.Content_Type, "", False)) which is where we fall through the hole in VisitMethodCall.

If I just expand the If in VisitMethodCall to be

    If (
            m.Method.DeclaringType = GetType(Queryable) AndAlso
            m.Method.Name = "Where"
        ) Or (
            m.Method.DeclaringType = GetType(Microsoft.VisualBasic.CompilerServices.Operators) AndAlso
            m.Method.Name = "CompareString") Then

It throws an InvalidCastException on the trying to convert the StripQuotes(m.Arguments(1)) to LambdaExpression (says it's a ConstantExpression)

What do I need to do to handle string comparisons correctly from VB?

回答1:

Use the following class:

Imports System.Linq.Expressions

Public Class VbCompareReplacer
    Inherits ExpressionVisitor

    Public Overrides Function Visit(node As Expression) As Expression
        If Not TypeOf node Is BinaryExpression Then Return MyBase.Visit(node)

        Dim binaryExpression = DirectCast(node, BinaryExpression)

        If Not TypeOf binaryExpression.Left Is MethodCallExpression Then Return MyBase.Visit(node)

        Dim method = DirectCast(binaryExpression.Left, MethodCallExpression)

        If Not (method.Method.DeclaringType = GetType(Microsoft.VisualBasic.CompilerServices.Operators) AndAlso
            method.Method.Name = "CompareString") Then Return MyBase.Visit(node)

        Dim left = method.Arguments(0)
        Dim right = method.Arguments(1)

        Return If(binaryExpression.NodeType = ExpressionType.Equal,
                  Expression.Equal(left, right),
                  Expression.NotEqual(left, right))
    End Function
End Class

Now if you have an expression from the vb compiler, for example

Dim expressionFromVb As Expression(Of Func(Of String, Boolean)) = Function(x) x = "b"

which has the structure {(CompareString(x, "b", False) == 0)} use

Dim newExpression = New VbCompareReplacer().Visit(expressionFromVb)

instead of expressionFromVb which has the structure {x => (x == "b")}. All messy vb comparisons are replaced by the expected (in-)equality comparisons. If you put newExpression into your linq provider written for c#, it should work properly now.