Sim*_*mon 5 python google-app-engine transactions data-integrity
我在Google App Engine中遇到一些麻烦,确保在使用没有键名的祖先关系时我的数据是正确的.
让我再解释一下:我有一个父实体类,我想创建一个子实体项.我想创建一个带有类别名称和项目名称的函数,如果它们不存在则创建它们.最初我创建了一个事务,如果需要使用密钥名称在事务中创建,这很好.但是,我意识到我不想使用名称作为密钥,因为它可能需要更改,我在我的事务中尝试执行此操作:
def add_item_txn(category_name, item_name):
category_query = db.GqlQuery("SELECT * FROM Category WHERE name=:category_name", category_name=category_name)
category = category_query.get()
if not category:
category = Category(name=category_name, count=0)
item_query = db.GqlQuery("SELECT * FROM Item WHERE name=:name AND ANCESTOR IS :category", name=item_name, category=category)
item_results = item_query.fetch(1)
if len(item_results) == 0:
item = Item(parent=category, name=name)
db.run_in_transaction(add_item_txn, "foo", "bar")
Run Code Online (Sandbox Code Playgroud)
我试图运行它时发现的是App Engine拒绝这个,因为它不会让你在事务中运行查询:Only ancestor queries are allowed inside transactions.
查看Google提供的示例,了解如何解决此问题:
def decrement(key, amount=1):
counter = db.get(key)
counter.count -= amount
if counter.count < 0: # don't let the counter go negative
raise db.Rollback()
db.put(counter)
q = db.GqlQuery("SELECT * FROM Counter WHERE name = :1", "foo")
counter = q.get()
db.run_in_transaction(decrement, counter.key(), amount=5)
Run Code Online (Sandbox Code Playgroud)
我试图在交易之前将我的类别提取移到:
def add_item_txn(category_key, item_name):
category = category_key.get()
item_query = db.GqlQuery("SELECT * FROM Item WHERE name=:name AND ANCESTOR IS :category", name=item_name, category=category)
item_results = item_query.fetch(1)
if len(item_results) == 0:
item = Item(parent=category, name=name)
category_query = db.GqlQuery("SELECT * FROM Category WHERE name=:category_name", category_name="foo")
category = category_query.get()
if not category:
category = Category(name=category_name, count=0)
db.run_in_transaction(add_item_txn, category.key(), "bar")
Run Code Online (Sandbox Code Playgroud)
这看起来很有效,但我发现当我运行这个请求时,我创建了重复的类别,这是有意义的,因为在事务之外查询类别,多个请求可以创建多个类别.
有谁知道如何正确创建这些类别?我尝试将类别创建放入事务中,但仅再次收到有关祖先查询的错误.
谢谢!
西蒙
这是解决您的问题的方法。从很多方面来说,这都不是一种理想的方法,我真诚地希望其他 AppEnginer 能够提出比我更简洁的解决方案。如果没有,请尝试一下。
我的方法采用以下策略:它创建充当类别实体别名的实体。类别的名称可以更改,但别名实体将保留其键,我们可以使用别名键的元素为您的类别实体创建键名,因此我们将能够通过名称查找类别,但是它的存储与其名称是分离的。
别名全部存储在单个实体组中,这允许我们使用事务友好的祖先查询,因此我们可以查找或创建 CategoryAlias,而不必冒创建多个副本的风险。
当我想要查找或创建类别和项目组合时,我可以使用类别的键名以编程方式在事务内生成密钥,并且允许我们通过事务内的密钥获取实体。
class CategoryAliasRoot(db.Model):
count = db.IntegerProperty()
# Not actually used in current code; just here to avoid having an empty
# model definition.
__singleton_keyname = "categoryaliasroot"
@classmethod
def get_instance(cls):
# get_or_insert is inherently transactional; no chance of
# getting two of these objects.
return cls.get_or_insert(cls.__singleton_keyname, count=0)
class CategoryAlias(db.Model):
alias = db.StringProperty()
@classmethod
def get_or_create(cls, category_alias):
alias_root = CategoryAliasRoot.get_instance()
def txn():
existing_alias = cls.all().ancestor(alias_root).filter('alias = ', category_alias).get()
if existing_alias is None:
existing_alias = CategoryAlias(parent=alias_root, alias=category_alias)
existing_alias.put()
return existing_alias
return db.run_in_transaction(txn)
def keyname_for_category(self):
return "category_" + self.key().id
def rename(self, new_name):
self.alias = new_name
self.put()
class Category(db.Model):
pass
class Item(db.Model):
name = db.StringProperty()
def get_or_create_item(category_name, item_name):
def txn(category_keyname):
category_key = Key.from_path('Category', category_keyname)
existing_category = db.get(category_key)
if existing_category is None:
existing_category = Category(key_name=category_keyname)
existing_category.put()
existing_item = Item.all().ancestor(existing_category).filter('name = ', item_name).get()
if existing_item is None:
existing_item = Item(parent=existing_category, name=item_name)
existing_item.put()
return existing_item
cat_alias = CategoryAlias.get_or_create(category_name)
return db.run_in_transaction(txn, cat_alias.keyname_for_category())
Run Code Online (Sandbox Code Playgroud)
买者自负:我还没有测试过这段代码。显然,您需要更改它以匹配您的实际模型,但我认为它使用的原理是合理的。
更新:西蒙,在你的评论中,你的想法大多是正确的;不过,有一个重要的微妙之处您不应错过。您会注意到类别实体不是虚拟根的子实体。它们不共享父实体,并且它们本身就是自己的实体组中的根实体。如果类别实体确实都具有相同的父级,那么这将形成一个巨大的实体组,并且您将面临性能噩梦,因为每个实体组一次只能运行一个事务。
相反,CategoryAlias 实体是虚假根实体的子实体。这允许我在事务内部进行查询,但实体组不会变得太大,因为属于每个类别的项目未附加到类别别名。
此外,CategoryAlias 实体中的数据可以更改,而无需更改实体的键,并且我使用 Alias 的键作为数据点来生成可用于创建实际类别实体本身的键名。因此,我可以更改存储在 CategoryAlias 中的名称,而不会失去将该实体与相同类别相匹配的能力。
| 归档时间: |
|
| 查看次数: |
453 次 |
| 最近记录: |