SQLAlchemy ленивое декларативное наследование
У нас есть несколько таблиц с декларативным наследованием между ними, и мы пытались сделать наследование "ленивым", что означает, что мы не хотим охотно присоединяться к родительской таблице.
Например:
class Entity(Base):
__tablename__ = 'entities'
id = Column(Integer, primary_key=True)
entity_type = Column(String, nullable=False)
__mapper_args__ = {'polymorphic_on': entity_type, 'polymorphic_identity': 'entities'}
class Person(Entity):
__tablename__ = 'persons'
person_id = Column(None, ForeignKey('entities.id'), primary_key=True)
name = Column(String, nullable=False)
__mapper_args__ = {'polymorphic_identity': 'persons'}
При запросе Person sqlalchemy всегда присоединяется к Entity. Я хотел бы, чтобы это было как-то лениво, и я не мог найти способ сделать это.
Тем не менее, при запросе сущности я все же хотел бы иметь возможность получать объекты Person, если эта сущность является Person. Вот почему мы не можем просто заставить Person иметь отношения с Entity или использовать Mixin, у которого он есть.
1 ответ
Сущность Person здесь является объединением Entity->Person. Если вы хотите просто запросить таблицу "entity" для начала, то запросите объекты Entity. Если вы хотите, чтобы объекты Entity были только типа "Person", то примените фильтр к дискриминатору:
persons = query(Entity).filter_by(entity_type='persons').all()
Вышеприведенное вернет объекты Person без использования соединения, и при доступе к атрибутам Person таблица "Person" будет выбираться для каждой строки отдельно.
Редактировать: ОК, вы хотите это наоборот. Во-первых, вот как это возможно, во-вторых, моя рекомендация изменить ваше отображение.
Вы всегда можете получить строки от "person", выполнив запрос к таблице:
ptable = Person.__table__ rows = query(ptable).filter(ptable.c.foo == 'bar').all()
это возвращает вам кортежи, а не объекты Person.
Еще один способ, который может сработать, хотя я не пробовал и это было бы довольно необычно, это использовать неосновное сопоставление с Person:
from sqlalchemy.orm import mapper nperson = mapper(Person, Person.__table__, non_primary=True) people = query(nperson).filter(nperson.c.foo == 'bar').all()
Причина наследования в пользу таблицы Entity заключается в том, что в классовом наследовании Person называется специализацией Entity - каждый получаемый вами объект на самом деле является просто Entity, с некоторыми дополнительными качествами, которые делают его Person. ORM считает первичный ключ Person столбцом "entity.id". Это связано с тем, что пространство всех подклассов Entity охватывает много таблиц, но полный набор первичных ключей Entity остается именно такими значениями в "entity.id". ORM также должен видеть столбец "entity.entity_type" здесь, чтобы увидеть, каков тип объекта. Если к столу присоединились "лица", а у вас
class Employee(Person)
запрос только таблицы "персоны" не только не даст нам первичный ключ записи (оставляя в стороне столбец FK в персонах), но также не будет различать Person и Employee.Семантически запрос на использование может указывать на то, что наследование не подходит. Предполагая, что Entity имеет много подклассов - Person, Animal, Vehicle, и для каждого это таблица подклассов, которая на самом деле является "данными записи", тогда как столбцы, специфичные для Entity, на самом деле являются просто связанными значениями, которые предлагают вместо этого моделировать отношения как один к одному между человеком и сущностью. Это отображение даст вам именно то поведение при загрузке, которое вы хотите; запрос к Person будет только против "people", и при доступе к Person.entity будет выдан один оператор SQL для получения в соответствующем Entity.
Другая мысль состоит в том, что, хотя я не вижу здесь вашего полного отображения, если "сущность" на самом деле не содержит каких-либо значимых данных, кроме как служить "основой всех объектов", ИМХО, это антипаттерн, поскольку реляционная БД касается. Хорошо иметь базовые классы в Python, но у меня точно не будет соответствующей ей таблицы базы данных, это только усложнит запросы и обновления.