How to query navigation property of a singleton entity?
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.
- 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
- 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";
- 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.
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}).
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}).
- Yes, you can create an implementation of IODataControllerActionConvention and inject an instance into the
Conventionsproperty when callingAddOData(opt => ....) - You can add an action decorated with
[HttpGet('odata/users({key})/orders({navKey})'].
Let me know if it cannot work.