2010-11-12 8 views
8

Tengo una entidad Car con una relación de varios a uno con una entidad Owner. Si selecciono todos los automóviles, Doctrine realiza una consulta en la tabla Car y, posteriormente, una consulta en la tabla Ownerpara cada auto. Así que ir a buscar N autos se convierte en N + 1 consultas en lugar de una sola consulta JOIN entre las tablas Car y Owner.La asociación muchos-a-uno de Doctrine2 no usará la consulta JOIN

Mis entidades son las siguientes:

/** @Entity */ 
class Car { 

    /** @Id @Column(type="smallint") */ 
    private $id; 

    /** @ManyToOne(targetEntity="Owner", fetch="EAGER") 
     @JoinColumn(name="owner", referencedColumnName="id") */ 
    private $owner; 

    public function getId() { return $this->id; } 
    public function getOwner() { return $this->owner; } 
} 

/** @Entity */ 
class Owner { 

    /** @Id @Column(type="smallint") */ 
    private $id; 

    /** @Column(type="string") */ 
    private $name; 

    public function getName() { return $this->name; } 
} 

Si quiero una lista de los coches con sus dueños, que hago:

$repo = $em->getRepository('Car'); 
$cars = $repo->findAll(); 

foreach($cars as $car) 
    echo 'Car no. ' . $car->getId() . 
     ' owned by ' . $car->getOwner()->getName() . '\n'; 

Ahora todo esto funciona muy bien, aparte del hecho de que Doctrine emite una consulta para cada automóvil.

SELECT * FROM Car; 
SELECT * FROM Owner WHERE id = 1; 
SELECT * FROM Owner WHERE id = 2; 
SELECT * FROM Owner WHERE id = 3; 
.... 

Por supuesto que me gustaría que mi registro de consultas a tener este aspecto:

SELECT * FROM Car JOIN Owner ON Car.owner = Owner.id; 

si tengo fetch="EAGER" o fetch="LAZY" no importa, e incluso si hago una consulta DQL personalizado con JOIN entre las dos entidades, $car->getOwner() todavía hace que Doctrine consulte la base de datos (a menos que use EAGER, en cuyo caso $repo->findAll() los causa a todos).

¿Estoy demasiado cansado aquí, y esta es la forma en que se supone que funciona, o hay una manera inteligente de forzar a Doctrine a hacer la consulta JOIN en su lugar?

Respuesta

5

Al menos en 1.x Doctrine, si desea consultar los objetos relacionados, debe usar DQL. Para su caso, la consulta DQL sería algo como esto:

//Assuming $em is EntityManager 
$query = $em->createQuery('SELECT c, o FROM Car c JOIN c.owner o'); 
$cars = $query->execute(); 
+1

Parece que es también el caso en 2.x. Había intentado la consulta 'SELECT c FROM Car c JOIN c.owner o', que todavía hacía consultas N + 1, pero la tuya funcionaba muy bien. ¡Muchas gracias! Supongo que tendré que vivir sin usar los métodos findXxx. – Frode

+4

Para métodos de conveniencia como ese, sugiero crear una clase de estilo "capa de servicio" o "repositorio" que maneje tareas como cargar modelos. De esta forma, también podría reutilizar fácilmente la consulta sin tener que duplicar la lógica DQL por todos lados. –

1

Su consulta ...

$car->getOwner() // "go and fetch this car's owner" 

... está en un bucle foreach por lo que será sin duda emita la consulta varias veces.

Si está escribiendo un DQL personalizado para hacer frente a esto, $car->getOwner() no debería figurar en esto. Esta es una función de la clase Car. El DQL personalizado que escribiría imitaría la consulta SQL exacta que señala y hará que su unión se realice de manera eficiente.

+0

+1 Gracias por su respuesta. Siempre y cuando el miembro propietario esté cargado EAGERly, no es un problema hacer '$ car-> getOwner()' dentro del ciclo. Si esto acaba de pasar con la recuperación perezosa, lo entendería, pero con la carga ansiosa no es ideal. Vea mi respuesta a Jani Hartikainen con respecto al DQL :-) – Frode

+0

@Frode: De hecho, entienda mejor su pregunta ahora. Todavía no he encontrado esto y habría esperado la misma eficiencia de la carga ansiosa que tú. Parece que se necesita DQL. – Tom

4

Ejecute primero una consulta DQL donde seleccione todos los automóviles unidos (DQL JOIN) con el propietario. Coloque al propietario en el select().

// preload cars 
$qb = $em->createQueryBuilder() 
     ->select('car, owner') 
     ->from('\Entity\Car', 'car') 
     ->leftJoin('c.owner', 'owner'); 

    $query = $qb->getQuery(); 

    // the following seems not needed, but I think it depends on the conf 
    $query->setFetchMode("\Entity\Car", "owner", "EAGER"); 

    $query->execute(); //you don't have to use this result here, Doctrine will keep it 

Doctrina 2 entonces realizará un JOIN (normalmente más rápido, ya que requiere menos consultas db, dependiendo del número de registros). Ahora ejecute su foreach, Doctrine encontrará las entidades internamente y no ejecutará consultas individuales cuando necesite el owner.

supervisar el número de consultas primero/después de cada cambio (por ejemplo. Mysql registro general)

Cuestiones relacionadas