我正在使用F#对应用程序进行建模,并且在尝试为以下递归类型构建数据库表时遇到了困难:
type Base =
| Concrete1 of Concrete1
| Concrete2 of Concrete2
and Concrete1 = {
Id : string
Name : string }
and Concrete2 = {
Id : string
Name : string
BaseReference : Base }
我目前得到的解决方案(我在http://www.sqlteam.com/article/implementing-table-inheritance-in-sql-server找到灵感)是:
我对此解决方案有两个顾虑:
match
F#关键字相同的内容。我是否担心这些担忧?或者,有没有更好的方法来在SQL表中建模这种递归F#类型?
第1部分:在关系表中编码代数数据类型
我曾多次与这件事作斗争。我终于发现了关系表中代数数据类型建模的关键:Check constraints。
使用检查约束,您可以为多态类型的所有成员使用公用表,但仍然强制执行每个成员的不变量。
请考虑以下SQL架构:
CREATE TABLE ConcreteType (
Id TINYINT NOT NULL PRIMARY KEY,
Type VARCHAR(10) NOT NULL
)
INSERT ConcreteType
VALUES
(1,'Concrete1'),
(2,'Concrete2')
CREATE TABLE Base (
Id INT NOT NULL PRIMARY KEY,
Name VARCHAR(100) NOT NULL,
ConcreteTypeId TINYINT NOT NULL,
BaseReferenceId INT NULL)
GO
ALTER TABLE Base
ADD CONSTRAINT FK_Base_ConcreteType
FOREIGN KEY(ConcreteTypeId)
REFERENCES ConcreteType(Id)
ALTER TABLE Base
ADD CONSTRAINT FK_Base_BaseReference
FOREIGN KEY(BaseReferenceId)
REFERENCES Base(Id)
简单吧?
我们通过消除该表,解决了在表中表示无意义数据的问题#1。我们还组合了用于独立建模每种具体类型的表,而是选择在同一个表中存储所有Base
实例 - 无论其具体类型如何。
原样,此架构不会限制Base
类型的多态性。原样,可以插入ConcreteType1
行与非null BaseReferenceId
或ConcereteType2
行与null BaseReferenceId
。没有什么可以阻止您插入无效数据,因此您需要非常勤奋地进行插入和编辑。
这是检查约束真正发挥作用的地方。
ALTER TABLE Base
ADD CONSTRAINT Base_Enforce_SumType_Properties
CHECK
(
(ConcreteTypeId = 1 AND BaseReferenceId IS NULL)
OR
(ConcreteTypeId = 2 AND BaseReferenceId IS NOT NULL)
)
检查约束Base_Enforce_SumType_Properties
定义每种具体类型的不变量,保护插入和更新时的数据。继续运行所有DDL,在您自己的数据库中创建ConcreteType
和Base
表。然后尝试在Base
中插入行,这违反了检查约束中描述的规则。你不能!最后,您的数据模型保持在一起。
解决问题#2:既然您的类型的所有成员都在一个表中(强制执行不变量),您的查询将更简单。你甚至不需要“相当于SQL中的match
F#关键字”。添加新的具体类型就像在ConcreteType
表中插入新行一样简单,在Base
表中添加任何新属性作为列,并修改约束以反映任何新的不变量。
第2部分:在SQL Server中编码分层(读取:递归)关系
关注的一部分#2我想到了查询ConcreteType2
和Base
之间存在的“亲子关系”的复杂性。有很多方法可以处理这种查询并选择一种,我们需要考虑一个特定的用例。
示例用例:我们希望查询每个Base
实例并组装包含每一行的对象图。这很简单;我们甚至不需要加入。我们只需要一个可变的Dictionary<int,Base>
,其中Id
用作关键。
进入这里会有很多东西,但需要考虑的事项:有一个名为HierarchyID
(docs)的MSSQL数据类型,它实现了“物化路径”模式,允许像你这样的层次结构更容易建模。您可以尝试在HierarchyID
/ INT
列上使用Base.ID
而不是Base.BaseReferenceID
。
我希望这有帮助。