[英]Doctrine - Hydrate collection in Entity class
我有一個關於我的實體Device
和Event
之間的雙向OneToMany <-> ManyToOne
關系的問題。 這是映射的樣子:
// 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
。 發生這種情況是因為它是相關文檔中報告的 STI 實體
單表繼承有一個一般的性能考慮:如果多對一或一對一關聯的目標實體是 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
實體類中水合物Gateway->events
?
我建議你在這里考慮幾個選擇。
1) 根據Doctrine 的文檔,您可以使用fetch="EAGER"
來提示 Doctrine 您希望在加載實體時立即獲取關系:
/**
* @ORM\OneToMany(targetEntity="AppBundle\Entity\Device", mappedBy="gateway", fetch="EAGER")
*/
protected $devices;
如果謹慎使用,這可以避免您在迭代時觸發額外的查詢,但也有其自身的缺點。
如果您開始廣泛使用強制預加載,您可能會發現自己處於加載實體以從中讀取簡單屬性將導致加載數十甚至數百個關系的情況。 從 SQL 的角度來看,這可能看起來並沒有那么糟糕(可能是單個查詢),但請記住,所有結果都將作為對象進行水合,並附加到工作單元以監視它們的變化。
2)如果您將其用於報告目的(例如顯示設備的所有事件),那么最好不要使用實體,而是從 Doctrine 請求陣列水化。 在這種情況下,您將能夠通過顯式連接(或不連接)來控制進入結果的內容。 作為一個額外的好處,您將跳過昂貴的水化和 UoM 監控,因為在這種情況下不太可能修改實體。 在使用 Doctrine 進行報告時,這也被認為是“最佳實踐”。
您有一個循環引用,其中一個節點(設備)將強制執行FETCH EAGER
。 更糟糕的是,其中一個節點(網關)的行為就像其他兩個節點之間的多對多連接表,導致FETCH EAGER
在近乎無限的循環中加載所有內容(或至少是大塊的相關數據)。
+──< OneToMany
>──+ ManyToOne
>──< ManyToMany
+──+ OneToOne
┌──────< Gateway >──────┐
│ │
+ +
Event +──────────────< Device*
如您所見,當設備執行 EAGER 提取時,它將收集許多Gateways
,因此許多Events
,因此許多Devices
,因此更多Gateways
等。 Fetch EAGER
將繼續進行,直到填充所有引用。
構建您自己的水化器需要一些仔細的數據操作,但對於您的用例來說可能會有些簡單。 請記住使用 Doctrine 注冊您的 hydrator,並將其作為參數傳遞給$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;
}
}
從Device
移除$gateway => Gateway
映射,從Gateway->device
映射中Gateway->device
mappedBy="gateway"
,從 Doctrine 的角度來看,Device 將有效地變成一片葉子。 這將避免引用循環,但有一個缺點:必須手動設置 Device->gateway 屬性(可能在 Gateway 和 Event setDevice
方法中)。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.