SQLAlchemy命令式映射下通过外键新增实体时foreign_id被置空如何解决
问题根本原因
你遇到的foreign_id被自动覆盖为None的核心原因有两个:
- 代码中两次定义
configure_mappers函数,后定义的覆盖了前者,导致Foreign类没有完成映射,本身就会触发映射异常。 - SQLAlchemy的relationship默认会和关联外键字段做值同步:你给Primary类的
foreign属性设置了默认值None,实例化时没有传入foreign参数,attr模块会自动将foreign赋值为None,SQLAlchemy检测到关联对象为None,就会自动将对应的外键foreign_id也同步为Null,触发非空约束报错。
解决方法
方法1:调整foreign属性的初始化配置(最推荐)
给Primary类的foreign属性添加init=False参数,禁止实例化时自动为foreign赋值默认的None,即可避免同步覆盖外键值,修改后的Primary类代码如下:
@attr.s class Primary: id = attr.ib(type=int, validator=optional(instance_of(int)), default=None) foreign_id = attr.ib(type=int, validator=optional(instance_of(int)), default=None) # 增加init=False,实例化时不会自动初始化该属性 foreign = attr.ib(type=Foreign, validator=optional(instance_of(Foreign)), default=None, init=False)
同时修正重复定义的configure_mappers函数,把两个映射逻辑放在同一个函数中:
def configure_mappers(): mapper_registry.map_imperatively(Foreign, foreign) mapper_registry.map_imperatively( Primary, primary, properties={ "foreign": relationship(Foreign), }, )
修改后你原来的新增代码就可以正常执行。
方法2:修改relationship配置,明确外键关联
如果不想修改类属性配置,可以在定义relationship时明确指定关联的外键字段,关闭默认的主动同步逻辑:
def configure_mappers(): mapper_registry.map_imperatively(Foreign, foreign) mapper_registry.map_imperatively( Primary, primary, properties={ "foreign": relationship(Foreign, foreign_keys=[primary.c.foreign_id], sync_backref=False), }, )
方法3:调整赋值逻辑(无需修改现有配置)
如果不想改动现有配置,可以调整实例赋值的顺序,避开初始化时的同步覆盖:
# 先创建空实例,避免初始化时foreign默认值覆盖外键 primary_obj = Primary() primary_obj.foreign_id = foreign_id session.add(primary_obj) session.commit()
或者直接传入关联的Foreign实例,由SQLAlchemy自动填充外键值:
foreign_obj = session.get(Foreign, foreign_id) session.add(Primary(foreign=foreign_obj)) session.commit()
内容的提问来源于stack exchange,提问作者Jorge Lima
相关产品推荐
相关产品推荐

