# 增删改查

本文档介绍创建、读取、更新和删除操作。

## 目录

- [读取实体](#读取实体)
- [创建实体](#创建实体)
- [更新实体（PATCH）](#更新实体patch)
- [替换实体（PUT）](#替换实体put)
- [删除实体](#删除实体)
- [错误处理](#错误处理)

## 读取实体

### 获取所有实体

```python
response = await client.get_async(client.for_("Products"))

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

### 获取所有页

当结果跨越多页时，`get_all_async` 会自动追踪 `@odata.nextLink`：

```python
response = await client.get_all_async(
    client.for_("Products").filter("Price gt 50").order_by("Name")
)
print(f"共获取 {len(response.value)} 条产品")
```

### 按 Key 获取

```python
# 整数 key
product = await client.get_by_key_async("Products", 123)

# 字符串 key
customer = await client.get_by_key_async("Customers", "ALFKI")

# 附带 select/expand
query = client.for_("Products").select("Id,Name,Price").expand("Category")
product = await client.get_by_key_async("Products", 123, query=query)
```

### 获取单个匹配实体

```python
query = client.for_("Products").filter("SKU eq 'ABC-123'")

# 无匹配或多个匹配时抛出异常
product = await client.get_single_async(query)

# 无匹配返回 None，多个匹配时抛出异常
product = await client.get_single_or_default_async(query)

# 返回第一个匹配，无匹配返回 None
product = await client.get_first_or_default_async(query)
```

## 创建实体

使用 `create_async` 通过 POST 创建新实体：

```python
new_product = {
    "Name": "超级组件",
    "Description": "一个很棒的组件",
    "Price": 29.99,
    "Rating": 5,
}

created = await client.create_async("Products", new_product)
print(f"创建成功，ID：{created['ID']}")
```

### 使用类型化模型

```python
from dataclasses import dataclass

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

new_product = {"Name": "超级组件", "Price": 29.99}
created = await client.create_async("Products", new_product, model=Product)
print(f"ID：{created.ID}")
```

### 附带自定义请求头

```python
created = await client.create_async(
    "Products",
    new_product,
    headers={"Prefer": "return=representation"},
)
```

### 实体模型（Pydantic 示例）

```python
from pydantic import BaseModel
from datetime import datetime

class Product(BaseModel):
    ID: int = 0
    Name: str = ""
    Description: str | None = None
    Price: float | None = None
    Rating: int | None = None
    ReleaseDate: datetime | None = None

new_product = Product(Name="超级组件", Price=29.99, Rating=5)
created = await client.create_async("Products", new_product, model=Product)
```

## 更新实体（PATCH）

使用 `update_async` 进行部分更新——只发送你修改的字段：

```python
# 更新单个字段
updated = await client.update_async("Products", 123, {"Price": 39.99})

# 更新多个字段
updated = await client.update_async(
    "Products",
    123,
    {"Name": "超级组件 Pro", "Price": 49.99, "Description": "更好的组件"},
)
```

### 字符串 / GUID Key

```python
# 字符串 key
updated = await client.update_async("Customers", "ALFKI", {"ContactName": "张三"})

# UUID key
import uuid
order_id = uuid.UUID("...")
updated = await client.update_async("Orders", order_id, {"Status": "已发货"})
```

### 附带乐观并发控制（ETag）

详见 [ETag 与并发控制](etag-concurrency.md)。

```python
from odms_odata import ODataConcurrencyError

result = await client.get_by_key_with_etag_async("Products", 123)
etag   = result.etag

try:
    updated = await client.update_async("Products", 123, {"Price": 39.99}, etag=etag)
except ODataConcurrencyError as exc:
    print(f"并发冲突！你的 ETag：{exc.request_etag}，当前：{exc.current_etag}")
```

### PATCH 与 PUT 的区别

| 方面 | PATCH（`update_async`） | PUT（`replace_async`） |
|------|------------------------|----------------------|
| 发送字段 | 只发送修改的字段 | 发送所有字段 |
| 未发送字段 | 服务器保持不变 | 被设为默认值/null |
| 适用场景 | 部分更新 | 完全替换 |

## 替换实体（PUT）

使用 `replace_async` 完全替换实体：

```python
full_product = {
    "ID": 123,
    "Name": "全新产品",
    "Description": "替换所有字段",
    "Price": 99.99,
    "Rating": 4,
}

replaced = await client.replace_async("Products", 123, full_product)
```

### 附带 ETag

```python
result = await client.get_by_key_with_etag_async("Products", 123)

replaced = await client.replace_async(
    "Products",
    123,
    {"ID": 123, "Name": "新名称", "Price": 49.99},
    etag=result.etag,
)
```

## 删除实体

```python
# 按整数 key 删除
await client.delete_async("Products", 123)

# 按字符串 key 删除
await client.delete_async("Customers", "ALFKI")

# 按 GUID key 删除
await client.delete_async("Orders", order_id)
```

### 附带乐观并发控制（ETag）

```python
result = await client.get_by_key_with_etag_async("Products", 123)

try:
    await client.delete_async("Products", 123, etag=result.etag)
except ODataConcurrencyError:
    print("实体在获取后已被修改")
```

## 错误处理

```python
from odms_odata import (
    ODataNotFoundError,
    ODataUnauthorizedError,
    ODataForbiddenError,
    ODataConcurrencyError,
    ODataClientError,
)

try:
    product = await client.get_by_key_async("Products", 999)

except ODataNotFoundError as exc:
    # 404 — 实体不存在
    print(f"未找到：{exc.request_url}")

except ODataUnauthorizedError as exc:
    # 401 — 需要身份验证
    print(f"未授权：{exc}")

except ODataForbiddenError as exc:
    # 403 — 拒绝访问
    print(f"禁止访问：{exc}")

except ODataConcurrencyError as exc:
    # 412 — ETag 不匹配
    print(f"并发冲突：你的={exc.request_etag}，当前={exc.current_etag}")

except ODataClientError as exc:
    # 其他 HTTP 错误
    print(f"错误 {exc.status_code}：{exc.response_body}")
```
