SQLAlchemy多父类关联同一Pricing子表的映射配置问题
实现方案
直接用单张通用关联中间表+可复用Mixin配置实现多态一对多关系即可,完全满足所有约束条件:
- Pricing表结构完全符合要求:仅保留
id、PricingDate、PriceValue三个业务字段,不存储任何关联的产品类型信息 - 无冗余表:仅新增1张公共关联表,后续不管新增多少水果类、哪怕动态生成上百个产品实体,都不需要额外建表、不需要修改Pricing表结构
- 无多余外键:不需要在Pricing表中为每个产品类型加外键列
- 自动处理级联逻辑:新增、删除、查询价格的逻辑全部由SQLAlchemy接管,产品类只要继承指定Mixin就能获得价格关联能力,和C#里实现
IPricing接口的效果完全一致
第一步:定义基础模型和Pricing表
Pricing表完全按照你给出的结构编写,没有额外字段:
from datetime import date from sqlalchemy import Column, Integer, String, Date, Float, ForeignKey, Table, and_, create_engine from sqlalchemy.orm import relationship, declarative_base, declared_attr, sessionmaker Base = declarative_base() class Pricing(Base): __tablename__ = 'Pricing' id = Column(Integer, primary_key=True) PricingDate = Column(Date, nullable=False) PriceValue = Column(Float, nullable=False)
第二步:定义通用关联中间表
这张表唯一作用是存储产品和价格的映射关系,所有产品的价格关联都存在这一张表里,后续不需要修改:
pricing_relation = Table( 'pricing_relation', Base.metadata, Column('pricing_id', Integer, ForeignKey('Pricing.id', ondelete='CASCADE'), primary_key=True), Column('owner_table', String(50), nullable=False, primary_key=True), Column('owner_id', Integer, nullable=False, primary_key=True) )
第三步:编写可复用的价格关联Mixin
这个Mixin就相当于你C#里的IPricing接口,所有需要关联价格的产品类只要继承它,就会自动获得prices属性和对应的关联配置,不需要重复写relationship代码:
class HasPricingMixin: @declared_attr def prices(cls): return relationship( "Pricing", secondary=pricing_relation, # 自动关联当前产品表对应的数据 primaryjoin=lambda: and_( cls.id == pricing_relation.c.owner_id, pricing_relation.c.owner_table == cls.__tablename__ ), secondaryjoin=lambda: Pricing.id == pricing_relation.c.pricing_id, # 级联配置:产品删除时自动清理关联的价格和映射记录 cascade="all, delete-orphan", lazy="select" )
第四步:定义产品实体
你现有的水果类只要继承HasPricingMixin即可,原有业务字段不需要改动:
class Apple(HasPricingMixin, Base): __tablename__ = 'Apple' id = Column(Integer, primary_key=True) name = Column(String) class Banana(HasPricingMixin, Base): __tablename__ = 'Banana' id = Column(Integer, primary_key=True) name = Column(String) class Pear(HasPricingMixin, Base): __tablename__ = 'Pear' id = Column(Integer, primary_key=True) name = Column(String)
如果需要动态生成产品类,直接用type创建即可,不需要改任何关联逻辑:
# 动态生成橙子类示例 Orange = type( 'Orange', (HasPricingMixin, Base), { '__tablename__': 'Orange', 'id': Column(Integer, primary_key=True), 'name': Column(String) } )
使用效果
完全匹配你预期的查询和操作逻辑,不需要额外处理关联关系:
# 初始化数据库连接 engine = create_engine('你的数据库连接字符串') Base.metadata.create_all(engine) Session = sessionmaker(bind=engine) session = Session() # 新增数据 apple1 = Apple(name='first_apple') apple1.prices.append(Pricing(PricingDate=date(2019, 6, 1), PriceValue=2.00)) session.add(apple1) apple2 = Apple(name='second_apple') apple2.prices.append(Pricing(PricingDate=date(2018, 12, 12), PriceValue=3.45)) session.add(apple2) session.commit() # 查询数据,返回结构和你给出的示例完全一致 apples = session.query(Apple).all() apple_records = [ { 'id': a.id, 'name': a.name, 'prices': [ { 'id': p.id, 'PricingDate': p.PricingDate.isoformat(), 'PriceValue': p.PriceValue } for p in a.prices ] } for a in apples ]
配置说明
- 中间表用联合主键,避免同一个价格重复关联到同一个产品
- 默认配置了
delete-orphan级联:删除产品时,会自动删除关联表中的映射记录,以及该产品专属的价格记录;如果需要复用价格,把cascade中的delete-orphan移除即可,删除产品时只会清理映射关系,不会删除Pricing表中的数据 - 因为你不需要从Pricing反向查询所属产品,所以没有配置反向关系,没有多余性能损耗,查询效率和普通一对多关联基本一致
内容的提问来源于stack exchange,提问作者CozyAzure
相关产品推荐
相关产品推荐

