DSL 和 SQL Server 建模 CTP





5.00/5 (4投票s)
本文讨论 DSL 和 SQL Server 建模。
什么是 DSL?
在业务领域解决问题有两种语言:通用语言和领域特定语言 (DSL)。通用语言可以解决许多领域的问题,而领域特定语言则专注于解决特定领域的问题。
通用语言提供了许多功能、抽象结构、控制、视图等。编程语言就是通用语言,例如 C# 中有许多可以使用的功能,例如用于实现面向对象、LINQ 编写查询等的语言特性。所有这些都很有用,但有一个问题:“它们容易学习和使用吗?”
领域特定语言并非新概念,每个行业都有其专业术语,领域专家可以将其用作语言。SQL 对于数据库专业人士来说是一种 DSL,但它在后台是用编程语言实现的。
DSL 是一种具有有限关注点、针对非常具体条件设计的特定工具,它可以提高生产力、避免重复,并提供抽象,从而更容易地说明代码中的内容。它有助于改善领域专家与客户之间的互动,他们可以阅读代码;验证代码,甚至在不需要开发人员翻译代码的情况下编写代码。
外部 DSL 与内部 DSL
当我们称一种语言为外部 DSL 时,意味着该语言存在于现有语言之外。在外部 DSL 中,语言的许多方面都必须在语言本身中实现。SQL 就是外部 DSL 的一个例子。
内部 DSL 是建立在现有语言之上或嵌入到通用语言中的语言。它没有原始编程语言的语法,但有自己的、有限的语法。可以将其看作是一种 API,有使用规则,用户知道如何使用它们,但它只是建立在编程语言之上的 API。HTML 和 CSS 就是内部 DSL。
图形 DSL 与文本 DSL
领域特定语言可以是可视化绘图语言(如 UML),也可以是文本语言。图形 DSL 非常适合全面了解事物进展情况,因为它消除了额外信息,从而能够从更高层面看待问题。但在文本 DSL 中,我们拥有所有细节的 DSL,并且可以轻松搜索和比较文本,对鼠标的依赖性也更小。
正如您肯定知道的,Windows Workflow Foundation 和 BizTalk Orchestration 是图形 DSL 的示例,而 Visual Studio DSL Tools 是用于创建图形 DSL(如类图、序列图、活动图等)的框架。
元数据
关于元数据的一个常见描述是“关于数据的数据”,但在这种情况下,我们也可以说“关于应用程序的数据”。
利用元数据,可以检索有关图片(如分辨率)的信息,而无需直接访问图片的原始数据。搜索引擎也广泛使用了元数据。
元数据最出色的实现之一是 XAML,它可用于描述应用程序行为,并且如您所知,我们可以创建仅 XAML 的应用程序。
如今,越来越多的软件转向元数据编程,并发现了元数据的价值。元数据有三种类型:
- 描述性元数据,用于发现资源
- 结构性元数据,用于显示资源不同部分之间的关系
- 管理性元数据,用于管理资源
SQL Server 建模 CTP
SQL Server 建模 CTP 是一套未来的 Microsoft 建模技术,旨在通过使开发人员、架构师和 IT 专业人员能够更有效地协同工作,从而在 .NET Framework 应用程序的整个生命周期中显著提高生产力。其目标是大幅提高编程生产力。
该技术的主要目标是通过特定于领域的语言创建的、混合了其他可视化建模工具的模型和元数据来构建应用程序。
SQL Server 建模 CTP 由三个组件组成:
- 一个元数据存储库 (SQL Server Modeling Services),用于驱动应用程序设计、开发和管理
- 一个可视化工具(代号 Quadrant),用于直观地构建和查看模型
- 一种建模语言(代号 M),用于构建文本形式的领域特定语言
SQL Server Modeling Services
SQL Server Modeling Services 是一项服务,类似于 SQL Server 下一个版本中的其他服务,如 Analysis Services 和 Reporting Services。它只是一个包含一组通用模型的数据库,可以将其想象成一个声明式的、以软件应用程序生命周期为中心的 .NET Framework,并且其中包含应用程序元数据。
为什么它仅仅是个数据库?
数据库中存在的所有功能都可以用于 SQL Server Modeling Services,例如复制、横向扩展、分区、备份、报告、查询等。
Base Domain Library (BDL) 包含一些模型,例如:
- 系统运行时
- 系统标识
- 系统管理
- Microsoft .Uml2
- 您的模型
除了这些模型之外,还有一组用于高效管理多用户信息的模式:
- 文件夹
- 安全
- 生命周期
- 包含
- 引用
- 关系
- 视图
- 全球化/本地化
集成应用程序生命周期数据
数据在应用程序生命周期中生成,例如业务需求、设计文档、开发人员任务、bug 和测试数据由特定工具创建,并且这些数据之间的协调非常难以实现,但在 SQL Server Modeling Services 中,我们拥有所有数据,并且可以通过单个查询检索。
System.Runtime
包含有关 CLR 对象的所有数据,如程序集、模块、方法、类型等。
Microsoft.UML2
包含 UML 图,如用例图、类图、序列图、活动图等。
文件夹
数据库中的数据可以组织成文件夹层次结构,就像文件系统一样,我们可以复制、移动、删除、应用安全性等。
建模工具
我们可以使用建模工具将我们的模型添加到 Modeling Services 中。
M 语言
M 是一种基于文本的领域特定建模语言。在文本中进行建模的一个关键优势是计算机和人类都可以轻松地存储和处理文本。
M 既可以作为一种模式语言,验证文本输入是否符合给定语言,也可以作为一种转换语言,将文本输入投影到便于进一步处理或存储的数据结构中。
使用 M 创建模型
M 帮助我设计模型,然后我们可以将其放入 SQL Server Modeling Services 中。Intellipad 是一个辅助编写 M 代码的工具。
这是 M 和生成的 SQL 之间的隐式类型创建映射示例。
" M 语言 "
module Sales
{
Customer:
{
(
{
CustomerID:Integer32 => AutoNumber;
FirstName:Text(255);
LastName:Text(255);
}
)*
}where identity CustomerID;
Order:
{
(
{
OrderID:Integer32 => AutoNumber;
OrderNumber:Text(255);
OrderDate:Date;
Customer:Customer;
}
)*
}where identity OrderID;
}
" SQL "
execute [sp_executesql] N'create schema [Sales]';
go
create table [Sales].[Customer]
(
[CustomerID] int not null identity,
[FirstName] nvarchar(255) not null,
[LastName] nvarchar(255) not null,
constraint [PK_Customer] primary key clustered ([CustomerID])
);
go
create table [Sales].[Order]
(
[OrderID] int not null identity,
[OrderNumber] nvarchar(255) not null,
[OrderDate] date not null,
[Customer] int not null,
constraint [PK_Order] primary key clustered ([OrderID]),
constraint [FK_Order_Customer_Sales_Customer] foreign key _
([Customer]) references [Sales].[Customer] ([CustomerID])
);
go
这就是创建类型和数据库中的表的方式,代码非常简单。
M 作为 Entity Framework 中的 EDM
正如您肯定知道的,EDM 是应用程序和数据之间的桥梁,也是允许您以概念方式处理数据而不是直接操作数据库并尝试弄清楚后端模式的组件。
现在,我们可以使用 M 语言创建实体模型,而不是使用 EDM。
" C# "
Sales.SalesContainer context = new Sales.SalesContainer
("Data Source=.;Initial Catalog=MyOsloTestDB;Integrated Security=True");
var customer = Sales.Customer.CreateCustomer(0, "Joe", "Brown");
var order = Sales.Order.CreateOrder(0, "Test", DateTime.Now);
order.Customer = customer;
context.AddToOrders(order);
context.SaveChanges();
GridView1.DataSource = context.Orders.Select(o=>o.Customer).ToList();
GridView1.DataBind();
M 语法
“M”语言本身就是一个外部 DSL 的例子,因为它是一种独立的、为建模优化的语言。
M Grammar 提供了一套广泛的工具集,支持语言创建、测试和部署。
我们可以将语法文件编译成图像形式,然后运行图像文件来处理用 DSL 编写的输入文本。
当您将编译后的图像文件针对输入文本流运行时,由 MGrammar
生成的解析器会创建一个名为 MGraph
的输出数据结构。MGrammar
提供了一个 API,允许您操作输出数据结构,根据您的应用程序需求进行转换。
创建语言
此示例显示了如何使用 M 和 M 中的语言定义功能创建名为 SalesLanguage
的自定义语言。
语言定义功能
- Token 规则:语言的最低级别结构,如字母和单词,我们可以使用 Regex 来指定规则。
- 语法规则:定义语言的语法,它们是 token 的组合。
- 交错规则:排除空格等 token。
- 投影:输出的形状。
这是您可以在 Intellipad 中编写的示例语法。
" M 语法 "
module Sales
{
language SalesLanguage
{
syntax Main = p:Product* => Product {valuesof(p)};
syntax Product = ProductStart NumberAttribute
Equals num:Number NameAttribute Equals n:Name
=> { Number => num, Name => n };
syntax Name = t:Text => t;
token ProductStart = "product";
token NumberAttribute = "Number";
token NameAttribute = "Name";
token Text = '"' t:TextWithWhitespace '"' => t;
token AlphaNumerical ='a'..'z' | 'A'..'Z' | '0'..'9';
token Number = '0'..'9';
token Equals = '=';
token TextWithWhitespace = (AlphaNumerical | Whitespace)+;
token Whitespace = '\t' | ' ';
interleave IgnoreChars = '\r' | '\n' | Whitespace;
}
}
主语法在自定义语言中是必需的,它是 M 语言在语法模式下的入口点。Product*
表示 Product 集合,p:
是 Product 集合的别名。这类似于 LINQ 中的投影。
var productNumbers = Products.Select(p => Number = p.Number, Name = p.Name);
正如您所见,在 M 中使用投影的方式与在 LINQ 中使用它相同。
=> { Number => num, Name => n }
示例语法中的 token 是单词以及可接受单词和字母的模式。
token AlphaNumerical ='a'..'z' | 'A'..'Z' | '0'..'9';
创建 MGrammar 文档后,您应该使用 M Compiler (M.exe) 进行编译。

之后,编译器会创建一个名为 Product.mx 的编译后的语法文件。下一步是组合一个文本文件和语法文件以推断模型文件。这是示例输入文本文件。
" DSL 输出 "
product Number=1 Name="Monitor"
product Number=2 Name="Mouse"
product Number=3 Name="Keyboard"
您可以使用 MGrammar Executor (MGX.exe) 来推断模型文件。

您可以看到,我们使用的输入是一个文本文件和一个编译后的语法文件,输出是 Product.m。
您甚至可以为 MGrammar Executor 指定输出类型,例如 XAML。

这是从输入和语法生成的示例输出模型文件。
module Sales
{
Product :
{
{
ID : Integer32 => AutoNumber;
Name : Text;
Number : Integer32 ?;
}*
} where identity ID;
Product
{
{
Number => 1,
Name => "Monitor"
},
{
Number => 2,
Name => "Mouse"
},
{
Number => 3,
Name => "Keyboard"
}
}
}
第一部分是 Product 集合字段的约束部分。
ID : Integer32 => AutoNumber;
这一行是对 ID 字段的约束,它应该是 Integer32
类型。
此代码等同于此 SQL 脚本。
[ID] int not null identity
where identity ID;
这一行等同于此 SQL 脚本。
constraint [PK_Product] primary key clustered ([ID])
这是生成的 T-SQL 脚本示例。
"生成的 T-SQL"
set xact_abort on;
go
begin transaction;
go
set ansi_nulls on;
go
if not exists
(
select *
from [sys].[schemas]
where [name] = N'Sales'
)
execute [sp_executesql] N'create schema [Sales]';
go
if not exists
(
select *
from [sys].[schemas]
where [name] = N'$MRuntime.Sales'
)
execute [sp_executesql] N'create schema [$MRuntime.Sales]';
go
create table [Sales].[Product]
(
[ID] int not null identity,
[Name] nvarchar(max) not null,
[Number] int null,
constraint [PK_Product] primary key clustered ([ID])
);
go
create table [$MRuntime.Sales].[Product_Labels]
(
[Label] nvarchar(444) not null,
[Value] int not null,
constraint [PK_Product_Labels] primary key clustered ([Label]),
constraint [FK_Product_Labels_Value_Sales_Product] foreign key _
([Value]) references [Sales].[Product] ([ID]) on delete cascade
);
go
create index [IR_Value] on [$MRuntime.Sales].[Product_Labels] ([Value]);
go
create function [$MRuntime.Sales].[LookupInProduct_Labels]
(
@name as nvarchar(max)
)
returns table
as
return
select top (1)
[t3].[ID] as [ID],
[t3].[Name] as [Name],
[t3].[Number] as [Number]
from [$MRuntime.Sales].[Product_Labels] as [p]
cross apply
(
select [$Product2].[ID] as [ID],
[$Product2].[Name] as [Name],
[$Product2].[Number] as [Number]
from [Sales].[Product] as [$Product2]
where [$Product2].[ID] = [p].[Value]
) as [t3]
where [p].[Label] = @name;
go
declare @seed_Sales_Product bigint = 0;
declare @increment_Sales_Product bigint = ident_incr(N'[Sales].[Product]');
declare @reseed_Sales_Product bigint = coalesce(@seed_Sales_Product + _
@increment_Sales_Product * 3, 0);
dbcc checkident(N'[Sales].[Product]', reseed, @reseed_Sales_Product);
set identity_insert [Sales].[Product] on;
insert into [Sales].[Product] ([ID], [Number], [Name])
values (@seed_Sales_Product + @increment_Sales_Product * 1, 1, N'Monitor'),
(@seed_Sales_Product + @increment_Sales_Product * 2, 2, N'Mouse'),
(@seed_Sales_Product + @increment_Sales_Product * 3, 3, N'Keyboard');
set identity_insert [Sales].[Product] off;
go
commit transaction;
go
您不必从 M 文件生成 SQL 脚本,您可以使用 M 命令行实用程序将模型文件安装到现有数据库或创建新数据库。

历史
- 2010 年 9 月 21 日:初始版本