【问题标题】:F# recursive types to SQL tablesF# 递归类型到 SQL 表
【发布时间】:2018-03-23 11:38:08
【问题描述】:

我正在使用 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)是:

我对这个解决方案有两个顾虑:

  1. 基表上会有行,即使这在我的模型中没有意义。但我可以忍受。

  2. 似乎要查找有关 Concrete2 的 BaseReference 的所有信息的查询会很复杂,因为我必须考虑类型的递归性和不同的具体表。此外,向模型添加新的具体类型必须修改这些查询。除非当然有与 SQL 中的match F# 关键字等效的关键字。

我是否过于担心这些问题?或者,有没有更好的方法在 SQL 表中对这种递归 F# 类型进行建模?

【问题讨论】:

  • 在我看来,您的类型 Base 大致相当于 (Id, Name) 元组的非空列表?是这样吗,还是你为了问它而删减了这个例子?
  • @RobertNielsen :起初我为了询问而修剪了这个例子,但现在我想我会这样离开((Id,Type)的列表),我将创建另一个表来包含具体类型的公共信息,每个具体表都将使用外键引用的 BasedInformation 表。我这样做是因为否则 Base 表将在行之间包含相同的信息。

标签: f# data-modeling modeling recursive-query recursive-type


【解决方案1】:

第 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 类型的多态性。照原样,可以插入带有非空BaseReferenceIdConcreteType1 行或带有空BaseReferenceIdConcereteType2 行。 没有什么可以阻止您插入无效数据,因此您需要非常认真地进行插入和编辑。

这是检查约束真正发挥作用的地方。

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 以在您自己的数据库中创建 ConcreteTypeBase 表。然后尝试将行插入到Base 中,这会破坏检查约束中描​​述的规则。你不能!最后,您的数据模型结合在一起。

解决问题 #2:现在您的类型的所有成员都在一个表中(强制执行不变量),您的查询会更简单。您甚至不需要“相当于 SQL 中的match F# 关键字”。添加一个新的具体类型就像在ConcreteType 表中插入一个新行,在Base 表中添加任何新属性作为列,并修改约束以反映任何新的不变量一样简单。

第 2 部分:在 SQL Server 中编码分层(阅读:递归)关系

部分关注#2 我考虑了查询ConcreteType2Base 之间存在的“父子”关系的复杂性。有很多方法可以处理这种查询并选择一种,我们需要记住一个特定的用例。

示例用例:我们希望查询每个Base 实例并组装包含每一行的对象图。这很容易;我们甚至不需要加入。我们只需要一个可变的Dictionary<int,Base> 并使用Id 作为键。

这里有很多内容,但需要考虑:有一个名为 HierarchyID (docs) 的 MSSQL 数据类型实现了“物化路径”模式,从而可以更轻松地对像您这样的层次结构进行建模。您可以尝试在 Base.ID/Base.BaseReferenceID 列上使用 HierarchyID 而不是 INT

我希望这会有所帮助。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-08
    • 2023-03-14
    相关资源
    最近更新 更多