【问题标题】:Following subclass relationships with select_related遵循与 select_related 的子类关系
【发布时间】:2012-07-27 20:37:33
【问题描述】:

我有一个类似于以下的多表继承架构:

class NodeData(models.Model):
    node = models.ForeignKey(Node, db_index = True)
    value = models.FloatField(default = 0)
    name = models.TextField(unique=True, blank=True)

class Node(models.Model):
    name = models.CharField(max_length=50, blank=True)
    description = models.TextField(blank=True)
    node_tree = models.ForeignKey(NodeTree, db_index = True)
    unique_name = models.TextField(unique=True, blank=True)
    last_updated_timestamp = models.DateTimeField('date last updated', blank=True)

class ConceptNode(Node):
    node_parent = models.ForeignKey(Node, related_name="nodeParent", null=True, blank=True)

class DerivedNode(Node):
    node_source = models.ForeignKey(Node, related_name="nodeSource")
    node_target = models.ForeignKey(Node, related_name="nodeTarget")

出于性能原因,每当我获取大量 NodeData 元素时,我都会使用 select_related(depth = 2)。但是,select_related 不遵循与子类的关系,因此以下代码* 导致调用 myFunction 的 ConceptNode 实现,而该函数中使用的 ConceptNode 对象没有预取数据:

nd = NodeData.objects.get(id = 1)
nd.node.conceptnode.myFunction()

这使得 select_related 没那么有用,因为有很多这样调用的函数,它们并没有从 select_related 缓存的数据中受益。

我的问题是:我可以获取 select_related 来为我获取此信息,以便我获取的每个 NodeData 对象都缓存了 Node 和 ConceptNode/DerivedNode 实例吗?*

注意:这实际上是使用访问器函数模型完成的,就像我问过的 here

注意:我正在尝试做的与this 类似,但有点不同,因为我有多个子类。

编辑:感谢 chris-platt 的提示,我发现以下内容可以满足我的需要:

nd = NodeData.objects.select_related('conceptnode','derivednode').get(id = 1)
nd.node.conceptnode.myFunction()

第一行将 DerivedNode 或 ConceptNode 实例预取到相关的缓存对象中。

【问题讨论】:

    标签: django django-models django-orm


    【解决方案1】:

    如果您执行ConceptNode.objects.get(...) 之类的操作,则将同时获取Node 实例,以便您拥有实例的完整数据。但是,当您执行Node.objects.get(...) 之类的操作时,来自ConceptNode 等子类的数据 包括在内。要选择这些,您需要使用select_related(<related_name>),并且要获得多个子类,您只需将related_names 添加为逗号分隔的列表,例如:

    Node.objects.select_related('nodeParent', 'nodeSource', 'nodeTarget')
    

    【讨论】:

    • 感谢克里斯 - 您的回复让我朝着正确的方向前进。提供为多表继承定义的隐式定义的 related_name 字段。
    【解决方案2】:

    我认为这里对select_related 的工作方式存在一些误解。它不适用于所有关系,它适用于外键或一对一关系 - 其中相关数据是单个记录,并且可以使用连接修改原始查询以获取它。所以这个例子不起作用:

    nd = NodeData.objects.get(id = 1)
    nd.node.conceptnode.myFunction()
    

    ... 因为 Node 是 ConceptNode 的父级,所以它可以有很多相关的。事实上,我认为nd.node.conceptnode 会出现属性错误。我认为您可以通过以下方式访问这些子概念节点:

    nd = NodeData.objects.get(id = 1)
    nd.node.nodeParent.all() # Loop through this and do stuff with the children...
    

    我在这里使用'nodeParent',因为你已经设置了related_name(你可能想把它改成更合适的东西,比如'nodeChildren'或其他东西),但默认情况下它是'conceptnode_set',例如:nd.node.conceptnode_set.all()

    如果您使用的是 Django 1.4,有一个名为 [prefetch_related][1] 的新 QuerySet 方法,我没有使用过,但它应该适用于这种确切的情况(减少对“多”类型关系的查询),而您理论上应该可以做到:

    nd = NodeData.objects.get(id = 1).prefetch_related('conceptnode')
    

    ...尽管您必须使用 'conceptnode' 参数,并且通常使用 prefetch_related 来查看它是否能满足您的需求。

    【讨论】:

    • 谢谢克里斯。我认为我的问题可能令人困惑,因为您和@chris-platt 都以相同的方式解释它。我不需要获取特定节点的所有子节点,如果节点也是 ConceptNode 或 DerivedNode,我只想预获取 DerivedNode 或 ConceptNode 实例。
    • 问题是没有办法知道Node 的子类是什么,直到之后它已经从数据库中获取并且所有的类都被实例化了。这就是为什么你select_related 提供所有可能性,然后在你需要的时候合适的人就会出现。
    • @chris-platt,你是对的。这就是我在解决方案中同时引用derivednode 和conceptnode 的原因。
    猜你喜欢
    • 2011-12-27
    • 2017-10-26
    • 2013-11-03
    • 2019-08-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多