【问题标题】:Django - Populate model instance related field from cached queryDjango - 从缓存查询中填充模型实例相关字段
【发布时间】:2019-03-17 10:15:33
【问题描述】:

情况与Django prefetch_related children of children 相同,但问题不同:

我有一个模型Node,看起来像这样:

class Node(models.Model):
    parent = models.ForeignKey('self', related_name='children', on_delete=models.CASCADE, null=True)

一个节点可以有几个孩子,每个孩子都可以有自己的孩子。

我想做这样的事情:

def cache_children(node):
    for child in node.children.all():
        cache_children(child)

root_node = Node.objects.prefetch_related('children').get(pk=my_node_id) 

all_nodes = Node.objects.all()  # get all the nodes in a single query

# Currently: hit database for every loop
# Would like: to somehow use the already loaded data from all_nodes
cache_children(root_node)  

由于我已经获取了all_nodes 查询中的所有节点,因此我想重用此查询中的缓存数据,而不是每次都执行一个新数据。

有什么方法可以实现吗?

【问题讨论】:

    标签: django django-orm


    【解决方案1】:

    我设法让它以这种方式工作,并用 2 个 db 调用填充整个树:

    def populate_prefetch_cache(node, all_nodes):
        children = [child for child in all_nodes if child.parent_id==node.id]
    
        # will not have the attribute if no prefetch has been done
        if not hasattr(node, '_prefetched_objects_cache'):
            node._prefetched_objects_cache = {}
    
        # Key to using local data to populate a prefetch!
        node._prefetched_objects_cache['children'] = children
        node._prefetch_done = True
    
        for child in node.children.all():
            populate_prefetch_cache(child , all_nodes )
    
    
    all_nodes = list(Node.objects.all())  # Hit database once
    root_node = Node.objects.get(pk=my_node_id)  # Hit database once
    
    # Does not hit the database and properly populates the children field
    populate_prefetch_cache(root_node, all_nodes)
    

    感谢这个答案,我发现了 _prefetched_objects_cache 属性:Django: Adding objects to a related set without saving to DB

    【讨论】:

      【解决方案2】:

      树状结构中的数据不太适合关系数据库,但是有一些策略可以解决这个问题 - 请参阅tree implemenations in the docs of django-treebeard 章节。

      如果你的树不是太大,你可以将树完全存储在 python dict 中并缓存结果。

      示例(未经测试 - 根据您的喜好调整数据结构...)

      from django.core.cache import cache
      
      # ...
      
      def get_children(nodes, node):
          node['children'] = [n for n in nodes if n['parent']==node['id']]
          for child_node in node['children']:
              child_node = get_children(nodes, child_node)
          return node
      
      
      def get_tree(timeout_in_seconds=3600)
          tree = cache.get('your_cache_key')
          if not tree:
              # this creates a list of dicts with the instances values - one DB hit!
              all_nodes = list(Node.objects.all().values())
              root_node = [n for n in nodes if n['parent']==None][0]
              tree = get_children(all_nodes, root_node)
      
              cache.set('your_cache_key', tree, timeout_in_seconds)
          return tree
      
      • 当然你必须有你的cache enabled
      • 您可以在 Node.save 方法中使缓存无效

      【讨论】:

      • 我实际上对“构建你的树”部分非常感兴趣:D。在我看来,我需要过滤我的 all_node 查询以查找每个节点的子节点并构建树,但这会导致每个过滤器都有一个新的数据库查询...
      • query is evaluated 时,您将在内存中获得结果而不再有任何数据库命中。顺便说一句:您的 parent 字段应该允许为空,因为它对于根节点的值是多少?
      • 不幸的是,每次我使用 filter() 时,都会重新评估查询。即使我在已经评估过一次的all_nodes 上这样做。你是对的null=True - 修复它
      • 您不会使用过滤器,而是使用常规 Python 处理数据。我用一个简单的例子更新了我的答案。
      • 好的,谢谢指点!不幸的是,我不能像你展示的那样分配孩子。获取'Node' object does not support item assignment。我确实设法让它工作(见我的回答)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2015-08-31
      • 1970-01-01
      • 2020-07-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-03-20
      相关资源
      最近更新 更多