{"id":5299,"date":"2023-06-09T00:36:03","date_gmt":"2023-06-09T07:36:03","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=5299"},"modified":"2023-06-09T00:36:03","modified_gmt":"2023-06-09T07:36:03","slug":"deep-insert-support-in-odata-web-api","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/deep-insert-support-in-odata-web-api\/","title":{"rendered":"Deep insert support in OData Web API"},"content":{"rendered":"<h3>Background<\/h3>\n<p>In <code>OData Web API 7.7.0<\/code>, we added support for deep insert.<span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><span data-contrast=\"auto\">In deep insert, we create an object and its related items or link existing items in a single request.<\/span><\/p>\n<p>This blog post is a continuation of <a href=\"https:\/\/devblogs.microsoft.com\/odata\/bulk-operations-support-in-odata-web-api\/\">Bulk Operations Support in OData Web API<\/a>. In that blog post, we explained how to use <code>ODataAPIHandler<\/code> and <code>ODataAPIHandlerFactory<\/code> classes. We will not repeat that in this blog post. Bulk update and deep insert share the same <code>ODataAPIHandler<\/code> and <code>ODataAPIHandlerFactory<\/code> classes.<\/p>\n<p><span class=\"ui-provider gl b c d e f g h i j k l m n o p q r s t u v w x y z ab ac ae af ag ah ai aj ak\" dir=\"ltr\">In the following sections, we will cover how deep insert is implemented in <code>OData Web API<\/code> and the things that the developer needs to do to use it in their OData service.<\/span><\/p>\n<h3>Controller<\/h3>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">[ODataRoute(\"Customers\")]\r\n[HttpPost]\r\n[EnableQuery]\r\npublic IActionResult Post([FromBody] Customer customer)\r\n{\r\n    var handler = new CustomerAPIHandler();\r\n\r\n    handler.DeepInsert(customer, Request.GetModel(), new APIHandlerFactory(Request.GetModel()));\r\n\r\n    return Created(customer);\r\n}<\/code><\/pre>\n<p><span class=\"ui-provider gl b c d e f g h i j k l m n o p q r s t u v w x y z ab ac ae af ag ah ai aj ak\" dir=\"ltr\"> For the controller action to handle deep insert, the <code>HttpPost<\/code> attribute must be applied together with the <code>EnableQuery<\/code> attribute to ensure<\/span> that the response has the navigation properties expanded to the level of the request.<\/p>\n<p>According to the OData <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part1-protocol.html#sec_CreateRelatedEntitiesWhenCreatinganE\"><span data-ccp-charstyle=\"Hyperlink\">spec<\/span><\/a><span data-contrast=\"auto\"> \u201c<\/span><em><span data-contrast=\"none\">On success, the service MUST create all entities and relate them. If the service responds with\u202f<\/span><\/em><span data-ccp-charstyle=\"datatype\" data-ccp-charstyle-defn=\"{&quot;ObjectId&quot;:&quot;4f0a3593-bad0-4fbd-86fb-ae472a97fd30|20&quot;,&quot;ClassId&quot;:1073872969,&quot;Properties&quot;:[469775450,&quot;datatype&quot;,201340122,&quot;1&quot;,134233614,&quot;true&quot;,469778129,&quot;datatype&quot;,335572020,&quot;1&quot;,469778324,&quot;Default Paragraph Font&quot;]}\"><em>201 Created<\/em><\/span><em><span data-contrast=\"none\">, the response MUST be expanded to at least the level that was present in the deep-insert request<\/span><\/em><span data-contrast=\"none\">.<\/span><span data-contrast=\"auto\">\u201d\u00a0<\/span><span data-ccp-props=\"{&quot;201341983&quot;:0,&quot;335559739&quot;:160,&quot;335559740&quot;:259}\">\u00a0<\/span><\/p>\n<h3>IExpandQueryBuilder interface<\/h3>\n<p><code>IExpandQueryBuilder<\/code> is the base interface for generating an <code>$expand<\/code> query parameter from a payload. Currently we only have one method in the interface.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public interface IExpandQueryBuilder\r\n{\r\n    string GenerateExpandQueryParameter(object value, IEdmModel model);\r\n}<\/code><\/pre>\n<p>The default implementation of the <code>IExpandQueryBuilder<\/code> interface is the <code>ExpandQueryBuilder<\/code> class.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public class ExpandQueryBuilder : IExpandQueryBuilder\r\n{\r\n    \/\/\/ &lt;inheritdoc \/&gt;\r\n    public virtual string GenerateExpandQueryParameter (object value, IEdmModel model)\r\n    {\r\n        return expandString;\r\n    }\r\n}<\/code><\/pre>\n<p>This is the class that makes it possible to expand the navigation properties in the response. The class takes the payload object and generates an expand query parameter which is then appended to the <code>HttpRequest<\/code>.<\/p>\n<p>The <code>GenerateExpandQueryParameter<\/code> method takes a <code>payload object<\/code> and <code>IEdmModel<\/code> and returns an <code>$expand<\/code> string.<\/p>\n<p>For example: If we have an <code>Employee<\/code> object with nested <code>Friends<\/code> and <code>NewFriends<\/code> as below:<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><span class=\"ui-provider gl b c d e f g h i j k l m n o p q r s t u v w x y z ab ac ae af ag ah ai aj ak\" dir=\"ltr\">Employee employee = new Employee\r\n{\r\n\u00a0\u00a0\u00a0 ID = 1,\r\n\u00a0\u00a0\u00a0 Friends = new List&lt;Friend&gt;\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 new Friend\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Id = 1001,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Orders = new List&lt;Order&gt; { new Order { Id = 10001 } }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 },\r\n\u00a0\u00a0\u00a0 NewFriends = new List&lt;NewFriend&gt;\r\n\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 new NewFriend\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 {\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 Id = 1001,\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 NewOrders = new List&lt;NewOrder&gt; { new NewOrder { Id = 10001 } }\r\n\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0\u00a0 }\r\n\u00a0\u00a0\u00a0 }\r\n};<\/span><\/pre>\n<p>We return an expand string: <code>$expand=Friends($expand=Orders),NewFriends($expand=NewOrders)<\/code><\/p>\n<p>In the above block of code, <code>Friends<\/code> property has nested <code>Orders<\/code> property and <code>NewFriends<\/code> property has nested <code>NewOrders<\/code> property.<\/p>\n<p><span class=\"ui-provider gl b c d e f g h i j k l m n o p q r s t u v w x y z ab ac ae af ag ah ai aj ak\" dir=\"ltr\">The customer can provide their own custom implementation of <code>IExpandQueryBuilder<\/code> and inject it into the dependency injection container as follows:<\/span><\/p>\n<p><strong>In the Startup.cs<\/strong><\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public void ConfigureServices(IServiceCollection services)\r\n{\r\n    services.AddOData();\r\n    services.AddMvc(options =&gt;\r\n    {\r\n        options.EnableEndpointRouting = false;\r\n    });\r\n\r\n    services.AddSingleton&lt;IExpandQueryBuilder, CustomExpandQueryBuilder&gt;();\r\n}<\/code><\/pre>\n<p><span class=\"ui-provider gl b c d e f g h i j k l m n o p q r s t u v w x y z ab ac ae af ag ah ai aj ak\" dir=\"ltr\">The <code>CustomExpandQueryBuilder<\/code> should implement the <code>IExpandQueryBuilder<\/code> interface or alternatively subclass the <code>ExpandQueryBuilder<\/code> class and override the virtual methods. <\/span><\/p>\n<h3>OData API Handler<\/h3>\n<p>In the controller method above, we initialized a <code>CustomerAPIHandler<\/code> class.<\/p>\n<p>The <code>ODataAPIHandler&lt;TStructuralType&gt;<\/code> has a default implementation of <code>DeepInsert<\/code> method which can be used.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public abstract class ODataAPIHandler&lt;TStructuralType&gt;: IODataAPIHandler where TStructuralType : class\r\n{\r\n    \/\/ Other  methods\r\n    public virtual void DeepInsert(TStructuralType resource, IEdmModel model, ODataAPIHandlerFactory apiHandlerFactory)\r\n    {\r\n        if (resource == null || model == null)\r\n        {\r\n            return;\r\n        }\r\n\r\n        \/\/ Other code\r\n\r\n        CopyObjectProperties(resource, model, this, apiHandlerFactory);\r\n    }\r\n    \r\n    internal static void CopyObjectProperties(object resource, IEdmModel model, IODataAPIHandler apiHandler, ODataAPIHandlerFactory apiHandlerFactory)\r\n    {\r\n        if (resource == null || model == null || apiHandler == null)\r\n        {\r\n            return;\r\n        }\r\n        \/\/ Other code\r\n    }\r\n}\r\n<\/code><\/pre>\n<p>We can override the <code>DeepInsert<\/code> method and add our custom implementation.<\/p>\n<pre class=\"prettyprint language-cs language-csharp\"><code class=\"language-cs language-csharp\">public class CustomerAPIHandler : ODataAPIHandler&lt;Customer&gt;\r\n{\r\n    public override void DeepInsert(TStructuralType resource, IEdmModel model, ODataAPIHandlerFactory apiHandlerFactory)\r\n    {\r\n        base.DeepInsert(resource, model, apiHandlerFactory);\r\n    }\r\n}<\/code><\/pre>\n<h3>Query options<\/h3>\n<p>We can add query options in our <code>POST<\/code> request. If there is an expand query parameter in the query options, we will not generate <code>$expand<\/code> query parameters using the <code>ExpandQueryBuilder<\/code><strong>.<\/strong><\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">POST http:\/\/localhost:6285\/odata\/Customers?$expand=Orders\r\nContent-Type: application\/json\r\n{\r\n    \"Id\": 1,\r\n    \"Name\": \"Customer1\",\r\n    \"Orders\": [\r\n        {\r\n            \"@odata.id\": \"Customers(3)\/Orders(1005)\"\r\n        },\r\n        {\r\n            \"Id\": 2000,\r\n            \"Price\": 200,\r\n            \"Quantity\": 90\r\n        }\r\n    ]\r\n}\r\n<\/code><\/pre>\n<p>Below will be the response:<\/p>\n<pre class=\"prettyprint language-json\"><code class=\"language-json\">{\r\n    \"@odata.context\": \"http:\/\/localhost:6285\/odata\/$metadata#Customers(Orders())\/$entity\",\r\n    \"Id\": 1,\r\n    \"Name\": \"Customer1\",\r\n    \"Age\": 0,\r\n    \"Orders\": [\r\n        {\r\n            \"Id\": 1005,\r\n            \"Price\": 40,\r\n            \"Quantity\": 15\r\n        },\r\n        {\r\n            \"Id\": 2000,\r\n            \"Price\": 200,\r\n            \"Quantity\": 90\r\n        }\r\n    ]\r\n}<\/code><\/pre>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Background In OData Web API 7.7.0, we added support for deep insert.\u00a0In deep insert, we create an object and its related items or link existing items in a single request. This blog post is a continuation of Bulk Operations Support in OData Web API. In that blog post, we explained how to use ODataAPIHandler and [&hellip;]<\/p>\n","protected":false},"author":20598,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1,117],"tags":[],"class_list":["post-5299","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-odata","category-webapi"],"acf":[],"blog_post_summary":"<p>Background In OData Web API 7.7.0, we added support for deep insert.\u00a0In deep insert, we create an object and its related items or link existing items in a single request. This blog post is a continuation of Bulk Operations Support in OData Web API. In that blog post, we explained how to use ODataAPIHandler and [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5299","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/users\/20598"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=5299"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/5299\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media\/3253"}],"wp:attachment":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/media?parent=5299"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=5299"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=5299"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}