odata.net
odata.net copied to clipboard
How to create an entity with a linked entity?
Trying to create an entity with a linked entity.
Assemblies affected
Microsoft.OData.Client 7.10.0
Reproduce steps
public class A
{
[Key]
public int Id { get; set; }
public int Value { get; set; }
public B B { get; set; }
}
public class B
{
[Key]
public int Id { get; set; }
public string Value { get; set; }
}
A a = new() { Value = 1 };
B b = new() { Value = "Test" };
context.AddObject("As", a);
context.AddObject("Bs", b);
context.SetLink(a, nameof(A.B), b);
await context.SaveChangesAsync();
Expected result
Requests that makes it possible to link the two entities together.
Actual result
POST http://localhost:52769/api/As {"Id":0,"Value":1}
POST http://localhost:52769/api/Bs {"Id":0,"Value":"Test"}
Hi @Robelind You can issue a batch request that includes the different operations in a single changeset:
await context.SaveChangesAsync(SaveChangesOptions.BatchWithSingleChangeset);
The batch request will contain 2 POST operations to create the entities and a PUT $1/B/$ref
operation to create the link where $1 is the ID of the a
instance. The body of the PUT
request will contain the ID of the second instance: { "@odata.id": "$2" }
. These IDs will be retrieved from results of the POST operations.
For this 2 work, your service should be able to handle the PUT As/{id}/$ref
request to add a link to entities of type A`.
Here's a what a sample batch request looks like:
--batch_1a72fe12-e8b1-4865-bcf9-6dcad4cb1be9
Content-Type: multipart/mixed; boundary=changeset_e569d051-f158-49d8-95aa-0d5820cbe139
--changeset_e569d051-f158-49d8-95aa-0d5820cbe139
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 1
POST https://services.odata.org/TripPinRESTierService/(S(h3ndhdpzkfhixddhee1juhxf))/People HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
User-Agent: Microsoft.OData.Client/7.11.0
{"@odata.type":"#Trippin.Person","Age":null,"[email protected]":"#Collection(String)","Emails":[],"[email protected]":"#Trippin.Feature","FavoriteFeature":"Feature1","[email protected]":"#Collection(Trippin.Feature)","Features":[],"FirstName":"John","[email protected]":"#Trippin.PersonGender","Gender":"Male","LastName":"Done","MiddleName":null,"UserName":"jd","[email protected]":"#Collection(Trippin.Location)","AddressInfo":[],"HomeAddress":null}
--changeset_e569d051-f158-49d8-95aa-0d5820cbe139
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 2
POST https://services.odata.org/TripPinRESTierService/(S(h3ndhdpzkfhixddhee1juhxf))/People HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Content-Type: application/json;odata.metadata=minimal
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
User-Agent: Microsoft.OData.Client/7.11.0
{"@odata.type":"#Trippin.Person","Age":null,"[email protected]":"#Collection(String)","Emails":[],"[email protected]":"#Trippin.Feature","FavoriteFeature":"Feature1","[email protected]":"#Collection(Trippin.Feature)","Features":[],"FirstName":"Mike","[email protected]":"#Trippin.PersonGender","Gender":"Male","LastName":null,"MiddleName":null,"UserName":"mike","[email protected]":"#Collection(Trippin.Location)","AddressInfo":[],"HomeAddress":null}
--changeset_e569d051-f158-49d8-95aa-0d5820cbe139
Content-Type: application/http
Content-Transfer-Encoding: binary
Content-ID: 3
PUT $1/BestFriend/$ref HTTP/1.1
OData-Version: 4.0
OData-MaxVersion: 4.0
Accept: application/json;odata.metadata=minimal
Accept-Charset: UTF-8
Content-Type: application/json;odata.metadata=minimal
User-Agent: Microsoft.OData.Client/7.11.0
{"@odata.id":"$2"}
--changeset_e569d051-f158-49d8-95aa-0d5820cbe139--
--batch_1a72fe12-e8b1-4865-bcf9-6dcad4cb1be9--
Could you please show exactly how the controller method that handles the PUT-request should look like.
Ping @habbes
@Robelind You could create a CreateRef(int key, string navigationProperty, [FromBody] link)
action in your controller, or CreateRefTo<RelatedPropertyName>(int key, [FromBody] Uri link)
.
Here's a more detailed example of creating such an endpoint and extracting the ID from the link: https://docs.microsoft.com/en-us/aspnet/web-api/overview/odata-support-in-aspnet-web-api/odata-v4/entity-relations-in-odata-v4#creating-a-relationship-between-entities
Would be great if the context would allow the linkage of an existing or new entity in the same payload as described in the official odata documentation: http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-protocol.html#sec_LinktoRelatedEntitiesWhenCreatinganE
This feature would allow the creation of an entity where that navigation property is required upon creation. Any insight on adding this feature to the library?
there is a workaround is to use SetLink here is an example:
var context = new DataServiceContext(new Uri("http://odataServiceUri"));
var customer = new Customer { Name = "John Doe" };
var account = new Account { Balance = 100 };
context.AddObject("Customers", customer);
context.AddObject("Accounts", account);
context.SetLink(customer, "Account", account);
context.SaveChanges();
but it smiles easier than:
var context = new DataServiceContext(new Uri("http://odataServiceUri"));
var customer = new Customer { Name = "John Doe" };
var account = new Account { Balance = 100 };
customer.Account = account;
context.AddObject("Customers", customer);
context.AddObject("Accounts", account);
context.SaveChanges();
@Yglioh yes there is on-going work on deep-insert and deep-update that will add support for creating an entity and its related entity using a single request.