Подтвердить что ты не робот

Первичный ключ владельца в качестве столбца объединения

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

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

Я создал объекты 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 для каждого пользователя.

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

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

Итак, есть три способа обойти это:

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

Я решил пойти с №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, так как они всегда будут иметь одинаковое значение? Понимаете ли вы какие-либо потенциальные проблемы с этим способом выполнения вещей?

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

4b9b3361

Ответ 1

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

use Doctrine\ORM\Query;

//...
$query->setHint(Query::HINT_FORCE_PARTIAL_LOAD, true);

Ответ 2

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

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

В вашем вопросе предлагается одно и то же решение, предложенное вкладчиками к Доктрине: измените собственную сторону отношений.

Другие параметры Explored

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

Manual JOIN

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

Extra Lazy

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

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

Но это не влияет на гидратацию. Подробные сведения о том, что делает EXTRA_LAZY, см. в Документах Doctrine.

Тип гидратации: HYDRATE_ARRAY

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

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

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

Ответ 3

Мое обходное решение состояло в том, чтобы превратить отношения OneToOne в ManyToOne (с ассоциированным ArrayCollection) с пользовательским getter и setter, который только устанавливает и возвращает 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.

Ответ 4

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

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

Чтобы исправить это для проблемы user/userdata, попробуйте добавить это в репозиторий пользователя и использовать, когда вам нужно получить всех пользователей, даже если вы не хотите, чтобы пользовательские данные. Это может быть дополнительно улучшено путем выбора частичного:      - > select ('partial 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();
    }