学说:具有复合键的实体之间的 ManyToX 关系

Posted

技术标签:

【中文标题】学说:具有复合键的实体之间的 ManyToX 关系【英文标题】:Doctrine: ManyToX relation between entities with composite keys 【发布时间】:2018-03-20 12:05:14 【问题描述】:

我有三个实体,我们称它们为 SiteCategoryTag。在这种情况下,CategoryTag 具有从 Site 实体生成的复合 ID 和不唯一的外部 ID(siteid 一起是唯一的)。 CategoryTag 具有多对多关系。 (尽管我的问题也可以通过多对一关系重现。)

Site 实体:

/**
 * @ORM\Entity
 */
class Site

    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    private $id;

    /**
     * @ORM\Column(type="string")
     */
    private $name;

Category 实体:

/**
 * @ORM\Entity
 */
class Category extends AbstractSiteAwareEntity

    /**
     * @ORM\Column(type="string")
     */
    private $name;

    /**
     * @ORM\ManyToMany(targetEntity="Tag")
     */
    private $tags;

Tag 实体:

/**
 * @ORM\Entity
 */
class Tag extends AbstractSiteAwareEntity

    /**
     * @ORM\Column(type="string")
     */
    private $name;

最后两个实体继承自 AbstractSiteAwareEntity 类,它定义了复合索引:

abstract class AbstractSiteAwareEntity

    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Site")
     */
    public $site;

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     */
    public $id;

作为 ManyToOne 关系的示例,让我们想象一个 Post 实体,它有一个自增 ID 和对 Category 的引用:

/**
 * @ORM\Entity
 */
class Post

    /**
     * @ORM\Id
     * @ORM\GeneratedValue
     * @ORM\Column(type="integer")
     */
    public $id;

    /**
     * @ORM\ManyToOne(targetEntity="Category")
     */
    private $category;

    /**
     * @ORM\Column(type="string")
     */
    private $title;

    /**
     * @ORM\Column(type="string")
     */
    private $content;

使用bin/console doctrine:schema:update --dump-sql --force --complete 更新架构时,出现以下异常:

执行 'ALTER TABLE category_tag ADD 时发生异常 约束 FK_D80F351812469DE2 外键(category_id)参考 类别 (id) ON DELETE CASCADE':

SQLSTATE[HY000]:一般错误:1005 无法创建表 xxxxxxx.#sql-3cf_14b6 (errno: 150 "外键约束为 格式不正确")

我不确定可能出了什么问题……我的实体定义是否有错误?或者这是一个 Doctrine 错误?

注意:我在 Symfony 3.4 上使用教义/dbal 2.6.3。

【问题讨论】:

【参考方案1】:

您必须在注释中定义加入的列才能使其工作:

对于多对一关系:

/**
 * @ORM\ManyToOne(targetEntity="Tag")
 * @ORM\JoinColumns(
 *     @ORM\JoinColumn(name="tag_site_id", referencedColumnName="site_id"),
 *     @ORM\JoinColumn(name="tag_id", referencedColumnName="id")
 * )
 **/
protected $tag;

对于多对多关系:

/**
 * @ORM\ManyToMany(targetEntity="Tag")
 * @ORM\JoinTable(name="category_tags",
 *      joinColumns=
 *     @ORM\JoinColumn(name="category_site_id", referencedColumnName="site_id"),
 *     @ORM\JoinColumn(name="category_id", referencedColumnName="id")
 *     ,
 *      inverseJoinColumns=
 *     @ORM\JoinColumn(name="tag_site_id", referencedColumnName="site_id"),
 *     @ORM\JoinColumn(name="tag_id", referencedColumnName="id")
 *     
 *      )
 **/
 protected $tags;

工作原理:创建 2 列作为 FOREIGN KEYREFERENCES 复合外键。

注意:在“@ORM\JoinColumn”设置中:

“referencedColumnName”设置是相关表的id列名,所以应该已经存在了。

“name”是存储外键并被创建的列名,在 ManyToOne 的情况下,它不应与实体的任何列冲突。

joinColumns 定义的顺序很重要。复合外键应以引用表中具有索引的列开头。在这种情况下,"@ORM\JoinColumn" 到 "site_id" 应该是first。 Reference

【讨论】:

您的 ManyToMany 代码工作正常,我只是对 ManyToOne 示例有问题。您能否更改它以显示如果 Tag 与具有 ManyToOne 的类别相关,其中 Tag 具有自动增量 ID,Category 是站点和外部 ID 的组合? 我在几分钟前编辑了 ManyToOne 关系,因为我有一个错误,你能再试一次吗?你得到什么错误? 我已经试过那个了。我的实际实体命名有点不同,但错误是Foreign key constraint is incorrectly formed,D2生成的查询是ALTER TABLE Tag ADD CONSTRAINT FK_… FOREIGN KEY (category_id, category_site_id) REFERENCES Category (id, site_id) ON DELETE CASCADE。我不确定我是否正确“翻译”了您示例中的列名。 我也试过了……也没用。也许我的实体还有其他问题。对不起,伙计,我想我要放弃了。我现在已经实现了使用自动增量 ID 和复合键作为额外索引的解决方案,它完美地工作。我正在为您的努力 +1,但不幸的是,我们无法解决问题。再次感谢。 我怎么也想不到!考虑在学说的回购中创建一个问题,他们至少可以提供一些更好的文档。【参考方案2】:

基于Jannis’ answer,我终于找到了解决方案。解决方案由两部分组成:

第一部分有点奇怪:显然,Doctrine 在AbstractSiteEntity 中定义的复合主键中的$site 引用之前需要$id。这听起来很奇怪,但这绝对是真的,因为我尝试了几次来回交换它们,但出于某种原因,只有这个顺序有效。

abstract class AbstractSiteAwareEntity

    // $id must come before $site

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     */
    public $id;

    /**
     * @ORM\Id
     * @ORM\ManyToOne(targetEntity="Site")
     */
    public $site;

对于多对多关系,JoinTable 中的索引必须明确声明,因为 Doctrine 无法自行创建复合外键。我使用了 Jannis 的建议,并做了一些修改(实际上不需要列名):

/**
 * @ORM\ManyToMany(targetEntity="Tag")
 * @ORM\JoinTable(
 *  joinColumns=
 *      @ORM\JoinColumn(referencedColumnName="id"),
 *      @ORM\JoinColumn(referencedColumnName="site_id")
 *  ,
 *  inverseJoinColumns=
 *      @ORM\JoinColumn(referencedColumnName="id"),
 *      @ORM\JoinColumn(referencedColumnName="site_id")
 *  
 * )
*/
private $tags;

【讨论】:

【参考方案3】:

您不能对关系使用复合 PrK 键。 相反,定义一个 ID 和一个复合 UNI 键(或者在这种情况下只是 UNI 键)

复合 UNI 密钥示例

/**
 * SiteAware
 *
 * @ORM\Table(name="site_aware",
 *     uniqueConstraints=@ORM\UniqueConstraint(columns="site_id", "random_entity_id"))
 * @UniqueEntity(fields="site", "randomEntity", message="This pair combination is already listed")
 */
abstract class AbstractSiteAwareEntity

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     */
    public $id;
    /**
     * @ORM\ManyToOne(targetEntity="Site")
     */
    public $site;
    /**
     * @ORM\ManyToOne(targetEntity="RandomEntity")
     */
    public $randomEntity;

简单的唯一键

abstract class AbstractSiteAwareEntity

    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     */
    public $id;
    /**
     * @ORM\ManyToOne(targetEntity="Site")
     * @ORM\JoinColumn(unique=true)
     */
    public $site;

【讨论】:

感谢您的建议。你确定PK不能用于关系吗?根据 mysql 文档,应该可以:dev.mysql.com/doc/refman/5.5/en/create-table-foreign-keys.html. 不确定 MySQL,但可以肯定的是,Symfony 不允许将复合主键用于关系。我自己也遇到了这个问题。

以上是关于学说:具有复合键的实体之间的 ManyToX 关系的主要内容,如果未能解决你的问题,请参考以下文章

Spring OneToMany 与复合键的关系与另一个具有复合键的关系

如何通过学说实体之间的 ID 建立一对一的关系

级联保存具有外键的实体对象作为复合主键的一部分

在组合键上使用“链接”外键的问题

实体框架:如何从具有复合键的表中返回一行?

Hibernate 对具有复合键的子实体执行错误的插入顺序