我的实体OneToMany <-> ManyToOne
和Device
之间的双向Event
关系存在问题。这是映射的外观:
// Device entity
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Event", mappedBy="device")
*/
protected $events;
// Event entity
/**
* @ORM\ManyToOne(targetEntity="AppBundle\Entity\Device", inversedBy="events")
*/
protected $device;
问题出现是因为Device
是单表继承实体
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="device_class_type", type="string")
每次我获取并迭代某些Event
个实体时,总是会急切地提取$device
。发生这种情况是因为它是relative documentation
单表有一般的性能考虑因素 继承:如果是多对一或一对一的目标实体 association是STI实体,出于性能原因,它更可取 它是继承层次结构中的叶子实体,(即没有 子类)。否则,Doctrine无法创建此代理实例 实体,并将始终热切地加载实体。
现在有另一个名为Gateway
的实体与Device
和Event
都有关系:
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway")
*/
protected $devices;
/**
* @ORM\OneToMany(targetEntity="targetEntity="AppBundle\Entity\Event", mappedBy="gateway")
*/
protected $events;
public function getEvents(): Collection
{
return $this->events;
}
当然,每当我遍历$gateway->getEvents()
所有相关事件时,设备都会被急切地提取。即使我没有收到任何$device
信息,也会发生这种情况 - 空foreach
足以让Doctrine为每个对象执行1次查询以获取相关的$device
foreach ($gateway->getEvents() as $event) {}
现在我知道我可以使用QueryBuilder
来设置不同的水合模式,避免$device
抓取
return $this->getEntityManager()->createQueryBuilder()
->select('e')
->from('AppBundle:Event', 'e')
->where('e.gateway = :gateway')
->setParameter('gateway', $gateway)
->getQuery()->getResult(Query::HYDRATE_SIMPLEOBJECT);
但我想以某种方式直接在Gateway
实体中进行。
因此可以直接在Gateway->events
实体类中使用水合物Gateway
吗?
答案 0 :(得分:1)
我建议你在这里考虑几个选项。
1)根据Doctrine's documentation,您可以使用fetch="EAGER"
来暗示您希望在加载实体时急切获取关系的Doctrine:
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway", fetch="EAGER")
*/
protected $devices;
如果仔细使用,这可以避免在迭代时触发其他查询,但也有它自己的缺点。
如果您开始广泛使用强制预先加载,您可能会发现自己处于加载实体以从中读取简单属性将导致加载数十甚至数百个关系的情况。从SQL的角度来看,这可能看起来不那么糟糕(可能是单个查询),但请记住,所有结果都将作为对象加以水合并附加到工作单元以监视它们的变化。
2)如果您将其用于报告目的(例如显示设备的所有事件),那么最好不要使用实体,而是从Doctrine请求数组水合。在这种情况下,您将能够通过显式连接关系(或不关联)来控制进入结果的内容。作为额外的好处,您将跳过UoM昂贵的水合作用和监控,因为在这种情况下不太可能修改实体。使用Doctrine进行报告时,这也被视为“最佳做法”。
答案 1 :(得分:1)
您有一个循环引用,其中一个节点(设备)将强制mod
。更糟糕的是,其中一个节点(Gateway)就像其他两个节点之间的ManyToMany连接表一样,导致FETCH EAGER
在近无限循环(或至少大块相关数据)中加载所有内容。
FETCH EAGER
正如您所看到的,当设备执行EAGER提取时,它会收集许多 +──< OneToMany
>──+ ManyToOne
>──< ManyToMany
+──+ OneToOne
┌──────< Gateway >──────┐
│ │
+ +
Event +──────────────< Device*
,因此会收集很多Gateways
,因此会有很多Events
,因此会有更多Devices
,等Gateways
将一直持续到填充所有参考文献。
构建自己的保湿器需要一些谨慎的数据操作,但对于您的用例可能会有些简单。请记住使用Doctrine注册您的保湿器,并将其作为参数传递给Fetch EAGER
$query->execute([], 'GatewayHydrator');
从class GatewayHydrator extends DefaultEntityHydrator
{
public function hydrateResultSet($stmt)
{
$data = $stmt->fetchAll(PDO::FETCH_ASSOC);
$class = $this->em->getClassMetadata(Gateway::class);
$gateway = $class->newInstance();
$gateway->setName($data[0]['gateway_name']); // example only
return $gateway;
}
}
映射中删除$gateway => Gateway
Device
和mappedBy="gateway"
映射,设备将从Doctrine的角度有效地成为一片叶子。这将避免该参考循环,但有一个缺点:必须手动设置Device-&gt;网关属性(可能在网关和事件Gateway->device
方法中)。