bulk update/delete entities of different kind in db.run_in_transaction <p>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)</p> <pre><code>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) </code></pre> <p>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.</p> <p>So I modify example like this: prepare all updated/deleted entities before calling transaction.</p> <pre><code>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) </code></pre> <hr> <p>Now I found that iterating back-reference property force reload existing entities. So re-iterating back-reference property after modifying should be avoided!!</p> <p>All I want to verify is:</p> <blockquote> <p>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?</p> </blockquote> <hr> <p>Here goes full code example:</p> <pre><code>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() </code></pre>