4

in.dbPython アプリケーションが起動したら、すべてのデータを からロードして に入れたいと思います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

リレーションシップと backref に cascade="merge" を追加しようとしましたが、うまくいきませんでした。People[0] のすべての子供/親と関連する Edge を強制的に保存する方法はありますか?

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 に答える