如何使用SQLAlchemy实现复杂对象属性的深度映射?
问题
在SQLAlchemy中是否可以对类属性进行某种‘深度映射’?例如:obj.attr.nested_attr,希望告知obj的映射器如何将nested_attr直接映射到表中的nested_attr列。
环境版本:
- Python版本:3.10.7
- SQLAlchemy版本:1.4.41
现有领域模型:
from dataclasses import dataclass from datetime import datetime @dataclass class FullName: first_name: str last_name: str @dataclass class User: name: FullName date_of_birth: datetime # 其他属性...
对应的SQLAlchemy表模型:
import sqlalchemy metadata = sqlalchemy.MetaData() users = sqlalchemy.Table( "users", metadata, sqlalchemy.Column("first_name", sqlalchemy.String), sqlalchemy.Column("last_name", sqlalchemy.String), # 其他列... )
尝试过以下代码,但SQLAlchemy无法识别点符号的属性路径:
mapper_registry = sqlalchemy.orm.registry() mapper_registry.map_imperatively( User, users, properties={ "name.first_name": users.c.first_name, "name.last_name": users.c.last_name, }, )
请问有没有其他方法可以将user.name.first_name直接映射到users表的first_name列?
解决方案
方法一:使用混合属性同步嵌套属性
为User类添加与表字段对应的混合属性,在属性的get/set方法中自动同步到嵌套的FullName对象,既满足数据库映射需求,又保留领域模型的嵌套结构:
from sqlalchemy.ext.hybrid import hybrid_property from dataclasses import dataclass from datetime import datetime import sqlalchemy from sqlalchemy.orm import registry @dataclass class FullName: first_name: str last_name: str @dataclass class User: name: FullName date_of_birth: datetime @hybrid_property def first_name(self): return self.name.first_name @first_name.setter def first_name(self, value): self.name.first_name = value @hybrid_property def last_name(self): return self.name.last_name @last_name.setter def last_name(self, value): self.name.last_name = value # 表定义 metadata = sqlalchemy.MetaData() users = sqlalchemy.Table( "users", metadata, sqlalchemy.Column("first_name", sqlalchemy.String), sqlalchemy.Column("last_name", sqlalchemy.String), sqlalchemy.Column("date_of_birth", sqlalchemy.DateTime), ) # 映射配置 mapper_registry = registry() mapper_registry.map_imperatively( User, users, properties={ "first_name": users.c.first_name, "last_name": users.c.last_name, "date_of_birth": users.c.date_of_birth, }, )
方法二:动态添加属性访问逻辑
如果不想修改原User类定义,可以通过动态添加属性的方式,实现嵌套属性与数据库列的映射:
from sqlalchemy.orm import registry import sqlalchemy # 原领域模型和表定义保持不变 # ... # 映射配置 mapper_registry = registry() mapper_registry.map_imperatively( User, users, properties={ "first_name": users.c.first_name, "last_name": users.c.last_name, "date_of_birth": users.c.date_of_birth, }, ) # 为User类动态添加嵌套属性的访问逻辑 def _get_name_attr(self, attr_name): return getattr(self.name, attr_name) def _set_name_attr(self, attr_name, value): setattr(self.name, attr_name, value) User.first_name = property( lambda self: _get_name_attr(self, "first_name"), lambda self, v: _set_name_attr(self, "first_name", v) ) User.last_name = property( lambda self: _get_name_attr(self, "last_name"), lambda self, v: _set_name_attr(self, "last_name", v) )
方法三:使用复合类型(依赖数据库支持)
如果使用PostgreSQL这类支持复合类型的数据库,可以将FullName定义为SQLAlchemy复合类型,直接映射到对应列组合:
from sqlalchemy import CompositeType from sqlalchemy.orm import registry import sqlalchemy # 定义复合类型 full_name_type = CompositeType( "full_name", [ sqlalchemy.Column("first_name", sqlalchemy.String), sqlalchemy.Column("last_name", sqlalchemy.String), ] ) # 修改表定义,使用复合类型列 metadata = sqlalchemy.MetaData() users = sqlalchemy.Table( "users", metadata, sqlalchemy.Column("name", full_name_type), sqlalchemy.Column("date_of_birth", sqlalchemy.DateTime), ) # 直接映射User类的name属性 mapper_registry = registry() mapper_registry.map_imperatively( User, users, properties={ "name": users.c.name, "date_of_birth": users.c.date_of_birth, }, )
内容的提问来源于stack exchange,提问作者Maciej G
相关产品推荐
相关产品推荐

