Flask-SQLAlchemy:CircularDependencyError,多对一关系中的同一行可以与同一个表一对多关系

时间:2016-08-18 22:58:06

标签: python sqlalchemy relationship flask-sqlalchemy circular-dependency

我使用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'
    )

还有两个事件侦听器会自动为billing_address个实例添加任意集shipping_addressaddressesCustomer

@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没有被访问。但是,这不是一个解决方案,因为循环依赖性是由于Addressbilling_addressshipping_address中存在相同的addresses实例,我想允许{ {1}}包括当前的结算和送货地址。

根据relevant SQLAlchemy docs,可以通过在关系的一侧添加addresses参数并为其外键指定名称来解决此问题:

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外键,如某些相关的StackOverflow帖子所述:

customer_id

customer_id = db.Column( db.Integer, db.ForeignKey('customers.id', name='fk_customer_id', use_alter=True) ) 仍然发生。我找到了一个似乎有效的解决方案,我将在下面发布,但我不相信这是正确的解决方案。

1 个答案:

答案 0 :(得分:1)

在关系的两边设置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和/或shipping_address时,它会自动添加到addresses而不会出现问题。添加新的billing_addressshipping_address的行为也符合我的预期,将旧地址保留在addresses中,同时添加新地址。

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

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

由于某些原因,在post_update=True上设置addresses而不在customer上设置billing_address(反之亦然)不起作用,但在shipping_address和{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 ) 上设置QStackedWidget {1}}由@univerio建议。谢谢!

{{1}}