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

How to create an entity with a linked entity?

Open Robelind opened this issue 2 years ago • 4 comments

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"}

Robelind avatar May 20 '22 10:05 Robelind

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

habbes avatar May 24 '22 15:05 habbes

Could you please show exactly how the controller method that handles the PUT-request should look like.

Robelind avatar May 30 '22 07:05 Robelind

Ping @habbes

Robelind avatar Jun 07 '22 06:06 Robelind

@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

habbes avatar Jun 07 '22 14:06 habbes

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?

Yglioh avatar Oct 19 '22 14:10 Yglioh

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

Eleve2023 avatar Apr 13 '23 04:04 Eleve2023

@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.

ElizabethOkerio avatar Apr 13 '23 17:04 ElizabethOkerio