听起来你并不完全了解祖先。让我们先使用非祖先版本,这是一种合法的方式:
class Customer(ndb.Model):
# customer data fields
name = ndb.StringProperty()
class Purchase(ndb.Model):
customer = ndb.KeyProperty(kind=Customer)
# purchase data fields
price = ndb.IntegerProperty
这是基本的方法。对于每个客户,您将在数据存储中拥有一个实体。对于每次购买,您将在数据存储区中拥有一个实体,其密钥属性指向客户。
如果您有购买商品,并且需要找到相关客户,它就在那里。
purchase_entity.customer.get()
如果您有客户,您可以发出查询以查找属于该客户的所有购买:
Purchase.query(customer=customer_entity.key).fetch()
在这种情况下,每当您写入客户或购买实体时,GAE 数据存储都会将该实体写入该实体,该实体会在云中运行的任何一台不忙的数据存储机器中写入。通过这种方式,您可以获得非常高的写入吞吐量。但是,当您查询给定客户的所有购买时,您只需读回索引中的最新数据。如果添加了新购买,但索引尚未更新,那么您可能会得到陈旧的数据(最终一致性)。除非您使用祖先,否则您会被这种行为困扰。
现在至于祖先版本。基本概念基本相同。您仍然有一个客户实体,并且每次购买都有单独的实体。购买不是客户实体的一部分。但是,当您使用客户作为祖先创建购买时,它(大致)意味着购买存储在存储客户实体的数据存储中的同一台机器上。在这种情况下,您的写入性能仅限于那台机器的性能,并被宣传为每秒一次写入。不过,作为一个好处,您可以使用祖先查询来查询该机器,并获取给定客户所有购买的最新列表。
使用祖先的语法有点不同。客户部分是一样的。但是,当您创建购买时,您将其创建为:
purchase1 = Purchase(ancestor=customer_entity.key)
purchase2 = Purchase(ancestor=customer_entity.key)
此示例创建两个单独的购买实体。每次购买都有不同的密钥,客户也有自己的密钥。但是,每个购买密钥都将嵌入 customer_entity 的密钥。所以你可以认为购买密钥的长度是原来的两倍。但是,您不再需要为客户保留单独的 KeyProperty(),因为您可以在购买键中找到它。
class Purchase(ndb.Model):
# you don't need a KeyProperty for the customer anymore
# purchase data fields
price = ndb.IntegerProperty
purchase.key.parent().get()
为了查询给定客户的所有购买:
Purchase.query(ancestor=customer_entity.key).fetch()
实体的实际结构没有太大变化,主要是语法。但是祖先查询是完全一致的。
不推荐您描述的第三个选项。我只是为了完整性而将其包括在内。这有点令人困惑,并且会像这样:
class Purchase(ndb.Model):
# purchase data fields
price = ndb.IntegerProperty()
class Customer(ndb.Model):
purchases = ndb.StructuredProperty(Purchase, repeated=True)
这是使用 ndb.StructuredProperty 的特殊情况。在这种情况下,您将在数据存储区中只有一个客户实体。虽然有一个购买类,但您的购买不会作为单独的实体存储 - 它们只会作为数据存储在 Customer 实体中。
这样做可能有几个原因。您只处理一个实体,因此您的数据获取将是完全一致的。当您必须更新大量购买时,您还可以降低写入成本,因为您只编写一个实体。您仍然可以查询 Purchase 类的属性。但是,这是为仅具有有限数量或重复对象而设计的,而不是数百或数千个对象。每个实体的总大小限制为 1MB,因此您最终会达到该大小,并且您将无法添加更多购买。