SQLAlchemy 在一对多关系中添加子项

30 投票
1 回答
48632 浏览
提问于 2025-04-17 10:06

这是我第一次使用ORM(对象关系映射),所以我不太确定该怎么处理这个问题。我有一个一对多的关系,每个父级可以有多个子级:

class Parent(Base):
    __tablename__ = 'Parent'

    name = Column(String(50))
    gid = Column(String(16), primary_key = True)
    lastUpdate = Column(DateTime)

    def __init__(self,name, gid):
        self.name = name
        self.gid = gid
        self.lastUpdate = datetime.datetime.now()


class Child(Base):
    __tablename__ = 'Child'

    id = Column(Integer, primary_key = True)
    loc = Column(String(50))
    status = Column(String(50))

    parent_gid = Column(String(16), ForeignKey('Parent.gid'))

    parent = relationship("Parent", backref=backref('children'))

现在,更新信息通过网络传过来了。当更新到达时,我想更新相应的父级记录(更新lastUpdate这一列),并将新的子级记录插入到数据库中。我不知道该如何用ORM来做到这一点。这是我之前的尝试,但失败了:

engine = create_engine('sqlite+pysqlite:///file.db',
                       module=dbapi2)
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()

def addChildren(parent):
    p = session.query(Parent).filter(Parent.gid == p1.gid).all()
    if len(p) == 0:
        session.add(p1)
        session.commit()
    else:
        updateChildren = parent.children[:]
        parent.chlidren = []
        for c in updateChildren:
            c.parent_gid = parent.gid

        session.add_all(updateChildren)
        session.commit()

if __name__ == '__main__':

    #first update from the 'network'
    p1 = Parent(name='team1', gid='t1')
    p1.children = [Child(loc='x', status='a'), Child(loc='y', status='b')]
    addChildren(p1)

    import time
    time.sleep(1)

    #here comes another network update
    p1 = Parent(name='team1', gid='t1')
    p1.children = [Child(loc='z', status='a'), Child(loc='k', status='b')]
    #this fails
    addChildren(p1)

我最开始尝试做合并操作,但这导致旧的子级和父级失去了关联(外键ID被设置为null)。用ORM来处理这个问题的最佳方法是什么呢?谢谢!

编辑

我想当更新通过网络到达时,完全创建新的对象其实没有意义。我应该先查询会话,找到合适的父级,然后在必要时创建新的子级并进行合并?比如说:

def addChildren(pname, pid, cloc, cstat):
    p = session.query(Parent).filter(Parent.gid == pid).all()
    if len(p) == 0:
        p = Parent(pname, pid)
        p.children = [Child(loc=cloc, status=cstat)]
        session.add(p)
        session.commit()
    else:
        p = p[0]
        p.children.append(Child(loc=cloc, status=cstat))
        session.merge(p)
        session.commit()

1 个回答

48

你说得对,不能重复创建同一个父级。至于添加子级,实际上只需要添加它们,不用太在意已经存在的子级。所以你修改后的代码应该可以正常工作。不过,你可以让它更简洁、更易读:

def addChildren(pname, pid, cloc, cstat):
    p = session.query(Parent).get(pid) # will give you either Parent or None
    if not(p):
        p = Parent(pname, pid)
        session.add(p)
    p.children.append(Child(loc=cloc, status=cstat))
    session.commit()

这种做法的缺点是,当你要给一个已经存在的父级添加新子级时,系统会先把这个父级下的所有子级都加载到内存中,然后再添加新的子级并保存到数据库。如果每个父级的子级数量很多并且还在增加,这时候可以考虑使用 lazy='noload' 选项:

parent = relationship("Parent", backref=backref('children', lazy='noload'))

这样做可以显著提高插入的速度,但在这种情况下,访问 p.children 时将永远不会从数据库中加载已有的对象。在这种情况下,定义另一个关系就足够了。在这些情况下,我更倾向于使用 构建查询启用的属性,这样你就可以得到一个仅用于添加对象的属性,和一个仅用于查询已保存结果的属性,这样不同的系统部分可以更好地使用这些属性。

撰写回答