# 服务元数据

OData V4 服务通过元数据接口描述数据模型：实体类型、属性、关系和可用操作。

## 目录

- [概述](#概述)
- [服务文档](#服务文档)
- [解析后的元数据](#解析后的元数据)
- [原始元数据 XML](#原始元数据-xml)
- [元数据缓存](#元数据缓存)
- [OData 类型映射](#odata-类型映射)

## 概述

OData 服务提供两个发现接口：

| 接口 | 返回内容 | 用途 |
|------|---------|------|
| `/` | 服务文档（JSON） | 列出可用的实体集、单例、函数导入 |
| `/$metadata` | CSDL XML Schema | 完整类型定义、Key、导航属性等 |

## 服务文档

### 获取服务文档

```python
doc = await client.get_service_document_async()

print(f"Context：{doc.context}")
```

### 枚举资源

```python
doc = await client.get_service_document_async()

print("实体集：")
for es in doc.entity_sets:
    print(f"  {es.name}  →  {es.url}")

print("单例：")
for s in doc.singletons:
    print(f"  {s.name}  →  {s.url}")

print("函数导入：")
for fi in doc.function_imports:
    print(f"  {fi.name}  →  {fi.url}")
```

### 查找特定资源

```python
products = doc.get_resource("Products")
if products is not None:
    print(f"Products 地址：{products.url}")
    print(f"类型：{products.kind}")   # ODataServiceResourceKind.EntitySet
```

### 资源类型

```python
from odms_odata import ODataServiceResourceKind

# resource.kind 的取值：
ODataServiceResourceKind.EntitySet      # 实体集
ODataServiceResourceKind.Singleton      # 单例
ODataServiceResourceKind.FunctionImport # 函数导入
```

### 动态服务发现

```python
async def get_available_entity_sets() -> list[str]:
    doc = await client.get_service_document_async()
    return [es.name for es in doc.entity_sets]
```

## 解析后的元数据

### 获取解析后的元数据

`get_metadata_async()` 获取 `/$metadata`，解析 CSDL XML，返回带强类型字段的 `ODataMetadata` 对象：

```python
md = await client.get_metadata_async()

print(f"命名空间：  {md.namespace}")
print(f"实体类型数：{len(md.entity_types)}")
print(f"实体集数：  {len(md.entity_sets)}")
```

### 浏览实体类型

```python
md = await client.get_metadata_async()

for et in md.entity_types:
    print(f"\n实体：{et.name}")
    print(f"  基类型：    {et.base_type or '无'}")
    print(f"  是否抽象：  {et.is_abstract}")
    print(f"  是否开放：  {et.is_open_type}")
    print(f"  包含流：    {et.has_stream}")
    print(f"  Key：       {', '.join(et.key)}")

    for prop in et.properties:
        nullable = "?" if prop.is_nullable else ""
        print(f"  - {prop.name}: {prop.type}{nullable}")
        if prop.precision:
            print(f"      精度={prop.precision}, 小数位={prop.scale}")

    for nav in et.navigation_properties:
        kind = "集合" if nav.is_collection else "单值"
        print(f"  -> {nav.name}: {nav.target_type} ({kind})")
```

### 按名称查找实体类型

```python
product_type = md.get_entity_type("Product")
if product_type is not None:
    price_prop = product_type.get_property("Price")
    if price_prop:
        print(f"Price 类型：  {price_prop.type}")
        print(f"Price 可空：  {price_prop.is_nullable}")
        print(f"Price 精度：  {price_prop.precision}")

    cat_nav = product_type.get_navigation_property("Category")
    if cat_nav:
        print(f"Category 目标类型：  {cat_nav.target_type}")
        print(f"Category 是否集合：  {cat_nav.is_collection}")
```

### 浏览实体集

```python
for es in md.entity_sets:
    print(f"实体集：{es.name}  →  {es.entity_type}")
    for binding in es.navigation_property_bindings:
        print(f"  绑定：{binding.path} → {binding.target}")
```

### 浏览单例

```python
for s in md.singletons:
    print(f"单例：{s.name}  类型={s.type}")
```

### 浏览复杂类型

```python
for ct in md.complex_types:
    print(f"复杂类型：{ct.name}")
    for prop in ct.properties:
        print(f"  - {prop.name}: {prop.type}")
```

### 浏览枚举类型

```python
for et in md.enum_types:
    print(f"枚举：{et.name}  (底层类型={et.underlying_type}, 标志位={et.is_flags})")
    for member in et.members:
        print(f"  {member.name} = {member.value}")
```

### 浏览函数和操作导入

```python
for fi in md.function_imports:
    print(f"函数导入：{fi.name}  ref={fi.function}  实体集={fi.entity_set}")

for ai in md.action_imports:
    print(f"操作导入：{ai.name}  ref={ai.action}")
```

### 按名称查找实体集

```python
es = md.get_entity_set("Products")
if es is not None:
    print(f"Products 实体类型：{es.entity_type}")
```

### 直接解析 XML

如果已有原始 CSDL XML 字符串，可直接调用 `parse_metadata_xml`：

```python
from odms_odata import parse_metadata_xml

xml = await client.get_metadata_xml_async()
md = parse_metadata_xml(xml)
```

## 原始元数据 XML

```python
# 返回原始 CSDL XML 字符串
xml = await client.get_metadata_xml_async()
print(xml[:500])
```

## 元数据缓存

OData 元数据在运行时很少变化，启用缓存可避免重复网络请求：

### 启用缓存

```python
from datetime import timedelta
from odms_odata import ODataClient, ODataClientOptions

client = ODataClient(ODataClientOptions(
    base_url="https://...",
    metadata_cache_duration=timedelta(hours=1),
))

# 第一次调用——从服务器获取
md1 = await client.get_metadata_async()

# 后续调用——返回缓存数据（不发起 HTTP 请求）
md2 = await client.get_metadata_async()
```

### 强制刷新

```python
# 绕过缓存，从服务器获取最新数据
md  = await client.get_metadata_async(force_refresh=True)
xml = await client.get_metadata_xml_async(force_refresh=True)
```

两个方法共享同一缓存，获取其中一个会使另一个也受益。

### 手动清除缓存

```python
# 清除缓存数据，下次调用时重新从服务器获取
client.invalidate_metadata_cache()
```

### 缓存时长选项

| `metadata_cache_duration` | 行为 |
|---------------------------|------|
| `None`（默认） | 不缓存，每次都请求服务器 |
| `timedelta(hours=1)` | 缓存 1 小时 |
| `timedelta.max` | 永久缓存（直到手动清除） |

## OData 类型映射

常见 EDM 类型与 Python 类型的对应关系：

| EDM 类型 | Python 类型 |
|----------|-------------|
| `Edm.String` | `str` |
| `Edm.Int16` / `Edm.Int32` / `Edm.Int64` | `int` |
| `Edm.Boolean` | `bool` |
| `Edm.Decimal` / `Edm.Double` / `Edm.Single` | `float` |
| `Edm.DateTime` / `Edm.DateTimeOffset` | `datetime` |
| `Edm.Guid` | `uuid.UUID` |
| `Edm.Binary` | `bytes` |
| `Collection(Edm.String)` | `list[str]` |
