sqlalchemy以一对多的关系添加孩子

Nea*_*eal 17 python orm sqlalchemy

这是我第一次使用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'))
Run Code Online (Sandbox Code Playgroud)

现在,通过网络进行更新.当有更新时,我想更新相应的Parent行(更新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)
Run Code Online (Sandbox Code Playgroud)

我最初尝试进行合并,但这导致旧的子节点与父节点解除关联(外部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()
Run Code Online (Sandbox Code Playgroud)

van*_*van 36

你是对的 - 你不应该两次创建同一个父母.在添加孩子方面,......好吧,你真的只需要添加它们而你不关心现有的......所以你编辑的代码应该可以正常工作.您可以使它更短,更易读:

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()
Run Code Online (Sandbox Code Playgroud)

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

parent = relationship("Parent", backref=backref('children', lazy='noload'))
Run Code Online (Sandbox Code Playgroud)

这可能会显着提高插入速度,但在这种情况下,访问p.children永远不会从数据库加载现有对象.在这种情况下,定义另一种关系就足够了.在这种情况下,我更喜欢使用大厦查询启用属性,所以你最终一个属性只是添加的对象,而其他为quering坚持的结果,这往往是由系统的不同部分使用.

  • 是的,当然。实际上,如果我们确定给定的“ pid”存在“父母”,并且当前的任务仅是存储“ Child(ren)”,那么可以做的就是不加载关系,而是直接设置“ parent_gid”的FK值:“ session.add(Child(loc = cloc,status = cstat,parent_gid = pid))”,在这种情况下,甚至不需要将关系欺骗为“ lazy =“ noload””,因为代码将不会使用该关系 (3认同)
  • 要添加孩子列表,可以使用`p.children.extend([list_of_children])`,不是吗? (2认同)
  • 您能否还展示如何在同一场景中更新孩子? (2认同)