2017-10-11 163 views
4

我造型在F#的应用程序,我遇到试图建构以下递归式数据库表时有困难:F#的递归类型的SQL表

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

enter image description here

我有两个问题与此解决方案:

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

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

我是否担心这些问题太多?或者,有没有更好的方法来模拟SQL表中的这种递归F#类型?

+1

在我看来,你的类型Base大致相当于(Id,Name)元组的非空列表?是这样吗?还是你调整了这个例子来问这个问题? –

+1

@RobertNielsen:起初,我修改了这个例子以问一个问题,但现在我想我会像这样(一个(Id,Type)列表),我将创建另一个表来包含具体类型的公共信息,一个基础信息表,每一个具体的表将用一个外键引用。我这样做是因为否则基表将在行之间包含相同的信息。 – Mario

回答

0

1部分:编码Algrebraic数据类型在关系表

我挣扎着这事很多次。我终于发现了在关系表中建模代数数据类型的关键: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与非空BaseReferenceId或行ConcereteType2与空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,在您自己的数据库中创建ConcreteTypeBase表。然后尝试将行插入Base,这些行破坏了检查约束中描​​述的规则。你不能!最后,你的数据模型保持在一起。

为了解决问题#2:现在您的类型的所有成员都在单个表中(使用不变式实施),您的查询将会更简单。你甚至不需要“等同于SQL中的match F#关键字”。添加新的具体类型非常简单,只需在ConcreteType表中插入新行,将任何新属性添加为表Base中的列,然后修改约束以反映任何新的不变量。

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

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

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

这里有很多需要考虑的东西:有一个名为HierarchyIDdocs)的MSSQL数据类型,它实现了'物化路径'模式,可以更容易地建模类似于你的层次结构。您可以在Base.ID/Base.BaseReferenceID列中尝试使用HierarchyID而不是INT

我希望这会有所帮助。