【问题标题】:sqlalchemy add child in one-to-many relationshipsqlalchemy在一对多关系中添加孩子
【发布时间】:2012-02-08 23:39:44
【问题描述】:

这是我第一次使用 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 设置为空)。用 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()

【问题讨论】:

    标签: python orm sqlalchemy


    【解决方案1】:

    你是对的 - 你不应该两次创建同一个父级。在添加子项方面,...嗯,您真的只需要添加它们并且您不关心现有的...所以您编辑的代码应该可以很好地完成工作。不过,您可以使其更短且更具可读性:

    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()
    

    这种方式的缺点是,对于现有的 Parent,Child 的整个集合将在添加新 Child 之前加载到内存中,然后再保存到数据库中。如果是这种情况(每个父母的孩子数量越来越多),那么lazy='noload' 可能有用:

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

    这可能会显着提高插入速度,但在这种情况下,对p.children 的访问将从不从数据库加载现有对象。在这种情况下,定义另一个关系就足够了。在这些情况下,我更喜欢使用 Building Query-Enabled Properties,因此您最终会得到一个属性 only 用于添加对象,而另一个 only 用于查询持久化结果,这通常被使用由系统的不同部分组成。

    【讨论】:

    • 要添加您可以使用p.children.extend([list_of_children]) 的孩子列表,不是吗?
    • 是的,当然。事实上,如果我们确定给定pid存在Parent,而手头的任务只是存储Child(ren),可以做的不是加载关系,而是直接设置FK值parent_gid:session.add(Child(loc=cloc, status=cstat, parent_gid=pid)),在这种情况下甚至不需要欺骗lazy="noload"的关系,因为代码不会使用关系
    • 能否请您也说明如何在同一场景中更新孩子?
    猜你喜欢
    • 2014-05-19
    • 2014-03-12
    • 1970-01-01
    • 2021-08-14
    • 1970-01-01
    • 2018-05-03
    • 1970-01-01
    • 1970-01-01
    • 2018-03-19
    相关资源
    最近更新 更多