具有关联对象的多对多以及定义的所有关系在删除时崩溃

2024-03-09

当具有描述的所有关系的成熟的多对多时,删除两个主要对象之一会崩溃。

描述

Car (.car_ownerships) (.car) 汽车保有量 (.person) (.car_ownerships) Person

Car (.people) (.cars) Person

Problem

当删除一个Car or a PersonSA首先删除关联对象汽车保有量(由于与secondary参数),然后尝试将同一关联对象中的外键更新为 NULL,因此崩溃。

我应该如何解决这个问题?我有点困惑地发现这个问题没有在文档中得到解决,也没有在我可以在网上找到的任何地方得到解决,因为我认为这种模式很常见:-/。我缺少什么?

我知道我可以拥有passive_deletes打开直通关系,但我想保留删除语句,只是为了防止更新发生或(使其在之前发生)。

Edit: 实际上,passive_deletes如果在会话中加载依赖对象,则不能解决问题,如下所示DELETE仍将发表声明。一个解决方案是使用viewonly=True,但随后我不仅失去了删除,还失去了关联对象的自动创建。我还发现viewonly=True相当危险,因为它让你append()无需坚持!

REPEX

Setup

from sqlalchemy import create_engine, Table, Column, Integer, String, ForeignKey
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship, backref, sessionmaker

engine = create_engine('sqlite:///:memory:', echo = False)
Base = declarative_base()
Session = sessionmaker(bind=engine)
session = Session()


class Person(Base):
    __tablename__ = 'persons'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    cars = relationship('Car', secondary='car_ownerships', backref='people')

    def __repr__(self):
        return '<Person {} [{}]>'.format(self.name, self.id)

class Car(Base):
    __tablename__ = 'cars'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    def __repr__(self):
        return '<Car {} [{}]>'.format(self.name, self.id)


class CarOwnership(Base):
    __tablename__ = 'car_ownerships'

    id = Column(Integer(), primary_key=True)
    type = Column(String(255))
    car_id = Column(Integer(), ForeignKey(Car.id))
    car = relationship('Car', backref='car_ownerships')
    person_id = Column(Integer(), ForeignKey(Person.id))
    person = relationship('Person', backref='car_ownerships')

    def __repr__(self):
        return 'Ownership [{}]: {} <<-{}->> {}'.format(self.id, self.car, self.type, self.person)

Base.metadata.create_all(engine)

归档对象

antoine = Person(name='Antoine')
rob = Person(name='Rob')
car1 = Car(name="Honda Civic")
car2 = Car(name='Renault Espace')

CarOwnership(person=antoine, car=car1, type = "secondary")
CarOwnership(person=antoine, car=car2, type = "primary")
CarOwnership(person=rob, car=car1, type = "primary")

session.add(antoine)
session.commit()

session.query(CarOwnership).all()

删除->崩溃

print('#### DELETING')
session.delete(car1)
print('#### COMMITING')
session.commit()


# StaleDataError                            Traceback (most recent call last)
# <ipython-input-6-80498b2f20a3> in <module>()
#       1 session.delete(car1)
# ----> 2 session.commit()
# ...

诊断

我上面提出的解释得到了引擎给出的 SQL 语句的支持echo=True:

#### DELETING
#### COMMITING
2016-07-07 16:55:28,893 INFO sqlalchemy.engine.base.Engine SELECT persons.id AS persons_id, persons.name AS persons_name 
FROM persons, car_ownerships 
WHERE ? = car_ownerships.car_id AND persons.id = car_ownerships.person_id
2016-07-07 16:55:28,894 INFO sqlalchemy.engine.base.Engine (1,)
2016-07-07 16:55:28,895 INFO sqlalchemy.engine.base.Engine SELECT car_ownerships.id AS car_ownerships_id, car_ownerships.type AS car_ownerships_type, car_ownerships.car_id AS car_ownerships_car_id, car_ownerships.person_id AS car_ownerships_person_id 
FROM car_ownerships 
WHERE ? = car_ownerships.car_id
2016-07-07 16:55:28,896 INFO sqlalchemy.engine.base.Engine (1,)
2016-07-07 16:55:28,898 INFO sqlalchemy.engine.base.Engine DELETE FROM car_ownerships WHERE car_ownerships.car_id = ? AND car_ownerships.person_id = ?
2016-07-07 16:55:28,898 INFO sqlalchemy.engine.base.Engine ((1, 1), (1, 2))
2016-07-07 16:55:28,900 INFO sqlalchemy.engine.base.Engine UPDATE car_ownerships SET car_id=? WHERE car_ownerships.id = ?
2016-07-07 16:55:28,900 INFO sqlalchemy.engine.base.Engine ((None, 1), (None, 2))
2016-07-07 16:55:28,901 INFO sqlalchemy.engine.base.Engine ROLLBACK

EDITS

Using association_proxy

我们可以使用关联代理来尝试实现“通过”关系。

尽管如此,为了.append()直接依赖对象,我们需要为关联对象创建一个构造函数。这个构造函数必须被“破解”才能成为双向的,所以我们可以使用这两个赋值:

my_car.people.append(Person(name='my_son'))
my_husband.cars.append(Car(name='new_shiny_car'))

生成的(经过中间测试的)代码如下,但我对它感觉不太舒服(因为这个hacky构造函数还会有什么问题?)。

EDIT:下面 RazerM 的回答中介绍了使用关联代理的方法。association_proxy()有一个创建者参数,可以减轻对我最终在下面使用的巨大构造函数的需求。

class Person(Base):
    __tablename__ = 'persons'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    cars = association_proxy('car_ownerships', 'car')

    def __repr__(self):
        return '<Person {} [{}]>'.format(self.name, self.id)

class Car(Base):
    __tablename__ = 'cars'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    people = association_proxy('car_ownerships', 'person')

    def __repr__(self):
        return '<Car {} [{}]>'.format(self.name, self.id)


class CarOwnership(Base):
    __tablename__ = 'car_ownerships'

    id = Column(Integer(), primary_key=True)
    type = Column(String(255))
    car_id = Column(Integer(), ForeignKey(Car.id))
    car = relationship('Car', backref='car_ownerships')
    person_id = Column(Integer(), ForeignKey(Person.id))
    person = relationship('Person', backref='car_ownerships')

    def __init__(self, car=None, person=None, type='secondary'):
        if isinstance(car, Person):
            car, person = person, car
        self.car = car
        self.person = person
        self.type = type        

    def __repr__(self):
        return 'Ownership [{}]: {} <<-{}->> {}'.format(self.id, self.car, self.type, self.person)

您正在使用一个关联对象 http://docs.sqlalchemy.org/en/latest/orm/basic_relationships.html#association-object,所以你需要以不同的方式做事。

我已经改变了这里的关系,仔细观察它们,因为一开始有点难以理解(至少对我来说是这样!)。

我用过back_populates因为它比backref在这种情况下。多对多关系双方都必须参考CarOwnership直接,因为这是您将要使用的对象。这也是你的例子所显示的;你需要使用它以便你可以设置type.

class Person(Base):
    __tablename__ = 'persons'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    cars = relationship('CarOwnership', back_populates='person')

    def __repr__(self):
        return '<Person {} [{}]>'.format(self.name, self.id)


class Car(Base):
    __tablename__ = 'cars'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    people = relationship('CarOwnership', back_populates='car')

    def __repr__(self):
        return '<Car {} [{}]>'.format(self.name, self.id)


class CarOwnership(Base):
    __tablename__ = 'car_ownerships'

    id = Column(Integer(), primary_key=True)
    type = Column(String(255))
    car_id = Column(Integer(), ForeignKey(Car.id))
    person_id = Column(Integer(), ForeignKey(Person.id))

    car = relationship('Car', back_populates='people')
    person = relationship('Person', back_populates='cars')

    def __repr__(self):
        return 'Ownership [{}]: {} <<-{}->> {}'.format(self.id, self.car, self.type, self.person)

请注意,删除任一侧后,car_ownerships行不会被删除,它只会将外键设置为 NULL。如果您想设置自动删除,我可以在答案中添加更多内容。

Edit:直接访问集合Car and Person对象,你需要使用association_proxy,然后类更改为:

from sqlalchemy.ext.associationproxy import association_proxy

class Person(Base):
    __tablename__ = 'persons'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    cars = association_proxy(
        'cars_association', 'car', creator=lambda c: CarOwnership(car=c))

    def __repr__(self):
        return '<Person {} [{}]>'.format(self.name, self.id)


class Car(Base):
    __tablename__ = 'cars'

    id = Column(Integer(), primary_key=True)
    name = Column(String(255))

    people = association_proxy(
        'people_association', 'person', creator=lambda p: CarOwnership(person=p))

    def __repr__(self):
        return '<Car {} [{}]>'.format(self.name, self.id)


class CarOwnership(Base):
    __tablename__ = 'car_ownerships'

    id = Column(Integer(), primary_key=True)
    type = Column(String(255), default='secondary')
    car_id = Column(Integer(), ForeignKey(Car.id))
    person_id = Column(Integer(), ForeignKey(Person.id))

    car = relationship('Car', backref='people_association')
    person = relationship('Person', backref='cars_association')

    def __repr__(self):
        return 'Ownership [{}]: {} <<-{}->> {}'.format(self.id, self.car, self.type, self.person)

Edit:在您的编辑中,将其转换为使用时犯了一个错误backref。您的汽车和人员关联代理不能同时使用“car_ownerships”关系,这就是为什么我有一个名为“people_association”和一个名为“cars_association”的原因。

您拥有的“car_ownerships”关系与关联表名为“car_ownerships”的事实无关,因此我对它们进行了不同的命名。

我修改了上面的代码块。要允许追加工作,您需要将创建者添加到关联代理。我变了back_populates to backref,并添加了默认值type to the Column对象而不是构造函数。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

具有关联对象的多对多以及定义的所有关系在删除时崩溃 的相关文章

随机推荐

  • 在 pyspark 中缓存数据帧

    我想更准确地了解pyspark中数据帧的方法缓存的使用 当我跑步时df cache 它返回一个数据帧 因此 如果我这样做df2 df cache 哪个数据帧在缓存中 是吗df df2 或两者 我找到了源代码DataFrame cache h
  • 如何用py2exe打包psutil?

    该应用程序在我的开发win8环境中运行良好 但是当它与py2exe打包并在生产机器上运行时 它抛出异常 无法在动态链接库 ntdll dll 中定位过程入口点 RtlIdnToAscii 日志文件的详细内容是 Traceback most
  • 使用文本字段编辑货币

    如何获得 JavaFX TextField 来编辑存储的没有派系数字 例如长整型 的货币 使用数据绑定 TextFormatter 和其他 javaFX 东西 目标应该是 Bo 拥有 LongProperty 货币价值以分为单位 可编辑的文
  • 禁用(删除)WooCommerce 4.3.x 中的营销菜单选项

    自 WooCommerce 发布以来4 3 x 之前修复了删除适用于的 营销 菜单选项的问题4 1 x不再起作用了 我想知道是否有人知道如何删除它4 3 x 我已经尝试了所有这些但没有成功 1 add filter woocommerce
  • C++ 使用 || 检查字符是否不在字符串中不工作

    我正在开发这款游戏 要求玩家再次玩 我习惯于检查条件是否不满足然后返回 false 所以最后我可以简单地添加 return true 这也有助于嵌套 如果我以相反的方式这样做 它会起作用 bool AskToPlayAgain cout l
  • 在 writeln 函数中写入撇号 - Pascal

    如何使用 Pascal 打印撇号writeln功能 例子 writeln My brother s book 行不通 因为s book超出了 写入 功能 因此编译器返回错误 Fatal Syntax error expected but i
  • 有人可以用最通俗的语言向我解释一下如何使用 EventArgs 吗?

    我知道他们和代表有关系 我已经尝试过 但我仍然不明白如何使用它们 我对事件处理程序了解一些 但我真正想知道的是如何使用大多数方法中的普通旧事件参数 下面的例子 void Page Load object sender EventArgs e
  • 使用 PHP Unit 测试特殊字符

    我正在使用 PHPUnit 和类从 Symfony2 测试我的控制器WebTestCase return self client gt request POST withdraw array amount gt 130 array arra
  • 单元测试中的 Xcode 内存泄漏检测

    运行单元测试时是否可以测试是否发生内存泄漏 我想检查我的内存管理是否正确处理 Thanks 您可以尝试使用泄漏检测仪器在仪器下运行单元测试 但是 如果您使用 OCUnit 则这仅适用于应用程序 捆绑包 测试 如果您碰巧使用其他东西 请告诉我
  • 使用Rails 5,如何使FriendlyId附加一个-“count+1”来重复slugs而不是UUID?

    显然 FriendlyId 已经更改了之前将数字序列附加到重复 slugs 的默认方法 这就是我想要的 现在使用 UUID Previous versions of FriendlyId appended a numeric sequenc
  • SwiftUI 通用拉动刷新视图

    我有一个 CustomScrollView 它包装了我的 HomeView 如果你下拉它会获取新数据 它工作正常 但问题是我想在多个视图中重用它 并且我不想为每个视图创建它的副本 我尝试过这样做var rootView View但它抛出一个
  • wordpress: previous_post_link() / next_post_link() 占位符

    我遇到了问题previous post link and next post link 功能 当没有之前的帖子时 该函数previous post link 不显示链接 同样对于next post link 和最后一篇文章 我想要一个占位符
  • 如何使用javascript将图像转换为二进制格式[重复]

    这个问题在这里已经有答案了 通过图像 url 将图像转换为二进制 我的网址如下 D MyProject Image image jpg 我想转换这个 image jpg 使用 JavaScript 转换为二进制格式字符串 在线找到了一个二进
  • 字体大小 1px 与 rem 单位一起使用

    我正在追求一种可以随着缩放而很好地缩放的布局 用户按 ctr cmd plus 为此 我需要尺寸与字体大小一起缩放 使用 em 单位太棘手 所以我正在考虑使用 rem 并复制旧 ie 的每个维度属性 我最初的想法是将 html 元素上的 f
  • 代号一:将图像保存到存储并创建小圆形预览

    我目前的图像有问题 1 我无法将图像保存到存储 因为不支持将其直接存储到存储 我希望用户能够用相机拍照 然后创建的照片必须保存在某个地方 以便我稍后可以再次检索它 你能告诉我怎么做吗 a 保存图像 b 如何检索它 我在 Stackoverf
  • 尝试在 laravel 5.5 中获取非对象的属性

    我试图显示我的变量值以查看页面 但它显示尝试获取非对象属性的错误 请有人帮助我 这是我的控制器 public function myAffiliates Request request if user id Sentinel getUser
  • C中字符数组输入输出的问题

    我编写了以下代码来读取字符数组并打印它 include
  • bcdiv 使用带有科学记数法的非常小的浮点导致“除以零”错误

    使用 bcdiv 我无法使用科学记数法除以小浮点数 工作代码 bcscale 30 a 1 b 0 00000001 result bcdiv a b var dump result 结果是 字符串 20 100000000 0000000
  • 在 google-apps-script 中,有没有办法知道谷歌驱动器的文件夹是否为空?

    我想编写一个谷歌应用程序脚本来将每个空文件夹放入我的谷歌驱动器中 我应该使用哪种方法 属性来检查当前文件夹 我可以写一个循环 是否真的是空的 id est没有任何文件或任何其他里面嵌套文件夹 而且 如果您删除了链接有某些文件的文件夹 会发生
  • 具有关联对象的多对多以及定义的所有关系在删除时崩溃

    当具有描述的所有关系的成熟的多对多时 删除两个主要对象之一会崩溃 描述 Car car ownerships car 汽车保有量 person car ownerships Person Car people cars Person Pro