Flask-SQLAlchemy:CircularDependencyError,其中多对一关系中的同一行可以与相同的一对多关系 table

Flask-SQLAlchemy: CircularDependencyError where same row in many-to-one relationship can be in one-to-many relationship with same table

我正在使用 Flask-SQLAlchemy 2.1 和 SQLAlchemy 1.0.13,我有两个表,AddressCustomer,它们之间有多种关系,如下所示:

class Address(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    ...  # Other rows including first_name, last_name, etc.
    customer_id = db.Column(
        db.Integer,
        db.ForeignKey('customers.id')
    )
    customer = db.relationship(
        'Customer',
        foreign_keys=customer_id,
        back_populates='addresses'
    )

class Customer(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    billing_address_id = db.Column(db.Integer, db.ForeignKey('addresses.id'))
    billing_address = db.relationship(
        'Address',
        foreign_keys=billing_address_id
    )
    shipping_address_id = db.Column(
        db.Integer,
        db.ForeignKey('addresses.id')
    )
    shipping_address = db.relationship(
        'Address',
        foreign_keys=shipping_address_id
    )
    addresses = db.relationship(
        'Address',
        foreign_keys='Address.customer_id',
        back_populates='customer'
    )

对于 Customer 个实例,还有两个事件侦听器会自动将任何集合 billing_addressshipping_address 添加到 addresses

@event.listens_for(Customer.billing_address, 'set')
def add_billing_address_event(target, value, oldvalue, initiator):
    """If a billing address is added to a `Customer`, add it to addresses."""
    if value is not None and value not in target.addresses:
        target.addresses.append(value)


@event.listens_for(Customer.shipping_address, 'set')
def add_shipping_address_event(target, value, oldvalue, initiator):
    """If a shipping address is added to `Customer`, add to addresses."""
    if value is not None and value not in target.addresses:
        target.addresses.append(value)

尝试设置 Customer.billing_addressCustomer.shipping_address 结果如我所料 CircularDependencyError

> c = Customer()
> c.billing_address = Address(first_name='Bill')
> c.shipping_address = Address(first_name='Ship')
> db.session.add(c)
> db.session.flush()

CircularDependencyError: Circular dependency detected. (ProcessState(ManyToOneDP(Customer.shipping_address), <Customer at 0x7f53aa5c9fd0>, delete=False), ProcessState(ManyToOneDP(Address.customer), <Address at 0x7f53aa4e4128>, delete=False), ProcessState(ManyToOneDP(Address.customer), <Address at 0x7f53aa4e4080>, delete=False), SaveUpdateState(<Customer at 0x7f53aa5c9fd0>), ProcessState(ManyToOneDP(Customer.billing_address), <Customer at 0x7f53aa5c9fd0>, delete=False), ProcessState(OneToManyDP(Customer.addresses), <Customer at 0x7f53aa5c9fd0>, delete=False), SaveUpdateState(<Address at 0x7f53aa4e4080>), SaveUpdateState(<Address at 0x7f53aa4e4128>))

如果我注释掉事件侦听器,这不会导致 CircularDependencyError,这也是我所期望的,因为未访问 Customer.address。然而,这不是一个解决方案,因为循环依赖是由于 billing_addressshipping_addressaddresses 中存在相同的 Address 实例,我想允许 addresses 以包括当前的帐单和送货地址。

根据 relevant SQLAlchemy docs 这应该可以通过在关系的一侧添加 post_update=True 参数并为其外键命名来解决:

class Address(db.Model):
    ...
    customer_id = db.Column(
        db.Integer,
        db.ForeignKey('customers.id', name='fk_customer_id')
    )
    customer = db.relationship(
        'Customer',
        foreign_keys=customer_id,
        back_populates='addresses',
        post_update=True
    )

这仍然会引发 CircularDependencyError,但是:

CircularDependencyError: Circular dependency detected. (ProcessState(OneToManyDP(Customer.addresses), <Customer at 0x7f620af3ff60>, delete=False), SaveUpdateState(<Address at 0x7f620ae5a080>), SaveUpdateState(<Address at 0x7f620ae5a128>), ProcessState(ManyToOneDP(Customer.billing_address), <Customer at 0x7f620af3ff60>, delete=False), SaveUpdateState(<Customer at 0x7f620af3ff60>), ProcessState(ManyToOneDP(Customer.shipping_address), <Customer at 0x7f620af3ff60>, delete=False))

我也试过将 use_alter=True 传递给 customer_id 外键,如一些相关的 Whosebug posts:

customer_id = db.Column(
    db.Integer,
    db.ForeignKey('customers.id', name='fk_customer_id', use_alter=True)
)

CircularDependencyError 仍然发生。我找到了一个似乎有效的解决方案,我将在下面 post,但我不确定它是否是正确的解决方案。

设置post_update=True两边关系出现问题解决:

class Address(db.Model):
    ...
    customer_id = db.Column(db.Integer, db.ForeignKey('customers.id'))
    customer = db.relationship(
        'Customer',
        foreign_keys=customer_id,
        back_populates='addresses',
        post_update=True
    )

class Customer(db.Model):
    ...
    addresses = db.relationship(
        'Address',
        foreign_keys='Address.customer_id',
        back_populates='customer',
        post_update=True
    )

现在,当添加 billing_address and/or 和 shipping_address 时,它会自动添加到 addresses 而不会出现问题。添加新的 billing_addressshipping_address 的行为也符合我的预期,将旧地址留在 addresses 中并添加新地址。

然而,我对这个答案并不完全有信心,因为 SQLAlchemy 文档明确提到应该为关系的一侧设置 post_update=True,而不是两者,所以我想知道我的解决方案是否会导致意外行为。

编辑 - 这是正确的解决方案:

出于某种原因,在 addresses 上设置 post_update=True 而不同时在 customer 上设置(反之亦然)不起作用,但在 billing_address 上设置它和 shipping_address 按照@univerio 的建议。谢谢!

class Customer(db.Model):
...
    billing_address = db.relationship(
        'Address',
        foreign_keys=billing_address_id,
        post_update=True
    )
    shipping_address = db.relationship(
        'Address',
        foreign_keys=shipping_address_id,
        post_update=True
    )