Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OData.Client DataServiceContext BulkUpdate() Always Uses Protocol Version 4.01 #3004

Open
AlpineJBoehnen opened this issue Jun 13, 2024 · 6 comments · May be fixed by OData/AspNetCoreOData#1272
Assignees

Comments

@AlpineJBoehnen
Copy link

AlpineJBoehnen commented Jun 13, 2024

In Microsoft.OData.Client 7.21.3 the Microsoft.OData.Client.DataService.Context.BulkUpdate() method appears to force the OData-Version header to 4.01 regardless of the
maxProtocolVersion provided to the DataServiceContext constructor. I am using Microsoft.AspNetCore.OData 8.2.5 on my server, and out of the box it cannot deal with this protocol of OData for bulk updates (Specifically the DeltaSet<T> parameter of my entity set patch action is always null). In Postman if I send an identical request as the client to the server but with the OData-Version header set to 4.0 the server handles this without issue. My questions are as follows:

  • How do I send a bulk update request to an OData 4.0 server with Microsoft.OData.Client?
  • Why does BulkUpdate() force the protocol version to 4.01?
  • Is this an issue with my client or my server (i.e. do I need to direct this issue at Microsoft.AspNetCore.OData)?

I should also note that I am using OData Connected Service on my client for code generation, but I am not sure this is relevant.

Assemblies affected

  • Microsoft.OData.Client 7.21.3
  • Microsoft.AspNetCore.OData 8.2.5

Reproduce steps

I implemented Sample Request 1 from the OData Client Bulk Update Operations examples on my client.
And implemented an endpoint on my server to "patch a collection of entities" from this Microsoft.AspNetCore.OData tutorial.

Note: I modified the client example to work with the Shape, Circle, Rectangle models from the server example.

Client code:

Default.Container context = new Default.Container(new Uri("https://localhost:7092/odata/"), Microsoft.OData.Client.ODataProtocolVersion.V4);

Shape s1 = new Shape { Id = 1, Area = 10 };
context.AddToShapes(s1);

Shape s2 = new Shape { Id = 2, Area = 20 };
context.AddToShapes(s2);

context.BulkUpdate(s1, s2); // fails

Server code:

// Program.cs
var builder = WebApplication.CreateBuilder(args);
var modelBuilder = new ODataConventionModelBuilder();
modelBuilder.EntitySet<Shape>("Shapes");

builder.Services.AddControllers()
    .AddOData(opts =>
    {
        opts.EnableQueryFeatures().AddRouteComponents("odata", modelBuilder.GetEdmModel());
    });
// ShapesController.cs
public class ShapesController : ODataController
{
    private static List<Shape> shapes = new List<Shape>
    {
        new Rectangle { Id = 1, Length = 7, Width = 4, Area = 28 },
        new Circle { Id = 2, Radius = 3.5, Area = 38.5 },
        new Rectangle { Id = 3, Length = 8, Width = 5, Area = 40 }
    };

    public async Task<ActionResult> Patch([FromBody] DeltaSet<Shape> deltaSet)
    {
        if (deltaSet == null)
        {
            return BadRequest();
        }

        foreach (Delta<Shape> delta in deltaSet)
        {
            if (delta.TryGetPropertyValue("Id", out object idAsObject))
            {
                var shape = shapes.SingleOrDefault(d => d.Id.Equals(idAsObject));
                delta.Patch(shape);
            }
        }

        return Ok();
    }
}

Expected result

The BulkUpdate() method would succeed on the client, and the DeltaSet<T> parameter on the server would not be null when the action executes.

Actual result

The DeltaSet<T> parameter is null when the server action executes, meaning the server returns BadRequest and the client update operation fails.

@habbes
Copy link
Contributor

habbes commented Jun 14, 2024

@AlpineJBoehnen the Bulk Updates feature is only supported in 4.01, that's why the client forces that 4.01 header when making bulk update requests. The format that we use to serialize bulk operation requests and responses is based on features only support in the 4.01 protocol. For example, the 4.01 protocol defines syntax for specifying changes to related/nested entities like so:

{
  "@type": "#Northwind.Manager",
  "FirstName": "Patricia",
  "DirectReports@delta": [
    {
      "@removed": {
        "reason": "deleted"
      },
      "@id": "Employees(3)"
    },
    {
      "@removed": {
        "reason": "changed"
      },
      "@id": "Employees(4)"
    },
    {
      "@id": "Employees(5)"
    },
    {
      "@id": "Employees(6)",
      "LastName": "Smith"
    },
    {
      "FirstName": "Suzanne",
      "LastName": "Brown"
    }
  ]
}

But this format is not supported in 4.0. 4.0 does support linking to entities using @odata.bind, like the following sample:

{
  "@odata.type":"#Northwind.Manager",
  "ID": 1,
  "FirstName": "Pat",
  "LastName": "Griswold",
  "[email protected]": [
    "http://host/service/Employees(5)",
    "http://host/service/Employees(6)"
  ]
}

But it's very limited and is not sufficient to support bulk updates.

For more info, see: https://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_LinktoRelatedEntitiesWhenCreatinganE

@AlpineJBoehnen
Copy link
Author

Thank you for the explanation. Exploring the spec further I can see this is correct client behavior. I have created a related issue for Microsoft.AspNetCore.OData to seek assistance on the server implementation.

@habbes
Copy link
Contributor

habbes commented Jun 18, 2024

@AlpineJBoehnen this blog post demonstrates how to use bulk ops on the server with Microsoft.AspNetCore.OData https://devblogs.microsoft.com/odata/bulk-operations-support-in-odata-web-api/, the blog post is based on the 7.x version, but it should work for 8 as well (@ElizabethOkerio correct me if I'm wrong)

@AlpineJBoehnen
Copy link
Author

AlpineJBoehnen commented Jun 19, 2024

@habbes I may be wrong, but I don't believe that blog post applies to Microsoft.AspNetCore.OData 8.x because it is no longer considered OData Web API? Perhaps bulk update is simply one of the features being referred to by this article when it says:

There are also features in ASP.NET Core OData 7 that have not been ported to ASP.NET Core OData 8

Any other insight would be greatly appreciated, I am hoping to continue development with 8.x opposed to 7.x. If bulk update is not available in 8.x yet is there a plan to add it? Looking forward to hearing your insight on this as well @ElizabethOkerio

@ElizabethOkerio
Copy link
Contributor

The feature is supported in 8.x. Look at some of these tests here: https://github.com/ElizabethOkerio/AspNetCoreOData/blob/1a0edd4cd9bae3ecc5cf34fe866d623515d935d1/test/Microsoft.AspNetCore.OData.E2E.Tests/BulkOperation/EmployeesController.cs . The issue you're experiencing today could be a bug that we're currently investigating.

@AlpineJBoehnen
Copy link
Author

AlpineJBoehnen commented Jun 20, 2024

@ElizabethOkerio Thanks for that example. I will experiment some more and see what I can deduce.

Might be worth mentioning, when I use the following code in those tests the PatchEmployees DeltaSet parameter is null and the tests fail.

HttpClient client = CreateClient();
client.DefaultRequestHeaders.Add("OData-Version", "4.01");

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants