FastAPI中使用生成器上下文管理器管理DB会话遇错排查
FastAPI + SQLModel 会话管理错误分析与修复
第一个错误:AttributeError: '_GeneratorContextManager' object has no attribute 'add'
错误根源
- 会话工厂创建错误:你直接实例化了
Session类,而非创建会话工厂。SessionLocal = Session(...)会生成一个单一Session实例,而非每次调用都生成新会话的工厂,这不仅会引发并发问题,还会导致后续上下文管理器逻辑混乱。 get_session装饰器误用:用@contextmanager装饰的函数返回的是_GeneratorContextManager对象,而非yield出来的Session实例。FastAPI的Depends注入的是这个上下文管理器对象,而非实际可用的Session,因此调用db.add()时会报错。
修复方案
1. 修正会话工厂配置(app/db/session.py)
改用sessionmaker创建会话工厂,确保每次调用都生成新的Session实例:
from sqlmodel import create_engine, Session from sqlmodel.session import sessionmaker from app.core.config import settings engine = create_engine(settings.SQLALCHEMY_DATABASE_URI, pool_pre_ping=True) # 使用sessionmaker创建会话工厂,指定Session为基类 SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine, class_=Session)
2. 修正get_session函数
移除@contextmanager,直接用生成器让FastAPI自动管理会话生命周期:
from sqlmodel import Session from app.db.session import SessionLocal def get_session(): db = SessionLocal() try: yield db finally: db.close()
此时Depends(get_session)会注入真正的Session实例,CRUD层调用db.add()等方法就不会报错。
第二个错误:DetachedInstanceError
错误根源
你在CRUD层新增上下文管理器后,相当于创建了一个独立的会话:
- CRUD层的会话关闭后,生成的
Book实例会脱离会话(游离状态) - 接口返回时,FastAPI需要将
Book实例转换为BookCreate模型,而Book中的authors等字段是延迟加载的关系字段,此时会话已关闭,无法触发加载,因此报错。
修复方案
- 共享会话:整个请求周期内使用同一个会话,即使用上面修复后的
get_session,让接口和CRUD层共用注入的Session,不要在CRUD层重复创建会话。 - 预加载关系字段:如果需要返回包含关系字段的数据,在会话关闭前预加载这些字段:
from sqlmodel import select, selectinload class CRUDBook(CRUDBase[Book, BookCreate, BookUpdate]): def create_with_owner( self, db: Session, *, obj_in: BookCreate, owner_id: UUID ) -> Book: obj_in_data = dict(obj_in) db_obj = self.model(**obj_in_data, owner_id=owner_id) db.add(db_obj) db.commit() db.refresh(db_obj) # 预加载authors等关系字段,避免后续延迟加载报错 db_obj = db.exec(select(Book).options(selectinload(Book.authors)).where(Book.id == db_obj.id)).first() return db_obj
- 修正请求模型:
BookCreate作为请求接收模型,不应直接使用ORM实例(list["Author"]),建议改用ID列表:
class BookCreate(BookBase): author_ids: list[UUID] = [] publisher_ids: list[UUID] = [] genre_ids: list[UUID] = [] narrator_ids: list[UUID] = []
然后在CRUD层根据ID查询对应的实例,关联到新创建的Book上。
总结错误点
- 错误地直接实例化
Session类,而非创建会话工厂 - 误用
@contextmanager导致注入对象错误 - CRUD层重复创建会话,引发实例游离问题
- 请求模型定义不合理,直接使用ORM实例作为请求字段
内容的提问来源于stack exchange,提问作者Olaw2jr
相关产品推荐
相关产品推荐

