AspNetCoreOData
AspNetCoreOData copied to clipboard
HasAlternateKeys doc out of date
https://docs.microsoft.com/en-us/odata/webapi/alternate-key There is no description on how to do this in 8.x.
edit: removed 'doesnt work' part, since it was just a matter of bad docs
Your "Also" seems completely unrelated to the original request about alternate keys. Can you please split into a different issue?
@davhdavh I created a sample. Please take a look and let me know any problems.
https://github.com/OData/AspNetCoreOData/tree/main/sample/ODataAlternateKeySample
@xuzhg Thanks a lot! I didn't realize it was possible to edit the model after it was built, since that both breaks the usual builder pattern and is not documented anywhere I could find.
Btw the way to specify this without doing it after GetEdmModel is very convoluted.
var tbl = builder.EntityType<Test>();
tbl.Property(x => x.Guid).HasAlternateKeys(x=>x.HasKey(y=>y.HasName(new EdmPropertyPathExpression(nameof(Guid))))).HasAlternateKeys();
Also, it doesn't work if the baseclass specifies an alternate key, it doesn't flow down to all of the derived classes.
Is it possible to get it working with ODataRouteComponent, so that it can be something that is on an abstract baseclass?
[HttpGet, EnableQuery]
[ODataRouteComponent("(Guid={guid})")]
public SingleResult<TDto> GetByGuid(Guid guid) => SingleResult.Create(Get().Where(x => x.Guid == guid));
ODataRouteComponent is designed to set the 'prefix' for conventional routing. For the attribute template, you should use '[HttpGetAttribute], .... [RouteAttribute]....'.
I tried with both [HttpGet("(Guid={guid})")] and [Route("(Guid={guid})")] in both cases I get ODataException: The key value (Guid=f1a71203-8e34-4623-baa9-3ae5936d6a04) from request is not valid. The key value should be format of type 'Edm.Int32'.
Found out about /$odata...

That is the result of these functions:
[HttpGet, EnableQuery]
public IQueryable<TDto> Get() => ...;
[HttpGet, EnableQuery]
public IActionResult Get(int key) => ...;
[HttpGet, EnableQuery, Route("(Guid={guid})")]
public IActionResult GetByGuid(Guid guid) => ...;
[HttpGet("(Guid={guid})"), EnableQuery]
public IActionResult GetByGuid1(Guid guid) => ...;
[HttpGet, EnableQuery, Route("(Guid={guid:Guid})")]
public IActionResult GetByGuid2(Guid guid) => ...;
[HttpGet, EnableQuery, Route("{guid}")]
public IActionResult GetByGuid3(Guid guid) => ...;
[HttpGet, EnableQuery, Route("{guid:Guid}")]
public IActionResult GetByGuid4(Guid guid) =>...;
As in none of the extra ones are picked up. $metadata is correctly reporting the existence of said key.
@davhdavh route constraints do not impact route discovery. It's not clear to me what you are attempting to test with all those variations where only the parameter constraint is changing.
Route constraints should only ever be used for disambiguation purposes.
Also, I'd recommend adding your route templates directly into the HttpGet attribute. Route is only needed if you have more than a single route for the same verb in the same method.
Trying to find the magic incantation that makes odata detect them... As you can see in the screenshot, only the first 2 end points are detected
@davhdavh does it work if you name the guid overloads as just Get as well, instead of GetByGuid...?
If that's the case, you are missing the attribute routing convention (by not inheriting from ODataController, for example).
No, also fails:
ODataException: The key value (2e4d04fa-dc24-4bd5-be64-31a55c44decf) from request is not valid. The key value should be format of type 'Edm.Int32'.
Microsoft.AspNetCore.OData.Routing.Template.KeySegmentTemplate.TryTranslate(ODataTemplateTranslateContext context)
Microsoft.AspNetCore.OData.Routing.Template.DefaultODataTemplateTranslator.Translate(ODataPathTemplate path, ODataTemplateTranslateContext context)
Microsoft.AspNetCore.OData.Routing.ODataRoutingMatcherPolicy.ApplyAsync(HttpContext httpContext, CandidateSet candidates)
Microsoft.AspNetCore.Routing.Matching.DfaMatcher.SelectEndpointWithPoliciesAsync(HttpContext httpContext, IEndpointSelectorPolicy[] policies, CandidateSet candidateSet)
Btw the way to specify this without doing it after
GetEdmModelis very convoluted.var tbl = builder.EntityType<Test>(); tbl.Property(x => x.Guid).HasAlternateKeys(x=>x.HasKey(y=>y.HasName(new EdmPropertyPathExpression(nameof(Guid))))).HasAlternateKeys();Also, it doesn't work if the baseclass specifies an alternate key, it doesn't flow down to all of the derived classes.
This is the method that I found as well and it has some odd behavior, particularly when using an OpenApi documenting tool like Swagger.
Implementing the alternate key endpoint with [HttpGet("odata/Test(Guid={guid})")] also implicitly creates a route "odata/Test" with a query parameter for "guid" (even if [FromRoute] is specified in the method params). I'm using Swagger, and if the usual [EnableQuery] public IActionResult Get() endpoint is defined then Swagger complains that multiple endpoints are defined for odata/Test.