我有一个使用TreeBehavior的模型“Customers”,它运行良好:
例如:
id | parent_id | type | name
----|-----------|---------|--------------
1 | NULL | husband | Jon Doe
2 | 1 | wife | Jane Doe
3 | 1 | child | Jim Doe
客户可以拥有许多“合同”。所有协会都运作良好。
现在,我尝试删除客户。这仅适用于子元素(妻子,孩子):如果删除妻子,则所有相关合同也将被删除。但是,如果我尝试删除丈夫(parent_id = NULL),我会收到错误:
错误:SQLSTATE [23000]:完整性约束违规:1451不能 删除或更新父行:外键约束失败 (`contracts`,CONSTRAINT`contacts_fk0` FOREIGN KEY(`customer_id`) 参考文献`customers`(`id`))
SQL查询:
DELETE FROM customers WHERE ((lft) >= :c0 AND (lft) <= :c1)
我认为有一个简单的技巧,我必须做的,但我找不到解决方案。也许你们其中一个人可以帮忙吗?
提前致谢。
UPDATE1
如果没有儿童客户有合同,那么它似乎正在运作。
答案 0 :(得分:1)
我认为没有办法让ORM自动处理这个问题,因为树行为使用Table::deleteAll()
删除子树中的节点,这将创建一个类似于你的DELETE
查询'显示,即记录被一次删除,不逐个删除,这将是依赖/级联删除所必需的。
你可能想建议一个增强功能,我想如果这样做会很好,不确定它有多复杂。
您可能的解决方法可能是检索父节点的子节点,并从最外层,最内层逐个手动删除它们。这可能不会很好地执行,因为树会在每次删除时同步,这反过来又要求在删除实体之前重新加载实体,因为它们所持有的lft
和rgt
值否则会过时
这是一个未经测试的例子来说明我在说什么:
$result = $this->Customers->connection()->transactional(function () {
// retrieve the parent to delete
$customer = $this->Customers->get(1);
// retrieve the parents children
$descendants = $this->Customers->find('children', ['for' => $customer->id]);
// Reverse the childrens order, by default they are ordered
// from the inner most to the outer most. This could probably
// also be done on query level by sorting on the `lft` field.
$entities = array_reverse($descendants->toArray());
// append the parent so that it is being deleted last
$entities[] = $customer;
foreach ($entities as $entity) {
$entity = $this->Customers->get($entity->id);
if (!$this->Customers->delete($entity, ['atomic' => false])) {
return false;
}
}
return true;
});
理论上,这应该逐个删除节点,导致他们的dependend合约首先被删除。
或者,您可以收集树节点及其关联的合同,首先手动删除合同,然后删除父树节点。这可能会表现得更好。有点像这样:
$result = $this->Customers->connection()->transactional(function () {
// retrieve the parent to delete
$customer = $this->Customers->get(1, [
'contain' => ['Contracts']
]);
// retrieve the parents children
$descendants = $this->Customers
->find('children', ['for' => $customer->id])
->contain(['Contracts']);
// collect all contracts
$contracts = $customer->contracts;
foreach ($descendants as $entity) {
$contracts = array_merge($contracts, $entity->contracts);
}
// delete contracts first
// in case no callbacks are required for deleting contracts,
// you could also collect the customer or contract ids instead
// and use `deleteAll()`
foreach ($contracts as $entity) {
if (!$this->Customers->Contracts->delete($entity, ['atomic' => false])) {
return false;
}
}
// then delete the customers
if (!$this->Customers->delete($customer, ['atomic' => false])) {
return false;
}
return true;
});
另见