diff --git a/sample/ODataAlternateKeySample/Controllers/CustomersController.cs b/sample/ODataAlternateKeySample/Controllers/CustomersController.cs index 155c0af6c..84f93b415 100644 --- a/sample/ODataAlternateKeySample/Controllers/CustomersController.cs +++ b/sample/ODataAlternateKeySample/Controllers/CustomersController.cs @@ -50,7 +50,8 @@ public IActionResult Post([FromBody]Customer c) } // Alternate key: SSN - [HttpGet("odata/Customers(SSN={ssn})")] + [HttpGet("odata/Customers(SSN={ssn})")] // use community alternate key + [HttpGet("odata/Customers(CoreSN={ssn})")] // use core alternate key public IActionResult GetCustomerBySSN(string ssn) { var c = _repository.GetCustomers().FirstOrDefault(c => c.SSN == ssn); @@ -62,7 +63,8 @@ public IActionResult GetCustomerBySSN(string ssn) return Ok(c); } - [HttpPatch("odata/Customers(SSN={ssnKey})")] + [HttpPatch("odata/Customers(SSN={ssnKey})")] // use community alternate key + [HttpPatch("odata/Customers(CoreSN={ssnKey})")] // use core alternate key public IActionResult PatchCustomerBySSN(string ssnKey, Delta delta) { var originalCustomer = _repository.GetCustomers().FirstOrDefault(c => c.SSN == ssnKey); diff --git a/sample/ODataAlternateKeySample/Controllers/OrdersController.cs b/sample/ODataAlternateKeySample/Controllers/OrdersController.cs index 323f62b1a..5c921ce00 100644 --- a/sample/ODataAlternateKeySample/Controllers/OrdersController.cs +++ b/sample/ODataAlternateKeySample/Controllers/OrdersController.cs @@ -42,7 +42,8 @@ public IActionResult Get(int key) } // alternate key: Name - [HttpGet("odata/Orders(Name={orderName})")] + [HttpGet("odata/Orders(Name={orderName})")] // use community alternate key + [HttpGet("odata/Orders(CoreName={orderName})")] // use core alternate key public IActionResult GetOrderByName(string orderName) { var c = _repository.GetOrders().FirstOrDefault(c => c.Name == orderName); @@ -55,7 +56,8 @@ public IActionResult GetOrderByName(string orderName) } // alternate key: Token - [HttpGet("odata/Orders(Token={token})")] + [HttpGet("odata/Orders(Token={token})")] // use community alternate key + [HttpGet("odata/Orders(CoreToken={token})")] // use core alternate key public IActionResult GetOrderByToken(Guid token) { var c = _repository.GetOrders().FirstOrDefault(c => c.Token == token); diff --git a/sample/ODataAlternateKeySample/Controllers/PeopleController.cs b/sample/ODataAlternateKeySample/Controllers/PeopleController.cs index 5786872dd..43454a58b 100644 --- a/sample/ODataAlternateKeySample/Controllers/PeopleController.cs +++ b/sample/ODataAlternateKeySample/Controllers/PeopleController.cs @@ -41,7 +41,8 @@ public IActionResult Get(int key) return Ok(c); } - [HttpGet("odata/People(c_or_r={cr},passport={passport})")] + [HttpGet("odata/People(c_or_r={cr},passport={passport})")] // use community alternate key + [HttpGet("odata/People(core_c_r={cr},core_passport={passport})")] // use core alternate key public IActionResult FindPeopleByCountryAndPassport(string cr, string passport) { var c = _repository.GetPeople().FirstOrDefault(c => c.CountryOrRegion == cr && c.Passport == passport); diff --git a/sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs b/sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs index 18de67924..5fd0fd6a1 100644 --- a/sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs +++ b/sample/ODataAlternateKeySample/Models/EdmModelBuilder.cs @@ -6,6 +6,8 @@ //------------------------------------------------------------------------------ using Microsoft.OData.Edm; +using Microsoft.OData.Edm.Csdl; +using Microsoft.OData.Edm.Vocabularies; using Microsoft.OData.ModelBuilder; namespace ODataAlternateKeySample.Models @@ -38,6 +40,13 @@ private static void SetCustomerAlternateKey(EdmModel model) { {"SSN", ssn} }); + + // Use Core Vocabulary version. + model.AddAlternateKeyAnnotation(customer, new Dictionary() + { + {"CoreSN", ssn} + }, + true /*true means to use core.alternatekeys term*/); } private static void SetOrderAlternateKey(EdmModel model) @@ -58,15 +67,14 @@ private static void SetOrderAlternateKey(EdmModel model) {"Token", token}, }); - // ODL doesn't support Org.OData.Core.V1.AlternateKeys to do the uri parsing - /* + // Use the APIs to build the core alternate keys var alternateKeysCollection = new List(); foreach (string item in new [] { "Name", "Token"}) { List propertyRefs = new List(); IEdmRecordExpression propertyRef = new EdmRecordExpression( - new EdmPropertyConstructor("Alias", new EdmStringConstant(item)), + new EdmPropertyConstructor("Alias", new EdmStringConstant($"Core{item}")), new EdmPropertyConstructor("Name", new EdmPropertyPathExpression(item))); propertyRefs.Add(propertyRef); @@ -81,7 +89,6 @@ private static void SetOrderAlternateKey(EdmModel model) annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); model.SetVocabularyAnnotation(annotation); - */ } private static void SetPersonAlternateKey(EdmModel model) @@ -98,32 +105,13 @@ private static void SetPersonAlternateKey(EdmModel model) {"passport", passport}, }); - // ODL doesn't support Org.OData.Core.V1.AlternateKeys to do the uri parsing - /* - List propertyRefs = new List(); - - IEdmRecordExpression propertyRef = new EdmRecordExpression( - new EdmPropertyConstructor("Alias", new EdmStringConstant("CountryOrRegion")), - new EdmPropertyConstructor("Name", new EdmPropertyPathExpression("CountryOrRegion"))); - propertyRefs.Add(propertyRef); - - propertyRef = new EdmRecordExpression( - new EdmPropertyConstructor("Alias", new EdmStringConstant("Passport")), - new EdmPropertyConstructor("Name", new EdmPropertyPathExpression("Passport"))); - propertyRefs.Add(propertyRef); - - EdmRecordExpression alternateKeyRecord = new EdmRecordExpression( - new EdmPropertyConstructor("Key", new EdmCollectionExpression(propertyRefs))); - - var alternateKeysCollection = new List(); - alternateKeysCollection.Add(alternateKeyRecord); - - var term = model.FindTerm("Org.OData.Core.V1.AlternateKeys"); - var annotation = new EdmVocabularyAnnotation(person, term, new EdmCollectionExpression(alternateKeysCollection)); - - annotation.SetSerializationLocation(model, EdmVocabularyAnnotationSerializationLocation.Inline); - model.SetVocabularyAnnotation(annotation); - */ + // Use Core Vocabulary version. + model.AddAlternateKeyAnnotation(person, new Dictionary + { + {"core_c_r", cr}, + {"core_passport", passport}, + }, + true); } } } diff --git a/sample/ODataAlternateKeySample/readme.md b/sample/ODataAlternateKeySample/readme.md index e86e4fabe..593db7598 100644 --- a/sample/ODataAlternateKeySample/readme.md +++ b/sample/ODataAlternateKeySample/readme.md @@ -5,24 +5,32 @@ This sample illustrates how to use the Alternate key in ASP.NET Core OData 8.x. Alternate key is an 'alternate' key compared to the declared key. +You can use `Org.OData.Core.V1.AlternateKey` term to define the alternate key vocabulary annotation, that's the recommended way. + +For backward compatible, it also supports `OData.Community.Keys.V1.AlternateKeys` alternate key term. + For example, any customer can have a `Id` as his declared key, meanwhile, he can also have `SSN` as his identity. The sample implements three alternate key scenarios: 1. Single alternate key - Using declared keys : ~/odata/Customers(3) - - Using alternate keys: ~/odata/Customer(SSN='SSN-3-103') + - Using community alternate keys: ~/odata/Customers(SSN='SSN-3-103') + - Using core alternate keys: ~/odata/Customers(CoreSN='SSN-3-103') 2. Multiple alternate keys - Using declared keys : ~/odata/Orders(2) - - Using alternate keys: ~/odata/Orders(Name='Order-2') - - Using alternate keys: ~/odata/Orders(Token=75036B94-C836-4946-8CC8-054CF54060EC) + - Using community alternate keys: ~/odata/Orders(Name='Order-2') + - Using community alternate keys: ~/odata/Orders(Token=75036B94-C836-4946-8CC8-054CF54060EC) + - Using core alternate keys: ~/odata/Orders(CoreName='Order-2') + - Using core alternate keys: ~/odata/Orders(CoreToken=75036B94-C836-4946-8CC8-054CF54060EC) 3. Composition alternate keys - Using declared keys : ~/odata/People(2) - - Using alternate keys: ~/odata/People(CountryOrRegion='USA',Passport='9999') + - Using community alternate keys: ~/odata/People(c_or_r='USA',passport='9999') + - Using core keys: ~/odata/People(core_c_r='USA',core_passport='9999') -You may noticed that you should use the `alternateKeyAlias=alternateKeyValue` pattern to invoke the API. +You may notice that we should use the `alternateKeyAlias=alternateKeyValue` pattern to invoke the API. It's only supported using attribute routing. ## Verify the Model @@ -32,9 +40,9 @@ Send `GET http://localhost:5219/odata/$metadata` You can get the following metadata. -1) Customer type has `OData.Community.Keys.V1.AlternateKeys` annotation with one alternate key -2) Order type has `OData.Community.Keys.V1.AlternateKeys` annotation with two alternate keys -3) Person type has `OData.Community.Keys.V1.AlternateKeys` annotation with one alternate key, in which has two records +1) Customer type has `OData.Community.Keys.V1.AlternateKeys` and `Org.OData.Core.V1.AlternateKey` annotation with one alternate key +2) Order type has `OData.Community.Keys.V1.AlternateKeys` and `Org.OData.Core.V1.AlternateKey` annotation with two alternate keys +3) Person type has `OData.Community.Keys.V1.AlternateKeys` and `Org.OData.Core.V1.AlternateKey` annotation with one composite alternate key ```xml @@ -48,6 +56,7 @@ You can get the following metadata. + @@ -62,6 +71,20 @@ You can get the following metadata. + + + + + + + + + + + + + + @@ -70,7 +93,7 @@ You can get the following metadata. - + @@ -96,6 +119,30 @@ You can get the following metadata. + + + + + + + + + + + + + + + + + + + + + + + + @@ -123,6 +170,24 @@ You can get the following metadata. + + + + + + + + + + + + + + + + + + @@ -142,7 +207,8 @@ Send one of the following requests ```C# GET http://localhost:5219/odata/Customers(2) -GET http://localhost:5219/odata/Customers(ssn='SSN-2-102') +GET http://localhost:5219/odata/Customers(SSN='SSN-2-102') +GET http://localhost:5219/odata/Customers(CoreSN='SSN-2-102') ``` you can get the following result: @@ -151,7 +217,12 @@ you can get the following result: "@odata.context": "http://localhost:5219/odata/$metadata#Customers/$entity", "Id": 2, "Name": "Jerry", - "SSN": "SSN-2-102" + "SSN": "SSN-2-102", + "Titles": [ + "abc", + null, + "efg" + ] } ``` @@ -163,6 +234,8 @@ Send one of the following requests GET http://localhost:5219/odata/orders(3) GET http://localhost:5219/odata/orders(Name='Order-3') GET http://localhost:5219/odata/orders(Token=75036b94-c836-4946-8cc8-054cf54060ec) +GET http://localhost:5219/odata/Orders(CoreName='Order-3') +GET http://localhost:5219/odata/Orders(CoreToken=75036B94-C836-4946-8CC8-054CF54060EC) ``` you can get the following result: @@ -183,7 +256,8 @@ Send one of the following requests ```C# GET http://localhost:5219/odata/People(3) -GET ttp://localhost:5219/odata/People(c_or_r='USA',passport='9999') +GET http://localhost:5219/odata/People(c_or_r='USA',passport='9999') +GET http://localhost:5219/odata/People(core_c_r='USA',core_passport='9999') ``` you can get the following result: @@ -196,3 +270,5 @@ you can get the following result: "Passport": "9999" } ``` + +Thanks! \ No newline at end of file diff --git a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml index 10eda6270..7a13ad99f 100644 --- a/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml +++ b/src/Microsoft.AspNetCore.OData/Microsoft.AspNetCore.OData.xml @@ -1094,6 +1094,20 @@ The type to test. True if the type is a DateTime; false otherwise. + + + Determine if a type is a . + + The type to test. + True if the type is a DateOnly; false otherwise. + + + + Determine if a type is a . + + The type to test. + True if the type is a TimeOnly; false otherwise. + Determine if a type is a TimeSpan. @@ -1474,22 +1488,7 @@ Helper method to check whether the given object is Delta resource set. The given object. - True/False. - - - - The Kind of the object within the DeltaPayload used to distinguish between - Resource/DeletedResource/DeltaDeletedLink/AddedLink. - - - - - Corresponds to EdmEntityObject (Equivalent of ODataResource in ODL). - - - - - Corresponds to EdmDeltaDeletedResourceObject (Equivalent of ODataDeletedResource in ODL). + Object (Equivalent of ODataDeletedResource in ODL). @@ -1570,6 +1569,20 @@ + + + A class the tracnew instance of . + + + + + Initializes a new instance of . + + The derived structural type for which the changes would be tracked. + + + + A class the tracks changes (i.e. the Delta) for a particular . @@ -1624,21 +1637,7 @@ The list of property names that can be updated. - When the list is modified, any modified properties that were removed from the list are no longer - considered to be changed. - - - - - - - - - - - - - Attempts to get the value of the nested Property called from the underlying resource. + When the list is modified, ttempts to get the value of the nested Property called from the underlying resource. Only properties that exist on Entity can be retrieved. Only modified nested properties can be retrieved. @@ -1700,6 +1699,19 @@ The entity to be updated. + + + Attempts to get the property by the specmarks> + + The entity to be updated. + + + + Overwrites the entity with the values stored in this Delta. + The semantics of this operation are equivalent to a HTTP PUT operation, hence the name. + + The entity to be updated. + Attempts to get the property by the specified name. @@ -2657,16 +2669,7 @@ This is how formatters create links to invoke bound actions or functions. - - - Create a new based on an entity link factory. - - The link factory this should use when building links. - - A value indicating whether the link factory generates links that follow OData conventions. - - - + Create a new based on a feed link factory. @@ -7978,6 +7981,18 @@ Enables a controller action to support OData query parameters. + + + Gets or sets a value indicating whether query composition should + alter the original query when necessary to ensure a stable sort order. + + A true value indicates the original query should + be modified when necessary to guarantee a stable sort order. + A false value indicates the sort order can be considered + stable without modifying the query. Query providers that ensure + a stable sort order shoa controller action to support OData query parameters. + + Gets or sets a value indicating whether query composition should @@ -10342,19 +10357,7 @@ - Translate a SingleNavigationNode. - - The node to be translated. - The translated node. - - - - Translate a SingleValueFunctionCallNode. - - The node to be translated. - The translated node. - - + "> Translate a SingleValueOpenPropertyAccessNode. @@ -10420,6 +10423,18 @@ The filter context. + + + Summary: + Called in decreasing Microsoft.AspNetCore.Mvc.Filters.IFilterProvider.Order, + after all Microsoft.AspNetCore.Mvc.Filters.IFilterProviders have executed once. + + The Mica.Query.QueryFilterProvider.OnProvidersExecuting(Microsoft.AspNetCore.Mvc.Filters.FilterProviderContext)"> + + Provides filters to apply to the specified action. + + The filter context. + Summary: @@ -10539,21 +10554,7 @@ Gets the raw $count value. - - - - Gets the value of the $count in a parsed form. - - - - - Gets or sets the $count query validator. - - - - - Validate the count query based on the given . - It throws an ODataException if validation failed. + validation failed. The instance which contains all the validation settings. @@ -11589,6 +11590,20 @@ Gets a list of properties one can orderby the result with. Note, by default this list is empty, it means it can be ordered by any property. + For example, having an empty col: any, all + + + + + + Gets or sets a list of allowed logical operators such as 'eq', 'ne', 'gt', 'ge', 'lt', 'le', 'and', 'or', 'not'. + + + + + Gets a list of properties one can orderby the result with. Note, by default this list is empty, + it means it can be ordered by any property. + For example, having an empty collection means client can order the queryable result by any properties. Adding "Name" to this list means that it only allows queryable result to be ordered by Name property.