SQL炼金术:嵌套的复合列类型和自定义类型

时间:2018-08-15 18:29:29

标签: python orm sqlalchemy relational-database composite

我是SQLAlchemy的新手,对有关复合和自定义类型的某些方法的想法和建议有疑问。

挑战:我大量使用值对象,有些值对象具有由值对象组成的属性。即。

class Customer:
name = CustomerName(first_name, last_name)
country = Country(country_name, region)

其中客户是实体。 CustomerName是具有原始类型(first_name,last_name)的值对象,Country是由值对象(CountryName,Region)组成的值对象。

我希望所有值对象都保留在单个Customer表中。对于CustomerName之类的东西,我可以轻松使用复合类型:

class Customer(Base)

first_name = Column(String)
last_name = Column(String)
customer_name = composite(CustomerName, first_name, last_name)

容易

我发现您不能嵌套复合类型(在使用sqlalchemy-utils的Postgres中可能是这样,但这对我来说不是一个选择)。

我对所尝试的一些方法的反馈意见感兴趣,如果有人有其他想法或更好的方法...

选项1: 当需要嵌套值对象时,创建一个自定义类型。在许多情况下,一旦我找到嵌套对象,它们就会包装单个基元。

class CountryNameType(TypeDecorator):
    impl = String

    def process_bind_param(self, country_name_object, dialect):
        if country_name_object is not None:
            value = country_name_object.value
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            country_name_object = CountryName(value)
        return country_object

我什至在考虑创建泛型类型。可以将单个类型用于带有字符串,数字等的任何VO ...

SingleStringValueObjectType
SingleNumericValueObjectType

他们将具有将类作为arg的init方法:

__init__(self, class_of_value_object)
super...

然后传递要表示的Type类。然后可以在两个处理方法中使用它来动态检查它是否是正确的类类型,并从传入的类中创建新对象。您甚至可以将通用类型(字符串,数字)作为arg传入,并使用load_dialect_impl()为任何单个原始值对象创建单个通用类型...

然后

class Customer(Base)

first_name = Column(String)
last_name = Column(String)
country_name = Column(CountryNameType)
region = Column(RegionType)

country_name = Column(SingleStringValueObjectType(CountryName))
region = Column(SingleStringValueObjectType(Region))

使用

customer_name = composite(CustomerName, first_name, last_name)
country = composite(Country, country_name, region)

选项2:

在类中使用@hybrid_property映射值对象。

即。

@hybrid_property
def country(self):
    country_name = CountryName(self.db_country_name)
    region = Region(self.db_country_region)
    return Country(country_name, region)

@country.setter
def country(self, country):
    self.db_country_name = country.name.value
    self.db_country_region = country.region.value
    self._country = country

当db_country_name和db_country_region被定义为表中的列时。

分类客户(基础)

db_country_name = Column(String)
db_country_region = Column(String)
...

但只能通过hybrid属性设置和检索。

自定义类型感觉更干净。但是,如果有人对任何其他(更好的)解决方案有任何经验,我会感兴趣吗?

1 个答案:

答案 0 :(得分:1)

Michael Bayer在Bitbucket的一个线程上与我分享了一些非常有用的代码。

他直接将自定义比较器与组合使用,以说明如何实现2级组合/比较器。

您可以在此处查看讨论和代码:

https://bitbucket.org/zzzeek/sqlalchemy/issues/4168/nested-composite-column-types

对于简单的值对象,我创建了一个自定义类型,该自定义类型似乎也可以正常工作:

class UnaryValueObjectType(TypeDecorator):
    impl = Numeric

    def __init__(self, class_of_value_object, type):
        self.class_of_value_object = class_of_value_object
        self.type = type
        super(UnaryValueObjectType, self).__init__()

    def load_dialect_impl(self, dialect):
        return dialect.type_descriptor(self.type)

    def process_bind_param(self, value_object, dialect):
        if isinstance(value_object, self.class_of_value_object) and value_object is not None:
            value = value_object.value
        return value

    def process_result_value(self, value, dialect):
        if value is not None:
            value_object = self.class_of_value_object(value)
        return value_object

然后

Column("country_name", UnaryValueObjectType(CountryName, String))

希望这两种方法都对某人有用。它们都符合我的用例(一个简短,快速的解决方案,另一个更强大但更长的解决方案)。