问题
I'm mapping a database using SQLAlchemy that have multiple cases of "false" many-to-many relationships. What I mean by this is, suppose I have the following objects:
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
addresses = relationship('Address', secondary='user_address')
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
users = relationship('User', secondary='user_address')
class UserAddressLink(Base):
__tablename__ = 'user_address'
id = Column(Integer, primary_key=True)
user_id = Column(Integer, ForeignKey('user.id'))
address_id = Column(Integer, ForeignKey('address.id'))
So, a simple many-to-many relationship, right? But there's one catch: it was never intended to be many-to-many. This is actually a one-to-one relationship that someone decided to design like this in the database for whatever reason. There's only one Address
per User
and vice-versa. I have no control over database design (in fact, I'm only reading from this database and never writing on it) so I can't change this.
Is there a standard way of dealing with this on SQLAlchemy? It automatically assumes that this is a many-to-many relationships and treat the User.adresses and Address.users as lists.
The way I'm dealing with it is creating properties:
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
_addresses = relatioship('Address', secondary='user_address')
@property
def address(self):
return self.addresses[0] if len(self.addresses) > 0 else None
@address.setter
def address(self, value):
self.addresses = [value]
And so on.
Is this the best way to deal with this or is there any other workaround?
回答1:
There is a very straightforward way to define such a relationship by using uselist = False
as done in One-to-One relationship definition on both side of the relationship:
class User(Base):
__tablename__ = 'user'
id = Column(Integer, primary_key=True)
# other columns
name = Column(String)
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key=True)
# other columns
name = Column(String)
# relationship(
user = relationship(
User,
secondary='user_address',
uselist=False,
backref=backref('address', uselist=False),
)
user_address = Table(
'user_address', Base.metadata,
Column('id', Integer, primary_key=True),
Column('use_id', Integer, ForeignKey('user.id')),
Column('address_id', Integer, ForeignKey('address.id')),
)
Then you can use the code as you desire to:
# add some data
u1 = User(name='JJ', address=Address(name='superstreet'))
a2 = Address(name='LA')
a2.user = User(name='John')
session.add(u1)
session.add(a2)
session.commit()
session.expunge_all()
# get users and preload addresses as well in one query
q = session.query(User).options(joinedload(User.address))
for u in q.all():
print(u)
print(" {}".format(u.address))
Few more notes on your code:
- you should not define the relationship on both sides, just use
backref
for this - you should not define the whole mapped class for the
user_address
table, table definition as above is
来源:https://stackoverflow.com/questions/27449462/how-to-deal-with-false-many-to-many-relationships-in-sqlalchemy