65.9K
CodeProject 正在变化。 阅读更多。
Home

使用“M”语言进行 SQL Server 域建模的介绍

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.75/5 (6投票s)

2011年3月30日

CPOL

15分钟阅读

viewsIcon

35884

downloadIcon

231

使用 Oslo CTP 和“M”语言进行 SQL Server 域建模。

引言

在本文中,我将介绍使用“M”语言进行 SQL Server 域建模,并展示如何使用“M”语言建模来为 SQL Server(作为目标存储数据库)创建架构。我们将了解该语言的基本构造、T-SQL 生成,以及通过将 Entity Framework 连接到数据库中创建的模型,在 Visual Studio 中查看此模型。但是,我们不会关注任何用于将前端连接到 Entity Framework 的客户端代码;在这方面还有很多其他资源。

背景

2009 年,代号为“Oslo”的项目被重命名为 SQL Server Modeling。这是为了巩固域建模方法并逐步将其集成到 SQL Server 中而有意为之。Oslo 由三个核心部分组成:

  • “M 语言”:一种用于描述模型的建模语言,由名为 Intellipad 的工具驱动。
  • Quadrant:一个用于查看模型和关系数据的可视化工具。
  • Repository:一个基于 SQL Server 的存储介质,用于存储模型和数据。

“M”的初步理论很简单:开发人员和数据库设计人员倾向于使用文本来描述模型中不直接与应用程序或数据库存储相关的需求。此外,对于 C# 开发人员或 SQL 开发人员来说,使用文本语言构建模型比使用可视化建模工具更容易。而且,如果您是非开发人员,例如业务分析师,您可以使用“M”来描述您的需求,而不是尝试创建可能被误解的伪代码或图表。

摘要:“M”独立于解决方案应用程序代码和存储介质来描述您的模型。

领域特定语言:DSL

SQL Server Modeling 的另一个基础是 DSL(领域特定语言),您可以在其中创建特定的或自定义的“M”类型语言,您和您的业务用户可以就此达成一致。理论上,这听起来是个好主意;但我对其实际应用感到困惑,因为业务用户甚至无法理解最简单的设计概念,如果一种自定义语言不能被准确理解,那我就不知道什么语言可以了。

为什么要建模

我一直(而且我相信我并不孤单)在为设计从何入手而苦恼。历史上,我会采用 SSADM 类型的方法,后来在我的职业生涯中,采用了一些敏捷原型方法。多年来,我尝试了很多方法,以下只是一部分:

  • 完全建模:在纸上,使用 SSADM 类型方法,固定整个设计;然后开始开发,不做任何更改。
  • 数据库优先:通过数据库建模,并在数据库和应用程序逻辑之间创建一个数据应用程序层。
  • 代码优先:在 C# 中创建我的对象,通过代码模拟数据库,并在此基础上工作直到我对我的应用程序满意,然后创建数据库和相关的视图和查询等(或者最近让代码创建数据库)。
  • 原型:根据客户反馈持续更新和更改,持续更改代码和数据库以反映任何变化。我认为原型最适合小型项目,用于向业务用户传达概念。

通过建模,您可以创建模型并对其进行检查,而不会分心于前端应用程序或存储问题。假设我们使用“M”语言,Oslo 可以根据模型创建数据库。典型的开发路线可能如下:

第一阶段

  • 确定您的模型!
  • 在“M”中创建模型
  • 为标准 SQL Server 数据库生成数据库脚本
  • 使用 Entity Framework 为您的对象生成 2010 年的 C# 代码
  • 原型应用程序前端

第二阶段

  • 确定您更改后的模型
  • 在“M”中更改模型
  • 为标准 SQL Server 数据库生成数据库脚本
  • 使用 Entity Framework 为您的对象生成 2010 年的 C# 代码
  • 更改原型前端。

阶段 (3..n) 将按此方式进行,以此类推。

这为您提供了巨大的优势,因为您不必去更改数据库、基类对象和数据访问层代码,因为这些都可以从模型中自动生成,并且您可以在找到适合您应用程序的正确方案之前快速完成几次设计迭代。

“M”带来了什么?

“一种无需承诺代码或数据库架构即可轻松创建模型的方法,而后者也不会承诺给您。”

这似乎是一个微不足道的陈述,但如果您在生产前代码库中投入了大量工时,项目经理或开发人员将需要极大的勇气才能承认,完全重写比继续沿着错误的设计方向前进更好。这就是“技术债务”在许多系统中根深蒂固的原因,因为从长远来看,废弃设计的成本可能更低,但初始的命中价格对于项目交付日期或开发领导者或团队成员的个人成本来说都太高了。

通过生成数据库和基类分层进行建模可以在一定程度上缓冲这一点,方法是将此封装在应用程序之外。至少,它减少了开发时间,并允许项目进行更灵活的变更管理。

用初级开发人员的术语来说:更改我的模型,使用 ORM 重新生成数据库和基类对象,然后修复前端。给有心人的提示:确保为您的模型投入足够的时间,以减少以后出现的问题。

“M”简介

现在我们将开始研究“M”的技术。我将重点介绍 Intellipad 工具,使用“M”创建一个模型,然后我们将使用该模型创建一个 SQL Server 数据库,并从该数据库创建一个 Entity Framework 模型。

下载 Oslo CTP

使用下面的链接从 Microsoft 下载 CTP。

请注意,Microsoft 会更改文件位置,并且上述链接可能会在没有警告的情况下发生更改。安装 Oslo CTP 后,您可以启动 Intellipad,如下所示。切换到“M 模式”,如下所示。

这就是我们要做的

  • M 语言简介
  • 创建基于供应链的模型示例
  • 在数据库中创建模型
  • 检查数据库中的模型
  • 检查 Entity Framework 从数据库创建的模型

方法

如果您阅读了其他关于 M 的示例,您可能会看到数据被添加到 M 语言模块中,因为您可以在同一个文件中混合建模和数据。我个人认为这对于解释 M 语言的某些观点没有帮助或建设性。相反,我将专注于 M 语言建模对数据库的影响,以及令人惊讶的是它可以为您节省多少 T-SQL 和时间。

M 语言快速入门

“M”是一种简单的语言。“M”基于三个基本概念:值、类型和范围(extents)。“M”中定义了以下概念:

  • 值:符合“M”语言规则的数据。
  • 类型:描述一组值。
  • 范围(Extent):为值提供动态存储。

M 到 T-SQL 到 C# 的值类型

M 类型 T-SQL C#
逻辑 bit bool
Integer32 int int
Integer64 bigint long
文本 nvarchar 字符串
double float double
Decimal28 Decimal(28,6) decimal
日期时间 DateTime2 日期时间
时间 时间 时间
日期 date 日期
Guid uniqueidentifier Guid

在这里,我们将创建一个简单的 `Address` 范围。

首先,我们打开 Intellipad,将模式设置为“M 模式”,并创建一个模块,如下所示,用于简单的地址。如果您单击 **M 模式** - **T-SQL 预览**,将会显示一个右侧面板,其中显示您的模块将创建的 SQL。

看起来足够简单,但有几点需要注意。

架构

您注意到它在数据库中创建了一个名为“Anything”的新架构吗?如果您使用具有不同名称的多个模块并创建多个架构,将表分散在它们之间,而不是放在 SQL Server 通常的默认架构“dbo”中,这可能会有问题。事实上,我很少看到数据库使用其他名称,但我的大多数数据库开发通常由单个架构驱动。

将模块名称重命名为“dbo”可能会使 T-SQL 更容易处理,除非您习惯于在同一数据库中使用多个架构。

下面,我标记了每个元素如何转换为其等效的 T-SQL 操作:

  • M 模块 = 架构
  • 范围 (Extent) = 表
  • 字段 (Fields) = 字段

所以快速回顾一下,对 M 语言模块的每个操作都会直接影响您的数据库。

字段

以 `PostCode` 字段为例,它被映射如下:

除了问号表示该字段可以为空之外,它应该相当明显。

因此,删除字段定义中的问号将创建一个等效的 T-SQL,例如:

M 语言中的类型

M 语言中的类型可以被描述为一个构建块或一个抽象类,您可以使用它来创建进一步的块并进行重用。 `Address` 是一个很好的例子。如果您采用上述示例,并在 `Address` 范围前加上 type,您会注意到没有生成 T-SQL。

没有生成 T-SQL,因为类型不是在具有适当 T-SQL 成员的 Extent 中创建的,它必须被继承。

要使用类型或多种类型,您必须创建一个 Extent 对象并继承类型,如下所示:

您会注意到 `Customer` 范围由 `Address` 类型和 `Contact` 类型组成。您可以在任何数量的对象中重用类型。这在设计大型系统架构时非常有用。

自动编号和标识/主键

每个数据库表通常都有一个主键,通常是基于整数的自动编号。这是一个相当简单的初识键构造,而且使用频率很高。您可以基于文本或任何其他字段组合等创建主键。M 有一个 `AutoNumber()`,它会在整数字段上设置一个标识。所以这一行将是这样的:

id : Integer32 => AutoNumber();

结合表的 Identity 约束(这不一定是 `AutoNumber`),如下所示:

where identity id;

将所有这些结合起来,我们得到以下结果:

您还可以将标识放在类型中,并添加其他常用字段以创建可重用类型。下面的示例是常见类型的一个相当典型的示例:

type Common : 
{   id : 
    Integer32 => AutoNumber(); 
    CreatedBy : Text(20)?; 
    CreatedAt : DateTime?; 
    EditedBy : Text(20)?; 
    EditedAt : DateTime?; 
    DeletedBy : Text(20)?; 
    DeletedAt : DateTime?; 
} where identity id;

其他主键

使用字符串作为主键

type StringPK : 
{ id : Text(20) 
  CreatedBy : Text(20)?; 
} where identity id;

使用复合主键

type StringPK : { 
    Key1: Text(20); 
    Key2 : Text(20); 
} where identity (Key1, Key2);

约束

约束可以非常轻松地放置在您的模型中。下面的一个简单示例表明,库存值永远不能低于 0。

这可以通过在 stock 字段中添加“`where value >0`”来实现。

它为如此小的一段代码生成的 T-SQL 非常可观;它添加了一个函数,然后将该函数作为约束添加到它创建的表上。大多数约束通常相当简单,因此如果您可以在模型中放置这些约束,那将非常有用。

默认值可以按如下方式设置:

StockValue : 
{   //Default Value 0 
    onOrder => 0 : Integer32; 
};

重构和可重用类型

如果您创建实体,您可能会注意到模型中存在通用类型。一个典型的例子是地址,您可能想要一个单一的地址表。

下面是一个用于 `Customer` 和 `Supplier` 的地址可重用类型的示例。

module dbo 
{
    //Create Type Address 
    type _address : 
    { 
        id : Integer32 => AutoNumber(); 
        Address1 : Text(20)?; 
        Address2 : Text(20)?; 
        Address3 : Text(20)?; 
        Address4 : Text(20)?; 
        PostCode : Text(20); 
    } where identity id; 

    //Create Collection of Address 
    Address: {_address*}; 

    //Create Customer Entity 

    Customer : 
    { 
        id : Integer32 => AutoNumber(); 
        Name : Text(20); 
        // Reference the address in another extent 
        AddressID : _address where value in Address; 
    } where identity id; 

    //Create Supplier Entity 
    Supplier : 
    { 
        id : Integer32 => AutoNumber(); 
        Name : Text(20); 
        // Reference the address in another extent 
        AddressID : _address where value in Address; 
    } where identity id; }

这是生成的 T-SQL 的一个样本

create table [dbo].[Address] 
(   [id] int not null identity, 
    [Address1] nvarchar(20) null, 
    [Address2] nvarchar(20) null, 
    [Address3] nvarchar(20) null, 
    [Address4] nvarchar(20) null, 
    [PostCode] nvarchar(20) not null, 
    constraint [PK_Address] primary key clustered ([id]) ); 

go 

create table [dbo].[Customer] 
( 
    [id] int not null identity, 
    [Name] nvarchar(20) not null, 
    [AddressID] int not null, 
    constraint [PK_Customer] primary key clustered ([id]), 
    constraint [FK_Customer_AddressID_dbo_Address] 
    foreign key ([AddressID]) references [dbo].[Address] ([id]) ); 

go 

create table [dbo].[Supplier] 
( 
    [id] int not null identity, 
    [Name] nvarchar(20) not null, 
    [AddressID] int not null, constraint 
    [PK_Supplier] primary key clustered ([id]), 
    constraint [FK_Supplier_AddressID_dbo_Address] 
    foreign key ([AddressID]) references [dbo].[Address] ([id]) ); go

上面示例生成的 SQL 非常广泛,创建的内容比我预期的要多得多,甚至比我手动编写的还要多得多。SQL 输出包含在名为 *Addesses.sql* 的示例文件中。

创建真实世界的模型

我们将构建一个典型的销售采购模型,然后在 SQL Server 中运行生成的 T-SQL 并检查模型。我们已经涵盖了“M”语言的基础知识,现在将使用这些技术来建模并创建一个简单的采购和销售系统。

非常简要的规范

一个采购和订单系统,用于存储客户和供应商信息。有销售订单和采购订单,每个订单都可以包含多个项目。每个订单必须关联一个员工。此外,每个订单都会有一个发票地址和一个送货地址。下面是完整的模型,它将生成一个在数据库中创建模型的 SQL 脚本。

module dbo 
{ 

//TYPE ADDRESS : UK Format 
type _address 
{ 
    id : Integer32 => AutoNumber; 
    Address1 : Text(20); 
    Address2 : Text(20)?; 
    Town : Text(20); 
    County : Text(20)?; 
    Country : Text(30)?; 
    PostCode : Text(10); 
} where identity id; 

// Create a collection of Addresses 
Address : {_address*}; 


//Type Employee 
type _employee : 
{ 
    id : Integer32 => AutoNumber; 
    Name : Text(30); 
    Actice : Logical; 
} where identity id; 

//Create a collection of 
Employees Employee : {_employee*}; 

// Type Header with 
type _header 
{ 
    Name : Text(30); 
    Description : Text(250); 
    InvoiceAddress : _address where value in Address; 
    DeliveryAddress : _address where value in Address; } 

type _common : 
{ 
    id : Integer32 => AutoNumber; 
    CreatedBy : _employee where value in Employee; 
    CreateDate : DateTime; 
} where identity id; 

//Item to be used 
type _item 
{ 
    ProductKey : Text(30); 
    Qty : Integer32; ItemCost : 
    Double; TotalCost : Double; 
} 

//Purchase Line 
type _purchaseline : _common & _item; 

PurchaseLine : {_purchaseline*}; 

PurchaseOrder : _common & _header & 
{ 
    Line : _purchaseline where value in PurchaseLine; 
}; 

//Create Sales Order Line 

type _salesline : _common & _item; 

SalesLine : {_salesline*}; 

SalesOrder : _common & _header & 
{ 
    Line : _salesline where value in SalesLine; 
}; 

}

该模型由各种类型组成,这些类型允许创建具体的范围(extents)。可以看到,`Sales` 和 `Purchase` 都有标题和行,它们由相同的类型组成;这非常强大,您可以非常快速地对模型进行重要且大的更改。

生成的 SQL 太大了,无法放入本文,但为了让您有一个概念,我们编写了 80 行“M”代码,生成了大约 300 行 T-SQL。但是模型和生成的 T-SQL 已包含在本文的下载文件中。

运行生成的 T-SQL

在 SQL Server 2008R2 Express 数据库上运行生成的 T-SQL,并可用于创建数据库图,如下所示:

正如您所见,通过生成脚本在 SQL Server 中创建的模型很好地表示了“M”模型。请注意,它创建了强大的关系,外键约束以模型指定的方式链接表。此外,根据我们的模型,有一个单独的地址表与销售和采购订单匹配,因此所有地址都存储在一个表中(这是一种相当常见的实现)。

销售、采购订单和行由相同的类型组合构成,但创建为不同的范围(Extents),这反映在单独的表中,从而匹配了模型。

您还会注意到为某些表(或者更确切地说,有多个外键依赖于该表的表,例如 Employee 或 Address)创建了“*_label”表。

下面是使用 Employee_Label 表和 Employee 表为 Employee 提供查找支持的函数示例。如果您真的不喜欢这样,您可以随时从生成的 SQL 脚本中删除它。

create function [dbo].[LookupInEmployee_Labels] 
( 
    @name as nvarchar(max) 
) 
returns table 
as 
    return 
        select top (1) [t3].[id] as [id], [t3].[Name] as [Name], [t3].[Actice] as [Actice] 
        from [dbo].[Employee_Labels] as [p] 
        cross apply 
        ( 
            select  [$Employee3].[id] as [id], [$Employee3].[Name] as [Name], 
                    [$Employee3].[Actice] as [Actice] 
                    from [dbo].[Employee] as [$Employee3] 
                    where [$Employee3].[id] = [p].[Value] 
        ) as [t3] where [p].[Label] = @name; go

正如您所看到的,“M”模型与创建的数据库非常匹配。现在我们将使用 Entity Framework 来检查数据库中的模型如何转换为 Visual Studio。

在 Visual Studio 中使用模型

现在我们将看看如何在 Visual Studio 中使用我们的模型。我们将使用 Visual Studio 2010 和 Entity Framework 4 从我们之前创建的数据库重新创建模型。因此,向应用程序添加一个新的 Entity Data Model,然后选择“从数据库生成”,如下所示:

连接到您的数据库

添加您想要的表到模型中

生成的数据库 Entity Framework 模型

正如您所看到的,该模型与在 SQL Server 中创建的模型非常相似。Entity Framework 还利用外键关系创建了它为数据库中的表创建的类之间的导航属性。

导航属性是 Entity Framework 的一大优势,因为您不必显式调用新查询。它们允许实体类型之间的关联导航。详细解释超出了本文的范围,但了解其用途是有价值的。如果您有兴趣,我将在下面提供一个 MSDN 链接。

摘要

“M”在模型开发方面可以带来显著的好处,并且您可以相当快地创建模型。“M”是一种简单的语言,开发人员或数据库管理员可以轻松掌握。可重用类型的使用以及在一个或多个范围(Extents)中使用多种类型,在模型创建方面提供了良好的灵活性。另一个优势是您可以快速更改模型、刷新数据库,并使用 Entity Framework 等 ORM 工具,经过几次迭代来完善模型。

SQL Server Modeling (Oslo) 的未来

Oslo 或 SQL Server Modeling 作为一项技术,由于内部团队重组和重新关注其他技术,目前已被搁置。这对于内部 Microsoft 项目来说并不少见,有些项目从未出现过,或者后来以不同的形式出现。“M”作为一种语言的基本概念非常出色,我希望它能在 SQL Server 2012 或更高版本中重新出现,因为它是一个优秀的工具,可以真正让你考虑你的选择,而不是走上一条事后后悔的道路。使用“M”来创建或增强 Entity Framework 建模将是一个好举措,因为当前的建模工具远非理想。MVC 3.0 建模中存在一些暗示,但这确实是另一个极端,专注于代码优先生成。

参考文献

关于在线示例的说明:M 语言的在线示例数量有限,并且语法似乎变化很大,与您可以下载的最新 Oslo CTP 不匹配。这不幸地降低了一个可以为开发人员提供很多帮助的优秀技术的价值。

我长期以来一直在寻找关于 Oslo 和 M 语言的参考资料;这是我找到的完整列表:

© . All rights reserved.