You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SQLAlchemy ORM中存储字典列表的实现问题及删除报错求助

解决SQLAlchemy ORM字典列表存储的两个问题

咱们来逐个搞定你遇到的这两个问题:

问题1:删除Item时触发AttributeError

原因分析

你自定义的KeyedListCollection把每个key映射成了Prop实例的列表,但SQLAlchemy的relationship集合要求每个元素都是合法的ORM实例(也就是单个Prop对象),而非列表。当执行db.delete(item)时,SQLAlchemy会遍历props集合尝试删除关联对象,结果拿到的是列表,自然就触发了'list' object has no attribute '_sa_instance_state'错误。

解决方案

放弃让集合存储列表,直接让props关系管理所有Prop实例,然后通过props_p属性来实现按key分组的字典列表格式读取。这样SQLAlchemy处理删除时,遍历的都是合法的Prop实例,就不会报错了。

问题2:让props_p支持反向赋值

实现思路

给props_p添加setter方法,遍历传入的字典列表数据:先清理掉当前Item关联的所有旧Prop实例,再根据新数据创建对应的Prop实例并关联到当前Item。如果是小规模数据,直接清空重建的方式简单直观;如果数据量大,也可以做更精细的差异对比更新,不过咱们先从简单版本入手。

修改后的完整代码

import operator
from sqlalchemy import Column, ForeignKey, Integer, String, create_engine
from sqlalchemy.orm import relationship, sessionmaker
from sqlalchemy.ext.declarative import declarative_base

connect_args = {}
connect_args["check_same_thread"] = False
engine = create_engine("sqlite:///test_orm.sqlite", connect_args=connect_args)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
db = SessionLocal()

Base = declarative_base()

class Prop(Base):
    __tablename__ = "props"
    id = Column(Integer, primary_key=True, index=True)
    item_id = Column(Integer, ForeignKey("items.id"))
    key = Column(String)
    value = Column(String)
    item = relationship("Item", back_populates="props")

class Item(Base):
    __tablename__ = "items"
    id = Column(Integer, primary_key=True, index=True)
    # 修改:用默认集合类型,直接管理所有Prop实例
    props = relationship(
        "Prop",
        cascade="all, delete-orphan",
        back_populates="item",
        lazy="joined"  # 可选:提前加载关联,避免多次查询
    )

    @property
    def props_p(self):
        # 按key分组生成字典列表
        out = {}
        for prop in self.props:
            if prop.key not in out:
                out[prop.key] = []
            out[prop.key].append(prop.value)
        return out

    @props_p.setter
    def props_p(self, data):
        # 先清空所有旧的Prop实例
        self.props.clear()
        # 根据新数据创建Prop实例并关联
        for key, values in data.items():
            for val in values:
                self.props.append(Prop(key=key, value=val))

Base.metadata.create_all(bind=engine)

dat = {
    "props": {
        "p1": ["a", "b", "c"],
        "p2": ["d", "e", "f"],
    },
}

# 测试反向赋值
item = Item()
# 直接给props_p赋值字典列表
item.props_p = dat["props"]
db.add(item)
db.commit()

# 读取测试
item = db.query(Item).order_by(Item.id.desc()).first()
print(item.props_p)  # 输出: {'p1': ['a', 'b', 'c'], 'p2': ['d', 'e', 'f']}

# 测试删除
db.delete(item)
db.commit()

# 验证删除成功
assert db.query(Item).count() == 0
assert db.query(Prop).count() == 0

db.close()

关键修改点说明

  1. 移除自定义集合类:不再用KeyedListCollection,让props直接管理所有Prop实例,SQLAlchemy可以正确处理删除逻辑。
  2. 新增props_p的setter方法:实现了反向赋值逻辑,直接接收字典列表格式的数据,自动转换为Prop实例关联到Item。
  3. 优化props_p的getter:遍历所有Prop实例按key分组,比原来从集合字典读取更直接。

这样两个问题就都解决了:删除Item时不会再报错,而且可以直接通过item.props_p = {...}来设置数据,非常方便。

内容的提问来源于stack exchange,提问作者Chebyshev

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.29 04:57:33