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

时间:2018-03-20 12:05:15

标签: php mysql symfony doctrine-orm doctrine

我有三个人,我们称他们为SiteCategoryTag。在此方案中,CategoryTag具有从Site实体生成的复合ID,并且外部ID不是唯一的(siteid在一起独特)。 CategoryTag上有多对多的关系。 (虽然我的问题也可以与ManyToOne关系重现。)

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时发生异常   CONSTRAINT FK_D80F351812469DE2 FOREIGN KEY(category_id)参考   类别(id)ON DELETE CASCADE':

     

SQLSTATE [HY000]:常规错误:1005无法创建表   xxxxxxx#sql-3cf_14b6(错误:150“外键约束是   错误形成“)

我不确定可能出现的问题......我的实体定义中是否有错误?或者这是一个Doctrine bug?

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

3 个答案:

答案 0 :(得分:1)

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

对于ManyToOne关系:

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

对于ManyToMany关系:

/**
 * @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 KEY 参考复合外键。

注意:在" @ORM \ JoinColumn"设定:

  • " referencedColumnName" setting是相关表上的id列名,因此它应该已经存在。

  • "名称"是存储外键和要创建的外键的列名,如果是ManyToOne,则不应与实体的任何列冲突。

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

答案 1 :(得分:1)

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

第一部分有点奇怪:显然,Doctrine需要在$id中定义的复合主键中$site引用之前的AbstractSiteEntity。这听起来很奇怪,但这绝对是真的,因为我曾经多次来回交换它们,只有这个订单才有效。

abstract class AbstractSiteAwareEntity
{
    // $id must come before $site

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

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

对于ManyToMany关系,必须明确声明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;

答案 2 :(得分:0)

您无法使用复合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;
}