3

私はSQLAlchemyリリース0.8.2を使用しています(python 2.7.5および3.3.2を試しました)

コードで関連付けオブジェクト パターン (多対多の関係用) を使用する必要がありましたが、関連付けを追加するたびに IntegrityError 例外が発生していました。これは、"INSERT INTO association (left_id, right_id, extra_data) [...]" を実行する代わりに、"INSERT INTO association (right_id, extra_data) [...]" を実行し、IntegrityError が発生するためです。主キーがないため例外です。

しばらく問題を絞り込み、コードをできるだけ単純化しようとした後、犯人を見つけましたが、なぜこのように動作するのかわかりません。

読者がそのままテストできるように、完全なコードを含めました。クラス宣言は、ドキュメントとまったく同じです (後方参照あり)。

#!/usr/bin/env python2
import sqlalchemy
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from sqlalchemy import ForeignKey
from sqlalchemy.orm import relationship, backref


Base = declarative_base()

class Association(Base):
    __tablename__ = 'association'
    left_id = Column(Integer, ForeignKey('left.id'), primary_key=True)
    right_id = Column(Integer, ForeignKey('right.id'), primary_key=True)
    extra_data = Column(String(50))
    child = relationship("Child", backref="parent_assocs")

class Parent(Base):
    __tablename__ = 'left'
    id = Column(Integer, primary_key=True)

    children = relationship("Association", backref="parent")

class Child(Base):
    __tablename__ = 'right'
    id = Column(Integer, primary_key=True)



def main():
    engine = sqlalchemy.create_engine('sqlite:///:memory:', echo=True)
    Base.metadata.create_all(engine)
    Session = sessionmaker(bind=engine)
    session = Session()

    # populate old data
    session.add(Child()) 

    # new data
    p = Parent()
    session.add(p) # Commenting this fixes the error. 
    session.flush()

    # rest of new data
    a = Association(extra_data="some data")
    a.child = session.query(Child).one()
    # a.child = Child() # Using this instead of the above line avoids the error - but that's not what I want. 
    p.children.append(a)
    # a.parent = p # Using this instead of the above line fixes the error! They're logically equivalent. 

    session.add(p)
    session.commit()

if __name__ == '__main__':
    main()

したがって、上記のコードのコメントで述べたように、問題を修正/回避するには 3 つの方法があります。

  1. 関連付けを宣言する前に親をセッションに追加しないでください
  2. 既存の子を選択する代わりに、関連付けの新しい子を作成します。
  3. 関連付けで後方参照を使用する

3 つのケースすべての動作がわかりません。

2 番目のケースは別のことを行うため、可能な解決策ではありません。ただし、動作がわかりません。この場合、問題が回避される理由の説明をいただければ幸いです。

最初のケースは「オブジェクトの状態」と関係があるのではないかと考えていますが、何が原因なのか正確にはわかりません。ああ、session.autoflush=False最初に発生する直前にsession.add(p)追加すると、混乱を招く問題も修正されます。

3 番目のケースでは、論理的に同等である必要があるため、完全な空白を描いています。

洞察をありがとう!

4

1 に答える 1

3

ここで何が起こるかというと、 を呼び出すとp.children.append()、SQLAlchemy は最初にロードしないとプレーン コレクションに追加できないということです。読み込みが進むと、autoflush が開始されます。これは、スタック トレースに次のような行が表示されるのでわかります。

File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/session.py", line 1183, in _autoflush
  self.flush()

次に、Association オブジェクトが不完全な状態でここにフラッシュされます。と言うa.child = some_persistent_childと、イベントがコレクションに追加aされ、オブジェクトがセッションにカスケードされるため、そもそもセッションにあります (これに関する背景と考えられる解決策については、Backref でのカスケードの制御を参照してください)parent_assocsChildAssociation

しかし、関係に影響を与えることなく、このニワトリ/卵のような問題がある場合の最も簡単な解決策は、 no_autoflush を使用してautoflushを一時的に無効にすることです。

with session.no_autoflush:
    p.children.append(a)

p.children がロードされたときに自動フラッシュを無効にすると、保留中のオブジェクトaはフラッシュされません。次に、すでに永続化されているものに関連付けられParent(既に追加してフラッシュしたため)、INSERT の準備が整います。

これにより、テスト プログラムが成功します。

于 2013-10-02T00:16:20.187 に答える