如何在Google App Engine中为模型定义唯一属性?

32 投票
2 回答
9857 浏览
提问于 2025-04-15 13:10

我需要一些属性是唯一的。该怎么做呢?

有没有类似于 unique=True 这样的设置?

我在使用 Google App Engine 和 Python。

2 个回答

25

谷歌提供了一个功能来实现这个目的:

http://code.google.com/appengine/docs/python/datastore/modelclass.html#Model_get_or_insert

Model.get_or_insert(key_name, **kwds)

这个功能会尝试根据给定的键名获取模型类型的实体。如果这个实体存在,get_or_insert() 就会直接返回它。如果不存在,就会创建一个新的实体,这个实体会根据给定的类型、名称和其他参数被存储并返回。

获取和后续可能的存储操作会被包裹在一个事务中,以确保操作的原子性。这意味着,get_or_insert() 不会覆盖已经存在的实体,只有在没有同类型和同名称的实体时,才会插入一个新的实体。

换句话说,get_or_insert() 的功能和下面这段 Python 代码是一样的:

def txn():
  entity = MyModel.get_by_key_name(key_name, parent=kwds.get('parent'))
  if entity is None:
    entity = MyModel(key_name=key_name, **kwds)
    entity.put()
  return entity
return db.run_in_transaction(txn)

参数:

key_name 实体的键名 **kwds 如果指定的键名的实例不存在,传递给模型类构造函数的关键字参数。如果想要的实体有父实体,parent 参数是必需的。

注意:get_or_insert() 不接受 RPC 对象。

这个方法会返回一个模型类的实例,表示请求的实体,无论它是已经存在的还是通过这个方法创建的。和所有数据存储操作一样,如果事务无法完成,这个方法可能会引发 TransactionFailedError 错误。

21

这里没有内置的方法来确保一个值是唯一的。不过,你可以这样做:

query = MyModel.all(keys_only=True).filter('unique_property', value_to_be_used)
entity = query.get()
if entity:
    raise Exception('unique_property must have a unique value!')

我使用 keys_only=True 是因为这样可以稍微提高性能,不用获取实体的数据。

更有效的方法是使用一个没有字段的单独模型,它的键名由属性名加上属性值组成。这样你就可以用 get_by_key_name 来获取一个或多个这样的复合键名。如果你得到一个或多个不是 None 的值,那就说明有重复的值(通过检查哪些值不是 None,你就能知道哪些值不是唯一的)。


正如 onebyone 在评论中提到的,这些方法由于先获取后存储的特性,可能会出现并发问题。理论上,在检查现有值之后,可能会创建一个实体,然后检查后的代码仍然会执行,这样就会导致重复值。为了防止这种情况,你需要使用事务:事务 - Google App Engine


如果你想在所有实体中检查唯一性,并且使用事务,你需要把它们放在同一个组里,这样做效率会非常低下。对于事务,使用第二种方法像这样:

class UniqueConstraint(db.Model):
    @classmethod
    def check(cls, model, **values):
        # Create a pseudo-key for use as an entity group.
        parent = db.Key.from_path(model.kind(), 'unique-values')

        # Build a list of key names to test.
        key_names = []
        for key in values:
            key_names.append('%s:%s' % (key, values[key]))

        def txn():
            result = cls.get_by_key_name(key_names, parent)
            for test in result:
                if test: return False
            for key_name in key_names:
                uc = cls(key_name=key_name, parent=parent)
                uc.put()
            return True

        return db.run_in_transaction(txn)

UniqueConstraint.check(...) 会假设每一个键/值对都必须是唯一的,才能返回成功。这个事务会为每种模型类型使用一个单独的实体组。这样,事务可以同时可靠地处理多个不同的字段(如果只有一个字段,这样做会简单得多)。而且,即使你在一个或多个模型中有相同名称的字段,它们也不会互相冲突。

撰写回答