Azure / azure-sdk-for-sap-odata

This repository serves as foundation to power SAP OData SDK development for .NET
MIT License
7 stars 2 forks source link

[BUG] Function sample for deferred toBusinessPartner call fails #10

Closed WillEastbury closed 1 year ago

WillEastbury commented 1 year ago

Is there an existing issue for this?

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

WillEastbury commented 1 year ago

I will take this one.

WillEastbury commented 1 year ago

In flight - Looking at this now.

WillEastbury commented 1 year ago

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.
WillEastbury commented 1 year ago

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);

WillEastbury commented 1 year ago

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

WillEastbury commented 1 year ago

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
WillEastbury commented 1 year ago

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 
WillEastbury commented 1 year ago

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.

WillEastbury commented 1 year ago

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

WillEastbury commented 1 year ago

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);

WillEastbury commented 1 year ago

Fixed locally - committing

image
WillEastbury commented 1 year ago

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.