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()