4

当我的 python 应用程序启动时,我想从中加载所有数据in.db并将其放入out.db(然后可能在 中进行更改out.db)。我使用session.merge(loaded_object),但问题是它不保存相关对象。

我的数据是简单的 Person 对象,它们之间有明显的父子关系(多对多):

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

Base = declarative_base()

class Person(Base):
    __tablename__ = "people"
    id = Column(Integer, primary_key=True)
    name = Column(String)

    def __init__(self, name):
        self.name = name

    def add_kid(self, kid):
        Edge(kid=kid, parent=self)
        return self

    def get_kids(self):
        return [edge.kid for edge in self.kid_edges]

    def add_parent(self, parent):
        Edge(kid=self, parent=parent)
        return self

    def get_parents(self):
        return [edge.parent for edge in self.parent_edges]

    def __repr__(self):
        return "<Person(id={id}, name={name})>".format(id=str(self.id),
                                                       name=self.name)

class Edge(Base):
    __tablename__ = "edges"
    id = Column(Integer, primary_key=True)
    kid_id = Column(Integer, ForeignKey("people.id"))
    parent_id = Column(Integer, ForeignKey("people.id"))
    kid = relationship("Person", primaryjoin="Edge.kid_id==Person.id",
                       backref=backref("parent_edges", 
                                       collection_class=set))
    parent = relationship("Person", primaryjoin="Edge.parent_id==Person.id",
                          backref=backref("kid_edges",
                                          collection_class=set))

    def __init__(self, kid, parent):
        self.kid = kid
        self.parent = parent

我通过以下方式初始化会话:

db_in_engine = create_engine("sqlite:///in.db", echo=True)
db_in_session_factory = sessionmaker(bind=db_in_engine)
db_in_session = db_in_session_factory()
db_out_engine = create_engine("sqlite:///out.db", echo=True)
db_out_session_factory = sessionmaker(bind=db_out_engine)
db_out_session = db_out_session_factory()
Base.metadata.create_all(db_out_engine)

问题是当我合并一个人时,孩子们没有合并:

people = db_in_session.query(Person).all()
db_out_session.merge(people[0])
db_out_session.commit() # related Edges, kids and parents of people[0] are not saved

我尝试将 cascade="merge" 添加到关系和反向引用中,但这不起作用。有什么办法可以强迫它拯救所有人[0]的孩子/父母和相关的边缘?

4

1 回答 1

5

首先,不要感到难过,因为我必须对此进行测试以了解它为什么不起作用,然后我写了这个东西。

merge() 用例是您从离线缓存或某些本地修改的结构中获取某种应用程序内数据并将其移动到新会话中的用例。merge() 主要是关于合并更改,因此当它看到没有“更改”的属性时,它假定不需要特殊工作。所以它会跳过卸载的关系。如果它确实遵循卸载的关系,则合并过程将成为一个非常缓慢且繁重的操作,因为它遍历关系的完整图以递归方式加载所有内容,可能会将数据库的很大一部分加载到内存中以实现高度互连的模式。这里没有预料到“从一个数据库复制到另一个”用例。

如果您只是确保提前加载所有这些边缘,则数据确实会进入,这是一个演示。默认级联也是“保存更新,合并”,因此您不必指定。

from sqlalchemy import create_engine, Column, String, Integer, ForeignKey
from sqlalchemy.orm import Session, relationship, backref, immediateload
from sqlalchemy.ext.declarative import declarative_base
import os

Base = declarative_base()

class Person(Base):
    __tablename__ = "people"
    id = Column(Integer, primary_key=True)
    name = Column(String)

    def __init__(self, name):
        self.name = name


class Edge(Base):
    __tablename__ = "edges"
    id = Column(Integer, primary_key=True)
    kid_id = Column(Integer, ForeignKey("people.id"))
    parent_id = Column(Integer, ForeignKey("people.id"))
    kid = relationship("Person", primaryjoin="Edge.kid_id==Person.id",
                       backref=backref("parent_edges",
                                       collection_class=set))
    parent = relationship("Person", primaryjoin="Edge.parent_id==Person.id",
                          backref=backref("kid_edges",
                                          collection_class=set))

    def __init__(self, kid, parent):
        self.kid = kid
        self.parent = parent

def teardown():
    for path in ("in.db", "out.db"):
        if os.path.exists(path):
            os.remove(path)

def fixture():
    engine = create_engine("sqlite:///in.db", echo=True)
    Base.metadata.create_all(engine)

    s = Session(engine)
    p1, p2, p3, p4, p5 = [Person('p%d' % i) for i in xrange(1, 6)]
    Edge(p1, p2)
    Edge(p1, p3)
    Edge(p4, p3)
    Edge(p5, p2)
    s.add_all([
        p1, p2, p3, p4, p5
    ])
    s.commit()
    return s

def copy(source_session):
    engine = create_engine("sqlite:///out.db", echo=True)
    Base.metadata.create_all(engine)

    s = Session(engine)
    for person in source_session.query(Person).\
            options(immediateload(Person.parent_edges),
                        immediateload(Person.kid_edges)):
        s.merge(person)

    s.commit()

    assert s.query(Person).count() == 5
    assert s.query(Edge).count() == 4

teardown()
source_session = fixture()
copy(source_session)
于 2012-12-23T17:27:44.923 回答