views:

113

answers:

1

Here goes pseudo code of bulk update/delete entities of different kind in single transaction. Note that Album and Song entities have AlbumGroup as root entity. (i.e. has same parent entity)

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)

But I met a famous "Only Ancestor Queries in Transactions" error at the iterating back-reference properties like "album.songs". I guess ancestor() filter does not help because those entities are modified in memory.

So I modify example like this: prepare all updated/deleted entities before calling transaction.

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)

Now I found that iterating back-reference property force reload existing entities. So re-iterating back-reference property after modifying should be avoided!!

All I want to verify is:

When need to bulk update/delete many entities of different kind, is there any good coding pattern for this situation? my last code can be good one?


Here goes full code example:

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  A: 

Please note the the ReferenceProperty is not enough to put the entities in the same group. When you create a Song model you should pass a parent argument with the model's parent (e.g., the Album).

It looks like this:

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

See the documentation about ancestors.

jbochi
Thanks for your kindness. But I already pass parent argument to newly created song entity. What I want to verify is "iterating reference property in transaction function is forbidden?"
Ray Yun