Первичный ключ собственной стороны в качестве столбца соединения - PullRequest
22 голосов
/ 24 марта 2012

ПРИМЕЧАНИЕ: Тема длинная, но подробная и может пригодиться, если вы используете отношения Doctrine2 и oneToOne.

Недавно я столкнулся с проблемой в Doctrine:

Я создал объекты User и UserData с двунаправленным отношением oneToOne:

User:
...
  oneToOne:
    userdata:
      targetEntity: UserData
      mappedBy: user

UserData:
...
  oneToOne:
    user:
      targetEntity: User
      inversedBy: userdata

Таким образом, UserData был владельцем с колонкой user_id:

user: id, ...
userdata: id, user_id, ...

Это создавало проблему, когда каждый раз, когда выизвлекать объект User (один пользователь, набор пользователей или набор других объектов с присоединенным пользователем). Doctrine будет лениво загружать UserObject для каждого пользователя.

Проблема, описанная здесь:

Предлагаемое решение, описанное здесь:

Таким образом, есть 3 способа обойти это:

  1. Подождите и посмотрите, будет ли предложенное решение учтено в Doctrine и исправлено ли в будущих выпусках (может и не произойти)
  2. Вручную оставляйте присоединение UserData к пользователю в каждом запросе (все еще пустая трата ресурсов, не нужно)UserData)
  3. Переключить обратную сторону и сделать Пользователя владельцем.

Я решил пойти с # 3.Таким образом, мои отношения схемы теперь выглядят так:

User:
...
  oneToOne:
    userdata:
      targetEntity: UserData
      inversedBy: user

UserData:
...
  oneToOne:
    user:
      targetEntity: User
      mappedBy: userdata

Это означает, что мои таблицы теперь выглядят так:

user: id, userdata_id, ...
userdata: id, ...

Я решил, что вместо автоинкрементного Userdata.id, я 'Я установлю его вручную и сопоставлю с user.id.Это означает, что UserData.id всегда будет соответствовать user.id.

Вопрос Могу ли я использовать user.id (первичный автоинкрементный ключ) в качестве joinColum вместо userdata_id, поскольку они всегда будут иметь одинаковое значение?Видите ли вы какие-либо потенциальные проблемы с этим способом ведения дел?

Любые другие советы или мнения по этому вопросу очень приветствуются и приветствуются.

Ответы [ 4 ]

18 голосов
/ 12 августа 2012

Вы также можете заставить частичные объекты избавиться от отложенной загрузки:

use Doctrine\ORM\Query;

//...
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);
14 голосов
/ 24 ноября 2014

Это известная проблема для ассоциаций OneToOne. Об этом стоит прочитать github , который стоит прочитать. Решение (запрос на извлечение) было предложено и отклонено.

Рекомендация

Ваш вопрос предлагает то же решение, предложенное авторами Doctrine: изменить сторону отношений.

Другие опции изучены

У меня была похожая проблема с сущностью под названием Version, у которой была OneToOne двунаправленная связь с Settings. Каждый раз, когда я запрашивал Version (скажем, для 10 записей конкретной версии), Doctrine выполнял дополнительные запросы для объединенного Settings (как будто это была отложенная загрузка этих объектов). Это произошло, хотя я нигде не упоминал Settings, например, $Version->getSettings()->getSomeProperty().

Ручной JOIN

Единственное «решение» (хак), которое работает для меня, - это вручную включать JOIN для этой Settings сущности каждый раз, когда я выполнял запрос на Version. Но поскольку мне не нужна дополнительная сущность (в данном случае), это все равно будет один дополнительный лишний запрос, каждый раз, когда я запрашиваю эту таблицу различными способами.

Extra Lazy

Основываясь на других предложениях, я подумал, что, если я явно укажу это отношение как «лишнее ленивое», оно будет работать, например,

/**
 * @ManyToMany(targetEntity="Settings", mappedBy="version", fetch="EXTRA_LAZY")
 */

Но это не влияет на гидратацию. См. Doctrine Docs для более подробной информации о том, что EXTRA_LAZY делает.

Тип увлажнения: HYDRATE_ARRAY

Что помогло в моем случае (когда мне не нужна сущность), было указать, что я хочу получить массив (а не объект).

$query = $queryBuilder->getQuery();
$query->getResult(Query:HYDRATE_ARRAY);

Это возвращает массив, и в результате он не лениво загружает ассоциации OneToOne. Однако в других случаях, когда мне нужен объект-сущность, мне приходилось явно присоединяться к сущности (несмотря на то, что я этого не хотел).

3 голосов
/ 15 февраля 2013

Мой обходной путь состоял в том, чтобы превратить отношение OneToOne в ManyToOne (со связанной коллекцией ArrayCollection) с помощью пользовательского метода получения и установки, который только устанавливает и возвращает 0-й элемент коллекции:

/**
 * Set pref
 *
 * @param \MyBundle\Entity\Pref $pref
 * @return Employee
 */
public function setPref(\MyBundle\Entity\Pref $pref = null) {
    $this->pref[0] = $pref;

    return $this;
}

/**
 * Get pref
 *
 * @return \MyBundle\Entity\Pref 
 */
public function getPref() {
    return $this->pref[0];
}

Эти методы (по-видимому) могут использоваться так же, как те, которые созданы отношением OneToOne.

doctrine:generate:entities по-прежнему создает обычные методы «добавления» и «удаления», но их можно игнорировать.

Примечание: я только недавно начал использовать их и в настоящее время только для чтения из базы данных. Я не знаю никаких побочных эффектов от этого обходного пути. Я обновлю этот ответ, если увижу какой-либо.

Это, по общему признанию, уродливый хак. Я надеюсь, что Doctrine исправит это в ближайшее время, поэтому я могу вернуться к использованию правильных отношений OneToOne.

0 голосов
/ 10 марта 2014

Я сталкивался с этой же проблемой и помню, что в учебнике symblog был приведен пример уменьшения отложенной загрузки путем явного добавления левых объединений в ненужные таблицы. Кажется странным включать таблицы в объединение, когда вам даже не нужны эти данные, но таким образом вы уменьшите все эти дополнительные запросы до 1, и он будет выполняться быстрее.

Поиск отложенной загрузки - примерно на 1/5 пути вниз http://tutorial.symblog.co.uk/docs/customising-the-view-more-with-twig.html

Чтобы исправить это для проблемы user / userdata, попробуйте добавить это в репозиторий пользователей и использовать всякий раз, когда вам нужно получить всех пользователей, даже если вы не хотите userdata. Это может быть дополнительно улучшено путем выбора частичного: -> выберите ('частичный p. {user_id, name,}')

   public function getAll($limit = 500) {
       $qb = $this->createQueryBuilder('u')
            ->select('u', 'd')
            ->leftJoin('p.userdata', 'd')
       if (false === is_null($limit))
           $qb->setMaxResults($limit);
     return $qb->getQuery()->getResult();
    }
...