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,我有两个表,Address
和 Customer
,它们之间有多种关系,如下所示:
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_address
或 shipping_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_address
和 Customer.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_address
或 shipping_address
和 addresses
中存在相同的 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_address
或 shipping_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
)
我正在使用 Flask-SQLAlchemy 2.1 和 SQLAlchemy 1.0.13,我有两个表,Address
和 Customer
,它们之间有多种关系,如下所示:
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_address
或 shipping_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_address
和 Customer.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_address
或 shipping_address
和 addresses
中存在相同的 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_address
或 shipping_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
)