1

まず、コード例:

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


Base = declarative_base()


class Foo(Base):

    __tablename__ = 'foo'

    id = Column(Integer, primary_key=True)
    label = Column(String)


class Bar(Base):

    __tablename__ = 'bar'

    id = Column(Integer, primary_key=True)
    foo_id = Column(Integer, ForeignKey('foo.id'))
    foo = relationship(Foo)

    def __init__(self, foo=None):

        self.foo = foo if foo else Foo()
        print 'init', self.id, self, self.foo.id, self.foo


def _adjust_label(target, value, old_value, initiator):

    print 'adjust', target, value, old_value, initiator
    if value and not target.foo.label:
        target.foo.label = 'autostring %d' % value
        print 'adjust', target.id, target, target.foo.id, target.foo, target.foo.label

event.listen(Bar.id, 'set', _adjust_label)

engine = create_engine('sqlite:////path/to/some.db')
Base.metadata.create_all(engine)
session = sessionmaker(bind=engine)()
bar = Bar()
print 'pre-add', bar.id, bar, bar.foo.id, bar.foo
session.add(bar)
print 'added', bar.id, bar, bar.foo.id, bar.foo
session.commit()
print 'commited', bar.id, bar, bar.foo.id, bar.foo, bar.foo.label

私が得るものは次のとおりです。

pre-add None <__main__.Bar object at 0x2929f50>
pre-add None <__main__.Foo object at 0x292e310>
added None <__main__.Bar object at 0x2929f50>
added None <__main__.Foo object at 0x292e310>
adjust <__main__.Bar object at 0x2929f50> 14 None Bar.id
adjust None <__main__.Bar object at 0x2929f50>
adjust 14 <__main__.Foo object at 0x292e310> autostring 14
commited 14 <__main__.Bar object at 0x2929f50>
commited 14 <__main__.Foo object at 0x2932e10> None

私が驚いたのは、 inがコミット前committedbar.fooは異なるインスタンスであり、その結果、bar.foo.labelin イベント リスナーへの変更が成功したことは窓の外に放り出されたことです。bar.foo.labelの自動生成された一意の文字列が必要であり、純粋なランダム文字列よりも少し意味のあるものが欲しかったので、これをやろうとしています。イベントリスナーの有無にかかわらず、これを自動的に行うことは可能ですか?それとも、ORM モデルよりも高いレベルでこれを処理する必要がありますか?

4

2 に答える 2

1

あなたのアプローチの問題は次のとおりです。 Bar.id は主キーです。したがって、データベースから自動的に作成されます。つまり、変更 (とsetの両方のオブジェクト作成) がデータベースにプッシュされた後です。FooBar

そのため、SQLAlchemy は変更のプッシュが完了したと考えます。さて、あなたは先に進んで変更を加えますが、SQLAlchemy はそれに気付かない (ように思われます) - 代わりに、この変更は Python には存在しますが、データベースには存在しません。

クエリ ログをオンにすると、最終ステップ (コミット) をより詳細に表示できますcreate_engine(..., echo=True)。これで、実行されたクエリが表示されます。echo="debug"そしてあなたはリターンを見ます。もう一度実行すると、データベースが再度クエリされていることがわかります。

ここで何が起こるかというと、SQLAlchemy は ID を含むすべての値を取得し、(for Bar) 「ああ、その ID を持つオブジェクトが既にセッションに存在しています」と認識し、正確にそのオブジェクトを返します。これで、あるケースでは古いオブジェクトが返され、別のケースでは新しいオブジェクトが返される理由については、はっきりとは言えません。これは私が答えることができるよりもはるかに SQLAlchemy に深く関わっていますが、ここに興味深い扱いがあります:

実行するBar()と ID が変更されますが、実行しても変更Bar(foo=Foo())されません (少なくとも私がテストした場合は変更されません)。しかし、どちらの場合も、変更が失われたため、これは ID の問題ではなく、いつ変更を行うかの問題であることがわかります。

解決策を考え出すのはそれほど簡単ではありません。最大の問題は、ID への依存によって発生します。複数のソリューションを試しましたが、本当に良いものはありませんでした。ただし、少なくとも次のクエリのアイデアを思いつくことができました。

UPDATE foo SET label="autostring " || (SELECT bar.id 
    FROM bar 
    WHERE bar.foo_id = foo.id) 
WHERE foo.id=1

fooこれにより、サーバー側の対応するエントリのラベルが設定されます。すべてに対してこれを行うクエリにすることができます。

UPDATE foo SET label="autostring " || (SELECT bar.id 
    FROM bar 
    WHERE bar.foo_id = foo.id) 
WHERE foo.label IS NULL

さらに、SQLAlchemy リストにこれに関するヘルプを求めることもできます。通常、ここよりも適切なヘルプを見つけることができます。

于 2013-08-29T11:35:55.020 に答える