2

我有两个 Python 类NoteLink映射到 Postgresql 表。 Note具有对 , 的外键引用Link,同时Link通过一段 JSON 文本指向节点。链接指向除Notes 之外的其他东西,但这在这里无关紧要。

               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

我会买那个。在 Note 存储到数据库之后,该@after_insert事件被调用(我认为) ,这发生在当前会话刷新期间。当然,如果我删除呼叫,那么它当然会打印elixir.session.flush()

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

这又是有道理的,因为我无法保留链接!

所以我的问题是,我怎样才能在一个请求中同时创建一个注释和一个链接,以便相互依赖的 id 可用并正确记录?

PS我知道这里的模式有点不寻常,我可以通过(1)产生一个异步创建链接的任务或(2)让Link.content方法懒惰地创建链接来解决这个问题。这些解决方案需要一些并发注意,所以我真的希望一个简单、直接的 SQLAlchemy 解决方案与一个会话可以工作。

4

2 回答 2

8

我建议不要使用 Elixir 的方法,例如误用 SQLAlchemy 的 API 的“save()”。这是使用标准 SQLAlchemy 事件的上述方法。一切都在一次冲洗中实现。

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

由于两个对象都有来自数据库的自动生成的 ID,并且想要存储彼此的 ID,因此您需要先保存两个对象,然后再次保存其中一个对象,并使用另一个对象的更新 ID。

所以我会删除flush调用,并可能save为所涉及的每个对象显式调用。

于 2013-01-08T23:04:34.857 回答