Started refactoring of the documentation towards smaller chapters, grouped into logical units with better explanations (hopefully).
This commit is contained in:
parent
19b7d4d0d4
commit
b0ec3dfb47
@ -63,10 +63,15 @@ Working with Objects
|
|||||||
:doc:`QueryBuilder <reference/query-builder>` |
|
:doc:`QueryBuilder <reference/query-builder>` |
|
||||||
:doc:`Native SQL Queries <reference/native-sql>`
|
:doc:`Native SQL Queries <reference/native-sql>`
|
||||||
|
|
||||||
|
* **UnitOfWork dissected**:
|
||||||
|
:doc:`Doctrine Internals explained <reference/unitofwork>` |
|
||||||
|
:doc:`Owning and Inverse Side Associations <reference/unitofwork-associations>`
|
||||||
|
|
||||||
* **Tutorials**:
|
* **Tutorials**:
|
||||||
:doc:`Indexed associations <tutorials/working-with-indexed-associations>` |
|
:doc:`Indexed associations <tutorials/working-with-indexed-associations>` |
|
||||||
:doc:`Extra Lazy Assocations <tutorials/extra-lazy-associations>` |
|
:doc:`Extra Lazy Assocations <tutorials/extra-lazy-associations>` |
|
||||||
:doc:`Composite Primary Keys <tutorials/composite-primary-keys>`
|
:doc:`Composite Primary Keys <tutorials/composite-primary-keys>` |
|
||||||
|
:doc:`Ordered associations <tutorials/ordered-associations>`
|
||||||
|
|
||||||
Advanced Topics
|
Advanced Topics
|
||||||
---------------
|
---------------
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -76,47 +76,6 @@ objects using ``serialize()/deserialize()`` which the DBAL Type
|
|||||||
The feature request for full value-object support
|
The feature request for full value-object support
|
||||||
`is described in the DDC-93 ticket <http://www.doctrine-project.org/jira/browse/DDC-93>`_.
|
`is described in the DDC-93 ticket <http://www.doctrine-project.org/jira/browse/DDC-93>`_.
|
||||||
|
|
||||||
Applying Filter Rules to any Query
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
There are scenarios in many applications where you want to apply
|
|
||||||
additional filter rules to each query implicitly. Examples
|
|
||||||
include:
|
|
||||||
|
|
||||||
|
|
||||||
- In I18N Applications restrict results to a entities annotated
|
|
||||||
with a specific locale
|
|
||||||
- For a large collection always only return objects in a specific
|
|
||||||
date range/where condition applied.
|
|
||||||
- Soft-Delete
|
|
||||||
|
|
||||||
There is currently no way to achieve this consistently across both
|
|
||||||
DQL and Repository/Persister generated queries, but as this is a
|
|
||||||
pretty important feature we plan to add support for it in the
|
|
||||||
future.
|
|
||||||
|
|
||||||
Restricing Associations
|
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~
|
|
||||||
|
|
||||||
There is currently no way to restrict associations to a subset of entities matching a given condition.
|
|
||||||
You should use a DQL query to retrieve a subset of associated entities. For example
|
|
||||||
if you need all visible articles in a certain category you could have the following code
|
|
||||||
in an entity repository:
|
|
||||||
|
|
||||||
.. code-block:: php
|
|
||||||
|
|
||||||
<?php
|
|
||||||
class ArticleRepository extends EntityRepository
|
|
||||||
{
|
|
||||||
public function getVisibleByCategory(Category $category)
|
|
||||||
{
|
|
||||||
$dql = "SELECT a FROM Article a WHERE a.category = ?1 and a.visible = true";
|
|
||||||
return $this->getEntityManager()
|
|
||||||
->createQuery($dql)
|
|
||||||
->setParameter(1, $category)
|
|
||||||
->getResult();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Cascade Merge with Bi-directional Associations
|
Cascade Merge with Bi-directional Associations
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
@ -162,7 +121,7 @@ to the same entity.
|
|||||||
Behaviors
|
Behaviors
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
||||||
Doctrine 2 *will never* include a behavior system like Doctrine 1
|
Doctrine 2 will **never** include a behavior system like Doctrine 1
|
||||||
in the core library. We don't think behaviors add more value than
|
in the core library. We don't think behaviors add more value than
|
||||||
they cost pain and debugging hell. Please see the many different
|
they cost pain and debugging hell. Please see the many different
|
||||||
blog posts we have written on this topics:
|
blog posts we have written on this topics:
|
||||||
@ -173,7 +132,7 @@ blog posts we have written on this topics:
|
|||||||
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
|
- `Doctrine 2 Behavioral Extensions <http://www.doctrine-project.org/blog/doctrine2-behavioral-extensions>`_
|
||||||
- `Doctrator <https://github.com/pablodip/doctrator`>_
|
- `Doctrator <https://github.com/pablodip/doctrator`>_
|
||||||
|
|
||||||
Doctrine 2 has enough hooks and extension points so that *you* can
|
Doctrine 2 has enough hooks and extension points so that **you** can
|
||||||
add whatever you want on top of it. None of this will ever become
|
add whatever you want on top of it. None of this will ever become
|
||||||
core functionality of Doctrine2 however, you will have to rely on
|
core functionality of Doctrine2 however, you will have to rely on
|
||||||
third party extensions for magical behaviors.
|
third party extensions for magical behaviors.
|
||||||
@ -199,11 +158,15 @@ backwards compatibility issues or where no simple fix exists (yet).
|
|||||||
We don't plan to add every bug in the tracker there, just those
|
We don't plan to add every bug in the tracker there, just those
|
||||||
issues that can potentially cause nightmares or pain of any sort.
|
issues that can potentially cause nightmares or pain of any sort.
|
||||||
|
|
||||||
|
See the Open Bugs on Jira for more details on `bugs, improvement and feature
|
||||||
|
requests
|
||||||
|
<http://www.doctrine-project.org/jira/secure/IssueNavigator.jspa?reset=true&mode=hide&pid=10032&resolution=-1&sorter/field=updated&sorter/order=DESC>`_.
|
||||||
|
|
||||||
Identifier Quoting and Legacy Databases
|
Identifier Quoting and Legacy Databases
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
For compatibility reasons between all the supported vendors and
|
For compatibility reasons between all the supported vendors and
|
||||||
edge case problems Doctrine 2 does *NOT* do automatic identifier
|
edge case problems Doctrine 2 does **NOT** do automatic identifier
|
||||||
quoting. This can lead to problems when trying to get
|
quoting. This can lead to problems when trying to get
|
||||||
legacy-databases to work with Doctrine 2.
|
legacy-databases to work with Doctrine 2.
|
||||||
|
|
||||||
@ -222,5 +185,5 @@ tables and column names to avoid the legacy quoting nightmare.
|
|||||||
Microsoft SQL Server and Doctrine "datetime"
|
Microsoft SQL Server and Doctrine "datetime"
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
Doctrine assumes that you use DateTime2 data-types. If your legacy database contains DateTime
|
Doctrine assumes that you use ``DateTime2`` data-types. If your legacy database contains DateTime
|
||||||
datatypes then you have to add your own data-type (see Basic Mapping for an example).
|
datatypes then you have to add your own data-type (see Basic Mapping for an example).
|
||||||
|
60
en/reference/unitofwork-associations.rst
Normal file
60
en/reference/unitofwork-associations.rst
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
Association Updates: Owning Side and Inverse Side
|
||||||
|
=================================================
|
||||||
|
|
||||||
|
When mapping bidirectional associations it is important to
|
||||||
|
understand the concept of the owning and inverse sides. The
|
||||||
|
following general rules apply:
|
||||||
|
|
||||||
|
- Relationships may be bidirectional or unidirectional.
|
||||||
|
- A bidirectional relationship has both an owning side and an inverse side
|
||||||
|
- A unidirectional relationship only has an owning side.
|
||||||
|
- Doctrine will **only** check the owning side of an assocation for changes.
|
||||||
|
|
||||||
|
Bidirectional Associations
|
||||||
|
--------------------------
|
||||||
|
|
||||||
|
The following rules apply to **bidirectional** associations:
|
||||||
|
|
||||||
|
- The inverse side has to use the ``mappedBy`` attribute of the OneToOne,
|
||||||
|
OneToMany, or ManyToMany mapping declaration. The mappedBy
|
||||||
|
attribute contains the name of the association-field on the owning side.
|
||||||
|
- The owning side has to use the ``inversedBy`` attribute of the
|
||||||
|
OneToOne, ManyToOne, or ManyToMany mapping declaration.
|
||||||
|
The inversedBy attribute contains the name of the association-field
|
||||||
|
on the inverse-side.
|
||||||
|
- ManyToOne is always the owning side of a bidirectional assocation.
|
||||||
|
- OneToMany is always the inverse side of a bidirectional assocation.
|
||||||
|
- The owning side of a OneToOne assocation is the entity with the table
|
||||||
|
containing the foreign key.
|
||||||
|
- You can pick the owning side of a many-to-many assocation yourself.
|
||||||
|
|
||||||
|
Important concepts
|
||||||
|
------------------
|
||||||
|
|
||||||
|
**Doctrine will only check the owning side of an assocation for changes.**
|
||||||
|
|
||||||
|
To fully understand this, remember how bidirectional associations
|
||||||
|
are maintained in the object world. There are 2 references on each
|
||||||
|
side of the association and these 2 references both represent the
|
||||||
|
same association but can change independently of one another. Of
|
||||||
|
course, in a correct application the semantics of the bidirectional
|
||||||
|
association are properly maintained by the application developer
|
||||||
|
(that's his responsibility). Doctrine needs to know which of these
|
||||||
|
two in-memory references is the one that should be persisted and
|
||||||
|
which not. This is what the owning/inverse concept is mainly used
|
||||||
|
for.
|
||||||
|
|
||||||
|
**Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)**
|
||||||
|
|
||||||
|
The owning side of a bidirectional association is the side Doctrine
|
||||||
|
"looks at" when determining the state of the association, and
|
||||||
|
consequently whether there is anything to do to update the
|
||||||
|
association in the database.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
"Owning side" and "inverse side" are technical concepts of
|
||||||
|
the ORM technology, not concepts of your domain model. What you
|
||||||
|
consider as the owning side in your domain model can be different
|
||||||
|
from what the owning side is for Doctrine. These are unrelated.
|
||||||
|
|
135
en/reference/unitofwork.rst
Normal file
135
en/reference/unitofwork.rst
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
Doctrine Internals explained
|
||||||
|
============================
|
||||||
|
|
||||||
|
Object relational mapping is a complex topic and sufficiently understanding how Doctrine works internally helps you use its full power.
|
||||||
|
|
||||||
|
How Doctrine keeps track of Objects
|
||||||
|
-----------------------------------
|
||||||
|
|
||||||
|
Doctrine uses the Identity Map pattern to track objects. Whenever you fetch an
|
||||||
|
object from the database, Doctrine will keep a reference to this object inside
|
||||||
|
its UnitOfWork. The array holding all the entity references is two-levels deep
|
||||||
|
and has the keys "root entity name" and "id". Since Doctrine allows composite
|
||||||
|
keys the id is a sorted, serialized version of all the key columns.
|
||||||
|
|
||||||
|
This allows Doctrine room for optimizations. If you call the EntiyManager and
|
||||||
|
ask for an entity with a specific ID twice, it will return the same instance:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
public function testIdentityMap()
|
||||||
|
{
|
||||||
|
$objectA = $this->entityManager->find('EntityName', 1);
|
||||||
|
$objectB = $this->entityManager->find('EntityName', 1);
|
||||||
|
|
||||||
|
$this->assertSame($objectA, $objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
Only one SELECT query will be fired against the database here. In the second
|
||||||
|
``EntityManager#find()`` call Doctrine will check the identity map first and
|
||||||
|
doesn't need to make that database roundtrip.
|
||||||
|
|
||||||
|
Even if you get a proxy object first then fetch the object by the same id you
|
||||||
|
will still end up with the same reference:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
public function testIdentityMapReference()
|
||||||
|
{
|
||||||
|
$objectA = $this->entityManager->getReference('EntityName', 1);
|
||||||
|
// check for proxyinterface
|
||||||
|
$this->assertInstanceOf('Doctrine\ORM\Proxy\Proxy', $objectA);
|
||||||
|
|
||||||
|
$objectB = $this->entityManager->find('EntityName', 1);
|
||||||
|
|
||||||
|
$this->assertSame($objectA, $objectB)
|
||||||
|
}
|
||||||
|
|
||||||
|
The identity map being indexed by primary keys only allows shortcuts when you
|
||||||
|
ask for objects by primary key. Assume you have the following ``persons``
|
||||||
|
table:
|
||||||
|
|
||||||
|
::
|
||||||
|
|
||||||
|
id | name
|
||||||
|
-------------
|
||||||
|
1 | Benjamin
|
||||||
|
2 | Bud
|
||||||
|
|
||||||
|
Take the following example where two
|
||||||
|
consecutive calls are made against a repository to fetch an entity by a set of
|
||||||
|
criteria:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
public function testIdentityMapRepositoryFindBy()
|
||||||
|
{
|
||||||
|
$repository = $this->entityManager->getRepository('Person');
|
||||||
|
$objectA = $repository->findOneBy(array('name' => 'Benjamin'));
|
||||||
|
$objectB = $repository->findOneBy(array('name' => 'Benjamin'));
|
||||||
|
|
||||||
|
$this->assertSame($objectA, $objectB);
|
||||||
|
}
|
||||||
|
|
||||||
|
This query will still return the same references and `$objectA` and `$objectB`
|
||||||
|
are indeed referencing the same object. However when checking your SQL logs you
|
||||||
|
will realize that two queries have been executed against the database. Doctrine
|
||||||
|
only knows objects by id, so a query for different criteria has to go to the
|
||||||
|
database, even if it was executed just before.
|
||||||
|
|
||||||
|
But instead of creating a second Person object Doctrine first gets the primary
|
||||||
|
key from the row and check if it already has an object inside the UnitOfWork
|
||||||
|
with that primary key. In our example it finds an object and decides to return
|
||||||
|
this instead of creating a new one.
|
||||||
|
|
||||||
|
The identity map has a second use-case. When you call ``EntityManager#flush``
|
||||||
|
Doctrine will ask the identity map for all objects that are currently managed.
|
||||||
|
This means you don't have to call ``EntityManager#persist`` over and over again
|
||||||
|
to pass known objects to the EntityManager. This is a NO-OP for known entities,
|
||||||
|
but leads to much code written that is confusing to other developers.
|
||||||
|
|
||||||
|
The following code WILL update your database with the changes made to the
|
||||||
|
``Person`` object, even if you did not call ``EntityManager#persist``:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
$user = $entityManager->find("Person", 1);
|
||||||
|
$user->setName("Guilherme");
|
||||||
|
$entityManager->flush();
|
||||||
|
|
||||||
|
How Doctrine Detects Changes
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
Doctrine is a data-mapper that tries to achieve persistence-ignorance (PI).
|
||||||
|
This means you map php objects into a relational database that don't
|
||||||
|
necessarily know about the database at all. A natural question would now be,
|
||||||
|
"how does Doctrine even detect objects have changed?".
|
||||||
|
|
||||||
|
For this Doctrine keeps a second map inside the UnitOfWork. Whenever you fetch
|
||||||
|
an object from the database Doctrine will keep a copy of all the properties and
|
||||||
|
associations inside the UnitOfWork. Because variables in the PHP language are
|
||||||
|
subject to "copy-on-write" the memory usage of a PHP request that only reads
|
||||||
|
objects from the database is the same as if Doctrine did not keep this variable
|
||||||
|
copy. Only if you start changing variables PHP will create new variables internally
|
||||||
|
that consume new memory.
|
||||||
|
|
||||||
|
Now whenever you call ``EntityManager#flush`` Doctrine will iterate over the
|
||||||
|
Identity Map and for each object compares the original property and association
|
||||||
|
values with the values that are currently set on the object. If changes are
|
||||||
|
detected then the object is qeued for an SQL UPDATE operation. Only the fields
|
||||||
|
that actually changed are updated.
|
||||||
|
|
||||||
|
This process has an obvious performance impact. The larger the size of the
|
||||||
|
UnitOfWork is, the longer this computation takes. There are several ways to
|
||||||
|
optimize the performance of the Flush Operation:
|
||||||
|
|
||||||
|
- Mark entities as read only. These entities can only be inserted or removed,
|
||||||
|
but are never updated. They are omitted in the changeset calculation.
|
||||||
|
- Temporarily mark entities as read only. If you have a very large UnitOfWork
|
||||||
|
but know that a large set of entities has not changed, just mark them as read
|
||||||
|
only with ``$entityManager->getUnitOfWork()->markReadOnly($entity)``.
|
||||||
|
- Flush only a single entity with ``$entityManager->flush($entity)``.
|
||||||
|
- Use :doc:`Change Tracking Policies <change-tracking-policies>` to use more
|
||||||
|
explicit strategies of notifying the UnitOfWork what objects/properties
|
||||||
|
changed.
|
@ -7,7 +7,7 @@ collections of objects. When it comes to persistence, it is
|
|||||||
important to understand three main things:
|
important to understand three main things:
|
||||||
|
|
||||||
|
|
||||||
- The :ref:`concept of owning and inverse sides <association-mapping-owning-inverse>`
|
- The :doc:`concept of owning and inverse sides <unitofwork-associations>`
|
||||||
in bidirectional associations.
|
in bidirectional associations.
|
||||||
- If an entity is removed from a collection, the association is
|
- If an entity is removed from a collection, the association is
|
||||||
removed, not the entity itself. A collection of entities always
|
removed, not the entity itself. A collection of entities always
|
||||||
|
@ -11,6 +11,7 @@ Tutorials
|
|||||||
tutorials/working-with-indexed-associations
|
tutorials/working-with-indexed-associations
|
||||||
tutorials/extra-lazy-associations
|
tutorials/extra-lazy-associations
|
||||||
tutorials/composite-primary-keys
|
tutorials/composite-primary-keys
|
||||||
|
tutorials/ordered-associations
|
||||||
|
|
||||||
Reference Guide
|
Reference Guide
|
||||||
---------------
|
---------------
|
||||||
@ -28,8 +29,10 @@ Reference Guide
|
|||||||
reference/inheritance-mapping
|
reference/inheritance-mapping
|
||||||
reference/working-with-objects
|
reference/working-with-objects
|
||||||
reference/working-with-associations
|
reference/working-with-associations
|
||||||
reference/transactions-and-concurrency
|
|
||||||
reference/events
|
reference/events
|
||||||
|
reference/unitofwork
|
||||||
|
reference/unitofwork-associations
|
||||||
|
reference/transactions-and-concurrency
|
||||||
reference/batch-processing
|
reference/batch-processing
|
||||||
reference/dql-doctrine-query-language
|
reference/dql-doctrine-query-language
|
||||||
reference/query-builder
|
reference/query-builder
|
||||||
@ -48,7 +51,6 @@ Reference Guide
|
|||||||
reference/limitations-and-known-issues
|
reference/limitations-and-known-issues
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
Cookbook
|
Cookbook
|
||||||
--------
|
--------
|
||||||
|
|
||||||
|
79
en/tutorials/ordered-associations.rst
Normal file
79
en/tutorials/ordered-associations.rst
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
Ordering To-Many Assocations
|
||||||
|
----------------------------
|
||||||
|
|
||||||
|
There use-cases you will want to sort collections when they are
|
||||||
|
retrieved from the database. In userland you do this as long as you
|
||||||
|
haven't initially saved an entity with its associations into the
|
||||||
|
database. To retrieve a sorted collection from the database you can
|
||||||
|
use the ``@OrderBy`` annotation with an collection that specifies
|
||||||
|
an DQL snippet that is appended to all queries with this
|
||||||
|
collection.
|
||||||
|
|
||||||
|
Additional to any ``@OneToMany`` or ``@ManyToMany`` annotation you
|
||||||
|
can specify the ``@OrderBy`` in the following way:
|
||||||
|
|
||||||
|
.. code-block:: php
|
||||||
|
|
||||||
|
<?php
|
||||||
|
/** @Entity **/
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @ManyToMany(targetEntity="Group")
|
||||||
|
* @OrderBy({"name" = "ASC"})
|
||||||
|
**/
|
||||||
|
private $groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
The DQL Snippet in OrderBy is only allowed to consist of
|
||||||
|
unqualified, unquoted field names and of an optional ASC/DESC
|
||||||
|
positional statement. Multiple Fields are separated by a comma (,).
|
||||||
|
The referenced field names have to exist on the ``targetEntity``
|
||||||
|
class of the ``@ManyToMany`` or ``@OneToMany`` annotation.
|
||||||
|
|
||||||
|
The semantics of this feature can be described as follows.
|
||||||
|
|
||||||
|
|
||||||
|
- ``@OrderBy`` acts as an implicit ORDER BY clause for the given
|
||||||
|
fields, that is appended to all the explicitly given ORDER BY
|
||||||
|
items.
|
||||||
|
- All collections of the ordered type are always retrieved in an
|
||||||
|
ordered fashion.
|
||||||
|
- To keep the database impact low, these implicit ORDER BY items
|
||||||
|
are only added to an DQL Query if the collection is fetch joined in
|
||||||
|
the DQL query.
|
||||||
|
|
||||||
|
Given our previously defined example, the following would not add
|
||||||
|
ORDER BY, since g is not fetch joined:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
SELECT u FROM User u JOIN u.groups g WHERE SIZE(g) > 10
|
||||||
|
|
||||||
|
However the following:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10
|
||||||
|
|
||||||
|
...would internally be rewritten to:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name ASC
|
||||||
|
|
||||||
|
You can't reverse the order with an explicit DQL ORDER BY:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC
|
||||||
|
|
||||||
|
...is internally rewritten to:
|
||||||
|
|
||||||
|
.. code-block:: sql
|
||||||
|
|
||||||
|
SELECT u, g FROM User u JOIN u.groups g WHERE u.id = 10 ORDER BY g.name DESC, g.name ASC
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user