1

我使用Flask-SQLAlchemy 2.1和SQLAlchemy 1.0.13,並且我有兩個表, AddressCustomer有彼此多重關係,具體如下:Flask-SQLAlchemy:CircularDependencyError其中多對一關係中的同一行可以與同一表中的一對多關係

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_addressshipping_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沒有被訪問。然而,這不是一種解決方案,因爲在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外鍵,如某些相關的StackOverflow帖子中提到的:

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

CircularDependencyError仍然發生。我找到了一個似乎可行的解決方案,我將在下面發佈,但我不確定這是否是正確的解決方案。

回答

1

設置上關係的雙方post_update=True出現解決了問題:當添加一個billing_address和/或shipping_address

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 
    ) 

現在,它會自動添加到addresses沒有問題。添加新的billing_addressshipping_address的行爲與我預期的一樣,將舊地址留在addresses以及添加新地址。

但是,我不完全相信這個答案,因爲SQLAlchemy文檔明確提到post_update=True應該設置爲關係的一方,而不是兩個,所以我想知道我的解決方案是否會導致意外的行爲。

編輯 - 下面是一個正確的解決方案:

出於某種原因,設置在addressespost_update=True不能設置在customer(反之亦然)的不是工作,而是把它放在billing_addressshipping_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 
    ) 
+1

「國旗應放在剛關係之一」時,這指的是設置'post_update = TRUE;在任一'addresses'關係,或者,兩個'shipping_address'和'billing_address'關係,而不是同一個「地址」/「客戶」關係的雙方。 – univerio