odata.net
odata.net copied to clipboard
Adding related entity
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 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.
- A POST request for
TestA
entity - A POST request for
TestB
entity - A POST request to create a relationship between
TestA
andTestB
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.
- 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);
}
- 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 yourTestAsController
.
[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
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 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/
Fixed by #2653 #2716 #2691