2

2 つの Python クラスNoteLinkPostgresQL テーブルへのマッピングがあります。 Noteは への外部キー参照を持っていますが、JSON テキストの一部を介してノードLinkLink指しています。リンクは s 以外のものを指しますNoteが、それはここでは問題ではありません。

               Note
+------+------------------+---------+
|  ID  |       NAME       | NOTE_ID |
+------+------------------+---------+
|   1  |  Alice           |     5   |
|   2  |  Bob             |    20   |
|   3  |  Carla           |     6   |
+------+------------------+---------+

          Link
+------+--------------+
|  ID  |  CONTENT     |
+------+--------------+
| ...  |    ...       |
|   5  |  {"t":1}     |
|   6  |  {"t":3}     |
| ...  |    ...       |
|  20  |  {"t":2}     |
+------+--------------+

今私が望むのは、新しいものを作成するたびにNote

note = Note('Danielle')

自動的に行に入ります

(4, 'Danielle', 21)

Note、そして入力します

(21, '{"t":4}')

Link。これが私がこれまでに試したことです:私はNoteオブジェクトを作成しLink、次に@events.after_insertイベントで作成しようとします:

class Note(Entity):
    name = Field(Unicode)
    link = ManyToOne('Link')

    . . .

    @events.after_insert
    def create_link(self):
        """
        Create and persist the short link for this note.  Must be done
        in this after_insert event because the link table has a foreign
        key that points back to the note.  We need the note to be 
        already inserted so we can use its id.
        """
        self.link = Link.build_link_for_note(self)
        elixir.session.flush()
        print("NOTE %s GOT LINK %s" % (self, self.link))

私が持っているリンククラスで

class Link(Entity):
    . . . 
    @classmethod
    def build_link_for_note(cls, note):
        return Link(content='{"id": %d}' % note.id)

どちらのテーブルにも自動インクリメントされた主キーがあるため、心配する必要はありません。このコードで発生するエラーは次のとおりです。

File ".../sqlalchemy/orm/session.py", line 1469, in flush
    raise sa_exc.InvalidRequestError("Session is already flushing")
    InvalidRequestError: Session is already flushing

私はそれを買います。現在のセッションのフラッシュ中に発生した、メモがデータベースに保存された@after_insert、イベントが呼び出されます(私は思います) 。そしてもちろん、通話を削除すると、もちろん印刷されますelixir.session.flush()

NOTE <Note id:4 name:Danielle> GOT LINK <id:None content:{"t": 4}>

リンクを永続化できなかったので、これも理にかなっています!

私の質問は、相互に依存する ID が利用可能で適切に記録されるように、単一のリクエストでメモとリンクの両方を作成するにはどうすればよいですか?

PS ここでのスキーマは少し変わっていることを理解しています。この問題は、(1) タスクを生成してリンクを非同期的に作成するか、(2)Link.contentメソッドにリンクを遅延作成させることで解決できることを理解しています。これらのソリューションには並行性に注意を払う必要があるため、1 つのセッションを使用する単純で直接的な SQLAlchemy ソリューションが機能することを本当に望んでいます。

4

2 に答える 2

8

SQLAlchemyのAPIを誤用する「save()」などのElixirのメソッドを使用しないことをお勧めします。これは、標準のSQLAlchemyイベントを使用した前述のアプローチです。すべてが1回のフラッシュでも達成されます。

from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
import json

Base = declarative_base()

class Note(Base):
    __tablename__ = "note"

    id = Column(Integer, primary_key=True)
    name = Column(String)
    note_id = Column(Integer, ForeignKey('link.id'))

    link = relationship("Link")

    # if using __init__ here is distasteful to you,
    # feel free to use the "init" event illustrated
    # below instead
    def __init__(self, name):
        self.name = name
        self.link = Link()

class Link(Base):
    __tablename__ = "link"

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

# using an event instead of Note.__init__
#@event.listens_for(Note, "init")
#def init(target, args, kwargs):
#    target.link = Link()

@event.listens_for(Note, "after_insert")
def after_insert(mapper, connection, target):
    connection.execute(
        Link.__table__.update().\
            values(content=json.dumps({"t": target.id}))
    )

e = create_engine("sqlite://", echo=True)

Base.metadata.create_all(e)

s = Session(e)

note = Note('Danielle')

s.add(note)
s.commit()

note = s.query(Note).first()
assert s.query(Link.content).scalar() == ('{"t": %d}' % note.id)
于 2013-01-09T16:40:48.790 に答える
0

両方のオブジェクトには DB から自動生成された ID があり、互いの ID を保存する必要があるため、最初に両方のオブジェクトを保存してから、オブジェクトの 1 つをもう 1 つのオブジェクトの更新された ID でもう一度保存する必要があります。

flushそのため、呼び出しを削除し、save関連するオブジェクトごとに明示的に呼び出します。

于 2013-01-08T23:04:34.857 に答える