在 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 Method | Route 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 Method | Route 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,以便拥有如下能力:
- OData 路由支持
继承 ODataController 后,控制器自动支持 OData 路由(如 /odata/Shapes(1)),可以直接响应 OData 标准的 URL 路径和操作。 - OData 查询参数支持
可以使用 [EnableQuery] 特性,自动支持 $filter、$select、$orderby、$expand 等 OData 查询参数,无需手动解析。 - OData 响应格式
返回的数据会自动序列化为 OData 标准格式(如 JSON OData),方便前端或其他系统消费。 - OData Delta 支持
支持 Delta<T>、DeltaSet<T> 等类型,便于实现 PATCH、批量 PATCH 等 OData 特有的部分更新操作。 - 更丰富的 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 managers
customer.RelationshipManagers.Add(relatedEmployee);
}
return NoContent();
}
CreateRefToRelationshipManagersFromEnterpriseCustomer
根据主键 key 查找对应的 EnterpriseCustomer实例,然后根据Employee主键 relatedKey 查找员工对象,如果不存在则新建并加入集合。最后在EnterpriseCustomer的导航属性集合RelationshipManagers假如该员工对象。
POST
Request Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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 Method | Route 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