我想用至少2个左联接对一个复杂的请求进行分页,但我使用的分页捆绑包(KnpPaginationBundle)无法告诉Doctrine如何计算结果(这是分页过程所需的),并且一直存在此异常。
Cannot count query which selects two FROM components, cannot make distinction
以下是使用Doctrine QueryBuilder构建的请求示例。
public function findGroupsByUser(User $user, $listFilter, $getQuery = false, $order = 'ASC')
{
$query = $this->createQueryBuilder('r')
->select('r as main,g')
->select('r as main,g, count(gg) as members')
->leftjoin('r.group', 'g')
->innerjoin('MyBundle:GroupMemberRel', 'gg', 'WITH', 'r.group = gg.group')
->addGroupBy('g.groupId')
->add('orderBy', 'g.name ' . $order);
if ($getQuery == true) {
return $query;
}
return $query->getQuery()->getResult();
}
然后我把这个请求提供给knp_pageator服务,然后我得到了异常
$groupQuery = $this->em->getRepository('MyBundle:GroupMemberRel')->findGroupsByUser($user, $listFilter, true);
$paginator = $this->container->get('knp_paginator');
/* @var $groups KnpComponentPagerPaginationPaginationInterface */
$groups = $paginator->paginate(
$groupQuery, $this->container->get('request')->query->get('page', 1), 10 /* limit per page */
);
关于如何对复杂的请求进行分页的任何想法,我确信这个用例很常见,不想在分页后使我的结果水合。
对于任何想要找到答案的人来说,有一个很好的解决方案:https://github.com/KnpLabs/KnpPaginatorBundle/blob/master/Resources/doc/manual_counting.md
$paginator = new Paginator;
// determine the count of the entire result set
$count = $entityManager
->createQuery('SELECT COUNT(c) FROM EntityCompositeKey c')
->getSingleScalarResult();
// create a query to be used with the paginator, and specify a hint
$query = $entityManager
->createQuery('SELECT c FROM EntityCompositeKey c')
->setHint(
'knp_paginator.count',
$count
);
// paginate, and set "disctinct" option to false
$pagination = $paginator->paginate(
$query,
1,
10,
array(
'distinct' => false,
)
);
基本上,您正在做的是创建自己的"count"查询,并指示knp-paginator使用它。
很难理解原始问题中的实体,但我遇到了同样的问题,它确实是可以解决的。
假设你有一个这样的实体:
class User
{
// ...
/**
* @ORMOneToMany(targetEntity="Registration", mappedBy="user")
*/
private $registrations;
// ...
}
重要的是,它与另一个实体有一对多的关系,无论出于何种原因,您都希望能够通过QueryBuilder加入该关系(例如,您可能希望在查询中添加HAVING
子句,以仅选择具有一个或多个其他实体的实体)。
您的原始代码可能看起来像:
$qb = $this->createQueryBuilder();
$query = $qb
->select('u')
->add('from', 'YourBundleORMModelUser u')
->leftJoin('YourBundleORMModelRegistration', 'r', 'WITH', 'u.id = r.user')
->groupBy('u.id')
->having($qb->expr()->gte($qb->expr()->count('u.registrations'), '1')
->getQuery();
这将引发异常:Cannot count query which selects two FROM components, cannot make distinction
要解决此问题,请重写查询,使QueryBuilder只有一个"from"组件——正如异常所示——通过内联移动联接。像这样:
$qb = $this->createQueryBuilder();
$query = $qb
->select('u')
->add('from', 'YourBundleORMModelUser u LEFT JOIN u.registrations r')
->groupBy('u.id')
->having($qb->expr()->gte($qb->expr()->count('r'), '1')
->getQuery();
应该可以很好地使用Doctrine的分页器类,不需要其他捆绑包。还要注意联接上的简写语法;在Doctrine中,你看不到明确指定连接的实体(尽管你可以),因为映射已经知道它是什么
希望这能帮助到一些人,这个问题上没有太多。您可以在这里查看@halfer的评论,了解更多关于内部如何处理这一问题的信息。
将doctrine/dbal
更新为版本2.5
为我修复了此问题。
如果您没有按照上面答案中提供的方式处理常规映射实体,联接将在结果集中创建其他组件。这意味着集合不能被计数,因为它不是标量结果。
因此,关键是将结果集保持为一个组件,这将允许产生和计数标量结果。
可以在运行时将常规字段转换为实体映射。这允许您编写只返回一个组件的查询,即使您是随机加入其他实体的。"其他实体"成为主实体的子实体,而不是结果的附加根实体或附加组件。
下面的示例显示了一对一的映射。我还没有尝试过更复杂的映射,但我在这方面的成功表明这应该是可能的。Registration实体有一个名为user的字段,该字段包含用户Id,但它不是映射实体,只是一个普通字段。
(这是从一个工作示例中修改的,但没有按原样进行测试,因此视为伪代码)
$queryBuilder // The query builder already has the other entities added.
->innerJoin('r.user', 'user')
;
$mapping['fieldName'] = 'user';
$mapping['targetEntity'] = 'YourBundleORMModelUser';
$mapping['sourceEntity'] = 'YourBundleORMModelRegistration';
$mapping['sourceToTargetKeyColumns'] = array('user' => 'id');
$mapping['targetToSourceKeyColumns'] = array('id' => 'user');
$mapping['fetch'] = 2;
$mapping['joinColumns'] = array(
array(
'name' => 'user',
'unique' => false,
'nullable' => false,
'onDelete' => null,
'columnDefinition' => null,
'referencedColumnName' => 'id',
)
);
$mapping['mappedBy'] = 'user';
$mapping['inversedBy'] = null; // User doesn't have to link to registrations
$mapping['orphanRemoval'] = false;
$mapping['isOwningSide'] = true;
$mapping['type'] = ClassMetadataInfo::MANY_TO_ONE;
$vm = $this->em->getClassMetadata('YourBundle:Registration');
$vm->associationMappings["user"] = $mapping;
注意:我使用的是PagerFanta而不是KNP,但问题在于Doctrine,所以我希望这在任何地方都能奏效。