{"id":4831,"date":"2022-01-11T17:51:53","date_gmt":"2022-01-12T00:51:53","guid":{"rendered":"https:\/\/devblogs.microsoft.com\/odata\/?p=4831"},"modified":"2022-01-14T10:30:58","modified_gmt":"2022-01-14T17:30:58","slug":"compute-and-search-in-asp-net-core-odata-8","status":"publish","type":"post","link":"https:\/\/devblogs.microsoft.com\/odata\/compute-and-search-in-asp-net-core-odata-8\/","title":{"rendered":"$compute and $search in ASP.NET Core OData 8"},"content":{"rendered":"<h2>Introduction<\/h2>\n<p>OData <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_SystemQueryOptions\">system query options<\/a>, such as <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_SystemQueryOptionfilter\">$filter<\/a>, <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_SystemQueryOptionorderby\">$orderby<\/a>, are a set of query string parameters that control the amount and order of the data returned for the resource identified by the URL. In the latest version of ASP.NET Core OData, two new system query options as follows are enabled:<\/p>\n<ul>\n<li><a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#_Toc31361047\"><strong>$compute<\/strong><\/a>: allows clients to define computed properties that can be used in a\u00a0<a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_SystemQueryOptionselect\">$select<\/a>\u00a0or within a\u00a0$filter\u00a0or\u00a0$orderby\u00a0expression.<\/li>\n<li><a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_SystemQueryOptionsearch\"><strong>$search<\/strong><\/a>: allows clients to request items within a collection matching a free-text\u00a0<a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_SearchExpressions\">search expression<\/a>.<\/li>\n<\/ul>\n<p>Along with other query options, $compute or $search requests the service to perform a set of transformations at the server side to control the returned data. In this post, I will use a simple product-sale OData service to go through the $compute and $search scenarios and share ideas of how to use $compute and $search functionalities in <a href=\"https:\/\/www.nuget.org\/packages\/Microsoft.AspNetCore.OData\/8.0.6\">ASP.NET Core OData<\/a>.<\/p>\n<p>Let&#8217;s get started.<\/p>\n<h2>Prerequisites<\/h2>\n<p>I created an ASP.NET Core web application called \u201c<strong>NewQueryOptionIn8<\/strong>\u201d using Visual Studio 2022 to play as product-sale OData service. I installed the following NuGet packages:<\/p>\n<ul>\n<li>Microsoft.AspNetCore.OData -version 8.0.6<\/li>\n<\/ul>\n<p>Product-sale and related classes are an in-memory model as follows:<\/p>\n<p><img decoding=\"async\" width=\"787\" height=\"461\" class=\"wp-image-4832\" src=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/01\/word-image.png\" srcset=\"https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/01\/word-image.png 787w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/01\/word-image-300x176.png 300w, https:\/\/devblogs.microsoft.com\/odata\/wp-content\/uploads\/sites\/23\/2022\/01\/word-image-768x450.png 768w\" sizes=\"(max-width: 787px) 100vw, 787px\" \/><\/p>\n<p>Where:<\/p>\n<ul>\n<li><strong>Product<\/strong> &amp; <strong>Sale<\/strong>: the entity types<\/li>\n<li><strong>Address<\/strong>: the complex type<\/li>\n<li><strong>Color<\/strong> &amp; <strong>Category<\/strong>: the enum types<\/li>\n<li><strong>IProductSaleRepostiory<\/strong> &amp; <strong>ProdcutSaleInMemoryRespository<\/strong>: the data source repository<\/li>\n<\/ul>\n<p>You can refer to the Github repository <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/NewQueryOptionIn8\">here<\/a> for detail class definitions, sample data, routing information, and the controllers.<\/p>\n<h2>$compute query option<\/h2>\n<h2>$compute syntax<\/h2>\n<p>As mentioned, the $compute system query option allows clients to define computed properties that can be used in other query options. Basically, the computed properties should be included as dynamic properties in the result and must be included if\u00a0$select\u00a0is specified with the computed property name, or star (*). Here is an example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$compute=Price mul Qty as TotalPrice&amp;$select=TotalPrice<\/em><\/strong><\/p>\n<p>This request asks the server to compute the total price for products (using multiplication between <strong><em>Price<\/em><\/strong> and <strong><em>Qty<\/em><\/strong>) and return the total price only (included in $select) to the client. If you run the \u201c<strong>NewQueryOptionIn8<\/strong>\u201d application and send an HTTP Get request using the above URL, you should get the following response:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(TotalPrice)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"TotalPrice\":\u00a065.0\r\n    },\r\n    {\r\n      \"TotalPrice\":\u00a059.85\r\n    },\r\n    {\r\n      \"TotalPrice\":\u00a0149.5\r\n    },\r\n    {\r\n      \"TotalPrice\":\u00a0900.0\r\n    },\r\n    {\r\n      \"TotalPrice\":\u00a097.86\r\n    },\r\n    {\r\n      \"TotalPrice\":\u00a01875.0\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>Be noted, the remaining part after \u201c=\u201d in <strong><em>$compute=Price mul Qty as TotalPrice<\/em> <\/strong>represents a compute expression. A compute expression used in $compute typically includes three parts:<\/p>\n<ol>\n<li>common expression<strong><em>: Price mul Qty<\/em><\/strong> is a common expression, where <strong><em>Price<\/em><\/strong> and <strong><em>Qty<\/em><\/strong> are properties, <strong><em>mul<\/em><\/strong> is the keyword for multiplication. You can refer to <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_CommonExpressionSyntax\">here<\/a> for more detail information about common expression.<\/li>\n<li><strong><em>as<\/em><\/strong> is the keyword and must have one in one compute expression.<\/li>\n<li>an alias name<strong><em>: TotalPrice<\/em><\/strong> is an alias name or the computed dynamic property name for common expression. It should follow up OData property naming rule and be unique.<\/li>\n<\/ol>\n<h2>$compute on sub property<\/h2>\n<p>Beside computing using direct property, you can use $compute on sub-property of complex type property. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$compute=Location\/ZipCode div 1000 as MainZipCode&amp;$select=Id,MainZipCode<\/em><\/strong><\/p>\n<p>This request asks the server to compute the main zip code from <strong><em>Location<\/em><\/strong> of each product and return it with Id. The response should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Id,MainZipCode)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Id\":\u00a01,\r\n      \"MainZipCode\":\u00a058\r\n    },\r\n    {\r\n      \"Id\":\u00a02,\r\n      \"MainZipCode\":\u00a098\r\n    },\r\n    {\r\n      \"Id\":\u00a03,\r\n      \"MainZipCode\":\u00a013\r\n    },\r\n    {\r\n      \"Id\":\u00a04,\r\n      \"MainZipCode\":\u00a098\r\n    },\r\n    {\r\n      \"Id\":\u00a05,\r\n      \"MainZipCode\":\u00a058\r\n    },\r\n    {\r\n      \"Id\":\u00a06,\r\n      \"MainZipCode\":\u00a013\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$compute using built-in functions<\/h2>\n<p>More powerful, you can also use built-in <a href=\"http:\/\/docs.oasis-open.org\/odata\/odata\/v4.01\/odata-v4.01-part2-url-conventions.html#sec_CanonicalFunctions\">functions<\/a> in the $compute query option. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$compute=substring(Name,0,1) as FirstChar&amp;$select=FirstChar<\/em><\/strong><\/p>\n<p>This request asks the server to sub-string the first character from the product name and returns it. So, the response to the above request should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(FirstChar)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"FirstChar\":\u00a0\"B\"\r\n    },\r\n    {\r\n      \"FirstChar\":\u00a0\"P\"\r\n    },\r\n    {\r\n      \"FirstChar\":\u00a0\"N\"\r\n    },\r\n    {\r\n      \"FirstChar\":\u00a0\"F\"\r\n    },\r\n    {\r\n      \"FirstChar\":\u00a0\"P\"\r\n    },\r\n    {\r\n      \"FirstChar\":\u00a0\"V\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>Multiple compute expressions in $compute<\/h2>\n<p>$compute allows multiple compute expressions. Each expression uses comma (,) to separate. Here\u2019s an example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$compute=Price mul Qty as TotalPrice,TaxRate mul 1.1 as NewTaxRate&amp;$select=Id,TotalPrice,NewTaxRate<\/em><\/strong><\/p>\n<p>This request asks the server to compute the total price for products, increase the tax rate by 10% and return the Id, total price, and the new tax rate together to the client. The response should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Id,TotalPrice,NewTaxRate)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Id\":\u00a01,\r\n      \"TotalPrice\":\u00a065.0,\r\n      \"NewTaxRate\":\u00a00.11000000000000001\r\n    },\r\n    {\r\n      \"Id\":\u00a02,\r\n      \"TotalPrice\":\u00a059.85,\r\n      \"NewTaxRate\":\u00a00.15400000000000003\r\n    },\r\n    {\r\n      \"Id\":\u00a03,\r\n      \"TotalPrice\":\u00a0149.5,\r\n      \"NewTaxRate\":\u00a00.11000000000000001\r\n    },\r\n    {\r\n      \"Id\":\u00a04,\r\n      \"TotalPrice\":\u00a0900.0,\r\n      \"NewTaxRate\":\u00a00.15400000000000003\r\n    },\r\n    {\r\n      \"Id\":\u00a05,\r\n      \"TotalPrice\":\u00a097.86,\r\n      \"NewTaxRate\":\u00a00.264\r\n    },\r\n    {\r\n      \"Id\":\u00a06,\r\n      \"TotalPrice\":\u00a01875.0,\r\n      \"NewTaxRate\":\u00a00.48400000000000004\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$compute in $filter<\/h2>\n<p>Besides be used in $select, the computed properties defined in $compute also can be used in a\u00a0$filter\u00a0expression. Here is an example combining $compute and $filter:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$filter=TotalPrice lt 100&amp;$compute=Price mul Qty as TotalPrice<\/em><\/strong><\/p>\n<p>The request asks the server to compute the total price for product items, then return products whose total price is less than 100.<\/p>\n<p>The response to the above request only returns the following three products (Id=1,2,5, omit for other properties).<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\": \"http:\/\/localhost:5102\/odata\/$metadata#Products\",\r\n  \"value\": [\r\n    { \"Id\": 1, \u2026 },\r\n    { \"Id\": 2, \u2026 },\r\n    { \"Id\": 5, \u2026 }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$compute in $orderby<\/h2>\n<p>Same as $filter, the computed properties defined in $compute also can be used in a\u00a0$orderby\u00a0expression to do ordering. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$orderby=TotalPrice&amp;$compute=Price mul Qty as TotalPrice&amp;$select=Id,TotalPrice<\/em><\/strong><\/p>\n<p>The response to this request should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Id,TotalPrice)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Id\":\u00a02,\r\n      \"TotalPrice\":\u00a059.85\r\n    },\r\n    {\r\n      \"Id\":\u00a01,\r\n      \"TotalPrice\":\u00a065.0\r\n    },\r\n    {\r\n      \"Id\":\u00a05,\r\n      \"TotalPrice\":\u00a097.86\r\n    },\r\n    {\r\n      \"Id\":\u00a03,\r\n      \"TotalPrice\":\u00a0149.5\r\n    },\r\n    {\r\n      \"Id\":\u00a04,\r\n      \"TotalPrice\":\u00a0900.0\r\n    },\r\n    {\r\n      \"Id\":\u00a06,\r\n      \"TotalPrice\":\u00a01875.0\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$compute as nested within $select and $expand:<\/h2>\n<p>From the above examples, we know that we can use the computed properties from $compute into $select. Moreover, $compute also can be used as a nested query option within $select and $expand. For example:<\/p>\n<p><strong>GET <\/strong><a href=\"http:\/\/localhost:5102\/odata\/products\"><strong><em>http:\/\/localhost:5102\/odata\/products<\/em><\/strong><\/a><strong><em>?<\/em><\/strong><\/p>\n<p><strong><em>$expand=Sales($filter=SaleYear eq 1999;$compute=year(SaleDate) as SaleYear)<\/em><\/strong><\/p>\n<p><strong><em>&amp;$select=Location($compute=ZipCode div 1000 as MainZipCode;$select=City,MainZipCode)<\/em><\/strong><\/p>\n<p>(<em>Line breaks only for readability)<\/em><\/p>\n<p>The response should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Id,Location\/City,Location\/MainZipCode,Sales())\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Location\":\u00a0{\r\n        \"City\":\u00a0\"Sammsh\",\r\n        \"MainZipCode\":\u00a058\r\n      },\r\n      \"Sales\":\u00a0[]\r\n    },\r\n    {\r\n      \"Location\":\u00a0{\r\n        \"City\":\u00a0\"Redd\",\r\n        \"MainZipCode\":\u00a098\r\n      },\r\n      \"Sales\":\u00a0[]\r\n    },\r\n    {\r\n      \"Location\":\u00a0{\r\n        \"City\":\u00a0\"Issah\",\r\n        \"MainZipCode\":\u00a013\r\n      },\r\n      \"Sales\":\u00a0[\r\n        {\r\n          \"Id\":\u00a06,\r\n          \"SaleDate\":\u00a0\"1999-08-12\",\r\n          \"ProductId\":\u00a03,\r\n          \"Amount\":\u00a011\r\n        }\r\n      ]\r\n    },\r\n    {\r\n      \"Location\":\u00a0{\r\n        \"City\":\u00a0\"Redd\",\r\n        \"MainZipCode\":\u00a098\r\n      },\r\n      \"Sales\":\u00a0[]\r\n    },\r\n    {\r\n      \"Location\":\u00a0{\r\n        \"City\":\u00a0\"Sammsh\",\r\n        \"MainZipCode\":\u00a058\r\n      },\r\n      \"Sales\":\u00a0[]\r\n    },\r\n    {\r\n      \"Location\":\u00a0{\r\n        \"City\":\u00a0\"Issah\",\r\n        \"MainZipCode\":\u00a013\r\n      },\r\n      \"Sales\":\u00a0[\r\n        {\r\n          \"Id\":\u00a013,\r\n          \"SaleDate\":\u00a0\"1999-06-23\",\r\n          \"ProductId\":\u00a06,\r\n          \"Amount\":\u00a03\r\n        }\r\n      ]\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>Where, only the sales whose sale year is equal to 1999 are expanded.<\/p>\n<h2>$search query option<\/h2>\n<p>As mentioned, the\u00a0$search\u00a0system query option restricts the result from the server to include only those items\u00a0matching\u00a0the specified, free-text\u00a0search expression. Since the search expression is freestyle, the definition of what it means to match is dependent upon the customized implementation.<\/p>\n<h2>Implement $search binder<\/h2>\n<p>In the latest ASP.NET Core OData, we introduce the \u201c<strong>ISearchBinder<\/strong>\u201d interface that can empower developers to implement their own $search matching logics. Here is the interface definition:<\/p>\n<pre class=\"lang:c# decode:true\">public interface ISearchBinder\r\n{\r\n    Expression BindSearch(SearchClause searchClause, QueryBinderContext context);\r\n}\r\n<\/pre>\n<p>Where:<\/p>\n<ul>\n<li><strong>SearchClause<\/strong> holds the search expression.<\/li>\n<li><strong>QueryBinderContext <\/strong>holds the information used in binding.<\/li>\n<\/ul>\n<p>Basically, it is easy to implement a $search binder just by creating a new class that implements the <strong>ISearchBinder<\/strong> interface. For example:<\/p>\n<pre class=\"lang:c# decode:true\">public class YourSearchBinder : ISearchBinder\r\n{\r\n    public Expression BindSearch(SearchClause searchClause, QueryBinderContext context)\r\n    { ...... }\r\n}\r\n<\/pre>\n<p>Since I want to use the built-in <strong>QueryBinder<\/strong> binding functionalities, I derive <strong>ProductSaleSearchBinder<\/strong> class from <strong>QueryBinder<\/strong> as follows:<\/p>\n<pre class=\"lang:c# decode:true\">public class ProductSaleSearchBinder : QueryBinder, ISearchBinder\r\n{\r\n    public Expression BindSearch(SearchClause searchClause, QueryBinderContext context)\r\n    { ...... }\r\n}\r\n<\/pre>\n<p>In <strong>ProductSaleSearchBinder<\/strong>, I use two ways to return an Expression based on <strong>SearchClause<\/strong> for your reference.<\/p>\n<p>1) <strong>Lambda Func<\/strong>: Directly using a Lambda Func to generate an Expression. For example (Codes are simplified for readability):<\/p>\n<pre class=\"lang:c# decode:true\">public Expression BindSearch(SearchClause searchClause, QueryBinderContext context)\r\n{\r\n    SearchTermNode node = searchClause.Expression as SearchTermNode;\r\n\r\n    Expression&lt;Func&lt;Product, bool&gt;&gt; exp = p =&gt; p.Category.ToString() == node.Text;\r\n\r\n    return exp;\r\n}\r\n<\/pre>\n<p>2) <strong>Expression Tree API<\/strong>: Creating Expression tree using the Expression APIs, for example:<\/p>\n<pre class=\"lang:c# decode:true\">public Expression BindSearch(SearchClause searchClause, QueryBinderContext context)\r\n{\r\n    SearchTermNode node = searchClause.Expression as SearchTermNode;\r\n\r\n    Expression source = context.CurrentParameter;\r\n\r\n    Expression categoryProperty = Expression.Property(source, \"Category\");\r\n\r\n    Expression categoryPropertyString = Expression.Call(categoryProperty, \"ToString\", typeArguments: null, arguments: null);\r\n\r\n    Expression body = Expression.Call(null, StringEqualsMethodInfo, categoryPropertyString, Expression.Constant(node.Text, typeof(string)), Expression.Constant(StringComparison.OrdinalIgnoreCase, typeof(StringComparison)));\r\n\r\n    LambdaExpression lambdaExp = Expression.Lambda(body, context.CurrentParameter);\r\n\r\n    return lambdaExp;\r\n}\r\n<\/pre>\n<p>Lambda Func Expression is more readable and understandable compared to Expression tree API. However, if you want to build more complicated expressions, Expression tree API could be a better choice.<\/p>\n<p>For the final <strong>ProductSaleSearchBinder<\/strong> implementation, please refer to Github repo <a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/blob\/master\/src\/NewQueryOptionIn8\/NewQueryOptionIn8\/Extensions\/ProductSaleSearchBinder.cs\">here<\/a>.<\/p>\n<h2>Register $search binder<\/h2>\n<p>It is easy to register the $search binder into service provider just updating the <strong>Program.cs<\/strong> as follows:<\/p>\n<pre class=\"lang:c# decode:true\">builder.Services.AddControllers()\r\n    .AddOData(opt =&gt; opt.EnableQueryFeatures()\r\n    .AddRouteComponents(\"odata\", ModelBuilder.GetEdmModel(), services =&gt; services.AddSingleton&lt;ISearchBinder, ProductSaleSearchBinder&gt;()));\r\n<\/pre>\n<h2>Basic $search example<\/h2>\n<p>Now we can filter products using $search. For example:<\/p>\n<p>GET <a href=\"http:\/\/localhost:5102\/odata\/products?$search=food&amp;$select=Name\"><strong><em>http:\/\/localhost:5102\/odata\/products?$search=food&amp;$select=Name<\/em><\/strong><\/a><\/p>\n<p>The response contains food product name should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Name)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Name\":\u00a0\"Bread\"\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Noodle\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$search using <strong>NOT<\/strong><\/h2>\n<p>We can use \u201c<strong><em>NOT<\/em><\/strong>\u201d keyword to reverse the search. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$search=NOT food&amp;$select=Name<\/em><\/strong><\/p>\n<p>The response contains non-food product name should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Name)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Name\":\u00a0\"Pencil\"\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Flute\"\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Paper\"\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Violin\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$search using <strong>AND<\/strong><\/h2>\n<p>We can use \u201c<strong><em>AND<\/em><\/strong>\u201d to narrow the search. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$search=food AND white&amp;$select=Id,Name<\/em><\/strong><\/p>\n<p>The response should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Id,Name)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Id\":\u00a01,\r\n      \"Name\":\u00a0\"Bread\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$search using <strong>OR<\/strong><\/h2>\n<p>Also, we can use \u201c<strong><em>OR<\/em><\/strong>\u201d to combine the search. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/products?$search=food OR White &amp;$select=Name,Color,Category<\/em><\/strong><\/p>\n<p>Here\u2019s the result:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Products(Name,Color,Category)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Name\":\u00a0\"Bread\",\r\n      \"Category\":\u00a0\"Food\",\r\n      \"Color\":\u00a0\"White\"\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Noodle\",\r\n      \"Category\":\u00a0\"Food\",\r\n      \"Color\":\u00a0\"Brown\"\r\n    },\r\n    {\r\n      \"Name\":\u00a0\"Paper\",\r\n      \"Category\":\u00a0\"Office\",\r\n      \"Color\":\u00a0\"White\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$search using non-string token<\/h2>\n<p>If the $search token contains non-string character, we can use double quote (\u201c\u2026\u201d) to wrapper the search token. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/sales?$search=&#8221;2022&#8243; OR &#8220;2018&#8221;&amp;$select=SaleDate<\/em><\/strong><\/p>\n<p>The response should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Sales(SaleDate)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"SaleDate\":\u00a0\"2018-03-19\"\r\n    },\r\n    {\r\n      \"SaleDate\":\u00a0\"2022-12-09\"\r\n    },\r\n    {\r\n      \"SaleDate\":\u00a0\"2022-05-29\"\r\n    },\r\n    {\r\n      \"SaleDate\":\u00a0\"2022-01-01\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<h2>$search with $filter<\/h2>\n<p>We can combine the $filter and $search together. The results include only those items that match both criteria. For example:<\/p>\n<p><strong>GET <em>http:\/\/localhost:5102\/odata\/sales?$search=&#8221;2022&#8243; OR &#8220;2018&#8221;&amp;$select=SaleDate,Id&amp;$filter=Id gt 8<\/em><\/strong><\/p>\n<p>The response should look like:<\/p>\n<pre class=\"lang:json decode:true\">{\r\n  \"@odata.context\":\u00a0\"http:\/\/localhost:5102\/odata\/$metadata#Sales(SaleDate,Id)\",\r\n  \"value\":\u00a0[\r\n    {\r\n      \"Id\":\u00a09,\r\n      \"SaleDate\":\u00a0\"2022-05-29\"\r\n    },\r\n    {\r\n      \"Id\":\u00a011,\r\n      \"SaleDate\":\u00a0\"2022-01-01\"\r\n    }\r\n  ]\r\n}\r\n<\/pre>\n<p>That\u2019s all.<\/p>\n<h2>Summary<\/h2>\n<p>This post went through $compute and $search query functionalities introduced in the latest ASP.NET Core OData 8 using lots of examples. Hope the contents and implementations in this post can help you easily apply $compute and $search in your service. Please do not hesitate to leave your comments below or let me know your thoughts through <a href=\"mailto:saxu@microsoft.com\">saxu@microsoft.com<\/a>. Thanks.<\/p>\n<p>I uploaded the whole project to\u00a0<a href=\"https:\/\/github.com\/xuzhg\/MyAspNetCore\/tree\/master\/src\/NewQueryOptionIn8\" target=\"_blank\" rel=\"noopener\">this<\/a>\u00a0repository.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Introduction OData system query options, such as $filter, $orderby, are a set of query string parameters that control the amount and order of the data returned for the resource identified by the URL. In the latest version of ASP.NET Core OData, two new system query options as follows are enabled: $compute: allows clients to define [&hellip;]<\/p>\n","protected":false},"author":514,"featured_media":3253,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"_acf_changed":false,"footnotes":""},"categories":[1472,1],"tags":[1476,1477,48],"class_list":["post-4831","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-asp-net-core","category-odata","tag-compute","tag-search","tag-odata"],"acf":[],"blog_post_summary":"<p>Introduction OData system query options, such as $filter, $orderby, are a set of query string parameters that control the amount and order of the data returned for the resource identified by the URL. In the latest version of ASP.NET Core OData, two new system query options as follows are enabled: $compute: allows clients to define [&hellip;]<\/p>\n","_links":{"self":[{"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4831","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\/514"}],"replies":[{"embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/comments?post=4831"}],"version-history":[{"count":0,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/posts\/4831\/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=4831"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/categories?post=4831"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/devblogs.microsoft.com\/odata\/wp-json\/wp\/v2\/tags?post=4831"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}