禁用 Doctrine 外键约束

Posted

技术标签:

【中文标题】禁用 Doctrine 外键约束【英文标题】:Disable Doctrine foreign key constraint 【发布时间】:2017-01-14 12:00:36 【问题描述】:

我与我的一个模型有关系:

/**
* @ORM\ManyToOne(targetEntity="Page", cascade="persist")
* @ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $parentPage;

当我删除父页面时,我得到这个错误:

Integrity constraint violation: 1451 Cannot delete or update a parent row: a foreign key constraint fails

基本上我的模型是一个页面和页面修订。当我删除页面时我不想删除修订。我还想在页面修订中保留page_id(即不将其设置为空)。

我怎样才能用 Doctrine 做到这一点?

【问题讨论】:

很抱歉错误的近距离投票,我误读了您的问题。您需要做的是删除实际的外键约束。如果约束是通过 Doctrine 创建的,您需要将其配置为不再这样做,否则您只需要继续删除它 另一种方法可能是添加一个属性(例如,enabled),将页面从搜索中排除。这样做会产生页面被删除而不违反外键约束的效果。 @Phil 我认为即便如此,从 Doctrine 的角度来看,数据不一致可能还会存在更多问题。我建议实现页面的软删除(在这种情况下我最喜欢)或将page_id 列复制为“备份”,然后在删除页面时在page_id 上设置null 软删除可能是最正确的解决方案,但是更改应用程序的所有其他部分以过滤掉已删除的实体会很耗时。切换到 MyISAM 也可以,这是我所做的,但并不理想。 @Petah 如果您使用gedmo 实现软删除,则无需更改任何内容即可过滤掉已删除的实体,只需在配置文件doctrine: orm: entity_managers: default: filters: softdeleteable: [class: Gedmo\SoftDeleteable\Filter\SoftDeleteableFilter & enabled: true] 中添加默认过滤器即可。然后,每当您想获取已删除的实体时,只需暂时禁用过滤器$filters = ...->get('doctrine')->getEntityManager()->getFilters(); $filters->disable('softdeleteable'); query then enable 【参考方案1】:

通过定义,如果不将键设置为空(onDelete="SET NULL")或级联删除操作(There are two options - ORM 级别:@987654324,则无法删除外键指向的记录@ | 数据库级别:onDelete="CASCADE")。 有setting a default value of a still existing record 的替代方案,但您必须手动执行此操作,我认为 Doctrine 不支持这种“开箱即用”(如果我错了,请纠正我,但在这种情况下无论如何都不需要设置默认值)。

这种严格性反映了具有外键约束的概念;就像@Théo 说的:

FK 是为了保证数据的一致性

软删除(已经提到)是一种解决方案,但您还可以添加一个额外的 removed_page_id 列,在您在 preRemove 事件处理程序(生命周期打回来)。我想知道这些信息是否有任何价值,但我想你对它有一些用处,否则你不会问这个问题。

我绝对 并不是说这是一种好的做法,但它至少可以用于你的边缘情况。所以有些东西是:

在你的Revision:

/**
 * @ORM\ManyToOne(targetEntity="Page", cascade="persist")
 * @ORM\JoinColumn(name="page_id", referencedColumnName="id", onDelete="SET NULL")
 */
private $parentPage;

/**
 * @var int
 * @ORM\Column(type="integer", name="removed_page_id", nullable=true)
 */
protected $removedPageId;

然后在你的Page:

/** 
 * @ORM\PreRemove 
 */
public function preRemovePageHandler(LifecycleEventArgs $args)

    $entityManager = $args->getEntityManager();
    $page = $args->getEntity();
    $revisions = $page->getRevisions();
    foreach($revisions as $revision)
        $revision->setRemovedPageId($page->getId());
        $entityManager->persist($revision);
    
    $entityManager->flush();

或者,您当然可以在构造 Revision 期间设置正确的 $removedPageId 值,然后您甚至不需要在删除时执行生命周期回调。

【讨论】:

保证约束需要额外的计算资源。应用服务器比数据库服务器更容易横向扩展,所以约束应该放在业务逻辑中。【参考方案2】:

我通过覆盖 symfony 4.3 中的一个学说类解决了这个问题,对我来说它看起来像这样:

<?php declare(strict_types=1);

namespace App\DBAL;

use Doctrine\DBAL\Platforms\mysqlPlatform;

/**
 * Class MySQLPlatformService
 * @package App\DBAL
 */
class MySQLPlatformService extends MySQLPlatform

    /**
     * Disabling the creation of foreign keys in the database (partitioning is used)
     * @return false
     */
    public function supportsForeignKeyConstraints(): bool
    
        return false;
    

    /**
     * Disabling the creation of foreign keys in the database (partitioning is used)
     * @return false
     */
    public function supportsForeignKeyOnUpdate(): bool
    
        return false;
    

【讨论】:

【参考方案3】:

您可以禁用特定模型的外键导出:

User:
  attributes:
    export: tables
  columns:

现在它只会导出表定义而不导出任何外键。您可以使用:无、表、约束、插件或全部。

【讨论】:

【参考方案4】:

您明确要求数据不一致,但我很确定您真的不希望这样。我想不出这种情况是可以辩护的。这是一种不好的做法,肯定会导致问题。例如:$revision-&gt;getPage() 的预期结果是什么?

有一个非常简单优雅的解决方案:softdeletable。它基本上为您的实体添加了一个名为deletedAt 的属性(换句话说:将列添加到您的表中)以存储该实体是否(或更好:何时)被删除。因此,如果该属性是 null,则不会删除该实体。

您唯一需要做的就是添加this bundle,为您的实体添加一个特征(Gedmo\SoftDeleteable\Traits\SoftDeleteableEntity)并更新您的数据库。实现起来非常简单:这个包将为您完成工作。阅读 the documentation 以了解此扩展。

或者,您可以添加“已启用”布尔属性或状态字段(例如“已发布”、“草稿”、“已删除”)。

【讨论】:

【参考方案5】:

当我删除页面时,我不想删除修订。我还想在页面修订中保留 page_id(即不将其设置为 null)。

我想你已经得到了答案:Doctrine 不会那样做,仅仅是因为它与外键的概念格格不入。 FK的原则是保证数据的一致性,所以如果你有一个FK,它必须引用一个已有的ID。在删除时,某些数据库引擎(例如 InnoDB for MySQL)允许您将 FK 放入 NULL(假设您确实将 FK 列设置为可空)。但是引用不存在的 ID 是不可行的,或者它不是 FK。

如果您真的想这样做,请不要在这种特定情况下使用 Doctrine,它不会阻止您在代码库的其他地方使用 Doctrine。另一种解决方案是在查询之前手动删除 FK 约束或使用 DB 语句来跳过 FK 检查。

【讨论】:

MySQL 不是数据库引擎,它是一个 DBMS。 MySQL 的流行数据库引擎(InnoDB 和 MyISAM)处理外键的方式不同。如果您想了解更多信息,请阅读此问题:***.com/questions/12614541/… $objectManager-&gt;getConnection()-&gt;exec('SET FOREIGN_KEY_CHECKS = 0;');

以上是关于禁用 Doctrine 外键约束的主要内容,如果未能解决你的问题,请参考以下文章

Doctrine 固定装置加载,有问题设置引用(外键)违反 -not-null 约束

oracle 删除外键约束 禁用约束 启用约束

Oracle中禁用了外键约束对系统有没有影响

如何在 MySQL 中临时禁用外键约束?

如何在 MySQL 中临时禁用外键约束?

如何在 MySQL 中临时禁用外键约束?