Hon*_*rek 27 python sqlalchemy flask flask-sqlalchemy
这是我的Flask-SQLAlchemy声明代码:
from sqlalchemy.ext.associationproxy import association_proxy
from my_flask_project import db
tagging = db.Table('tagging',
db.Column('tag_id', db.Integer, db.ForeignKey('tag.id', ondelete='cascade'), primary_key=True),
db.Column('role_id', db.Integer, db.ForeignKey('role.id', ondelete='cascade'), primary_key=True)
)
class Tag(db.Model):
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), unique=True, nullable=False)
def __init__(self, name=None):
self.name = name
@classmethod
def delete_orphans(cls):
for tag in Tag.query.outerjoin(tagging).filter(tagging.c.role_id == None):
db.session.delete(tag)
class Role(db.Model):
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete='cascade'))
user = db.relationship('User', backref=db.backref('roles', cascade='all', lazy='dynamic'))
...
tags = db.relationship('Tag', secondary=tagging, cascade='all', backref=db.backref('roles', cascade='all'))
tag_names = association_proxy('tags', 'name')
__table_args__ = (
db.UniqueConstraint('user_id', 'check_id'),
)
Run Code Online (Sandbox Code Playgroud)
基本上,它是使用Declarative进行多对多标记.从标记中删除一些条目时,我希望SQLAlchemy整理孤儿.正如我在文档中发现的那样,要打开此功能,我应该这样做:
class Role(db.Model):
...
tags = db.relationship('Tag', secondary=tagging, cascade='all,delete-orphan', backref=db.backref('roles', cascade='all'))
...
Run Code Online (Sandbox Code Playgroud)
但是,此类设置会导致AssertionError:此AttributeImpl未配置为跟踪父项.我搜索了它,除了SQLAlchemy的开源代码之外什么都没找到.因此,我创建了classmethod Tag.delete_orphans()
(它在上面的代码中),每当我想到一些孤儿可能会发生时,它就会调用它,但这似乎并不是很优雅.
我设置的任何想法或解释delete-orphan
都不起作用?
zzz*_*eek 75
好的,在这种情况下,你需要仔细观察,虽然这里有一个警告,可能会成为一个例外,我会调查一下.这是您的示例的工作版本:
from sqlalchemy.ext.associationproxy import association_proxy
from sqlalchemy import *
from sqlalchemy.orm import *
from sqlalchemy.ext.declarative import declarative_base
Base= declarative_base()
tagging = Table('tagging',Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id', ondelete='cascade'), primary_key=True),
Column('role_id', Integer, ForeignKey('role.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 Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
tag_names = association_proxy('tags', 'name')
tags = relationship('Tag',
secondary=tagging,
cascade='all,delete-orphan',
backref=backref('roles', cascade='all'))
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
r1 = Role()
r1.tag_names.extend(["t1", "t2", "t3"])
s.add(r1)
s.commit()
Run Code Online (Sandbox Code Playgroud)
现在让我们运行:
... creates tables
/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/properties.py:918: SAWarning: On Role.tags, delete-orphan cascade is not supported on a many-to-many or many-to-one relationship when single_parent is not set. Set single_parent=True on the relationship().
self._determine_direction()
Traceback (most recent call last):
... stacktrace ...
File "/Users/classic/dev/sqlalchemy/lib/sqlalchemy/orm/attributes.py", line 349, in hasparent
assert self.trackparent, "This AttributeImpl is not configured to track parents."
AssertionError: This AttributeImpl is not configured to track parents.
Run Code Online (Sandbox Code Playgroud)
所以这里是重要的部分:SAWarning:在Role.tags上,当没有设置single_parent时,多对多或多对一关系不支持delete-orphan级联.在关系()上设置single_parent = True.
所以错误是固定的,如果你这样说:
tags = relationship('Tag',
secondary=tagging,
cascade='all,delete-orphan',
single_parent=True,
backref=backref('roles', cascade='all'))
Run Code Online (Sandbox Code Playgroud)
但是,您可能会注意到,这不是您想要的:
r1 = Role()
r2 = Role()
t1, t2 = Tag("t1"), Tag("t2")
r1.tags.extend([t1, t2])
r2.tags.append(t1)
Run Code Online (Sandbox Code Playgroud)
输出:
sqlalchemy.exc.InvalidRequestError: Instance <Tag at 0x101503a10> is already associated with an instance of <class '__main__.Role'> via its Role.tags attribute, and is only allowed a single parent.
Run Code Online (Sandbox Code Playgroud)
这就是你的"单亲" - "删除孤儿"功能只适用于所谓的生命周期关系,其中孩子完全存在于其单亲的范围内.因此,使用带有"孤儿"的多对多几乎没有任何意义,并且它只是支持,因为有些人真的,真的想用关联表来获得这种行为(可能是传统的数据库).
继承人的文档为:
delete-orphan cascade意味着每个子对象一次只能有一个父对象,所以在绝大多数情况下配置为一对多关系.将它设置为多对一或多对多关系更加尴尬; 对于此用例,SQLAlchemy要求使用single_parent = True函数配置relationship(),该函数建立Python端验证,以确保对象一次只与一个父对象关联.
当你说"我希望它能清除孤儿"时,隐含着什么?这意味着,如果你要说r1.tags.remove(t1)
,那么你说"冲洗".SQLAlchemy会看到,"r1.tags,t1已被删除,如果它是一个孤儿,我们需要删除!好吧,所以让我们去"标记",然后扫描整个表格中剩下的任何条目."要做到这一点对于每个标签一次天真地显然是非常低效的 - 如果你在一个会话中影响了几百个标签集合,那么就会有几百个这些潜在的巨大查询.要做到这一点不是天真的,这将是一个非常复杂的功能添加,因为工作单元倾向于一次考虑一个集合 - 它仍然会增加人们可能不想要的明显的查询开销.工作单元做得非常好,但它试图避开不寻常的边缘情况,增加了许多复杂性和意外.实际上,只有当对象B与内存中的对象A分离时,"删除孤儿"系统才会发挥作用 - 没有扫描数据库或类似的东西,它比这简单得多 - 并且刷新过程必须保持事情尽可能简单.
所以你在这里用"删除孤儿"做的事情是在正确的轨道上,但让我们把它放在一个事件中,并使用更有效的查询,并一次性删除我们不需要的一切:
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
Base= declarative_base()
tagging = Table('tagging',Base.metadata,
Column('tag_id', Integer, ForeignKey('tag.id', ondelete='cascade'), primary_key=True),
Column('role_id', Integer, ForeignKey('role.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 Role(Base):
__tablename__ = 'role'
id = Column(Integer, primary_key=True)
tag_names = association_proxy('tags', 'name')
tags = relationship('Tag',
secondary=tagging,
backref='roles')
@event.listens_for(Session, 'after_flush')
def delete_tag_orphans(session, ctx):
session.query(Tag).\
filter(~Tag.roles.any()).\
delete(synchronize_session=False)
e = create_engine("sqlite://", echo=True)
Base.metadata.create_all(e)
s = Session(e)
r1 = Role()
r2 = Role()
r3 = Role()
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
Run Code Online (Sandbox Code Playgroud)
现在每次刷新我们最后得到这个查询:
DELETE FROM tag WHERE NOT (EXISTS (SELECT 1
FROM tagging, role
WHERE tag.id = tagging.tag_id AND role.id = tagging.role_id))
Run Code Online (Sandbox Code Playgroud)
因此,我们不需要将对象拉入内存以便删除它们,当我们可以删除一个简单的SQL标准时(依赖于在数据库可以更高效地执行操作时将行拉入内存中,通过激活行编程来调用行)).与OUTER JOIN相比,"NOT EXISTS"在搜索相关行的缺失时效果非常好,而OUTER JOIN在规划器中往往更加昂贵.
归档时间: |
|
查看次数: |
10848 次 |
最近记录: |