SQL Alchemy:嵌套复合列类型和自定义类型

SQL Alchemy: Nested Composite Column Types and Custom Types

我对 SQLAlchemy 比较陌生,并且有一个关于复合和自定义类型的一些方法的想法和建议的问题。

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

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

其中 Customer 是一个实体。 CustomerName 是一个原始类型的值对象(first_name, last_name),Country 是由值对象(CountryName, Region) 组成的值对象。

我希望值对象全部保留在单个客户中 table。对于像 CustomerName 这样的东西,我可以很容易地使用复合类型:

class Customer(Base)

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

简单。

我发现你不能嵌套复合类型(这在 Postgres 中使用 sqlalchemy-utils 是可能的,但我不适合)。

我对我正在尝试的一些方法的反馈很感兴趣,如果有人有任何其他想法或更好的方法...

选项 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...:[=​​21=]

SingleStringValueObjectType
SingleNumericValueObjectType

他们将有一个将 class 作为参数的 init 方法:

__init__(self, class_of_value_object)
super...

然后传入所表示的Type的class。然后可以在两个过程方法中使用它来动态检查它是否是正确的 class 类型并根据传入的 class 创建新对象。您甚至可以将泛型类型(String、Numeric)作为 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:

在class中使用@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 定义为 table 中的列时。

class 客户(基础)

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

但只能通过混合 属性.

设置和检索

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

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))

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