# Core API 模式

构建 REST 响应——或任何用例 DTO——享受与 GraphQL 模式相同的 DataLoader 批量加载和 N+1 预防。

核心概念按顺序递进：**隐式自动加载 → `resolve_*` → `post_*` → 跨层数据流**。

## 第 1 步：DefineSubset + 隐式自动加载

最简单的 Core API 用法：从 SQLModel 实体选择字段，声明关系字段，它们自动加载。

```python
from sqlmodel import SQLModel, select
from nexusx import DefineSubset, ErManager

class UserDTO(DefineSubset):
    __subset__ = (User, ("id", "name"))

class TaskDTO(DefineSubset):
    __subset__ = (Task, ("id", "title", "owner_id"))
    owner: UserDTO | None = None   # 名称匹配 Task.owner 关系 → 自动加载

class SprintDTO(DefineSubset):
    __subset__ = (Sprint, ("id", "name"))
    tasks: list[TaskDTO] = []      # 名称匹配 Sprint.tasks 关系 → 自动加载
```

!!! tip
    心智模型很简单：`DefineSubset` 选择要暴露哪些字段，Resolver 自动填充关系字段。

## ErManager 初始化

```python
# 应用启动时——执行一次
er = ErManager(base=SQLModel, session_factory=async_session)
Resolver = er.create_resolver()
```

- `ErManager` 发现所有 SQLModel 实体及其 ORM 关系
- `create_resolver()` 返回一个绑定了实体图的 Resolver 类

!!! warning
    `base` 和 `entities` 参数是**互斥的**——你不能同时传两个。

## 在 FastAPI 中使用

```python
from fastapi import FastAPI

app = FastAPI()

@app.get("/api/sprints")
async def get_sprints():
    async with async_session() as session:
        sprints = (await session.exec(select(Sprint))).all()
    dtos = [SprintDTO(id=s.id, name=s.name) for s in sprints]
    return await Resolver().resolve(dtos)
```

## 隐式自动加载的四个条件

当以下**四个**条件全部满足时，Resolver 自动加载关系字段（无需手写 `resolve_*`）：

1. 字段没有对应的 `resolve_*` 方法
2. 字段是额外字段（不在 `__subset__` 定义中）
3. 字段名匹配已注册的 ORM/自定义关系
4. 字段类型是 BaseModel DTO 且与关系目标实体兼容

## DefineSubset 规则

- `__subset__` 接受元组 `(Entity, ('field1', 'field2'))`
- FK 字段（如 `owner_id`）自动从序列化输出中隐藏，但内部仍可在 `resolve_*` 中访问
- 关系字段声明在类体中（非 `__subset__`），类型必须是 DTO 类型

## 工作原理

```
SprintDTO(id=1, name="Sprint 1")
  → 隐式自动加载: tasks → [TaskDTO(...), TaskDTO(...)]
    → 每个 TaskDTO: 隐式自动加载: owner → UserDTO(...)
  → 结果：完整的嵌套响应树
```

每个关系只执行一次 DataLoader 查询，无论有多少个 Sprint 或 Task。

## DTO 类型约束

```python
# 错误——禁止直接使用 SQLModel 实体
class TaskDTO(DefineSubset):
    owner: User | None = None  # TypeError!

# 正确——使用 DTO 类型
class TaskDTO(DefineSubset):
    owner: UserDTO | None = None  # OK
```

!!! warning
    关系字段的类型**必须**是 DTO 类型（`DefineSubset` 或 `BaseModel` 的子类）。直接使用 SQLModel 实体类型会抛出 `TypeError`。

## 回顾

- `DefineSubset` 从 SQLModel 实体生成 DTO——选择字段，隐藏 FK
- 隐式自动加载在字段名匹配已注册关系时自动填充关系字段
- `ErManager` 发现实体并创建 Resolver
- 关系字段类型必须是 DTO 类型，不能是 SQLModel 实体

## 下一步

- [Core API 进阶](./core_api_advanced.zh.md) — `resolve_*` / `post_*` / 跨层数据流
- [自定义关系](./custom_relationship.zh.md) — 非 ORM 关系声明
