簡體   English   中英

Doctrine - 實體類中的水合物集合

[英]Doctrine - Hydrate collection in Entity class

我有一個關於我的實體DeviceEvent之間的雙向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的實體,它與DeviceEvent都有關系:

/**
 * @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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM