2010-11-12 6 views
8

Ich habe eine Car Entität mit einer Viele-zu-eins-Beziehung mit einer Entität Owner. Wenn ich alle Autos auswähle, führt Doctrine eine Abfrage auf der Tabelle Car und anschließend eine Abfrage auf der Owner Tabelle für jedes Auto. So wird das Holen von N Autos zu N + 1 Anfragen anstelle einer einzelnen JOIN Abfrage zwischen den Car und Owner Tabellen.Doctrine2 Viele-zu-eins-Verknüpfung verwendet keine JOIN-Abfrage

Meine Einheiten sind wie folgt:

/** @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; } 
} 

Wenn ich die Autos mit ihren Besitzern auflisten wollen, ich mache:

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

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

Das ist aber alles funktioniert sehr gut, abgesehen von der Tatsache, dass Doctrine gibt für jedes Auto eine Abfrage aus.

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

Natürlich würde ich meine Abfrageprotokoll wie folgt aussehen wollen:

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

Ob ich fetch="EAGER" oder fetch="LAZY" spielt keine Rolle, und selbst wenn ich eine benutzerdefinierte DQL-Abfrage machen mit JOIN zwischen den beiden Entitäten veranlasst $car->getOwner() immer noch Doctrine, die Datenbank abzufragen (es sei denn, ich verwende EAGER, in diesem Fall verursacht $repo->findAll() alle von ihnen).

Bin ich einfach zu müde hier, und so soll es funktionieren - oder gibt es eine kluge Möglichkeit, Doctrine dazu zu zwingen, stattdessen die JOIN-Abfrage zu machen?

Antwort

5

Zumindest in 1.x Doctrine, wenn Sie für die verwandten Objekte abfragen wollten, mussten Sie DQL verwenden. Für Ihren Fall so etwas wie dies die DQL Abfrage aussehen:

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

Sieht aus wie das auch der Fall in 2.x. ist Ich hatte versucht, die Abfrage 'SELECT c FROM Auto c JOIN c.owner o', die immer noch N + 1 Abfragen gemacht, aber deins funktioniert gut. Danke vielmals! Ich denke, ich muss nur damit leben, dass ich die findXxx-Methoden nicht verwende. – Frode

+4

Für solche Convenience-Methoden würde ich vorschlagen, eine Style-Klasse "Service-Layer" oder "Repository" zu erstellen, die Aufgaben wie das Laden von Modellen übernimmt. Auf diese Weise können Sie die Abfrage auch problemlos wiederverwenden, ohne die DQL-Logik überall duplizieren zu müssen. –

1

Ihre Anfrage ...

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

... ist in einer foreach-Schleife, so dass es sicherlich die Abfrage mehrmals ausgeben.

Wenn Sie benutzerdefinierte DQL schreiben, um damit umzugehen, sollte $car->getOwner() in diesem überhaupt nicht enthalten sein. Dies ist eine Funktion der Fahrzeugklasse. Die benutzerdefinierte DQL, die Sie schreiben würden, würde die exakte SQL-Abfrage, auf die Sie hinweisen, nachahmen und Ihren Beitritt effizient erledigen.

+0

+1 Vielen Dank für Ihre Antwort. Solange das Eigentümermitglied EAGERlylyly geladen ist, ist es kein Problem, '$ car-> getOwner()' innerhalb der Schleife zu machen. Wenn das nur mit faulem Holen passiert wäre, würde ich es verstehen, aber mit eifrigem Laden ist es nicht ideal. Siehe meine Antwort an Jani Hartikainen bezüglich des DQL :-) – Frode

+0

@Frode: In der Tat, verstehe deine Frage jetzt besser. Ich bin noch nicht darüber gestolpert und hätte vom eifrigen Laden dieselbe Effizienz erwartet wie Sie. Sieht so aus, als ob DQL benötigt wird. – Tom

4

Führen Sie zuerst eine DQL-Abfrage aus, bei der Sie alle verbundenen Fahrzeuge (DQL JOIN) mit dem Eigentümer auswählen. Setzen Sie den Besitzer in die select().

Doctrine 2 führt dann einen JOIN durch (normalerweise schneller, da weniger Datenbankabfragen erforderlich sind, abhängig von der Anzahl der Datensätze). Starten Sie nun Ihre foreach, Doctrine findet die Entitäten intern und es werden keine einzelnen Abfragen ausgeführt, wenn Sie die owner benötigen.

Überwachen Sie die Anzahl der Abfragen ersten/nach jeder Änderung (z. B. allgemeine Protokoll MySQL)