Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

sqlalchemy add child in one-to-many relationship

Tags:

This is the first time I've used ORM, so I'm not sure the best way to handle this. I have a one-to-many relationship where each Parent can have many Children:

class Parent(Base):     __tablename__ = 'Parent'      name = Column(String(50))     gid = Column(String(16), primary_key = True)     lastUpdate = Column(DateTime)      def __init__(self,name, gid):         self.name = name         self.gid = gid         self.lastUpdate = datetime.datetime.now()   class Child(Base):     __tablename__ = 'Child'      id = Column(Integer, primary_key = True)     loc = Column(String(50))     status = Column(String(50))      parent_gid = Column(String(16), ForeignKey('Parent.gid'))      parent = relationship("Parent", backref=backref('children')) 

Now, updates are coming in over the network. When an update comes in, I want to UPDATE the appropriate Parent row (updating lastUpdate column) and INSERT new children rows into the database. I don't know how to do that with ORM. Here is my failed attempt:

engine = create_engine('sqlite+pysqlite:///file.db',                        module=dbapi2) Base.metadata.create_all(engine) session = sessionmaker(bind=engine)()  def addChildren(parent):     p = session.query(Parent).filter(Parent.gid == p1.gid).all()     if len(p) == 0:         session.add(p1)         session.commit()     else:         updateChildren = parent.children[:]         parent.chlidren = []         for c in updateChildren:             c.parent_gid = parent.gid          session.add_all(updateChildren)         session.commit()  if __name__ == '__main__':      #first update from the 'network'     p1 = Parent(name='team1', gid='t1')     p1.children = [Child(loc='x', status='a'), Child(loc='y', status='b')]     addChildren(p1)      import time     time.sleep(1)      #here comes another network update     p1 = Parent(name='team1', gid='t1')     p1.children = [Child(loc='z', status='a'), Child(loc='k', status='b')]     #this fails     addChildren(p1) 

I initially tried to do a merge, but that caused the old children to be disassociated with the parent (the foreign IDs were set to null). What is the best way to approach this with ORM? Thanks

EDIT

I guess it doesn't really make sense to create entirely new objects when updates come in over the network. I should just query the session for the appropriate parent, then create new children if necessary and merge? E.g.

def addChildren(pname, pid, cloc, cstat):     p = session.query(Parent).filter(Parent.gid == pid).all()     if len(p) == 0:         p = Parent(pname, pid)         p.children = [Child(loc=cloc, status=cstat)]         session.add(p)         session.commit()     else:         p = p[0]         p.children.append(Child(loc=cloc, status=cstat))         session.merge(p)         session.commit() 
like image 211
Neal Avatar asked Jan 12 '12 17:01

Neal


People also ask

How do you create a one to many relationship in Flask-SQLAlchemy?

The comments class attribute defines a One-to-Many relationship between the Post model and the Comment model. You use the db. relationship() method, passing it the name of the comments model ( Comment in this case). You use the backref parameter to add a back reference that behaves like a column to the Comment model.

How do I create a many to many relationship in SQLAlchemy?

Many to Many relationship between two tables is achieved by adding an association table such that it has two foreign keys - one from each table's primary key.

What is Backref in SQLAlchemy?

In Flask-SQLAlchemy, the backref parameter in relationship method allows you to declare a new property under a specified class as seen in the example in their docs: class Person(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(50)) addresses = db.relationship('Address', backref='person ...

What does First () do in SQLAlchemy?

first() applies a limit of one within the generated SQL, so that only one primary entity row is generated on the server side (note this may consist of multiple result rows if join-loaded collections are present). Calling Query. first() results in an execution of the underlying query.


1 Answers

You are right - you should not create the same parent twice. In terms of adding children, ... well, you really need only to add them and you do not care about the existing ones... So your edited code should do the job just fine. You can make it shorter and more readable though:

def addChildren(pname, pid, cloc, cstat):     p = session.query(Parent).get(pid) # will give you either Parent or None     if not(p):         p = Parent(pname, pid)         session.add(p)     p.children.append(Child(loc=cloc, status=cstat))     session.commit() 

The disadvantage of this way is that for existing Parent the whole collection of Children will be loaded into memory before a new Child is added and later saved to the database. If this is the case (many and increasing number of children for each parent), then the lazy='noload' might be useful:

parent = relationship("Parent", backref=backref('children', lazy='noload')) 

This might dramatically improve the speed of inserts, but in this case the access to p.children will never load the existing objects from the database. In such scenarios it is enough to define another relationship. In these situations I prefer to use Building Query-Enabled Properties, so you end up with one property only for adding objects, and the other only for quering persisted results, which often are used by different parts of the system.

like image 56
van Avatar answered Oct 07 '22 03:10

van