大纲
在《Open Data Protocol理论和实践——Lesson1-1搭建服务(C#)》一文中,我们搭建了一个服务。它创建了3个Customer Entity,每个Customer Entity又有2个Order Entity。
private static Random random = new Random();
// 生成 3 个客户,每个客户有 2 个订单
private static readonly List<Customer> customers = [.. Enumerable.Range(1, 3).Select(idx => new Customer
{
Id = idx,
Name = $"Customer {idx}",
Orders = [.. Enumerable.Range(1, 2).Select(dx => new Order
{
Id = (idx - 1) * 2 + dx,
Amount = random.Next(1, 9) * 10
})]
})];
本文我们将通过Postman查询这些信息。
资源文档
一个 OData 服务会公开两个明确定义的、用于描述其数据模型的资源:服务文档(Service Document)
和元数据文档(Metadata Document)
。
服务文档(Service Document)
服务文档(Service Document)
是客户端访问 OData 服务时的入口,通常通过 GET /odata/ 获取。它返回当前服务中可用的实体集(Entity Sets)
、函数(Functions)
和单例(Singletons)
,主要用于告诉客户端:
- 这个 OData 服务有哪些可用的资源(如实体集、单例、函数等)
- 每个资源的访问路径
这有助于客户端动态发现和导航 OData 服务的数据结构。
主要作用
- 资源发现
告诉客户端有哪些数据集合和操作可以访问。例如,哪些实体集(如 Customers)可以被查询。 - 导航入口
客户端可以根据服务文档中的信息,进一步访问具体的数据资源或元数据文档。 - 自描述性
服务文档让客户端无需事先了解服务结构,就能动态发现和使用服务提供的资源。
访问方式
Request
GET http://localhost:5119/odata
Response
{
"@odata.context": "http://localhost:5119/odata/$metadata",
"value": [
{
"name": "Customers",
"kind": "EntitySet",
"url": "Customers"
}
]
}
这个返回结果包含了两组信息:
-
@odata.context:“http://localhost:5119/odata/$metadata”
指向当前 OData 服务的元数据文档(metadata)
,描述了所有实体类型(Entity Type)
、实体集(Entity Set)
、关系(Relationship)
等详细结构。客户端可以通过访问这个 URL 获取完整的服务模型定义。 -
value 数组中的对象
- name:“Customers”
资源的名称,这里是实体集 Customers,对应模型和控制器。 - kind:“EntitySet”
资源类型,这里表示这是一个实体集(EntitySet)
,即一组 Customer 实体。 - url:“Customers”
访问该资源的相对路径。完整路径为http://localhost:5119/odata/Customers
,可以通过这个地址获取所有客户数据。
- name:“Customers”
元数据文档(Metadata Document)
元数据文档(Metadata Document)
通常通过访问 /odata/$metadata
获取,是一个用 XML 格式描述的文档。它的主要作用是详细描述 OData 服务的数据结构和能力,包括:
- 所有实体类型(如 Customer、Order)及其属性
- 实体之间的关系(如
导航属性(Navigation Properties)
) - 实体集(如 Customers)
- 复杂类型、枚举类型、操作(Function/Action)等
主要用途
- 自描述性
让客户端(如前端应用、第三方系统、自动化工具)能够自动了解服务的数据模型和结构,无需人工文档。 - 代码生成
很多开发工具(如 Visual Studio、Postman、OData Client)可以根据元数据文档自动生成客户端代码或数据访问层。 - 动态查询和验证
客户端可以根据元数据文档动态构建查询、校验字段和类型,提升兼容性和灵活性。
访问方式
Request
GET 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="Lesson1.Models" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<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="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(Lesson1.Models.Order)" />
</EntityType>
</Schema>
<Schema Namespace="Default" xmlns="http://docs.oasis-open.org/odata/ns/edm">
<EntityContainer Name="Container">
<EntitySet Name="Customers" EntityType="Lesson1.Models.Customer" />
</EntityContainer>
</Schema>
</edmx:DataServices>
</edmx:Edmx>
可以看到上述返回结果中有两个Namespace。
- Namespace="Lesson1.Models"下展示了该命名空间下的模型定义。
- Namespace="Default"是 OData 默认生成。EntityContainer 被放在 Default 命名空间下是OData生成器的默认行为,表示“服务的根容器”属于 Default 命名空间。
EntityContainer(实体容器)
是 OData 和 EDM(实体数据模型)
中的一个核心概念,用于组织和管理服务中所有可访问的实体集(EntitySet)
、单例(Singleton)
、操作(Action/Function)
等资源的集合。它起到如下作用:
• 资源入口:EntityContainer 是 OData 服务的“资源目录”,定义了客户端可以直接访问的所有数据集合和操作。
• 实体集注册:在例子中,EntitySet Name=“Customers” 表示有一个名为 Customers 的实体集,类型为 Lesson1.Models.Customer,客户端可以通过 /odata/Customers 访问它。
• 唯一性:每个 OData 服务通常只有一个 EntityContainer,它是服务的根级容器。
Entity数据
通过元数据文档(Metadata Document)
的查询,我们知道了该服务可以查询Customers的数据,并且得知Customers的属性列表和特性(Orders是导航属性(Navigation Properties)
)。
查询所有数据
- Request
GET http://localhost:5119/odata/Customers
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers",
"value": [
{
"Id": 1,
"Name": "Customer 1"
},
{
"Id": 2,
"Name": "Customer 2"
},
{
"Id": 3,
"Name": "Customer 3"
}
]
}
可以发现,默认情况下导航属性(Navigation Properties)
(Orders)没有返回。
排序
通过使用 $orderby
查询选项,可以为返回的结果指定自定义排序顺序 —— 既可以使用asc
表示升序,也可以使用desc
表示降序。$orderby
查询选项接受以逗号分隔的排序列表以及对应的排序顺序。若未指定排序顺序,则默认采用升序。
- Request
http://localhost:5119/odata/Customers?$orderby=Id desc
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers",
"value": [
{
"Id": 3,
"Name": "Customer 3"
},
{
"Id": 2,
"Name": "Customer 2"
},
{
"Id": 1,
"Name": "Customer 1"
}
]
}
筛选
可以通过使用$filter
查询选项指定查询的表达式。
- Request
GET http://localhost:5119/odata/Customers?$filter=Id eq 1 or Id eq 3
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers",
"value": [
{
"Id": 1,
"Name": "Customer 1"
},
{
"Id": 3,
"Name": "Customer 3"
}
]
}
指定字段
通过使用$select
查询选项指定返回的属性(逗号(,)分隔多个属性)。
- Request
GET http://localhost:5119/odata/Customers?$select=Name
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers(Name)",
"value": [
{
"Name": "Customer 1"
},
{
"Name": "Customer 2"
},
{
"Name": "Customer 3"
}
]
}
查询单个数据
- Request
GET http://localhost:5119/odata/Customers(1)
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers/$entity",
"Id": 1,
"Name": "Customer 1"
}
展开导航属性(Navigation Properties)
通过使用$expand
查询选项指定需要展开的导航属性(Navigation Properties)
(逗号(,)分隔多个属性)。
- Request
GET http://localhost:5119/odata/Customers(1)?$expand=Orders
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers(Orders())/$entity",
"Id": 1,
"Name": "Customer 1",
"Orders": [
{
"Id": 1,
"Amount": 80
},
{
"Id": 2,
"Amount": 60
}
]
}
分页
$top
系统查询选项用于请求将被查询集合中的一定数量的项目包含在结果中。
$skip
系统查询选项用于请求跳过被查询集合中的一定数量的项目,且这些项目不包含在结果中。
客户端可以通过结合使用$top
和$skip
来请求特定页码的项目。
- Request
GET http://localhost:5119/odata/Customers?$top=1&skip=1
- Response
{
"@odata.context": "http://localhost:5119/odata/$metadata#Customers",
"value": [
{
"Id": 2,
"Name": "Customer 2"
}
]
}