Azure/azure-sdk-for-sap-odata

[BUG] Function sample for deferred toBusinessPartner call fails

WillEastbury opened this issue · 12 comments

Is there an existing issue for this?

  • I have searched the existing issues

Type of issue

bug

Describe the bug

Transferred from old repo #20

Fetching address with deferred attribute fails with internal server error. Message doesn't show up on APIM or SAP GW.

Expected Behavior

Should defer the fetch.

Steps To Reproduce

var AddressCity = (await salesOrderInput.ToBusinessPartner.GetAsync()).Address.City;

Add screenshots to help explain your problem

No response

Additional context

No response

I will take this one.

In flight - Looking at this now.

I'm having difficulty replicating this.

I've added 2 lines to the console test output

        // get me a sales order line item with id 0500000000 and pull the city asynchronously from the business partner
        var salesOrderInput = await sos.GetAsync("0500000000");
        _logger.LogInformation((await salesOrderInput.ToBusinessPartner.GetAsync()).Address.City);
        
        And it correctly seems to output "GWSAMPLE_BASIC.TestService: Information: Walldorf", which is what I would expect.
        
        I'll check the functions sample next.

Right I got it - the Dispatcher is throwing a null reference exception in the GetAsync method call from L57 of the Deferred Class

Result = await DispatchThroughDeferredURL(__deferred.uri);

But it only seems to happen in Functions weirdly - I'm on it.

This call is just a stub to IOperationsDispatcher.DispatchThroughDeferredURL(string uri) implemented in ODataOperationsDispatcher.cs here (Line 131), but for some reason it looks like the IOperationsDispatcher variable is null here at this call ?

image

I think I may have found the problem. Functions must be internally serializing the bound object and we have these attributes present on the object internally.

    [Newtonsoft.Json.JsonIgnore()]
    [System.Text.Json.Serialization.JsonIgnore(Condition = JsonIgnoreCondition.Always)]
    public IOperationsDispatcher Dispatcher {protected internal get; set; }
    
    Which means serialization is dropping the context's attachment 

Or it's not getting set in the first place by the Binding. If you access the Sales Order outside the binding, like this

            var salesOrderInput2 = await sos.GetAsync(ID);
            var bp = await salesOrderInput2.ToBusinessPartner.GetAsync();

It works fine.

OK, I have located the issue. In the wire up of the reader in the function binding we call the dispatcher objects directly instead of going through the ODataEntitySetOperations class's GetAsync method (which calls AttachDispatcher).

The reason we did this I think was to try and save some complexity in knowing which ODataEntitySetOperations to implement in the binding method.

I think an ODataEntitySetOperationsFactory method might be appropriate, then I can fix it in the codegen stage

I can add this

    public interface IQuerySetOperationsFactory
    {
        IQuerySetOperations<T> Create<T>() where T : IBaseDTOWithIDAndETag;
    }

    // This is the factory that is injected into the DI container to create the IODataEntitySetOperations<T> for each type of DTO
    public class ODataEntitySetOperationsFactory : IQuerySetOperationsFactory
    {
        private IOperationsDispatcher _dispatcher;
        public ODataEntitySetOperationsFactory(IOperationsDispatcher dispatcher)
        {
            _dispatcher = dispatcher;
        }

        public IQuerySetOperations<T> Create<T>() where T : IBaseDTOWithIDAndETag
        {
            return new ODataEntitySetOperations<T>(_dispatcher);
        }
    }

And instantiate that in the ConfigureBindings Method

IQuerySetOperationsFactory esops = new ODataEntitySetOperationsFactory(dispatcher);
Then we can change the ConfigureBindings methods from

context.BindToInput<Input_GWSAMPLE_BASIC_BusinessPartnerAttribute, BusinessPartner>((x) => dispatcher.GetAsync<BusinessPartner>(x.BusinessPartnerID).Result);

To

context.BindToInput<Input_GWSAMPLE_BASIC_SalesOrderAttribute, SalesOrder>((x) => esops.Create<SalesOrder>().GetAsync(x.SalesOrderID).Result);

Fixed locally - committing

image

I've also added an extra function to test this feature

Calling http://localhost:7071/api/GetDeliveryAddressLabel/0500000000 on the sample should render a dispatch label.

See the image above

Checking in now.