当前位置: 首页 > 面试题库 >

在SQLAlchemy关系上设置delete-orphan会导致AssertionError:此AttributeImpl未配置为跟踪父级

翟单弓
2023-03-14
问题内容

这是我的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'),
    )

基本上,它是使用Declarative进行多对多标记。从标记中删除某些条目时,我希望SQLAlchemy整理这些孤儿。正如我在文档中发现的那样,要启用此功能,我应该这样做:

class Role(db.Model):
    ...
    tags = db.relationship('Tag', secondary=tagging, cascade='all,delete-orphan', backref=db.backref('roles', cascade='all'))
    ...

但是,这种设置会导致 AssertionError:此AttributeImpl未配置为跟踪父级。
我用它搜索了一下,除了SQLAlchemy的开源代码外,什么都没找到。因此,我创建了类方法Tag.delete_orphans()(在上面的代码中),每次我认为可能会出现一些孤儿时都调用它,但这似乎不是很优雅。

有什么想法或解释为什么我的设置delete-orphan不起作用?


问题答案:

好的,在这种情况下,您需要仔细查看,尽管这里有一个警告可能会成为例外,我将对此进行调查。这是您的示例的有效版本:

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()

现在开始运行:

... 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.

因此,这是重要的部分: SAWarning:在Role.tags上,未设置single_parent时,多对多或多对一关系不支持删除孤立级联。
在relationship()上设置single_parent = True。

因此,错误是固定的,如果您这样说:

tags = relationship('Tag', 
                    secondary=tagging, 
                    cascade='all,delete-orphan', 
                    single_parent=True,
                    backref=backref('roles', cascade='all'))

但是,您可能会发现,这并不是您真正想要的:

r1 = Role()
r2 = Role()

t1, t2 = Tag("t1"), Tag("t2")
r1.tags.extend([t1, t2])
r2.tags.append(t1)

输出:

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.

那就是您的“单亲”-“删除-孤立”功能仅适用于所谓的 生命周期
关系,其中子完全位于其单亲的范围内。因此,使用多对多的“孤立”几乎没有任何意义,它仅受支持,因为有些人确实非常想通过关联表获得此行为(无论是传统DB的东西)。

继承人的文档:

delete-
orphan级联意味着每个子对象一次只能有一个父对象,因此在大多数情况下都将其配置为一对多关系。将其设置为多对一或多对多关系比较麻烦;对于此用例,SQLAlchemy要求使用single_parent
= True函数配置Relationship(),该函数建立Python端验证,以确保该对象一次仅与一个父对象关联。

当您说“我要它清理孤儿”时,意味着什么?这意味着在这里,如果您要说r1.tags.remove(t1),那么您说的是“flush”。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

现在每次刷新时,我们都会在末尾得到以下查询:

DELETE FROM tag WHERE NOT (EXISTS (SELECT 1 
FROM tagging, role 
WHERE tag.id = tagging.tag_id AND role.id = tagging.role_id))

因此,当我们可以按照简单的SQL准则删除对象时,就不需要将对象拉入内存以将其删除(通过痛苦的行编程,当数据库可以更有效地执行操作时,依靠将行拉入内存已被称为行)。)。与在计划器中往往更昂贵的“外部联接”相比,“不存在”在搜索相关行的缺失时也非常有效。



 类似资料:
  • 我正在尝试设置一个使用Jaeger/Prometheus的Spring应用程序。我已经通过prometheus.yaml文件成功配置了Prometheus,但我不明白如何配置Jaeger目标endpoint。我必须创建一个新的yaml文件并在其中指定配置吗?如果是,使用哪种语法?

  • 问题内容: 我有一个使用jQuery.ajax对另一个主机执行请求的Web应用程序(现在实际上是相同的,因为我使用的是“ localhost”的不同端口)。然后服务器返回一个cookie。 Chrome的开发工具中显示的HTTP响应中的cookie值为 因此未来的有效期为4个小时。 但是,该cookie不会与后续请求一起存储和发送(已在Chrome和Firefox中进行了测试)。我首先认为它一定是

  • 本节包括 使用声明性定义映射属性 . 关系论据的评估 此分区已移至 关系论据的后期评估 . 配置多对多关系 此分区已移至 多对多关系的延迟评估 .

  • 本节介绍 relationship() 并深入探讨其用途。有关关系的介绍,请从 对象关系教程(1.x API) 并入 建立关系 . 基本关系模式 一对多 为一对多配置删除行为 多对一 一对一 多对多 从多对多表中删除行 关联对象 关系论据的后期评估 多对多关系的延迟评估 相邻列表关系 复合邻接表 自引用查询策略 配置自引用预加载 链接与backref的关系 backref参数 设置反向引用的层叠

  • 有关zipkin跟踪配置的示例,请参阅zipkin沙箱设置。 返回 上一级

  • cmf_set_option($key, $data, $replace = false) 功能 设置系统配置,通用 参数 $key: string 配置键值,都小写 $data: array 配置值,数组 $replace: array 是否完全替换 返回 bool 是否成功