我有两个Python类Note
和Link
映射到PostgresQL表。 Note
具有对Link
的外键引用,而Link
通过一段JSON文本指向节点。链接指向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))
在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可用并正确记录?
P.S。我知道这里的模式有点不同寻常,我可以通过(1)产生任务来异步创建链接或(2)使Link.content
方法懒惰地创建链接来解决这个问题。这些解决方案需要一些并发关注,所以我真的希望一个简单,直接的SQLAlchemy解决方案可以使用一个会话。
答案 0 :(得分:8)
我建议不要使用Elixir的方法,例如“save()”,这会错误地使用SQLAlchemy的API。以下是使用标准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)
答案 1 :(得分:0)
由于两个对象都有自动生成的ID,这些ID来自DB 和想要存储彼此的ID,因此需要先保存两个对象,然后再次保存其中一个对象,并使用更新的ID其他对象。
所以我要删除flush
调用,并为每个相关对象明确调用save
。