AspNetCoreOData icon indicating copy to clipboard operation
AspNetCoreOData copied to clipboard

How to query navigation property of a singleton entity?

Open RonnyRen opened this issue 3 months ago • 3 comments

I want to support the following OData APIs, user is singleton, users is entity set, user has many orders, orders is entity set. /odata/users: return info of all the users. /odata/users({key}): return info of an user /odata/user: return the info of current login user /odata/orders: return all the orders /odata/orders({key}): return an order based on the key /odata/user/orders: return all the order of the current login user /odata/user/orders({key}): return the order based on key of current login user

Class: public class User { [Key] public string Id { get; set; } public string Name { get; set; } public ICollection<Order> Orders { get; set; } }

public class Order { [Key] public int Id { get; set; } public string UserId { get; set; } public string ProductName { get; set; } public User User { get; set; } } EDM setting builder.EntitySet<User>("Users"); builder.Singleton<User>("User"); builder.EntitySet<Order>("Orders"); // Navigation: User → Orders builder.EntityType<User>().HasMany(u => u.Orders);

Controllers public class UsersController : ODataController [EnableQuery] public IActionResult Get() //works for /odata/users public IActionResult Get([FromODataUri] string key) //works for /odata/users({key})

public class UserController : ODataController [EnableQuery] public IActionResult Get() //works for /odata/user

public class OrdersController : ODataController [EnableQuery] public IActionResult Get() //works for /odata/orders

[EnableQuery] public IActionResult Get([FromODataUri] int key) //works for /odata/orders({key})

I can make route /odata/user/orders working through adding method GetOrders() in Controller UserController, how to support both route /odata/user/orders and /odata/user/orders({key}) through standard OData API? According to the Microsoft Copilot, it says OrdersController can handle routes /odata/orders, /odata/user/orders, /odata/orders({key}) and /odata/user/orders({key}) by default based on EDM setting, but it doesn't work in my testing, does OData support this case? What extra configure is required? Thanks.

RonnyRen avatar Oct 14 '25 06:10 RonnyRen

  1. For the singleton, you'd setup the navigation property binding explicitly as follows: (I use 'me' as singleton name)
odataBuilder.Singleton<User>("Me").HasManyBinding(u => u.Orders, "Orders");

Then, the metadata contains

Image
  1. Then, you can build the routing to access the navigation property from singleton

The following builds an endpoint using the convention routing. It can query like: ~/odata/me/orders.

    public class MeController : Controller
    {
        [HttpGet]
        public string GetOrders() => "Hello from /me endpoint";

  1. If you want to continue single order using me, like ~/odata/me/orders(4), you should use the attribute routing since no convention routing built to support this pattern.
        [ODataAttributeRouting]
        [HttpGet("odata/me/Orders({key})")]
        public string GetOrders(int key) => $"Hello from /me/orders({key}) endpoint";

Please share more questions here. Thanks.

xuzhg avatar Oct 14 '25 17:10 xuzhg

Thanks for your answer. Is it possible to add a custom RoutingConvention to support route /odata/user/orders({key}) by Controller OrdersController? One more question, how can I make navigation key working? For example: /odata/users({key})/orders({navKey}).

RonnyRen avatar Oct 15 '25 03:10 RonnyRen

Thanks for your answer. Is it possible to add a custom RoutingConvention to support route /odata/user/orders({key}) by Controller OrdersController? One more question, how can I make navigation key working? For example: /odata/users({key})/orders({navKey}).

  1. Yes, you can create an implementation of IODataControllerActionConvention and inject an instance into the Conventions property when calling AddOData(opt => ....)
  2. You can add an action decorated with [HttpGet('odata/users({key})/orders({navKey})'].

Let me know if it cannot work.

xuzhg avatar Oct 15 '25 17:10 xuzhg