当前位置: 首页 > news >正文

ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对多Navigation Property(C#)

大纲

  • 主要模型设计
  • 支持的接口
  • 控制器设计
    • 数据源
    • 查询(GET)
      • 查询基类类型Entity的导航属性
      • 查询派生类型Entity的导航属性
      • 查询基类类型Entity的导航属性集合中指定Entity
      • 查询派生类类型Entity的导航属性集合中指定Entit
    • 新增(POST)和 完整更新(PUT)
      • 向基类类型Entity的导航属性建立或替换一个新的Entity
        • POST
        • PUT
      • 向派生类型Entity的导航属性建立或替换一个新的Entity
        • POST
        • PUT
      • 使用Payload的方式向导航属性设置一个新的Entity
    • 删除(DELETE)
      • 将基类类型Entity的导航属性的值设置为空
      • 将派生类型Entity的导航属性的值设置为空
      • 从基类类型Entity的导航属性中删除指定Entity的引用
      • 从派生类型Entity的导航属性中删除指定Entity的引用
  • 主程序
    • 服务文档
    • 模型元文档
  • 代码地址
  • 参考资料

在 OData 协议中,Reference(引用) 是一种专门用于管理实体之间关系的机制。它允许客户端通过标准的 RESTful 操作(如 POST、PUT、DELETE)直接操作实体之间的导航属性,而不是修改整个实体对象。这种方式非常适合处理如“订单-客户”、“员工-主管”等一对一或一对多的关联关系。
我们在《ASP.NET Core OData 实践——Lesson7使用Reference增删改查一对一Navigation Property(C#)》中已经介绍了Reference相关的知识,本文不再赘述。本文将以“员工-企业”这样的“一对多”案例进行讲解,其中主管是“引用对象”, “员工”是被引用对象。

主要模型设计

在项目下新增Models文件夹,并添加Customer、Employee、EnterpriseCustomer和Order四个类。

namespace Lesson7.Models
{public class Customer{public int Id { get; set; }public required string Name { get; set; }public List<Order> Orders { get; set; } = new List<Order>();}
}
namespace Lesson7.Models
{public class Employee{public int Id { get; set; }public required string Name { get; set; }}
}
namespace Lesson7.Models
{public class EnterpriseCustomer : Customer{public List<Employee> RelationshipManagers { get; set; } = new List<Employee>();}
}
namespace Lesson7.Models
{public class Order{public int Id { get; set; }public decimal Amount { get; set; }}
}

在这里插入图片描述
Customer有一个集合型Navigation Property——Orders 。
EnterpriseCustomer 继承于Customer,其自身有一个集合型Navigation Property——RelationshipManagers。

支持的接口

OData $ref 设计用于管理实体之间的关系(如添加、删除、替换引用),标准只定义了 GET、POST、PUT、DELETE 方法。
OData Reference($ref)操作本身不支持 PATCH 请求。这是因为PATCH是局部更新,而Reference不能更新“被引用对象”的属性,它只修改“被引用对象”和“引用对象”之间的关系;
对于不存在的“被引用对象”,我们可以通过POST或者PUT方法新建;如果“被引用对象”已经存在,则可以直接和“引用对象”建立关系。

以下是“一对一”和“一对多”都支持的接口

Request MethodRoute Template说明
GET~/{entityset}/{key}/{navigationproperty}/$ref查询基类类型Entity的导航属性
GET~/{entityset}/{key}/{cast}/{{navigationproperty}/$ref查询派生类型Entity的导航属性
GET~/{singleton}/{navigationproperty}/$ref查询基类类型单例的导航属性
GET~/{singleton}/{cast}/{navigationproperty}/$ref查询派生类型单例的导航属性
POST~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref向基类类型Entity的导航属性设置一个新的Entity
POST~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
POST~/{entityset}/{key}/{navigationproperty}/$ref向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
POST~/{entityset}/{key}/{cast}/{navigationproperty}/$ref向派生类型Entity的导航属性设置一个新的Entity(Payload传递)
POST~/{singleton}/{navigationproperty}/{relatedkey}/$ref向基类类型单例的导航属性设置一个新的Entity
POST~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型单例的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref向基类类型Entity的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{navigationproperty}/$ref向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
PUT~/{entityset}/{key}/{cast}/{navigationproperty}/$ref向派生类类型Entity的导航属性设置一个新的Entity(Payload传递)
PUT~/{singleton}/{navigationproperty}/{relatedkey}/$ref向基类类型单例的导航属性设置一个新的Entity
PUT~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型单例的导航属性设置一个新的Entity
PUT~/{singleton}/{navigationproperty}/$ref向基类类型单例的导航属性设置一个新的Entity(Payload传递)
DELETE~/{entityset}/{key}/{navigationproperty}/$ref将基类类型Entity的导航属性的值设置为空
DELETE~/{entityset}/{key}/{cast}/{navigationproperty}/$ref将派生类型Entity的导航属性的值设置为空
DELETE~/{singleton}/{navigationproperty}/$ref将基类类型单例的导航属性的值设置为空
DELETE~/{singleton}/{cast}/{navigationproperty}/$ref将派生类型单例的导航属性的值设置为空

以下是“一对多”支持的接口

Request MethodRoute Template说明
GET~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref查询基类类型Entity的导航属性集合中指定Entity
GET~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref查询派生类类型Entity的导航属性集合中指定Entity
GET~/{singleton}/{navigationproperty}/{relatedkey}/$ref查询基类类型单例的导航属性集合中的指定Entity
GET~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref查询派生类型单例的导航属性集合中指定Entity
DELETE~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref从基类类型Entity的导航属性中删除指定Entity的引用
DELETE~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref从派生类型Entity的导航属性中删除指定Entity的引用
DELETE~/{singleton}/{navigationproperty}/{relatedkey}/$ref从基类类型单例导航的属性中删除指定Entity的引用
DELETE~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref从派生类型单例的导航属性中删除指定Entity的引用

我们看到Reference是没有PATCH指令,即不能局部更新。就是连集合型导航属性的PATCH指令也不支持。

控制器设计

在项目中新增Controller文件夹,然后添加CustomersController类。该类注册于ODataController,以便拥有如下能力:

  1. OData 路由支持
    继承 ODataController 后,控制器自动支持 OData 路由(如 /odata/Shapes(1)),可以直接响应 OData 标准的 URL 路径和操作。
  2. OData 查询参数支持
    可以使用 [EnableQuery] 特性,自动支持 $filter、$select、$orderby、$expand 等 OData 查询参数,无需手动解析。
  3. OData 响应格式
    返回的数据会自动序列化为 OData 标准格式(如 JSON OData),方便前端或其他系统消费。
  4. OData Delta 支持
    支持 Delta<T>、DeltaSet<T> 等类型,便于实现 PATCH、批量 PATCH 等 OData 特有的部分更新操作。
  5. 更丰富的 OData 语义
    继承后可方便实现实体集、实体、导航属性、复杂类型等 OData 语义,提升 API 的表达能力。
using Lesson7.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.OData.Deltas;
using Microsoft.AspNetCore.OData.Query;
using Microsoft.AspNetCore.OData.Routing.Controllers;namespace Lesson7.Controllers
{public class CustomersController: ODataController{}
}

下面我们在该类中填充逻辑。

数据源

        private static List<Order> orders =[new Order { Id = 1, Amount = 80 },new Order { Id = 2, Amount = 40 },new Order { Id = 3, Amount = 50 },new Order { Id = 4, Amount = 65 }];private static List<Employee> employees =[new Employee { Id = 1, Name = "Employee 1" },new Employee { Id = 2, Name = "Employee 2" }];private static List<Customer> customers =[new Customer{Id = 1,Name = "Customer 1",Orders = new List<Order>{orders.SingleOrDefault(d => d.Id == 1) ?? throw new InvalidOperationException("Order with Id 1 not found."),orders.SingleOrDefault(d => d.Id == 2) ?? throw new InvalidOperationException("Order with Id 2 not found.")},},new EnterpriseCustomer{Id = 2,Name = "Customer 2",Orders = new List<Order>{orders.SingleOrDefault(d => d.Id == 3) ?? throw new InvalidOperationException("Order with Id 3 not found."),orders.SingleOrDefault(d => d.Id == 4) ?? throw new InvalidOperationException("Order with Id 4 not found.")},RelationshipManagers = new List<Employee> {employees.SingleOrDefault(d => d.Id == 1) ?? throw new InvalidOperationException("Employee with Id 1 not found."),employees.SingleOrDefault(d => d.Id == 2) ?? throw new InvalidOperationException("Employee with Id 2 not found.")}}];

我们构造了两个Customer:一个基础类型Customer的Entity,一个是派生类型EnterpriseCustomer的Entity。

查询(GET)

Request MethodRoute Template说明
GET~/{entityset}/{key}/{navigationproperty}/$ref查询基类类型Entity的导航属性
GET~/{entityset}/{key}/{cast}/{{navigationproperty}/$ref查询派生类型Entity的导航属性
GET~/{singleton}/{navigationproperty}/$ref查询基类类型单例的导航属性
GET~/{singleton}/{cast}/{navigationproperty}/$ref查询派生类型单例的导航属性
GET~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref查询基类类型Entity的导航属性集合中指定Entity
GET~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref查询派生类类型Entity的导航属性集合中指定Entity
GET~/{singleton}/{navigationproperty}/{relatedkey}/$ref查询基类类型单例的导航属性集合中的指定Entity
GET~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref查询派生类型单例的导航属性集合中指定Entity

查询基类类型Entity的导航属性

Request MethodRoute Template说明
GET~/{entityset}/{key}/{navigationproperty}/$ref查询基类类型Entity的导航属性
        [EnableQuery]public ActionResult<Order> GetRefToOrders([FromRoute] int key){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}return Ok(customer.Orders);}
  • Request
curl --location 'http://localhost:5119/odata/Customers(1)/Orders/$ref'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Collection(Lesson7.Models.Order)","value": [{"Id": 1,"Amount": 80},{"Id": 2,"Amount": 40}]
}

查询派生类型Entity的导航属性

Request MethodRoute Template说明
GET~/{entityset}/{key}/{cast}/{{navigationproperty}/$ref查询派生类型Entity的导航属性
        [EnableQuery]public ActionResult<Employee> GetRefToRelationshipManagersFromEnterpriseCustomer([FromRoute] int key) {var customer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}return Ok(customer.RelationshipManagers);}
  • Request
curl --location 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers/$ref'

这儿需要注意派生类型和导航属性的关系。
如果导航属性定义在派生类型自身,而不是派生类型通过继承获得,则可以通过Reference机制访问到。比如本例中导航属性RelationshipManagers是定义在EnterpriseCustomer中的。
如果导航属性是派生类型通过继承形式获得,则不可以通过派生类型查询到该导航属性。比如本例中Customer.Orders也是导航属性,EnterpriseCustomer通过继承于Customer获得该属性,那不能通过URIodata/Customers(2)/Lesson7.Models.EnterpriseCustomer/Orders/$ref请求到Orders,哪怕定义了对应的方法GetRefToOrdersFromEnterpriseCustomer也不行。

  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Collection(Lesson7.Models.Employee)","value": [{"Id": 1,"Name": "Employee 1"},{"Id": 2,"Name": "Employee 2"}]
}

查询基类类型Entity的导航属性集合中指定Entity

Request MethodRoute Template说明
GET~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref查询基类类型Entity的导航属性集合中指定Entity
        [EnableQuery]public ActionResult<Order> GetRefToOrders([FromRoute] int key, [FromRoute] int relatedKey){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}var relatedOrder = customer.Orders.SingleOrDefault(d => d.Id.Equals(relatedKey));if (relatedOrder == null){return NotFound();}return Ok(relatedOrder);}
  • Request
curl --location 'http://localhost:5119/odata/Customers(1)/Orders(2)/$ref'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Lesson7.Models.Order","Id": 2,"Amount": 40
}

查询派生类类型Entity的导航属性集合中指定Entit

Request MethodRoute Template说明
GET~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref查询派生类类型Entity的导航属性集合中指定Entity
        [EnableQuery]public ActionResult<Employee> GetRefToRelationshipManagersFromEnterpriseCustomer([FromRoute] int key, [FromRoute] int relatedKey){var customer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}var relatedEmployee = customer.RelationshipManagers.SingleOrDefault(d => d.Id == relatedKey);if (relatedEmployee == null){return NotFound();}return Ok(relatedEmployee);}
  • Request
curl --location 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers(1)/$ref'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata#Lesson7.Models.Employee","Id": 1,"Name": "Employee 1"
}

新增(POST)和 完整更新(PUT)

对于单值(一对一)导航属性(如 Order.Customer),OData 规范规定:

  • POST 和 PUT 到 /odata/Orders({key})/Customer/$ref 都表示“建立或替换”关系。
  • 两者的语义在单值导航属性下没有区别,都是把 Order.Customer 指向新的 Customer。

所以POST 和 PUT 都会路由到同一个方法(如 CreateRefToCustomer )。

Request MethodRoute Template说明
POST~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref向基类类型Entity的导航属性设置一个新的Entity
POST~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
POST~/{entityset}/{key}/{navigationproperty}/$ref向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
POST~/{entityset}/{key}/{cast}/{navigationproperty}/$ref向派生类型Entity的导航属性设置一个新的Entity(Payload传递)
POST~/{singleton}/{navigationproperty}/{relatedkey}/$ref向基类类型单例的导航属性设置一个新的Entity
POST~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型单例的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref向基类类型Entity的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{navigationproperty}/$ref向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
PUT~/{entityset}/{key}/{cast}/{navigationproperty}/$ref向派生类类型Entity的导航属性设置一个新的Entity(Payload传递)
PUT~/{singleton}/{navigationproperty}/{relatedkey}/$ref向基类类型单例的导航属性设置一个新的Entity
PUT~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型单例的导航属性设置一个新的Entity
PUT~/{singleton}/{navigationproperty}/$ref向基类类型单例的导航属性设置一个新的Entity(Payload传递)

向基类类型Entity的导航属性建立或替换一个新的Entity

Request MethodRoute Template说明
POST~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref向基类类型Entity的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref向基类类型Entity的导航属性设置一个新的Entity
        public ActionResult CreateRefToOrders([FromRoute] int key, [FromRoute] int relatedKey){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}var relatedOrder = customer.Orders.SingleOrDefault(d => d.Id.Equals(relatedKey));if (relatedOrder == null){relatedOrder = new Order { Id = relatedKey, Amount = relatedKey * 10 };orders.Add(relatedOrder);}if (!customer.Orders.Any(d => d.Id.Equals(relatedOrder.Id))){customer.Orders.Add(relatedOrder);}return NoContent();}

CreateRefToOrders根据订单主键 key 查找Customer 对象,然后根据客户主键 relatedKey 查找Order对象,如果不存在则新建并加入集合,最后在Customer的导航属性集合中新增该Order对象。

POST
  • Request
curl --location --request POST 'http://localhost:5119/odata/Customers(1)/Orders(5)/$ref'
PUT
curl --location --request PUT 'http://localhost:5119/odata/Customers(1)/Orders(3)/$ref'

向派生类型Entity的导航属性建立或替换一个新的Entity

Request MethodRoute Template说明
POST~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
PUT~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
        public ActionResult CreateRefToRelationshipManagersFromEnterpriseCustomer([FromRoute] int key, [FromRoute] int relatedKey){var customer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}var relatedEmployee = customer.RelationshipManagers.SingleOrDefault(d => d.Id == relatedKey);if (relatedEmployee == null){relatedEmployee = new Employee { Id = relatedKey, Name = $"Employee {relatedKey}" };employees.Add(relatedEmployee);}if (!customer.RelationshipManagers.Any(d => d.Id == relatedKey)){// Add the employee to the relationship managerscustomer.RelationshipManagers.Add(relatedEmployee);}return NoContent();}

CreateRefToRelationshipManagersFromEnterpriseCustomer根据主键 key 查找对应的 EnterpriseCustomer实例,然后根据Employee主键 relatedKey 查找员工对象,如果不存在则新建并加入集合。最后在EnterpriseCustomer的导航属性集合RelationshipManagers假如该员工对象。

POST
Request MethodRoute Template说明
POST~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
  • Request
curl --location --request POST 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers(2)/$ref'
PUT
Request MethodRoute Template说明
PUT~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref向派生类型Entity的导航属性设置一个新的Entity
  • Request
curl --location --request PUT 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers(2)/$ref'

使用Payload的方式向导航属性设置一个新的Entity

Request MethodRoute Template说明
POST~/{entityset}/{key}/{navigationproperty}/$ref向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
POST~/{entityset}/{key}/{cast}/{navigationproperty}/$ref向派生类型Entity的导航属性设置一个新的Entity(Payload传递)
PUT~/{entityset}/{key}/{navigationproperty}/$ref向基类类型Entity的导航属性设置一个新的Entity(Payload传递)
PUT~/{entityset}/{key}/{cast}/{navigationproperty}/$ref向派生类类型Entity的导航属性设置一个新的Entity(Payload传递)

见《ASP.NET Core OData 实践——Lesson7通过Payload修改Reference(C#)》。

删除(DELETE)

Request MethodRoute Template说明
DELETE~/{entityset}/{key}/{navigationproperty}/$ref将基类类型Entity的导航属性的值设置为空
DELETE~/{entityset}/{key}/{cast}/{navigationproperty}/$ref将派生类型Entity的导航属性的值设置为空
DELETE~/{singleton}/{navigationproperty}/$ref将基类类型单例的导航属性的值设置为空
DELETE~/{singleton}/{cast}/{navigationproperty}/$ref将派生类型单例的导航属性的值设置为空
DELETE~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref从基类类型Entity的导航属性中删除指定Entity的引用
DELETE~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref从派生类型Entity的导航属性中删除指定Entity的引用
DELETE~/{singleton}/{navigationproperty}/{relatedkey}/$ref从基类类型单例导航的属性中删除指定Entity的引用
DELETE~/{singleton}/{cast}/{navigationproperty}/{relatedkey}/$ref从派生类型单例的导航属性中删除指定Entity的引用

将基类类型Entity的导航属性的值设置为空

Request MethodRoute Template说明
DELETE~/{entityset}/{key}/{navigationproperty}/$ref将基类类型Entity的导航属性的值设置为空
        public ActionResult DeleteRefToOrders([FromRoute] int key){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}customer.Orders.Clear();return NoContent();}
  • Request

将派生类型Entity的导航属性的值设置为空

Request MethodRoute Template说明
DELETE~/{entityset}/{key}/{cast}/{navigationproperty}/$ref将派生类型Entity的导航属性的值设置为空
        public ActionResult DeleteRefToRelationshipManagersFromEnterpriseCustomer([FromRoute] int key){var customer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}customer.RelationshipManagers.Clear();return NoContent();}
  • Request
curl --location --request DELETE 'http://localhost:5119/odata/Customers(1)/Orders/$ref'

从基类类型Entity的导航属性中删除指定Entity的引用

Request MethodRoute Template说明
DELETE~/{entityset}/{key}/{navigationproperty}/{relatedkey}/$ref从基类类型Entity的导航属性中删除指定Entity的引用
        public ActionResult DeleteRefToOrders([FromRoute] int key, [FromRoute] int relatedKey){var customer = customers.SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}var relatedOrder = customer.Orders.SingleOrDefault(d => d.Id.Equals(relatedKey));if (relatedOrder != null){customer.Orders.Remove(relatedOrder);}return NoContent();}
  • Request
curl --location --request DELETE 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers/$ref'

从派生类型Entity的导航属性中删除指定Entity的引用

Request MethodRoute Template说明
DELETE~/{entityset}/{key}/{cast}/{navigationproperty}/{relatedkey}/$ref从派生类型Entity的导航属性中删除指定Entity的引用
        public ActionResult DeleteRefToRelationshipManagersFromEnterpriseCustomer([FromRoute] int key, [FromRoute] int relatedKey){var customer = customers.OfType<EnterpriseCustomer>().SingleOrDefault(d => d.Id.Equals(key));if (customer == null){return NotFound();}var relatedEmployee = customer.RelationshipManagers.SingleOrDefault(d => d.Id == relatedKey);if (relatedEmployee != null){customer.RelationshipManagers.Remove(relatedEmployee);}return NoContent();}
  • Request
curl --location --request DELETE 'http://localhost:5119/odata/Customers(2)/Lesson7.Models.EnterpriseCustomer/RelationshipManagers(1)/$ref'

主程序

Reference机制并不需要在主程序中进行特殊设置。

using Lesson7.Models;
using Microsoft.AspNetCore.OData;
using Microsoft.OData.ModelBuilder;
using Microsoft.OData.Edm;var builder = WebApplication.CreateBuilder(args);// 提取 OData EDM 模型构建为方法,便于维护和扩展
static IEdmModel GetEdmModel()
{var modelBuilder = new ODataConventionModelBuilder();modelBuilder.EntitySet<Customer>("Customers");return modelBuilder.GetEdmModel();
}// 添加 OData 服务和配置
builder.Services.AddControllers().AddOData(options =>options.Select().Filter().OrderBy().Expand().Count().SetMaxTop(null).AddRouteComponents("odata", GetEdmModel())
);var app = builder.Build();app.UseRouting();app.MapControllers();app.Run();

服务文档

  • Request
curl --location 'http://localhost:5119/odata'
  • Response
{"@odata.context": "http://localhost:5119/odata/$metadata","value": [{"name": "Customers","kind": "EntitySet","url": "Customers"}]
}

模型元文档

  • Request
curl --location 'http://localhost:5119/odata/$metadata'
  • Response
<?xml version="1.0" encoding="utf-8"?>
<edmx:Edmx Version="4.0" xmlns:edmx="http://docs.oasis-open.org/odata/ns/edmx"><edmx:DataServices><Schema Namespace="Lesson7.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityType Name="Customer"><Key><PropertyRef Name="Id" /></Key><Property Name="Id" Type="Edm.Int32" Nullable="false" /><Property Name="Name" Type="Edm.String" Nullable="false" /><NavigationProperty Name="Orders" Type="Collection(Lesson7.Models.Order)" /></EntityType><EntityType Name="Order"><Key><PropertyRef Name="Id" /></Key><Property Name="Id" Type="Edm.Int32" Nullable="false" /><Property Name="Amount" Type="Edm.Decimal" Nullable="false" Scale="variable" /></EntityType><EntityType Name="EnterpriseCustomer" BaseType="Lesson7.Models.Customer"><NavigationProperty Name="RelationshipManagers" Type="Collection(Lesson7.Models.Employee)" /></EntityType><EntityType Name="Employee"><Key><PropertyRef Name="Id" /></Key><Property Name="Id" Type="Edm.Int32" Nullable="false" /><Property Name="Name" Type="Edm.String" Nullable="false" /></EntityType></Schema><Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm"><EntityContainer Name="Container"><EntitySet Name="Customers" EntityType="Lesson7.Models.Customer" /></EntityContainer></Schema></edmx:DataServices>
</edmx:Edmx>

代码地址

https://github.com/f304646673/odata/tree/main/csharp/Lesson/Lesson7

参考资料

  • https://learn.microsoft.com/en-us/odata/webapi-8/fundamentals/ref-routing?tabs=net60%2Cvisual-studio
http://www.xdnf.cn/news/712063.html

相关文章:

  • AU6815集成音频DSP的2x25W数字型ClaSS D音频功率放大器(替代TAS5805)
  • LabVIEW旋转机械智能监测诊断系统
  • 02.MySQL库的操作
  • 涨薪技术|0到1学会性能测试第90课-性能测试构建
  • 设计模式-发布订阅
  • Docker安装
  • SpringCloud基础知识
  • Unity 中 Update、FixedUpdate 和 LateUpdate 的区别及使用场景
  • AMBA-AHB仲裁机制
  • RabbitMQ备份与恢复技术详解:策略、工具与最佳实践
  • 如何在WSL的Ubuntu里面启动腾讯微搭
  • excel表格记账 : 操作单元格进行加减乘除 | Excel中Evaluate函数
  • 关于DDOS
  • SQL Server 中创建链接服务器
  • android无root抓包(PCAPdroid)
  • Spring框架学习day1--基础概念
  • 深度解析UniApp盲盒系统开发:从源码架构到多端部署全流程
  • 基于STM32F10X的BMP280程序
  • 滚珠导轨:电子制造“纳米级”精度的运动基石
  • 如何用命令行将 PDF 表格转换为 HTML 表格
  • 责任链模式:构建灵活可扩展的请求处理体系(Java 实现详解)
  • ZYNQ移植FreeRTOS和固化和openAMP双核
  • 设备制造行业项目管理难点解析,如何有效解决?
  • 塔能科技:为多行业工厂量身定制精准节能方案
  • Kotlin 活动事件通讯跳转深度讲解
  • 职业本科院校无人机专业人才培养解决方案
  • KeePass安装与KeePass设置中文教程
  • springboot多模块父pom打包正常,单模块报错
  • clickhouse如何查看操作记录,从日志来查看写入是否成功
  • 湖北理元理律师事务所债务优化实践:在还款与生活间寻找平衡支点