5

我的数据库中有以下关联(简化版):

数据库模式

这是一个多对多关联,但在连接表上有一个属性,所以我必须使用一对多/多对一关联

我有一个表单,我可以在其中向一个订单项添加任意数量的关系并同时创建它(主要受文档中的如何嵌入表单集合教程的启发。

当我发布表单时,我收到以下错误:

TEST\MyBundle\Entity\Relation 类型的实体通过外部实体 TEST\MyBundle\Entity\Order 具有身份,但是该实体本身没有身份。您必须在相关实体上调用 EntityManager#persist() 并确保在尝试持久化“TEST\MyBundle\Entity\Relation”之前生成了一个标识符。在 Post Insert ID Generation(例如 MySQL Auto-Increment 或 PostgreSQL SERIAL)的情况下,这意味着您必须在两个持久化操作之间调用 EntityManager#flush()。

我理解这个错误,因为 Doctrine 试图保留Relation与订单相关的对象,因为我可以cascade={"persist"}选择OneToMany关系。但是我怎样才能避免这种行为呢?

我试图删除cascade={"persist"}并手动保留实体,但我得到了同样的错误(因为我需要flush()订购以获取 ID,当我这样做时,我有相同的错误消息)。
我也尝试过之前detach()Relation对象,flush()但没有运气。

4

3 回答 3

3

如果 1) 您正在使用带有复合键的连接表,2) 表单组件,以及 3) 连接表是由表单组件的“集合”字段构建的实体,则这个问题似乎是独一无二的。我看到很多人遇到问题但没有很多解决方案,所以我想我会分享我的。

我想保留我的复合主键,因为我想确保两个外键中只有一个实例会保留在数据库中。以此 实体设置为例

/** @Entity */
class Order
{
    /** @OneToMany(targetEntity="OrderItem", mappedBy="order") */
    private $items;

    public function __construct(Customer $customer)
    {
        $this->items = new Doctrine\Common\Collections\ArrayCollection();
    }
}

/** @Entity */
class Product
{
    /** @OneToMany(targetEntity="OrderItem", mappedBy="product") */
    private $orders;
    .....

    public function __construct(Customer $customer)
    {
        $this->orders = new Doctrine\Common\Collections\ArrayCollection();
    }
}

/** @Entity */
class OrderItem
{
    /** @Id @ManyToOne(targetEntity="Order") */
    private $order;

    /** @Id @ManyToOne(targetEntity="Product") */
    private $product;

    /** @Column(type="integer") */
    private $amount = 1;
}

我面临的问题是,如果我Order在表单中构建一个对象,该对象的集合字段为OrderItems,我将无法在没有先保存 Order 实体的情况下保存 OrderItem 实体(因为学说/SQL 需要订单 ID对于复合键),但 Doctrine EntityManager 不允许我保存具有 OrderItem 属性的 Order 对象(因为它坚持将它们一起保存)。您不能关闭级联,因为它会抱怨您没有先保存关联实体,并且您无法在保存之前保存关联实体Order。真是个难题。 我的解决方案是删除关联实体,保存Order然后将关联实体重新引入 Order 对象并再次保存. 所以首先我创建了一个 ArrayCollection 属性的质量分配函数$items

class Order
{
    .....
    public function setItemsArray(Doctrine\Common\Collections\ArrayCollection $itemsArray = null){
    if(null){
        $this->items->clear();
    }else{
        $this->items = $itemsArray;
    }
    ....
}

然后在我的控制器中处理订单表格。

//get entity manager
$em = $this->getDoctrine()->getManager();
//get order information (with items)
$order = $form->getData();
//pull out items array from order
$items = $order->getItems();
//clear the items from the order
$order->setItemsArray(null);
//persist and flush the Order object
$em->persist($order);
$em->flush();

//reintroduce the order items to the order object
$order->setItemsArray($items);
//persist and flush the Order object again ):
$em->persist($order);
$em->flush();

您必须坚持并刷新两次,这很糟糕(请参阅此处的更多信息Persist object with two foreign identities in dictionary)。但这对你来说是教义,凭借它的全部力量,它肯定会让你陷入困境。但幸运的是,您只需在创建新对象时执行此操作,而不是编辑,因为该对象已经在数据库中。

于 2015-05-20T21:53:29.477 回答
1

您需要先持久化并刷新原始数据,然后才能持久化和刷新关系记录。您对错误原因的判断是 100% 正确的。

我从图中假设您正在尝试同时添加和订购以及与联系人的关系?如果是这样,您需要先持久化并刷新订单,然后才能持久化并刷新关系。或者,您可以将主键添加到关系表。

于 2013-04-09T19:04:49.073 回答
1

我最终在我的Relation表上创建了一个单独的主键(而不是复合主键)。
看起来这是一个肮脏的修复,我相信有更好的方法来处理这种情况,但它现在有效。

这是我的Relations实体:

/**
 * Relation
 *
 * @ORM\Entity
 */
class Relation
{
    /**
     * @var integer
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\ManyToOne(targetEntity="Contact", inversedBy="relation")
     */
    protected $contact;

    /**
     * @ORM\ManyToOne(targetEntity="Order", inversedBy="relation")
     */
    protected $order;

    /**
     * @var integer
     *
     * @ORM\Column(name="invoice", type="integer", nullable=true)
     */
    private $invoice;

    //Rest of the entity...

然后我添加了与以下关系的cascade={"persist"}选项:OneToManyOrder

/**
 * Orders
 *
 * @ORM\Entity
 */
class Order
{   
    /**
     * @ORM\OneToMany(targetEntity="Relation", mappedBy="order", cascade={"persist"})
     */
    protected $relation;

    //Rest of the entity...

瞧!

于 2013-04-11T23:29:46.347 回答