我有一个关于SQLAlchemy的新问题,我在试图找到一个好的解决方案时打破了我的大脑。所以我有一些表格:
import sqlalchemy.orm.session
# other import statments . . .
Session = sqlalchemy.orm.session.Session
class Tempable(Base):
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
temporary = Column(Boolean, nullable=False)
class Generic(Base):
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
tempable_id = Column(Integer, ForeignKey(Tempable.id))
Tempable
表有一个名为temporary
的字段。如果此字段为True,则只有一个Generic
可以与此Tempable
表格行相关联,当相关Generic
行已删除时,还必须删除Tempable
。否则,许多Generic
可以与Tempable
相关联,并且删除其中一个Tempable
不会影响class Generic(Base):
# . . .
def before_delete(self, session):
""":type session: Session"""
condition = and_(Tempable.id == self.tempable_id, Tempable.temporary == 1)
# I've tried use bulk session deletion:
# session.query(Tempable).filter(condition).delete()
# but if Tempable tables has relationships then related objects not deleted,
# I don't understand such behaviour.
# But this works fine:
for obj in session.query(Tempable).filter(condition):
session.delete(obj)
@event.listens_for(Session, 'before_flush')
def _database_flush(session, flush_context, instances):
for p_object in session.deleted:
if hasattr(p_object, "before_delete"):
p_object.before_delete(session)
for p_object in session.dirty:
if hasattr(p_object, "before_update"):
p_object.before_update(session)
for p_object in session.new:
if hasattr(p_object, "before_insert"):
p_object.before_insert(session)
。
经过一些研究,我发现必须采用方便的方式来做事情。代码扩展如下:
deleted
但是发生了一些麻烦。删除Generic对象时,必须同时更新相应的GUI。为此,可以使用Session对象的Tempable
属性。但是我也有问题:已删除的class Database(object):
# . . .
def remove(name):
# before commit I need to obtain list of all objects that will be deleted
# that required to update GUI views
try:
this = self.__session.query(orm.Generic).filter(orm.Generic.name == name).one()
except orm.NoResultFound:
pass
else:
logging.info("Remove object: %s" % this)
self.__session.delete(this)
deleted = [obj for obj in self.__session.deleted]
# At this point, list of deleted objects of course is not contain any Tempable objects
print(deleted)
self.__session.commit()
# And here list is empty
print([obj for obj in self.__session.deleted])
return deleted
行没有出现在此属性列表中。
{{1}}
那么问题是什么是正确的方法来获取已删除的对象或可能是整个方法是完全错误的?
答案 0 :(得分:2)
批量删除系统不处理您的关系,因为它为所有行发出单个DELETE语句,而不尝试加载和协调这些行引用的内容。这是第一个"警告"列于the documentation for query.delete():
该方法不提供Python内级联关系 - 它是 假设ON DELETE CASCADE / SET NULL / etc.是为任何配置 需要它的外键引用,否则数据库可能 如果存在外键引用,则发出完整性违规 执行。
就" session.deleted"而言,该列表仅在刷新发生之前相关。 commit()表示flush(),在刷新之后,所有session.new,session.dirty,session.deleted都被清除。您需要先将session.deleted复制到另一个列表之前复制,或者可能更强大的是在您执行的过程中收集before_flush()中的session.deleted,并将您关心的对象复制到另一个列表,也许在session.info中;例如session.info['deleted_things'] = my_list_of_objects
。
答案 1 :(得分:1)
这是我根据@zzzeek提供的有用答案的实现。首先,我重写了Session.delete方法。在此方法中,如果被删除的实例是Generic和DeleteHook类实例,则在调用sqlalchemy.orm.session.Session.delete方法之前调用on_delete方法。这里的主要警告是,如果在on_delete中执行sqlalchemy.orm.session.Session.delete方法,则在重置最后一个名为session.deleted的sqlalchemy.orm.session.Session.delete方法之后。所以我做了一个解决方法:on_delete方法应该返回必须也被删除的对象的查询。然后,Session.delete在最后一步执行了删除autoflush off的所有对象。
我认为代码可以清除它:
import sqlalchemy.orm.session
import sqlalchemy.orm.query
# other import statments . . .
Query = sqlalchemy.orm.query.Query
# Interface of Generic objects that should perform operations before deleted
class DeleteHook(object):
def on_delete(self, session):
"""
Hook performed before inherited Generic object marked for deletion.
It can return query of others object that also required to delete.
:param Session session: Current database session
:return: Query of the objects to being delete
:rtype: Query
"""
return []
class Session(sqlalchemy.orm.session.Session):
def delete(self, instance):
"""
Override delete method of the Session to execute on_delete
method of instance if required
"""
deleted = [instance]
if isinstance(instance, Generic) and isinstance(instance, DeleteHook):
deleted.extend(instance.on_delete(self))
# Autoflush required to be off otherwise
# if object with relationships deleted then
# session.deleted will be reset
self.autoflush = False
# All removed objects collected in the session.deleted set
for p_object in deleted:
super(Session, self).delete(p_object)
self.autoflush = True
class Tempable(Base):
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
temporary = Column(Boolean, nullable=False)
class Generic(Base, DeleteHook):
id = Column(Integer, primary_key=True)
name = Column(String, nullable=False, unique=True)
tempable_id = Column(Integer, ForeignKey(Tempable.id))
def on_delete(self, session):
"""
:param Session session: Current database session
:return: Query of the objects to being delete
:rtype: Query
"""
# Possible some others statements can be placed here
# . . .
# Select only temporary records
condition = and_(Tempable.id == self.tempable_id,
Tempable.temporary == 1)
return session.query(Tempable).filter(condition)