simple-odata-client / Simple.OData.Client

MIT License
331 stars 197 forks source link

NRE when using client from Visual Basic #638

Open zspitz opened 5 years ago

zspitz commented 5 years ago

When I use the following C# code:

var client = new ODataClient("https://services.odata.org/v4/TripPinServiceRW/");

var command = client.For<Person>()
    .Filter(x => x.Trips.Any(y => y.Budget > 3000))
    .Top(2)
    .Select(x => new { x.FirstName, x.LastName });

var commandText = Task.Run(() => command.GetCommandTextAsync()).Result;
// generated relative URL
Console.WriteLine(commandText);
Console.WriteLine();

the code runs as expected and ouputs the relative URL. However, if I run the corresponding code in Visual Basic:

Dim client = New ODataClient("https://services.odata.org/v4/TripPinServiceRW/")

Dim command = client.For(Of Person).
    Filter(Function(x) x.Trips.Any(Function(y) y.Budget > 3000)).
    Top(2).
    Select(Function(x) New With {x.LastName, x.FirstName})

Dim commandText = Task.Run(Function() command.GetCommandTextAsync()).Result
Console.WriteLine(commandText)
Console.WriteLine()

I get a NullReferenceException:

System.AggregateException: 'One or more errors occurred. (Object reference not set to an instance of an object.)'

Inner Exception NullReferenceException: Object reference not set to an instance of an object.

The stack trace (for the inner exception) is as follows:

   at Simple.OData.Client.ODataExpression.FormatCallerReference()
   at Simple.OData.Client.ODataExpression.FormatAnyAllFunction(ExpressionContext context)
   at Simple.OData.Client.ODataExpression.FormatFunction(ExpressionContext context)
   at Simple.OData.Client.ODataExpression.Format(ExpressionContext context)
   at Simple.OData.Client.FluentCommand.Resolve()
   at Simple.OData.Client.FluentCommand.GetCommandTextAsync(CancellationToken cancellationToken)

I'm using the NuGet package version 5.8.0 of the V4 client.

The entity classes are as follows (for both the C# and VB code):

public class Person {
    public string LastName { get; set; }
    public string FirstName { get; set; }
    public ICollection<Trip> Trips { get; set; }
    public void Write() => Console.WriteLine($"LastName: {LastName}, FirstName: {FirstName}");
}

public class Trip {
    public long Budget { get; set; }
}
zspitz commented 5 years ago

Apparently, this is because the VB compiler inserts a conversion node whenever the type of the argument doesn't precisely match the parameter. In this case, the first parameter of Any is IEnumerable<Trip> while the Trips property is defined as ICollection<Trip>.

The Visual Basic expression tree looks like this: image

while the C# expression tree looks like this: image

So if I define Trips as IEnumerable<Trip>, the code works fine in VB as well.

Nevertheless, it might be a good idea to check if the pre-conversion node's Type (in this case x.Trips) is assignable to the post-conversion type (IEnumerable<Trip>) then the conversion should be ignored in the parsing.

object commented 5 years ago

Interesting. Thank you for the investigation. I will think about what can be done here.