批量更新/删除db.run_in_transaction中的不同类型的实体

时间:2010-05-04 09:14:21

标签: google-app-engine google-cloud-datastore

这里是单个事务中批量更新/删除不同种类实体的伪代码。请注意,Album和Song实体将AlbumGroup作为根实体。 (即具有相同的母体)

class Album:
  pass
class Song:
  album = db.ReferenceProperty(reference_class=Album,collection_name="songs")

def bulk_update_album_group(album):
  updated = [album]
  deleted = []
  for song in album.songs:
    if song.is_updated:
      updated.append(song)        
    if song.is_deleted:
      deleted.append(song)
  db.put(updated)
  db.delete(deleted)

a = Album.all().filter("...").get()

# bulk update/delete album.

db.run_in_transaction(bulk_update_album,a)

但是我在迭代的反向引用属性(如“album.songs”)中遇到了一个着名的“只有Ancestor Queries in Transactions”错误。我猜ancestor()过滤器没有帮助,因为这些实体在内存中被修改。

所以我修改这样的例子:在调用事务之前准备所有更新/删除的实体。

def bulk_update_album2(album):
  updated = [album]
  deleted = []
  for song in album.songs:
    if song.is_updated:
      updated.append(song)        
    if song.is_deleted:
      deleted.append(song)
  def txn(updated,deleted):
    db.put(updated)
    db.delete(deleted)
  db.run_in_transaction(txn,updated,deleted)

现在我发现迭代反向引用属性强制重新加载现有实体。因此,应该避免在修改后重新迭代反向引用属性!!

我要验证的是:

  

当需要批量更新/删除许多不同类型的实体时,   这种情况有什么好的编码模式吗?   我的上一个代码可以很好吗?


这里是完整的代码示例:

from google.appengine.ext import webapp
from google.appengine.ext.webapp import util

import logging

from google.appengine.ext import db

class Album(db.Model):
    name = db.StringProperty()
    def __repr__(self):
        return "%s%s"%(self.name,[song for song in self.songs])

class Song(db.Model):
    album = db.ReferenceProperty(reference_class=Album,collection_name='songs')
    name = db.StringProperty()
    playcount = db.IntegerProperty(default=0)
    def __repr__(self):
        return "%s(%d)"%(self.name,self.playcount)

def create_album(name):
    album = Album(name=name)
    album.put()
    for i in range(0,5):
        song = Song(parent=album, album=album, name='song#%d'%i)
        song.put()
    return album

def play_all_songs(album):
    logging.info(album)

    # play all songs
    for song in album.songs:
        song.playcount += 1
        logging.info(song)

    # play count also 0 here
    logging.info(album)

    def save_play_count(album):
        updated = []
        for song in album.songs:
            updated.append(song)
        db.put(updated)

    db.run_in_transaction(save_play_count,album)

def play_all_songs2(album):
    logging.info("loading : %s"%album)

    # play all songs
    updated = []
    for song in album.songs:
        song.playcount += 1
        updated.append(song)

    logging.info("updated: %s"%updated)
    db.put(updated)

    logging.info("after save: %s"%album)    

def play_all_songs3(album):
    logging.info("loading : %s"%album)

    # play all songs
    updated = []
    for song in album.songs:
        song.playcount += 1
        updated.append(song)

    # reload
    for song in album.songs:
        pass

    logging.info("updated: %s"%updated)
    def bulk_save_play_count(updated):
        db.put(updated)
    db.run_in_transaction(bulk_save_play_count,updated)

    logging.info("after save: %s"%album)

class MainHandler(webapp.RequestHandler):
    def get(self):
        self.response.out.write('Hello world!')

        album = Album.all().filter('name =','test').get()
        if not album:            
            album = db.run_in_transaction(create_album,'test')

        # BadRequestError: Only ancestor queries are allowed inside transactions.
        #play_all_songs(album)

        # ok
        #play_all_songs2(album)

        play_all_songs3(album)

def main():
    application = webapp.WSGIApplication([('/', MainHandler)],
                                         debug=True)
    util.run_wsgi_app(application)


if __name__ == '__main__':
    main()

1 个答案:

答案 0 :(得分:1)

请注意,ReferenceProperty不足以将实体放在同一组中。当您创建Song模型时,您应该使用模型的父级传递parent argument(例如,Album)。

看起来像这样:

album = Album.all().filter("...").get()
new_song = Song(name='Seven Nation Army', parent=album)
new_song.save()

请参阅documentation about ancestors