AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

HasAlternateKeys doc out of date

Open davhdavh opened this issue 3 years ago • 14 comments
trafficstars

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

davhdavh avatar Feb 23 '22 05:02 davhdavh

Your "Also" seems completely unrelated to the original request about alternate keys. Can you please split into a different issue?

julealgon avatar Feb 24 '22 13:02 julealgon

@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 avatar Feb 24 '22 23:02 xuzhg

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

davhdavh avatar Feb 25 '22 02:02 davhdavh

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.

davhdavh avatar Feb 25 '22 03:02 davhdavh

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

davhdavh avatar Feb 25 '22 10:02 davhdavh

ODataRouteComponent is designed to set the 'prefix' for conventional routing. For the attribute template, you should use '[HttpGetAttribute], .... [RouteAttribute]....'.

xuzhg avatar Feb 25 '22 17:02 xuzhg

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

davhdavh avatar Feb 27 '22 09:02 davhdavh

Found out about /$odata...

image

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 avatar Feb 27 '22 11:02 davhdavh

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

julealgon avatar Mar 02 '22 17:03 julealgon

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 avatar Mar 03 '22 03:03 davhdavh

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

julealgon avatar Mar 03 '22 12:03 julealgon

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)

davhdavh avatar Mar 03 '22 12:03 davhdavh

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.

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.

CharlesSchimmel avatar Jul 26 '22 16:07 CharlesSchimmel