# 查询数据

本文档介绍 odms-odata 支持的所有查询选项。

## 目录

- [基本查询](#基本查询)
- [链式执行](#链式执行)
- [过滤（$filter）](#过滤filter)
- [字段选择（$select）](#字段选择select)
- [展开关联实体（$expand）](#展开关联实体expand)
- [排序（$orderby）](#排序orderby)
- [分页（$skip、$top）](#分页skiptop)
- [计数（$count）](#计数count)
- [全文搜索（$search）](#全文搜索search)
- [聚合（$apply）](#聚合apply)
- [计算属性（$compute）](#计算属性compute)
- [派生类型（类型转换）](#派生类型类型转换)
- [辅助方法](#辅助方法)
- [自定义请求头](#自定义请求头)
- [原始 JSON 响应](#原始-json-响应)

## 基本查询

使用流式查询构建器创建查询：

```python
import asyncio
from odms_odata import ODataClient, ODataClientOptions

async def main():
    async with ODataClient(ODataClientOptions(base_url="https://services.odata.org/V4/OData/OData.svc/")) as client:

        # 为 Products 创建查询构建器
        query = client.for_("Products")

        # 执行查询，返回一页结果
        response = await client.get_async(query)

        for product in response.value:
            print(product)
```

### 使用类型化模型

传入模型类即可获得类型化结果：

```python
from dataclasses import dataclass

@dataclass
class Product:
    ID: int = 0
    Name: str = ""
    Price: float = 0.0
    Rating: int = 0

response = await client.get_async(client.for_("Products", model=Product))
for product in response.value:
    print(f"{product.ID}: {product.Name}")
```

odms-odata 支持 **Pydantic v2**、**Pydantic v1**、标准 **dataclass**、带 `from_dict` 类方法的类，以及普通 **dict**（不传 model 时的默认行为）。

## 链式执行

直接从构建器执行查询，代码更简洁：

```python
# 获取匹配的实体（一页）
response = await (
    client.for_("Products")
    .filter("Price gt 100")
    .order_by("Name")
    .get_async()
)

# 自动获取所有页（追踪 @odata.nextLink）
response = await (
    client.for_("Products")
    .filter("Rating gt 3")
    .get_all_async()
)

# 返回第一个匹配实体，无匹配则返回 None
product = await (
    client.for_("Products")
    .order_by("Price")
    .get_first_or_default_async()
)

# 恰好返回一个结果，0 个或多个时抛出异常
product = await (
    client.for_("Products")
    .filter("Name eq 'SpecialWidget'")
    .get_single_async()
)

# 返回一个或 None，多于一个时抛出异常
product = await (
    client.for_("Products")
    .filter("ID eq 123")
    .get_single_or_default_async()
)

# 仅返回数量，不返回实体数据
count = await (
    client.for_("Products")
    .filter("Price gt 50")
    .get_count_async()
)
```

## 过滤（$filter）

所有过滤表达式均为原始 OData 字符串。

### 简单比较

```python
# 数值比较
query = client.for_("Products").filter("Price gt 100")

# 多条件 AND
query = client.for_("Products").filter("Price gt 100 and Rating ge 4")

# 字符串函数
query = client.for_("Products").filter("contains(Name, 'Widget')")
query = client.for_("Products").filter("startswith(Name, 'Super')")
query = client.for_("Products").filter("endswith(Name, 'Pro')")

# 不区分大小写搜索
query = client.for_("Products").filter("contains(tolower(Name), 'widget')")
```

### 多个 `.filter()` 调用

每次调用追加一个子句，子句之间用 `and` 连接：

```python
query = (
    client.for_("Products")
    .filter("Price gt 50")
    .filter("Rating ge 3")
    .filter("InStock eq true")
)
# 生成：$filter=(Price gt 50) and (Rating ge 3) and (InStock eq true)
```

### 集合 Lambda（any/all）

```python
# any — 至少一个元素满足条件
query = client.for_("People").filter("Emails/any(e: contains(e,'@company.com'))")

# all — 所有元素都满足条件
query = client.for_("Orders").filter("Items/all(i: i/Price gt 10)")
```

### IN 子句

```python
categories = ["Electronics", "Tools"]
values = ",".join(f"'{c}'" for c in categories)
query = client.for_("Products").filter(f"Category in ({values})")
```

## 字段选择（$select）

```python
# 只返回指定字段，减少传输量
query = client.for_("Products").select("Id,Name,Price")

# 与其他选项组合使用
query = (
    client.for_("Products")
    .select("Id,Name,Price")
    .filter("Price gt 50")
)
```

## 展开关联实体（$expand）

```python
# 展开一个导航属性
query = client.for_("Products").expand("Category")

# 展开多个（逗号分隔或链式调用）
query = client.for_("Products").expand("Category,Supplier")

# 嵌套展开（原始 OData 字符串）
query = client.for_("Orders").expand("Customer($expand=Address)")

# 在展开中使用 $select
query = client.for_("Orders").expand("Items($select=Id,Quantity,Price)")
```

## 排序（$orderby）

```python
# 升序（默认）
query = client.for_("Products").order_by("Name")

# 降序
query = client.for_("Products").order_by("Price desc")

# 多字段排序（逗号分隔）
query = client.for_("Products").order_by("Category,Price desc")

# 链式调用
query = (
    client.for_("Products")
    .order_by("Category")
    .order_by("Price desc")
)
```

## 分页（$skip、$top）

```python
# 取前 10 条
query = client.for_("Products").top(10)

# 跳过前 20 条，取接下来的 10 条
query = client.for_("Products").skip(20).top(10)
```

### 服务器驱动分页

当服务器返回 `@odata.nextLink` 时，使用 `get_all_async` 自动追踪所有页，
或检查 `response.next_link` 手动分页：

```python
# 自动追踪所有页
response = await client.for_("Products").get_all_async()
print(f"共获取：{len(response.value)} 条")

# 手动分页
response = await client.for_("Products").top(100).get_async()
while response.next_link:
    # 处理下一页...
    break
```

## 计数（$count）

```python
# 在响应中包含 @odata.count
query = client.for_("Products").top(10).count()

response = await client.get_async(query)
print(f"总匹配数：{response.count}")
print(f"本页返回：{len(response.value)}")
```

### 仅获取数量

```python
# 只返回整数计数，不传输实体数据
count = await client.get_count_async(client.for_("Products").filter("Price gt 100"))

# 或通过链式简写
count = await client.for_("Products").filter("Price gt 100").get_count_async()
```

## 全文搜索（$search）

```python
# 全文搜索（服务器须支持 $search）
query = client.for_("Products").search("widget blue")
```

## 聚合（$apply）

```python
# 分组并聚合
query = client.for_("Products").apply(
    "groupby((Category),aggregate(Price with average as AvgPrice))"
)

# 先过滤再聚合
query = client.for_("Products").apply(
    "filter(Rating ge 4)/groupby((Category),aggregate($count as Count))"
)

response = await client.get_async(query)
for row in response.value:
    print(row)  # 包含 Category 和 AvgPrice 键的 dict
```

## 计算属性（$compute）

OData 4.01 的服务端计算列功能：

```python
query = (
    client.for_("OrderLines")
    .compute("Price mul Quantity as Total")
    .select("ProductName,Price,Quantity,Total")
)
```

## 派生类型（类型转换）

在继承层次中过滤到某个派生类型：

```python
query = (
    client.for_("People")
    .cast("Microsoft.OData.Service.Models.Employee")
    .filter("EmployeeId ne null")
)

response = await client.get_async(query)
```

## 辅助方法

### 按 Key 获取

```python
# 按 key 获取单个实体
product = await client.get_by_key_async("Products", 123)

# 同时获取 ETag（用于并发控制）
result  = await client.get_by_key_with_etag_async("Products", 123)
product = result.value
etag    = result.etag
```

### First / Single

```python
# 第一个匹配结果，无结果返回 None
product = await client.get_first_or_default_async(
    client.for_("Products").filter("Name eq 'Widget'")
)

# 恰好一个结果（0 个或多个时抛出异常）
product = await client.get_single_async(
    client.for_("Products").filter("Name eq 'UniqueWidget'")
)

# 一个或 None（多于一个时抛出异常）
product = await client.get_single_or_default_async(
    client.for_("Products").filter("ID eq 123")
)
```

## 自定义请求头

```python
query = (
    client.for_("Products")
    .with_header("Prefer", "return=representation")
    .with_header("OData-MaxVersion", "4.01")
)
```

## 原始 JSON 响应

```python
# 返回原始解析后的 JSON（dict），完全自主控制
data = await client.get_raw_async("Products?$filter=Price gt 100")
print(data)  # 包含 @odata.context、value 等键的 dict
```
