odata.net icon indicating copy to clipboard operation
odata.net copied to clipboard

Adding related entity

Open Robelind opened this issue 3 years ago • 2 comments

Assemblies affected

Microsoft.OData.Client 7.9.2

Reproduce steps

    public class TestA
    {
        public int Id { get; set; }
        public IEnumerable<TestB> TestBs { get; set; }
    }

    public class TestB
    {
        public int Id { get; set; }
        public int Value { get; set; }
    }

      <EntityType Name="TestA">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" />
        <NavigationProperty Name="TestBs" Type="Collection(Test.TestB)" />
      </EntityType>
      <EntityType Name="TestB">
        <Key>
          <PropertyRef Name="Id" />
        </Key>
        <Property Name="Id" Type="Edm.Int32" Nullable="false" />
        <Property Name="Value" Type="Edm.Int32" Nullable="false" />
      </EntityType>

        <EntitySet Name="TestAs" EntityType="Test.TestA">
          <NavigationPropertyBinding Path="TestBs" Target="TestBs" />
        </EntitySet>
        <EntitySet Name="TestBs" EntityType="Test.TestB" />

TestA a = new();
a.TestBs.Add(new() { Value = 1});
context.AddToTestAs(a);
context.SaveChanges();

POST http://localhost:56168/api/TestAs HTTP/1.1
Host: localhost:56168
OData-Version: 4.0
OData-MaxVersion: 4.0
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
User-Agent: Microsoft.OData.Client/7.9.2
Connection: Keep-Alive
Content-Type: application/json;odata.metadata=minimal
Content-Length: 36

{"@odata.type":"#Test.TestA","Id":0}

Expected result

The posted data should contain the data for the related TestB entity.

Actual result

The posted data doesn't contain the data for the related TestB entity.

Robelind avatar Sep 24 '21 11:09 Robelind

@Robelind The code in your client application won't currently work because OData WebAPI doesn't currently support deep inserts/updates. That feature is currently being worked on. However, you can still accomplish your objective a different way:

var testA = new TestA { Id = 1 };
var testB = new TestB { Id = 1, Value = 13 };
context.AddToTestAs(testA);
context.AddToTestBs(testB);
context.AddLink(testA, "TestBs", testB);

context.SaveChanges();

The call to SaveChanges will initiate 3 requests to the service.

  1. A POST request for TestA entity
  2. A POST request for TestB entity
  3. A POST request to create a relationship between TestA and TestB entities

There's an alternative that involves having all 3 requests batched in a single call. That involves passing a parameter SaveChangesOptions.BatchWithSingleChangeset to the SaveChanges method. However, it appears there's a possible bug that might require some investigation.

A few more tips to ensure everything works well together.

  1. Your Post controller actions should return the location of the newly created item. For example,
public ActionResult Post([FromBody] TestA testA)
{
    // ...
    return Created(new Uri($"{Request.Scheme}://{Request.Host}{Request.Path}/{testA.Id}"), testA);
}
  1. You need to implement a CreateRef controller action to handle $ref requests for creating a relationship between the entities. For example, you'd need to implement a method similar to the one below in your TestAsController.
[AcceptVerbs("POST", "PUT")]
public ActionResult CreateRef([FromODataUri] int key, [FromODataUri] string navigationProperty, [FromBody] Uri link)
{
    if (navigationProperty.Equals("TestBs"))
    {
        // Extract related key from link
        // Riding on key and related key, create a relationship between the two entities.
    }

    return Accepted();
}

Please let me know if this works for you

gathogojr avatar Sep 29 '21 13:09 gathogojr

Yes, I've seen examples of that, involving multiple requests to establish the link. It has some serious drawbacks imho:

  • Foreign keys need to be made nullable in the DB, although they're semantically not.
  • How to handle the inevitable problem that request 2 or 3 fails?

Robelind avatar Sep 29 '21 13:09 Robelind

@Robelind check out the latest version on nuget v7.18.0. We added both async and sync capabilities for Deep insert. This allows you to post an entity and it's related entitites. See E2E tests here as a guide. You can also read the blog post for guidance https://devblogs.microsoft.com/odata/deep-insert-support-in-odata-client/

KenitoInc avatar Sep 20 '23 08:09 KenitoInc

Fixed by #2653 #2716 #2691

KenitoInc avatar Sep 22 '23 13:09 KenitoInc