Doctrine DQL,类表继承和访问子类字段

21
我在使用DQL查询和实体专业化时遇到了问题。
我有一个名为“拍卖”的实体,与“物品”有一对一的关系。 “物品”是“电影”和“书”的映射超类。我需要一条查询语句来支持搜索引擎,允许用户查找不同属性的拍卖,并且售卖具有不同属性的物品(正是“AND”部分使其具有挑战性)。
问题是,即使“拍卖”将关联指向“物品”,我仍然需要访问“电影”和“书”的特定字段。用户将指定他们正在寻找的“物品”类型,但我没有看到除了在我的DQL查询中使用“INSTANCE OF”之外使用此信息的其他方法。
到目前为止,我已经尝试使用以下查询:
SELECT a FROM Entities\Auction a
    INNER JOIN a.item i 
    INNER JOIN i.bookTypes b 
    WHERE i INSTANCE OF Entities\Book 
    AND b.type = 'Fantasy' 
    AND ...". 

这样的查询会导致错误,显示:

Entities\Item 没有名为 bookTypes 的字段或关联项

对于 Book 来说是错误的,但对于 Item 是正确的。

我也尝试过

SELECT a FROM Entities\Book i 
    INNER JOIN i.auction a ...

我猜Doctrine要求我在SELECTFROM语句中引用相同的实体。

如果这很重要,我正在使用类表继承。尽管如此,我认为切换到单表继承也行不通。

有什么想法吗?


可能是磨刀不误砍柴工的重复问题。 - Ocramius
5个回答

15

正如Matt所述,这是Doctrine项目不会修复的一个旧问题(DDC-16)。

问题在于Doctrine的DQL是一种静态类型语言,其内部具有一定的复杂性。

我们考虑过允许向上转型,但实现的努力根本不值得,而且人们将滥用该语法做出非常危险的事情。

正如DDC-16所述,如果不发生糟糕的问题(例如多个子类定义具有不同列名称的相同属性),则无法理解属性属于哪个类。

如果您想要在CTI或JTI中的子类中过滤数据,则可以使用我在https://stackoverflow.com/a/14854067/347063上描述的技术。这将使您的DQL与所有相关子类耦合。

在您的情况下,您可能需要的DQL最可能是(假设Entities\BookEntities\Item的子类):

SELECT
    a
FROM
    Entities\Auction a 
INNER JOIN
    a.item i
INNER JOIN
    i.bookTypes b
WHERE
    i.id IN (
        SELECT 
            b.id
        FROM
            Entities\Book b
        WHERE
            b.type = 'Fantasy'
    )

这是您问题的伪代码。它并不完美,但需要记住的是 SQL 和 DQL 很不同,遵循不同的规则。


4

4

您可以通过使用id将基础实体与继承类进行左连接来轻松解决此问题:

SELECT a FROM Entities\Auction a
    INNER JOIN a.item i 
    INNER JOIN Entities\Book b WITH b.id = i.id 
    INNER JOIN b.bookTypes bt
    WHERE bt.type = 'Fantasy' 
    AND...

或者使用查询构建器:
$queryBuilderb->select('a')
   ->from('Entities\Auction', 'a')
   ->innerJoin('a.item', 'i')
   ->innerJoin('Entities\Book', 'b', 'WITH', 'b.id = i.id')
   ->innerJoin('b.bookTypes', 'bt')
   ->where('bt.type = :type')
   ->andWhere(...
   ->setParameter('type', 'Fantasy');

以下翻译内容基于Ian Philips在这里的回答。


2
Doctrine团队表示他们不打算为此添加支持:

https://github.com/doctrine/orm/issues/2237

该页面中相关的评论:
“这确实很棘手。然而,仅凭那个语法是行不通的,因为可能有多个子类都有一个名为“d”的字段,所以Doctrine不知道你指的是哪个字段。”
我正在关闭这个问题。这个问题的要求基本上违反了面向对象原则。如果你真的需要在继承中跨多个子实体进行过滤,则可以尝试以下内容:选择根节点r,其中r的ID位于(选择子节点c的ID,其中c的字段等于:value)。

0

我曾经遇到过同样的问题,没有找到解决方案,除非使用单独的查询来处理每个子类,然后在应用程序级别合并它们。

有一件事我可以确定,单表继承也无法完全解决这个问题。

还有另一种选择,虽然逻辑上不太好。在超类中定义所有字段(您需要的那些字段)。如果记录在逻辑上没有该字段,则为空。虽然不是很美观,但比2-3-4-...个查询更优化。在这种情况下,单表继承绝对是更好的选择。


2
合并结果在这里并不是必须的,因为我从来不需要在查询结果中同时出现电影和书籍。我最终想到的解决方法是添加拍卖品-物品关系的反向侧面,并查询物品而不是拍卖品('SELECT i FROM Entities\Book INNER JOIN i.auction a WHERE (...) ')。由于我知道我要查找哪个子类,所以可以相应地构建查询。谢谢您的回复,不过您的想法可能是一个好方法,因为在这种情况下使用Doctrine继承似乎没有什么意义。 - crizzis
这是一个相当糟糕的想法,更像是一种hack - 你不需要破坏你的模型来修复DQL。作为最后的手段,你总是可以使用原生SQL,但代价是要维护一些查询语句。 - Ocramius

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接