【问题标题】:Google App Engine Assigning Entity Groups / Parent Keys, Unique constraintGoogle App Engine 分配实体组/父键,唯一约束
【发布时间】:2012-10-30 15:17:19
【问题描述】:

我有一种“客户”。当即将插入新的“客户”时,我想运行一个锁定整个 Kind 的事务。事务将首先查询以检查新的“客户”名称是否不存在,然后事务的第二部分在未找到匹配项时运行插入。通过这种方式,我强制执行唯一约束(并将操作限制为每秒大约 1 次插入)。

让我的所有“客户”实体在同一个实体组中的不令人满意的解决方案是创建一个名为“EntityGroups”的种类,其中包含一个名为“CustomersGroup”的记录。这条记录每次都用作新创建的“客户”实体的父级,从而将整个 Kind 分组到一个实体组中。

我的问题是:我担心使用诸如“CustomerGroup”之类的虚拟记录,因为如果发生任何事情并且丢失或删除,我无法将任何新的“客户”实体分配给同一组!我想最好为每个“客户”实体的父级分配一个静态的任意父级,例如“1111111”?我认为术语是“虚拟根实体”,我该怎么做?

请就如何最好地处理这个问题提供任何建议!

【问题讨论】:

    标签: google-app-engine unique-constraint entity-groups


    【解决方案1】:

    为什么不使用:NDB 的 get_or_insert:以事务方式检索现有实体或创建新实体。

    https://developers.google.com/appengine/docs/python/ndb/modelclass#Model_get_or_insert

    【讨论】:

    • 感谢沃斯考萨。假设我有一个名为 ACME 的客户。我查询他,没有匹配。我开始为 ACME 执行 put 操作,就像另一个用户开始将他们的 ACME 条目放入数据库一样。当业务逻辑规定不允许重复时,我们最终在数据存储中得到 2 个 ACME。我不能将 ACME 存储为 KEY_NAME(其中 get_or_insert 将是解决方案,并且保证唯一性),因为业务逻辑规定公司名称字段可能会随着时间而改变!
    • 然后为公司分配一个唯一 ID,并将其用作数据存储区中的 ID。这永远不会改变,即使公司名称改变了。那么只需一个 get_by_id 就可以得到当前的公司名称。正如答案所示,使用 get_or_insert 正是在这里要做的事情!去做吧! :)
    【解决方案2】:

    您的CustomerGroup 记录不需要存在就可以作为父母。只需手动创建它的密钥并将其作为父项分配给相关记录。

    如果它不存在,你不必担心它会被删除!

    当您创建一个模型并将另一个模型设置为其父模型时,系统不会检查(也不需要)该模型是否确实存在。

    例如:

    rev_key = ndb.Key('CustomerGroup', '11111', 'Customer', 'New_Customer_Name')
    

    然而,具有('CustomerGroup', '11111') 键的模型实际上并不存在,但它仍然可以在祖先链中。

    【讨论】:

    • 太棒了保罗!这正是我想要的!
    【解决方案3】:

    GrantsV,您可以通过为每个唯一约束创建一个代理实体并使用跨组事务以正常写入提交约束来实现这一点。

    class UniqueConstraint(db.Model):
      # Consider adding a reference to the owner of the constraint.
      @db.transactional(propagation=db.MANDATORY, xg=True)
      @classmethod
      def reserve(cls, kind, property, value):
        key = cls.__get_key(kind, property, value)
        if db.get(key):
          raise Exception  # Already exists
        cls(key=key).put()
    
      @db.transactional(propagation=db.MANDATORY, xg=True)
      @classmethod
      def release(cls, kind, property, value):
        db.delete(cls.__get_key(kind, property, value))
    
      @classmethod
      def __get_key(cls, kind, property, value):
        # Consider using a larger entity group.
        return db.Key.from_path(cls.kind(), '%s:%s:%s' % (kind, property, value))
        # To restrict to 1 insert per second per kind, use:
        # return db.Key.from_path(cls.kind(), kind, cls.kind(), '%s:%s' % (property, value))
    

    【讨论】:

    • @db.transactional 装饰器与@classmethod 一起使用时,@classmethod 行必须放在@db.transactional 上方。否则此代码在调用时会引发异常。
    【解决方案4】:

    您可以像这样创建父实体:

    class CustomerParent(ndb.Model):
        pass
    

    然后你实例化并存储你的父实体:

    customers_parent = CustomerParent()
    customers_parent.put()
    

    最后,当您创建所有客户实体时,请指定父实体:

    a_customer = Customer(parent=customers_parent.key, ...)
    a_customer.put()
    

    希望这会有所帮助!

    【讨论】:

    • 你不想走使用单亲的路线。这会极大地限制您的表现。
    • 感谢灭霸,这正是我正在做的。但我担心的是,如果我的主“CustomerParent”实体被删除,或者意外创建了另一个条目,该怎么办。然后我无法将新客户添加到原始实体组或将新客户添加到新实体组!
    • 感谢 Dragonx,但这个性能限制不是问题。什么是显示停止器是创建 2 个具有相同名称字段的实体。而且我不能使用 KEY_NAME 来强制执行唯一性,因为 Name 字段可能会及时更改。
    • 您确实知道主“CustomerParent”实体实际上并不一定存在,但您仍然可以创建将其作为父实体的模型,对吧?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-09-24
    • 2014-03-01
    • 2011-05-28
    • 1970-01-01
    相关资源
    最近更新 更多