Python 在SQLAlchemy中删除父项后删除子项
我的问题如下: 我有两个模型Python 在SQLAlchemy中删除父项后删除子项,python,sqlalchemy,Python,Sqlalchemy,我的问题如下: 我有两个模型Entry和Tag,它们通过SQLAlchemy中的多对多关系进行链接。现在,我想在删除条目后,删除每个没有任何相应条目的标记 举例说明我想要什么: Entry 1带有标签python,java Entry 2带有标记python,c++ 通过这两个条目,数据库包含标签python、java和c++。如果我现在删除条目2,我希望SQLAlchemy自动从数据库中删除c++标记。是否可以在条目模型本身中定义此行为,或者是否有更优雅的方式 谢谢。我会让代码为我说话:
Entry
和Tag
,它们通过SQLAlchemy中的多对多关系进行链接。现在,我想在删除条目
后,删除每个没有任何相应条目的标记
举例说明我想要什么:
Entry 1
带有标签python
,java
Entry 2
带有标记python
,c++
通过这两个条目,数据库包含标签python
、java
和c++
。如果我现在删除条目2
,我希望SQLAlchemy自动从数据库中删除c++
标记。是否可以在条目
模型本身中定义此行为,或者是否有更优雅的方式
谢谢。我会让代码为我说话:
from sqlalchemy import create_engine, exc, event
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import func, Table, Column, Integer, String, Float, Boolean, MetaData, ForeignKey
from sqlalchemy.orm import relationship, backref
# Connection
engine = create_engine('sqlite:///', echo=True)
Base = declarative_base(bind=engine)
Session = sessionmaker(bind=engine)
# Models
entry_tag_link = Table('entry_tag', Base.metadata,
Column('entry_id', Integer, ForeignKey('entries.id')),
Column('tag_id', Integer, ForeignKey('tags.id'))
)
class Entry(Base):
__tablename__ = 'entries'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False, default='')
tags = relationship("Tag", secondary=entry_tag_link, backref="entries")
def __repr__(self):
return '<Entry %s>' % (self.name,)
class Tag(Base):
__tablename__ = 'tags'
id = Column(Integer, primary_key=True)
name = Column(String(255), nullable=False)
def __repr__(self):
return '<Tag %s>' % (self.name,)
# Delete listener
def delete_listener(mapper, connection, target):
print "---- DELETING %s ----" % (target,)
print '-' * 20
for t in target.tags:
if len(t.entries) == 0:
print ' ' * 5, t, 'is to be deleted'
session.delete(t)
print '-' * 20
event.listen(Entry, 'before_delete', delete_listener)
# Utility functions
def dump(session):
entries = session.query(Entry).all()
tags = session.query(Tag).all()
print '*' * 20
print 'Entries', entries
print 'Tags', tags
print '*' * 20
Base.metadata.create_all()
session = Session()
t1, t2, t3 = Tag(name='python'), Tag(name='java'), Tag(name='c++')
e1, e2 = Entry(name='Entry 1', tags=[t1, t2]), Entry(name='Entry 2', tags=[t1, t3])
session.add_all([e1,e2])
session.commit()
dump(session)
raw_input("---- Press return to delete the second entry and see the result ----")
session.delete(e2)
session.commit()
dump(session)
这意味着监听一个条目的所有删除
项,并调用我们的监听器,它将执行我们想要的操作。但是,文档不建议更改事件中的会话(请参阅我添加的链接中的警告)。但据我所知,它是有效的,所以这取决于你是否适合你。这个问题在这里被问了一会儿:
这就是“多对多孤儿”问题。jadkik94很接近,您应该使用事件来捕获它,但是我建议不要在mapper事件中使用会话,尽管它在这种情况下是有效的
下面,我逐字地从另一个SO问题中获得答案,并将“角色”替换为“条目”:
两个几乎相同的SO问题将其视为手头上的东西,因此我已将其添加到wiki中。此示例中存在一个缺陷,即在映射器级别刷新事件中调用session.delete()不一定会在该刷新中发生,因为此时已确定刷新计划。但是,在这种情况下,它似乎是有效的,这对我来说有点奇怪。我当然希望该会话能够在mapper flush事件中完全可用,但是有很多原因它现在不能这样做。是的,它的工作是由于一些实现细节,这些细节不一定是刻板的。例如,如果您针对与父类没有关系的其他类说session.delete(),则删除可能发生,也可能不发生,这取决于该类型的其他对象是否已设置为删除。@zzzeek我阅读了警告,但认为应该尝试一下,看看会发生什么。。。我认为这是有效的,因为我正在删除before_delete事件中的标记;也许那个计划还没有制定出来。结果证明这只是运气:)我注意到的唯一一件事是,这段代码将在每次刷新时对标记执行查询,而不仅仅是在删除条目时。但它解决了问题,谢谢!当然,您也可以在after_flush()中扫描session.deleted,查看是否有条目对象被删除。这可能会也可能不会增加比查询更多/更少的开销。我会加上一个注释。维基上也添加了另一个变体。
event.listen(Entry, 'before_delete', delete_listener)
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import event
from sqlalchemy.orm import attributes
Base= declarative_base()
tagging = Table('tagging',Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id', ondelete='cascade'), primary_key=True),
Column('entry_id', Integer, ForeignKey('entry.id', ondelete='cascade'), primary_key=True)
)
class Tag(Base):
__tablename__ = 'tag'
id = Column(Integer, primary_key=True)
name = Column(String(100), unique=True, nullable=False)
def __init__(self, name=None):
self.name = name
class Entry(Base):
__tablename__ = 'entry'
id = Column(Integer, primary_key=True)
tag_names = association_proxy('tags', 'name')
tags = relationship('Tag',
secondary=tagging,
backref='entries')
@event.listens_for(Session, 'after_flush')
def delete_tag_orphans(session, ctx):
# optional: look through Session state to see if we want
# to emit a DELETE for orphan Tags
flag = False
for instance in session.dirty:
if isinstance(instance, Entry) and \
attributes.get_history(instance, 'tags').deleted:
flag = True
break
for instance in session.deleted:
if isinstance(instance, Entry):
flag = True
break
# emit a DELETE for all orphan Tags. This is safe to emit
# regardless of "flag", if a less verbose approach is
# desired.
if flag:
session.query(Tag).\
filter(~Tag.entries.any()).\
delete(synchronize_session=False)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
r1 = Entry()
r2 = Entry()
r3 = Entry()
t1, t2, t3, t4 = Tag("t1"), Tag("t2"), Tag("t3"), Tag("t4")
r1.tags.extend([t1, t2])
r2.tags.extend([t2, t3])
r3.tags.extend([t4])
s.add_all([r1, r2, r3])
assert s.query(Tag).count() == 4
r2.tags.remove(t2)
assert s.query(Tag).count() == 4
r1.tags.remove(t2)
assert s.query(Tag).count() == 3
r1.tags.remove(t1)
assert s.query(Tag).count() == 2