NDB建模之一对多关系: 重复的KeyProperty和外键的优点

15

我的问题是如何在ndb中建模一对多关系。我理解这可以用至少两种不同的方式实现:使用重复属性或“外键”。我在下面创建了一个小例子。基本上,我们有一篇文章,它可以拥有任意数量的标签。假设标签可以被移除,但添加后不能更改。同时假设我们不担心事务安全。

我的问题是:哪种方式在建模这些关系时更好?

我的考虑:

  • 方法(A)需要为添加到文章中的每个标签进行两次写操作(一次为文章,一次为标签),而方法(B)只需要进行一次写操作(只需标签)。
  • 方法(A)在获取所有标签的情况下利用了ndb的缓存机制,而在方法(B)的情况下需要查询(并且还需要一些自定义缓存)。

我是否漏掉了其他需要考虑的因素?

非常感谢您的帮助。

示例(A):

class Article(ndb.Model):
    title = ndb.StringProperty()
    # some more properties
    tags = ndb.KeyProperty(kind="Tag", repeated=True)

    def create_tag(self):
        # requires two writes
        tag = Tag(name="my_tag")
        tag.put()
        self.tags.append(tag)
        self.put()

    def get_tags(self):
        return ndb.get_multi(self.tags)

class Tag(ndb.Model):
    name = ndb.StringProperty()
    user = ndb.KeyProperty(Kind="User") #  User that created the tag
    # some more properties

例子(B):

class Article(ndb.Model):
    title = ndb.StringProperty()
    # some more properties

    def create_tag(self):
        # requires one write
        tag = Tag(name="my_tag", article=self.key)
        tag.put()

    def get_tags(self):
        # obviously we could cache this query in memcache
        return Tag.gql("WHERE article :1", self.key)

class Tag(ndb.Model):
    name = ndb.StringProperty()
    article = ndb.KeyProperty(kind="Article")
    user = ndb.KeyProperty(Kind="User") #  User that created the tag
    # some more properties

2
请考虑使用appstats来检查性能,因为尽管您在这里提出的具体问题可能有特定的答案,但它更可能与实际使用相关,因此appstats可以告诉您上述哪些选项在实际情况下更有效。https://developers.google.com/appengine/docs/python/tools/appstats - Paul Collingwood
2
你会为每篇文章创建新标签吗,即使它们是相同的标签?我会选择选项A,因为你可以为每篇文章使用相同的Tag,并且你可以通过标签查询Articles - aschmid00
@PaulC 谢谢。确实我用 appstats 检查过了,在我的情况下选项 B 更有效率(1 次写入 vs 2 次)。然而,由于优化只是小幅度的,我不确定是否值得放弃文档中提到的方法(即选项 A)来解决一对多关系。 - Emiel vl
@aschmid00 是的,我会为每个“文章”创建一个新的“标签”。这在问题中不太清楚,我会做出相应的更改。这样会改变你的答案吗?谢谢。 - Emiel vl
仍然会选择 A,但这要取决于每篇文章将有多少标签。即使这些文章具有相同的名称,为什么要为每篇文章创建单独的标签?还要查看 @kasavbere 的答案... - aschmid00
3个回答

6

谢谢你的回答,结构化属性可以完成工作,但在我的特定情况下,我不认为它们是最好的解决方案。你所说的“向前看,因为不允许连接”是什么意思?这是GAE政策吗? - Emiel vl
1
是的,这是数据存储的限制。请参阅https://developers.google.com/appengine/docs/python/datastore/queries。"特别是,在Datastore查询引擎中不支持连接和聚合查询。" 数据存储还有其他限制,您可能应该熟悉:https://developers.google.com/appengine/docs/python/datastore/queries#Restrictions_on_Queries - kasavbere

1
作为之前所述,Datastore 中没有联接(join)的概念,因此所有“外键”概念都不适用。可以使用 Query 类来查询正确的标签(Tag)。例如,如果您正在使用 Endpoints,则:
class Tag(ndb.model):
    user = ndb.UserProperty()

在请求期间执行以下操作:
query.filter(Tag.user == endpoints.get_current_user())

1
在大多数情况下,应该优先选择方法(A)。虽然添加标签需要两次写入,但这比读取标签的频率要少得多。只要您没有大量标签,它们都应该适合于重复的键属性中。
正如您所提到的,通过它们的键获取标签比执行查询要快得多。此外,如果您只需要标签的名称和用户,则可以使用“用户”作为父键和“名称”作为标签ID来创建标签:
User -> Name -> Tag

要创建此标记,您将使用以下代码:

tag = Tag(id=name, parent=user, ...)
article.tags.push(tag)
ndb.put_multi([tag, article])

然后当您检索标签时,
for tag in article.tags:
    user = tag.parent()
    name = tag.id()

然后,您在 Article.tags 中存储的每个键都将包含用户键和 Tag 名称!这将使您无需读取 Tag 即可获取这些值。

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