2012-11-29 28 views
7

Tôi có một thực thể rất đơn giản (WpmMenu) chứa các mục menu được kết nối với nhau trong mối quan hệ tự tham khảo (danh sách phụ đề được gọi)? như vậy trong thực thể của tôi, tôi có:Học thuyết - tự tham chiếu thực thể - vô hiệu hóa việc tìm nạp trẻ em

protected $id 
protected $parent_id 
protected $level 
protected $name 

với tất cả các getters/setters các mối quan hệ là:

/** 
* @ORM\OneToMany(targetEntity="WpmMenu", mappedBy="parent") 
*/ 
protected $children; 

/** 
* @ORM\ManyToOne(targetEntity="WpmMenu", inversedBy="children", fetch="LAZY") 
* @ORM\JoinColumn(name="parent_id", referencedColumnName="id", onUpdate="CASCADE", onDelete="CASCADE") 
*/ 
protected $parent; 

public function __construct() { 
    $this->children = new ArrayCollection(); 
} 

Và tất cả mọi thứ hoạt động tốt. Khi tôi vẽ cây menu, tôi lấy phần tử gốc từ kho lưu trữ, lấy con của nó, và sau đó lặp qua mỗi đứa trẻ, nhận con của nó và làm điều này đệ quy cho đến khi tôi đã trả về mỗi mục.

Điều gì xảy ra (và cho những gì tôi đang tìm kiếm giải pháp) là: Hiện tại tôi có 5 cấp = 1 mục và mỗi mục này có 3 cấp = 2 mục đính kèm (và trong tương lai tôi sẽ sử dụng cấp = 3 mục). Để có được tất cả các yếu tố của thuyết cây đơn của tôi thực hiện:

  • 1 truy vấn cho các phần tử gốc +
  • 1 truy vấn để có được 5 trẻ em (level = 1) của phần tử gốc +
  • 5 truy vấn để có 3 trẻ em (level = 2) của mỗi cấp 1 mặt hàng +
  • 15 câu truy vấn (5x3) để có được những đứa trẻ (level = 3) của mỗi cấp 2 mục

TỔNG: 22 truy vấn

Vì vậy, tôi cần phải tìm một giải pháp cho điều này và lý tưởng tôi chỉ muốn có 1 truy vấn.

Vì vậy, đây là những gì tôi đang cố gắng để làm: Trong kho thực thể của tôi (WpmMenuRepository) Tôi sử dụng queryBuilder và có được một mảng phẳng của tất cả các mục trong menu lệnh theo trình độ. Lấy phần tử gốc (WpmMenu) và thêm "thủ công" con của nó từ mảng các phần tử đã tải. Sau đó làm điều này đệ quy về trẻ em. Làm theo cách này tôi có thể có cùng một cây nhưng với một truy vấn duy nhất.

Vì vậy, đây là những gì tôi có:

WpmMenuRepository:

public function setupTree() { 
    $qb = $this->createQueryBuilder("res"); 
    /** @var Array */ 
    $res = $qb->select("res")->orderBy('res.level', 'DESC')->addOrderBy('res.name','DESC')->getQuery()->getResult(); 
    /** @var WpmMenu */ 
    $treeRoot = array_pop($res); 
    $treeRoot->setupTreeFromFlatCollection($res); 
    return($treeRoot); 
} 

và trong thực thể WpmMenu của tôi, tôi có:

function setupTreeFromFlatCollection(Array $flattenedDoctrineCollection){ 
    //ADDING IMMEDIATE CHILDREN 
    for ($i=count($flattenedDoctrineCollection)-1 ; $i>=0; $i--) { 
    /** @var WpmMenu */ 
    $docRec = $flattenedDoctrineCollection[$i]; 
    if (($docRec->getLevel()-1) == $this->getLevel()) { 
     if ($docRec->getParentId() == $this->getId()) { 
      $docRec->setParent($this); 
      $this->addChild($docRec); 
      array_splice($flattenedDoctrineCollection, $i, 1); 
     } 
    } 
    } 
    //CALLING CHILDREN RECURSIVELY TO ADD REST 
    foreach ($this->children as &$child) { 
    if ($child->getLevel() > 0) {  
     if (count($flattenedDoctrineCollection) > 0) { 
      $flattenedDoctrineCollection = $child->setupTreeFromFlatCollection($flattenedDoctrineCollection); 
     } else { 
      break; 
     } 
    } 
    }  
    return($flattenedDoctrineCollection); 
} 

Và đây là những gì sẽ xảy ra:

Mọi thứ hoạt động tốt, NHƯNG, tôi kết thúc với từng mục trình bày hai lần. ;) Thay vì 22 truy vấn bây giờ tôi có 23. Vì vậy, tôi thực sự xấu đi trường hợp. Điều thực sự xảy ra, tôi nghĩ, là ngay cả khi tôi thêm trẻ em thêm "thủ công", thực thể WpmMenu KHÔNG được xem là đồng bộ với cơ sở dữ liệu và ngay sau khi tôi lặp lại vòng lặp cho trẻ em của nó được kích hoạt trong tải ORM và thêm các con tương tự đã được thêm "thủ công".

Q: Có cách nào để chặn/tắt hành vi này và nói với các thực thể này rằng chúng ĐƯỢC đồng bộ hóa với db để không cần truy vấn thêm?

+1

Xem điều này có giúp ích: http://docs.doctrine-project.org/en/latest/reference/partial-objects.html – gremo

+0

Không. Tôi không có một số đối tượng và có vẻ như là một ý tưởng tồi để đi theo cách đó. – jakabadambalazs

Trả lời

14

Với sự cứu trợ to lớn (và rất nhiều kiến ​​thức về Doctrine Hydration và UnitOfWork), tôi đã tìm thấy câu trả lời cho câu hỏi này. Và như với rất nhiều thứ một khi bạn tìm thấy câu trả lời bạn nhận ra rằng bạn có thể đạt được điều này với một vài dòng mã. Tôi vẫn đang thử nghiệm điều này cho các tác dụng phụ không rõ nhưng nó có vẻ hoạt động chính xác. Tôi đã có khá nhiều khó khăn để xác định vấn đề là gì - một khi tôi đã làm điều đó dễ dàng hơn nhiều khi tìm kiếm câu trả lời. Vì vậy, vấn đề là: Vì đây là một thực thể tự tham chiếu trong đó toàn bộ cây được nạp như một mảng phẳng các phần tử và sau đó chúng được "nạp thủ công" vào mảng $ children của mỗi phần tử bằng phương thức setupTreeFromFlatCollection - khi phương thức getChildren() được gọi trên bất kỳ thực thể nào trong cây (bao gồm phần tử gốc), Doctrine (KHÔNG biết về cách tiếp cận 'thủ công' này) thấy phần tử là "NOT INITIALIZED" và thực thi SQL để tìm nạp tất cả các trẻ em liên quan của nó từ cơ sở dữ liệu.

Vì vậy, tôi đã mổ xẻ lớp ObjectHydrator (\ Doctrine \ ORM \ Internal \ Hydration \ ObjectHydrator) và tôi theo sau (loại) quy trình mất nước và tôi đã đến một $reflFieldValue->setInitialized(true); @line: 369 là một phương thức trên \ Doctrine \ ORM \ PersistentCollection class thiết lập thuộc tính $ initialized trên lớp true/false. Vì vậy, tôi đã cố gắng và CNTT HOẠT ĐỘNG !!!

Thực hiện -> setInitialized (true) trên từng đối tượng được trả về bởi phương thức getResult() của queryBuilder (sử dụng HYDRATE_OBJECT === ObjectHydrator) và sau đó gọi -> getChildren() trên các thực thể bây giờ KHÔNG kích hoạt thêm bất kỳ SQL nào nữa !!!

Lồng ghép nó trong mã của WpmMenuRepository, nó trở thành:

public function setupTree() { 
    $qb = $this->createQueryBuilder("res"); 
    /** @var $res Array */ 
    $res = $qb->select("res")->orderBy('res.level', 'DESC')->addOrderBy('res.name','DESC')->getQuery()->getResult(); 
    /** @var $prop ReflectionProperty */ 
    $prop = $this->getClassMetadata()->reflFields["children"]; 
    foreach($res as &$entity) { 
    $prop->getValue($entity)->setInitialized(true);//getValue will return a \Doctrine\ORM\PersistentCollection 
    } 
    /** @var $treeRoot WpmMenu */ 
    $treeRoot = array_pop($res); 
    $treeRoot->setupTreeFromFlatCollection($res); 
    return($treeRoot); 
} 

Và đó là tất cả!

+2

Tôi chắc chắn có thể nói rằng không ai có thể tìm thấy giải pháp phức tạp này ở bất kỳ nơi nào khác trên web. thậm chí trong tài liệu học thuyết, bạn có thể tìm thấy một đề cập đến tình huống phổ biến này xảy ra rất nhiều ví dụ khi hiển thị các trình đơn hoặc nhận xét lồng nhau. cảm ơn bạn @jakabadambalazs bạn đã thực hiện một ngày của tôi. – sepehr

+0

Tôi đang đối mặt với cùng một vấn đề. Vấn đề bức xạ ở đây: 'Bản đồ nhận dạng được lập chỉ mục bởi các khóa chính chỉ cho phép các phím tắt khi bạn yêu cầu đối tượng bằng khóa chính.' Điều này có nghĩa là khi bạn gọi' getChildren() ', tất cả các trang này sẽ truy vấn UnitOfWork key field (aka '$ parent') và sau đó học thuyết truy vấn cơ sở dữ liệu một lần nữa ... Những gì bạn đang làm về cơ bản là ngăn chặn thêm Lazy Loading và khởi tạo các con PersistentCollection bằng tay ... vì bạn chỉ thiết lập' $ initialized' cho trẻ em 'PersistentCollection' Tôi không thấy bất kỳ hạn chế nào .. – KnF

+0

Đây thực sự là một giải pháp hacky .. Nhưng tốt nhất so với tất cả các mớ hỗn độn mà bộ lồng nhau – KnF

0

Thêm chú thích vào liên kết của bạn để bật tải mong muốn. Điều này sẽ cho phép bạn tải toàn bộ cây chỉ với 1 truy vấn và tránh phải tạo lại nó từ một mảng phẳng.

Ví dụ:

/** 
* @ManyToMany(targetEntity="User", mappedBy="groups", fetch="EAGER") 
*/ 

Chú thích là một trong những điều này, nhưng với giá trị thay đổi https://doctrine-orm.readthedocs.org/en/latest/tutorials/extra-lazy-associations.html?highlight=fetch

+1

Không hút xì gà! Tùy chọn 'fetch =" EAGER "' trên mối quan hệ kích hoạt tải toàn bộ cây một cách chính xác ngay khi tôi tìm nạp phần tử gốc của cây. Tuy nhiên để làm điều đó, tất cả các truy vấn khác được thực hiện với 'WHERE t0.parent_id =?' Như trước đây. Nói cách khác, thay đổi duy nhất là các truy vấn được thực hiện ngay cả khi tôi không truy cập vào các con nhưng cấu trúc thực tế và do đó các truy vấn cần thiết để tìm nạp các phần tử giống nhau - vì vậy kết quả là giống như – jakabadambalazs

0

Bạn không thể giải quyết vấn đề này nếu sử dụng danh sách kề. Đã từng trải qua rồi. Cách duy nhất là sử dụng bộ lồng nhau và sau đó bạn sẽ có thể tìm nạp mọi thứ bạn cần trong một truy vấn duy nhất.

Tôi đã làm điều đó khi tôi đang sử dụng Doctrine1. Trong tập hợp lồng nhau, bạn có các cột root, level, leftright mà bạn có thể sử dụng để giới hạn/mở rộng đối tượng đã tìm nạp. Nó đòi hỏi các truy vấn phụ hơi phức tạp nhưng nó có thể thực hiện được.

Tài liệu hướng dẫn cho bộ lồng nhau khá tốt, tôi khuyên bạn nên kiểm tra và bạn sẽ hiểu ý tưởng tốt hơn.

+0

Cảm ơn, nhưng vẫn ở tối! Đây là những gì tôi đã nhận: 1) bộ lồng nhau là thực sự mát mẻ - Tôi đã dành 4hrs để đọc lên trên nó và tôi đã học được rất nhiều nhưng vào cuối ngày tại một thời điểm tôi sẽ làm một truy vấn với QueryBuilder mà tôi có thể: '-> getResult()'/'-> getArrayResult()'/'-> getScalarResult()' và cả hai phương thức này đều không trả về những gì tôi cần. '-> getArrayResult()' là gần nhất 'cos nó trả về một mảng lồng nhau của mảng (ít nhất đây là những gì nó có vẻ đang làm nhưng tôi vẫn cần phải kiểm tra vào nó) - nhưng tôi nghĩ rằng vấn đề thực tế không phải là làm thế nào tôi đặt ra dữ liệu của tôi trong db nhưng hydration – jakabadambalazs

+0

Trong thực tế, như nó đứng trong (https://doctrine-orm.readthedocs.org/en/latest/reference/dql-doctrine-query-language.html?highlight = hydration-hydration-mode) cuốn sách tham khảo giáo lý @ 14.7.4 - Chế độ hydrat hóa - dường như không có gì sẽ hydrate dữ liệu 2D phẳng thành cấu trúc giống cây trên một tập hợp kết quả duy nhất. Không có vấn đề nếu danh sách liền kề hoặc tập lồng nhau. – jakabadambalazs

+0

Hãy để tôi kiểm tra xem tôi có hiểu chính xác bạn không; bạn muốn có một truy vấn sẽ lấy toàn bộ cây, thậm chí có thể với một số kết nối, phải không? Nếu vậy, bạn thực sự phải sử dụng tập hợp lồng nhau. Tôi có thể sao chép và dán mã mẫu tôi có nhưng nó là dành cho D1 và rất cụ thể cho dự án vì vậy nó sẽ không được sử dụng nhiều cho bạn. Bí quyết là sử dụng khéo léo các cột 'left' và' right'. Giải pháp đơn giản nhất là tạo một thực thể khác được đặt lồng nhau, tạo một số cây và kiểm tra các giá trị trong DB. Sau đó, bạn sẽ nhận được một ý tưởng làm thế nào để làm điều này. Và btw; Tôi ngậm nước cho các đồ vật, không cần chuyển đổi. – Zeljko

0

Điều này giống như một giải pháp hoàn thiện hơn và sạch hơn, nhưng dựa trên câu trả lời được chấp nhận ...

Điều duy nhất cần thiết là kho lưu trữ tùy chỉnh truy vấn cấu trúc cây bằng phẳng, và sau đó, bằng cách lặp lại mảng này, trước tiên hãy đánh dấu bộ sưu tập trẻ em như được khởi tạo và sau đó sẽ hydrat hóa nó với bộ chọn addChild hiện diện trong công ty mẹ ..

<?php 

namespace Domain\Repositories; 

use Doctrine\ORM\EntityRepository; 

class PageRepository extends EntityRepository 
{ 
    public function getPageHierachyBySiteId($siteId) 
    { 
     $roots = []; 
     $flatStructure = $this->_em->createQuery('SELECT p FROM Domain\Page p WHERE p.site = :id ORDER BY p.order')->setParameter('id', $siteId)->getResult(); 

     $prop = $this->getClassMetadata()->reflFields['children']; 
     foreach($flatStructure as &$entity) { 
      $prop->getValue($entity)->setInitialized(true); //getValue will return a \Doctrine\ORM\PersistentCollection 

      if ($entity->getParent() != null) { 
       $entity->getParent()->addChild($entity); 
      } else { 
       $roots[] = $entity; 
      } 
     } 

     return $roots; 
    } 
} 

chỉnh sửa: các getParent() phương pháp sẽ không kích hoạt các truy vấn thêm chừng nào mối quan hệ được thực hiện cho các khóa chính, trong trường hợp của tôi, thuộc tính $ mẹ là một mối quan hệ trực tiếp đến PK, vì vậy UnitOfWork sẽ trả về thực thể được lưu trong bộ nhớ cache và không truy vấn cơ sở dữ liệu .. Nếu thuộc tính của bạn không liên quan đến PK, nó sẽ tạo ra các truy vấn bổ sung.

Các vấn đề liên quan