Doctrine2 - Doctrine 生成带有关联实体的查询 - InvalidFieldNameException
Doctrine2 - Doctrine generating query with associated entity - InvalidFieldNameException
是的,标题表明:Doctrine 正在寻找一个不存在的字段名。这既是真实的又不是真实的,尽管我不知道如何解决它。
完整错误:
File: D:\path\to\project\vendor\doctrine\dbal\lib\Doctrine\DBAL\Driver\AbstractMySQLDriver.php:71
Message: An exception occurred while executing 'SELECT DISTINCT id_2
FROM (SELECT p0_.name AS name_0, p0_.code AS code_1, p0_.id AS id_2
FROM product_statuses p0_) dctrn_result ORDER BY p0_.language_id ASC, name_0 ASC LIMIT 25
OFFSET 0':
SQLSTATE[42S22]: Column not found: 1054 Unknown column
'p0_.language_id' in 'order clause'
导致错误的查询(来自上面的错误):
SELECT DISTINCT id_2
FROM (
SELECT p0_.name AS name_0, p0_.code AS code_1, p0_.id AS id_2
FROM product_statuses p0_
) dctrn_result
ORDER BY p0_.language_id ASC, name_0 ASC
LIMIT 25 OFFSET 0
显然,该查询不会起作用。 ORDER BY
应该在 sub-query 中,否则它应该用 dctrn_result
替换 ORDER BY 中的 p0_
并且还得到 language_id
列62=] 待返回。
查询是使用 Zend Framework 中控制器的 indexAction 中的 QueryBuilder 构建的。一切都非常正常,当对单个 ORDER BY
语句使用 addOrderBy()
函数时,相同的函数工作得很好。在这种情况下,我希望使用 2,首先按语言,然后按名称。但是发生了以上情况。
如果有人知道这个问题的完整解决方案(或者可能是一个错误?),那就太好了。否则,将不胜感激在正确方向上帮助我解决此问题的提示。
以下附加信息 - 实体和 indexAction()
ProductStatus.php - 实体 - 注意 language_id
列的存在
/**
* @ORM\Table(name="product_statuses")
* @ORM\Entity(repositoryClass="Hzw\Product\Repository\ProductStatusRepository")
*/
class ProductStatus extends AbstractEntity
{
/**
* @var string
* @ORM\Column(name="name", type="string", length=255, nullable=false)
*/
protected $name;
/**
* @var string
* @ORM\Column(name="code", type="string", length=255, nullable=false)
*/
protected $code;
/**
* @var Language
* @ORM\ManyToOne(targetEntity="Hzw\Country\Entity\Language")
* @ORM\JoinColumn(name="language_id", referencedColumnName="id")
*/
protected $language;
/**
* @var ArrayCollection|Product[]
* @ORM\OneToMany(targetEntity="Hzw\Product\Entity\Product", mappedBy="status")
*/
protected $products;
[Getters/Setters]
}
IndexAction - 删除了与 QueryBuilder 不直接相关的部分。添加在评论中,按原样显示参数。
/** @var QueryBuilder $qb */
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select($asParam) // 'pro'
->from($emEntity, $asParam); // Hzw\Product\Entity\ProductStatus, 'pro'
if (count($queryParams) > 0 && !is_null($query)) {
// [...] creates WHERE statement, unused in this instance
}
if (isset($orderBy)) {
if (is_array($orderBy)) {
// !!! This else is executed !!! <-----
if (is_array($orderDirection)) { // 'ASC'
// [...] other code
} else {
// $orderBy = ['language', 'name'], $orderDirection = 'ASC'
foreach ($orderBy as $orderParam) {
$qb->addOrderBy($asParam . '.' . $orderParam, $orderDirection);
}
}
} else {
// This works fine. A single $orderBy with a single $orderDirection
$qb->addOrderBy($asParam . '.' . $orderBy, $orderDirection);
}
}
============================================= ===
更新:我发现了问题
上述问题不是由不正确的映射或可能的错误引起的。这是 QueryBuilder
在创建查询时不会自动处理实体之间的关联。
我的期望是,当实体(例如上面的 ProductStatus)包含关系的 ID(即 language_id
列)时,可以在 QueryBuilder 中毫无问题地使用这些属性。
请在下面查看我自己的回答,我是如何修复我的功能以便能够默认处理单级嵌套(即 ProducStatus#language == Language,能够使用 language.name
作为 ORDER BY
标识符)。
好的,经过更多的搜索和深入研究这是如何以及在哪里出错后,我发现 Doctrine 在生成查询期间不处理实体的关系类型属性;或者如果没有指定,则可能不默认使用实体的主键。
在我上面问题的用例中,language
属性 与 Language
实体有 @ORM\ManyToOne
关联。
我的用例要求能够处理至少一级默认操作的关系。因此,在我意识到这不是自动处理的(或使用 language.id
或 language.name
作为标识符的修改)后,我决定为它编写一个小函数。
/**
* Adds order by parameters to QueryBuilder.
*
* Supports single level nesting of associations. For example:
*
* Entity Product
* product#name
* product#language.name
*
* Language being associated entity, but must be ordered by name.
*
* @param QueryBuilder $qb
* @param string $tableKey - short alias (e.g. 'tab' with 'table AS tab') used for the starting table
* @param string|array $orderBy - string for single orderBy, array for multiple
* @param string|array $orderDirection - string for single orderDirection (ASC default), array for multiple. Must be same count as $orderBy.
*/
public function createOrderBy(QueryBuilder $qb, $tableKey, $orderBy, $orderDirection = 'ASC')
{
if (!is_array($orderBy)) {
$orderBy = [$orderBy];
}
if (!is_array($orderDirection)) {
$orderDirection = [$orderDirection];
}
// $orderDirection is an array. We check if it's of equal length with $orderBy, else throw an error.
if (count($orderBy) !== count($orderDirection)) {
throw new \InvalidArgumentException(
$this->getTranslator()->translate(
'If you specify both OrderBy and OrderDirection as arrays, they should be of equal length.'
)
);
}
$queryKeys = [$tableKey];
foreach ($orderBy as $key => $orderParam) {
if (strpos($orderParam, '.')) {
if (substr_count($orderParam, '.') === 1) {
list($entity, $property) = explode('.', $orderParam);
$shortName = strtolower(substr($entity, 0, 3)); // Might not be unique...
$shortKey = $shortName . '_' . (count($queryKeys) + 1); // Now it's unique, use $shortKey when continuing
$queryKeys[] = $shortKey;
$shortName = strtolower(substr($entity, 0, 3));
$qb->join($tableKey . '.' . $entity, $shortName, Join::WITH);
$qb->addOrderBy($shortName . '.' . $property, $orderDirection[$key]);
} else {
throw new \InvalidArgumentException(
$this->getTranslator()->translate(
'Only single join statements are supported. Please write a custom function for deeper nesting.'
)
);
}
} else {
$qb->addOrderBy($tableKey . '.' . $orderParam, $orderDirection[$key]);
}
}
}
它绝不支持 QueryBuilder
提供的所有内容,并且绝对不是最终解决方案。但它为抽象函数提供了一个起点和坚实的"default functionality"。
是的,标题表明:Doctrine 正在寻找一个不存在的字段名。这既是真实的又不是真实的,尽管我不知道如何解决它。
完整错误:
File: D:\path\to\project\vendor\doctrine\dbal\lib\Doctrine\DBAL\Driver\AbstractMySQLDriver.php:71
Message: An exception occurred while executing 'SELECT DISTINCT id_2 FROM (SELECT p0_.name AS name_0, p0_.code AS code_1, p0_.id AS id_2 FROM product_statuses p0_) dctrn_result ORDER BY p0_.language_id ASC, name_0 ASC LIMIT 25 OFFSET 0':
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'p0_.language_id' in 'order clause'
导致错误的查询(来自上面的错误):
SELECT DISTINCT id_2
FROM (
SELECT p0_.name AS name_0, p0_.code AS code_1, p0_.id AS id_2
FROM product_statuses p0_
) dctrn_result
ORDER BY p0_.language_id ASC, name_0 ASC
LIMIT 25 OFFSET 0
显然,该查询不会起作用。 ORDER BY
应该在 sub-query 中,否则它应该用 dctrn_result
替换 ORDER BY 中的 p0_
并且还得到 language_id
列62=] 待返回。
查询是使用 Zend Framework 中控制器的 indexAction 中的 QueryBuilder 构建的。一切都非常正常,当对单个 ORDER BY
语句使用 addOrderBy()
函数时,相同的函数工作得很好。在这种情况下,我希望使用 2,首先按语言,然后按名称。但是发生了以上情况。
如果有人知道这个问题的完整解决方案(或者可能是一个错误?),那就太好了。否则,将不胜感激在正确方向上帮助我解决此问题的提示。
以下附加信息 - 实体和 indexAction()
ProductStatus.php - 实体 - 注意 language_id
列的存在
/**
* @ORM\Table(name="product_statuses")
* @ORM\Entity(repositoryClass="Hzw\Product\Repository\ProductStatusRepository")
*/
class ProductStatus extends AbstractEntity
{
/**
* @var string
* @ORM\Column(name="name", type="string", length=255, nullable=false)
*/
protected $name;
/**
* @var string
* @ORM\Column(name="code", type="string", length=255, nullable=false)
*/
protected $code;
/**
* @var Language
* @ORM\ManyToOne(targetEntity="Hzw\Country\Entity\Language")
* @ORM\JoinColumn(name="language_id", referencedColumnName="id")
*/
protected $language;
/**
* @var ArrayCollection|Product[]
* @ORM\OneToMany(targetEntity="Hzw\Product\Entity\Product", mappedBy="status")
*/
protected $products;
[Getters/Setters]
}
IndexAction - 删除了与 QueryBuilder 不直接相关的部分。添加在评论中,按原样显示参数。
/** @var QueryBuilder $qb */
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select($asParam) // 'pro'
->from($emEntity, $asParam); // Hzw\Product\Entity\ProductStatus, 'pro'
if (count($queryParams) > 0 && !is_null($query)) {
// [...] creates WHERE statement, unused in this instance
}
if (isset($orderBy)) {
if (is_array($orderBy)) {
// !!! This else is executed !!! <-----
if (is_array($orderDirection)) { // 'ASC'
// [...] other code
} else {
// $orderBy = ['language', 'name'], $orderDirection = 'ASC'
foreach ($orderBy as $orderParam) {
$qb->addOrderBy($asParam . '.' . $orderParam, $orderDirection);
}
}
} else {
// This works fine. A single $orderBy with a single $orderDirection
$qb->addOrderBy($asParam . '.' . $orderBy, $orderDirection);
}
}
============================================= ===
更新:我发现了问题
上述问题不是由不正确的映射或可能的错误引起的。这是 QueryBuilder
在创建查询时不会自动处理实体之间的关联。
我的期望是,当实体(例如上面的 ProductStatus)包含关系的 ID(即 language_id
列)时,可以在 QueryBuilder 中毫无问题地使用这些属性。
请在下面查看我自己的回答,我是如何修复我的功能以便能够默认处理单级嵌套(即 ProducStatus#language == Language,能够使用 language.name
作为 ORDER BY
标识符)。
好的,经过更多的搜索和深入研究这是如何以及在哪里出错后,我发现 Doctrine 在生成查询期间不处理实体的关系类型属性;或者如果没有指定,则可能不默认使用实体的主键。
在我上面问题的用例中,language
属性 与 Language
实体有 @ORM\ManyToOne
关联。
我的用例要求能够处理至少一级默认操作的关系。因此,在我意识到这不是自动处理的(或使用 language.id
或 language.name
作为标识符的修改)后,我决定为它编写一个小函数。
/**
* Adds order by parameters to QueryBuilder.
*
* Supports single level nesting of associations. For example:
*
* Entity Product
* product#name
* product#language.name
*
* Language being associated entity, but must be ordered by name.
*
* @param QueryBuilder $qb
* @param string $tableKey - short alias (e.g. 'tab' with 'table AS tab') used for the starting table
* @param string|array $orderBy - string for single orderBy, array for multiple
* @param string|array $orderDirection - string for single orderDirection (ASC default), array for multiple. Must be same count as $orderBy.
*/
public function createOrderBy(QueryBuilder $qb, $tableKey, $orderBy, $orderDirection = 'ASC')
{
if (!is_array($orderBy)) {
$orderBy = [$orderBy];
}
if (!is_array($orderDirection)) {
$orderDirection = [$orderDirection];
}
// $orderDirection is an array. We check if it's of equal length with $orderBy, else throw an error.
if (count($orderBy) !== count($orderDirection)) {
throw new \InvalidArgumentException(
$this->getTranslator()->translate(
'If you specify both OrderBy and OrderDirection as arrays, they should be of equal length.'
)
);
}
$queryKeys = [$tableKey];
foreach ($orderBy as $key => $orderParam) {
if (strpos($orderParam, '.')) {
if (substr_count($orderParam, '.') === 1) {
list($entity, $property) = explode('.', $orderParam);
$shortName = strtolower(substr($entity, 0, 3)); // Might not be unique...
$shortKey = $shortName . '_' . (count($queryKeys) + 1); // Now it's unique, use $shortKey when continuing
$queryKeys[] = $shortKey;
$shortName = strtolower(substr($entity, 0, 3));
$qb->join($tableKey . '.' . $entity, $shortName, Join::WITH);
$qb->addOrderBy($shortName . '.' . $property, $orderDirection[$key]);
} else {
throw new \InvalidArgumentException(
$this->getTranslator()->translate(
'Only single join statements are supported. Please write a custom function for deeper nesting.'
)
);
}
} else {
$qb->addOrderBy($tableKey . '.' . $orderParam, $orderDirection[$key]);
}
}
}
它绝不支持 QueryBuilder
提供的所有内容,并且绝对不是最终解决方案。但它为抽象函数提供了一个起点和坚实的"default functionality"。