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

从 XML 生成 SQL 数据库架构 - 第一部分(文件格式和 XSD)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.95/5 (13投票s)

2010 年 4 月 28 日

CPOL

14分钟阅读

viewsIcon

131523

downloadIcon

2864

本系列文章共三部分,旨在介绍从 XML 文件格式生成 SQL 的项目。本文是第一部分,将介绍项目的背景,并讨论文件格式和 XSD 架构的开发。

引言

这是三篇系列文章中的第一篇,介绍了我为解决工作环境中的特定问题而进行的一个项目。该项目有两个核心成果:一种用于描述数据库架构的新 XML 文件格式,以及一个用于将新格式转换为 SQL 的可执行文件。在此过程中,我将分享一些设计过程中的想法,并介绍一些可能在此项目之外也有应用的支持性代码库。我假设读者对几种技术有基本了解,但也会提供链接和定义,希望能让每个人都能理解。

在本文(第一部分)中,我将介绍项目的背景,并讨论文件格式和 XSD 架构的开发。在第二篇文章中,我将重点介绍创建映射到文件格式的代码模型。第三篇也是最后一篇文章将介绍一种简单的代码生成方法,并总结所有遗留问题。

背景

我经常在团队环境中使用 Microsoft SQL Server(通常简称为 SQL Server)。当我在这些文章中谈到SQL时,我实际上指的是 Microsoft 的语言版本,称为T-SQL,尽管将来也可以轻松地处理该语言的其他版本。SQL 拥有悠久而辉煌的历史,并被数百万人用于包括我的项目在内的各种项目中,但这个项目之所以存在,是因为它存在一些让我感到沮丧的不足之处。

SQL 通常以两种不同的方式使用:作为DDL和用于CRUD查询。当创建一个新的数据库时,它不仅是空的,而且结构也是空的。在添加数据(例如,一个人的详细信息)之前,必须添加一个来包含数据。在 SQL 中,可以使用如下语句来实现:

CREATE TABLE [dbo].[EXAMPLE_TABLE](
  [PK] [int] IDENTITY(1,1) NOT NULL,
  [EXAMPLE_COLUMN1] [varchar](50) NOT NULL,
)
定义一个名为 'EXAMPLE_TABLE' 的表,包含两列:'PK' 和 'EXAMPLE_COLUMN1'

类似的语句用于描述列的默认值、索引、关系、约束等等。所有这些定义统称为数据库架构。一旦定义了架构,就可以填充数据库数据,检索或修改数据,等等。对于查询目的,我没有意见,尽管越来越普遍的做法是避免使用原始 SQL 进行查询,而是“在后台”生成它,例如使用LINQ to SQL

不,问题在于我不喜欢用 SQL 来定义架构。首先,我通常认为数据最好以声明式的方式描述。数据库的架构无疑是数据;实际上,它存储在数据库的“系统表”中。但 SQL 相对难以解析。要将其视为数据,您通常必须将其加载到实际的 SQL Server 实例中,然后使用编程接口(例如,DbConnection.GetSchema)进行查询。这在我看来是错误的,所以我决定尝试解决这个问题。

那些经常使用数据库的人可能会抗议说,DDL 不仅仅是*创建*架构。一旦数据库充满了结构和数据,通常就不可能全部丢弃并从头开始!相反,管理变更变得必要。SQL 是一种完整的过程语言,因此可以根据特定条件进行更改,还可以用于操作数据、暂时放宽约束等。但是,这个项目的背景是我工作的地方有一个工具链,可以从使用简单 DDL 创建的空数据库快照更新实时数据库。我相信数据库架构的更改可以用声明式的方式更好地表示,这也是该项目的长期目标。但目前,该项目的范围仅限于从头开始创建一个新的空数据库。

“那么,您为什么想将数据库架构视为数据?”我听到您这样问。一个答案是用于代码生成。有几种现有的技术可以将数据库架构映射到自动生成的类,这种技术称为对象关系映射 (ORM)。简而言之,表和列被映射到类和属性,数据自动序列化到/从数据库到类实例。该领域中的两个主要参与者(Microsoft Entity FrameworkLLBLGen Pro)采用从数据库实例更新其生成代码的方法。

ORMFlow.PNG

生成 ORM 代码的流行过程

这是一种完全合理的方法,但它假定数据库已经具有正确的架构,而在团队环境中情况并非总是如此。在团队中工作时,最好是每个人都能同时对数据库架构进行更改,然后稍后合并他们的更改。还希望有更改历史记录以及谁所做的更改。换句话说,我指的是源代码管理。现在,完全有可能在源代码管理下使用 SQL 定义数据库架构。但是,当一个开发人员获取另一个开发人员的更改时,他们必须记住在更新 ORM 工具生成的代码之前更新自己的本地数据库实例。

ORMFlow2.PNG

实际上,在使用源代码管理时,这是一个四步过程

这种手动更新数据库实例仅是必需的,因为 ORM 工具依赖于它。否则,可以从应用程序代码内部自动更新数据库架构。碰巧,有一个名为NHibernate的项目,它不像这样依赖于数据库实例。在 NHibernate 中,代码类是起点,数据库架构是通过描述类如何映射到它的 XML 文件生成的。此外,还可以从 XML 映射文件生成代码。这更好:所有开发人员都可以编辑一个 XML 文件,并自动神奇地生成数据库架构和 ORM 代码。NHibernate 对我来说唯一的问题是映射文件以代码为中心。也就是说,它描述了如何将 NHibernate 兼容的代码类映射到数据库,而不是描述数据库本身。虽然数据库架构*隐含*在 NHibernate 映射文件中,但它*并不明确*。这对于许多应用程序来说都可以,但我并不使用 NHibernate 作为我的代码 ORM 解决方案,所以它对我不起作用。

我遇到的第二个主要问题是我不喜欢 SQL 作为一种语言来定义我的数据库架构。我发现它很难记住,语法奇怪且笨拙。说出来了。更改数据库架构是我不经常做的事情,以至于当我再次处理它时,我经常会查阅参考资料。另一方面,尽管有记忆衰退,但我永远不会忘记如何编辑 XML。当然,XML 可能有点丑陋,但至少它很规则,而且解析起来很容易。因此,我充分意识到我是在浪费自己的时间,我决定开发一种基于 XML 的文件格式来描述数据库架构,并开发某种解决方案来将新格式转换为 SQL。

更进一步,我意识到我最终希望能够以可视化方式编辑文件格式(类似于SQL Server Management Studio)。但可视化编辑器本身就是一个庞大的项目,所以我决定将其称为“阶段 2”,并在“阶段 1”中仅仅将其记在心里。同时,我的新文件格式必须易于手动编辑。

在本文的其余部分,我将描述新的文件格式以及其中一些设计决策。有时,这会非常详细,所以如果您对 XSD 完全不熟悉,可能值得简要了解一下 XSD

要求

这是我为文件格式和转换过程提出的要求列表

  • 描述数据库架构
  • 可进行版本控制,并具有有用的历史记录
  • 易于手动编辑
  • 易于记忆的语法

架构:为了使项目易于管理,我决定不求全责备地描述数据库架构。相反,我的目标是完整地描述一个真实的、实际的客户数据库。这意味着

  • 表格
  • Columns
  • Types
  • 默认值
  • 标识符规范
  • 主键
  • 唯一约束
  • 索引
  • 关系
  • 存储过程

版本控制:显然,任何文件都可以进行版本控制。我希望一种文件格式可以由多个人编辑并合并。我还希望能够在文件的版本历史中清楚地看到更改。

编辑:尽可能地,我希望文件格式的结构与我思考数据库架构的方式相同,并且是以手动编辑为目的设计的。

语法:如果我一段时间不使用 SQL,我倾向于忘记细节,然后查阅参考资料。所以我想要一种文件格式,通过保持一致和自解释来指导我。

除了将文件格式转换为 SQL 的明显要求之外,我的主要关注点是便利性。该过程应易于通过快捷方式、批处理文件或构建步骤运行。将其部署到新机器上也应很方便。

文件格式实现

正如本文标题所示,我选择XML作为我的文件格式。它满足了要求,并且具有更广泛的优势,超出了本文的范围。特别是,如果附带一个有效的XSD,Visual Studio 在编辑文件时将提供智能感知。对于初学者来说,XML 文件就像数据库一样有架构,尽管 XML 文件本质上是分层的,但架构的形式非常不同。虽然不是创建有效 XML 文件所必需的,但架构很有用,因为它限制了文件中允许存在的内容,并向任何查看它的人或代码标识了格式。XSD 本身是用 XML 定义的,是为 XML 文件定义架构的最“标准”方法。Visual Studio(和其他编辑器)将解析 XSD 并使用它在编辑 XML 文件时提供智能感知。

我在此包含完整的 XSD 文件以供参考,但它相当长,所以您可能想将其折叠起来,直到您准备查看它。

<?xml version="1.0" encoding="utf-8"?>
<xs:schema id="DatabaseSchemaModel"
    targetNamespace="http://olduwan.com/DatabaseSchemaModel.xsd"
    elementFormDefault="qualified"
    xmlns="http://olduwan.com/DatabaseSchemaModel.xsd"
    xmlns:dbsm="http://olduwan.com/DatabaseSchemaModel.xsd"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
>
  <!--Columns-->
  
  <xs:complexType name="parameterlessType"/>

  <xs:complexType name="identity">
    <xs:attribute name="seed" 
             use="optional" type="xs:integer"/>
    <xs:attribute name="increment" 
             use="optional" type="xs:integer"/>
    <xs:attribute name="notForReplication" 
             use="optional" type="xs:boolean"/>
  </xs:complexType>

  <xs:group name="withIdentity">
    <xs:sequence>
      <xs:element name="identity" minOccurs="0" 
                maxOccurs="1" type="identity"/>
    </xs:sequence>
  </xs:group>

  <xs:attributeGroup name="withDefaultExpression">
    <xs:attribute name="defaultExpression" 
               use="optional" type="xs:string"/>
  </xs:attributeGroup>
  
  <xs:complexType name="int">
    <xs:group ref="withIdentity"/>
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
          use="optional" type="xs:int"/>
  </xs:complexType>

  <xs:complexType name="bigint">
    <xs:group ref="withIdentity"/>
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
           use="optional" type="xs:long"/>
  </xs:complexType>

  <xs:complexType name="smallint">
    <xs:group ref="withIdentity"/>
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
           use="optional" type="xs:short"/>
  </xs:complexType>

  <xs:complexType name="tinyint">
    <xs:group ref="withIdentity"/>
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
           use="optional" type="xs:byte"/>
  </xs:complexType>

  <xs:complexType name="decimal">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="precision" 
           use="required" type="xs:integer"/>
    <xs:attribute name="scale" 
           use="required" type="xs:integer"/>
    <xs:attribute name="default" 
           use="optional" type="xs:decimal"/>
  </xs:complexType>

  <xs:complexType name="decimalScale0">
    <xs:group ref="withIdentity"/>
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="precision" 
           use="required" type="xs:integer"/>
    <xs:attribute name="default" 
           use="optional" type="xs:decimal"/>
  </xs:complexType>

  <xs:complexType name="bit">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
           use="optional" type="xs:boolean"/>
  </xs:complexType>

  <xs:simpleType name="moneydefault">
    <xs:restriction base="xs:decimal">
      <xs:totalDigits value="10"/>
      <xs:fractionDigits value="4"/>
      <xs:minInclusive value="-922337203685477.5808"/>
      <xs:maxInclusive value="922337203685477.5807"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:complexType name="money">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
           use="optional" type="moneydefault"/>
  </xs:complexType>
  
  <xs:simpleType name="smallmoneyDefault">
    <xs:restriction base="xs:decimal">
      <xs:totalDigits value="10"/>
      <xs:fractionDigits value="4"/>
      <xs:minInclusive value="-214748.3648"/>
      <xs:maxInclusive value="214748.3647"/>
    </xs:restriction>
  </xs:simpleType>
  
  <xs:complexType name="smallmoney">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
           use="optional" type="smallmoneyDefault"/>
  </xs:complexType>

  <xs:simpleType name="mantissaBits">
    <xs:restriction base="xs:integer">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="53"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:complexType name="float">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="mantissaBits" 
            use="optional" type="mantissaBits"/>
    <xs:attribute name="default" 
            use="optional" type="xs:decimal"/>
  </xs:complexType>

  <xs:complexType name="real">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
           use="optional" type="xs:decimal"/>
  </xs:complexType>

  <xs:attributeGroup name="withStringDefault">
    <xs:attribute name="default" 
           use="optional" type="xs:string"/>
  </xs:attributeGroup>

  <xs:simpleType name="fractionalSecondsPrecision">
    <xs:restriction base="xs:integer">
      <xs:minInclusive value="0"/>
      <xs:maxInclusive value="7"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:complexType name="variablePrecisionTime">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attributeGroup ref="withStringDefault"/>
    <xs:attribute name="fractionalSecondsPrecision" 
         use="optional" type="fractionalSecondsPrecision" />
  </xs:complexType>

  <xs:simpleType name="charInteger">
    <xs:restriction base="xs:integer">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="8000"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="charLength">
    <xs:union memberTypes="charInteger withMax"/>
  </xs:simpleType>

  <xs:complexType name="char">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attributeGroup ref="withStringDefault"/>
    <xs:attribute name="length" 
          use="optional" type="charLength"/>
  </xs:complexType>

  <xs:complexType name="binary">
    <xs:attribute name="length" 
          use="optional" type="charLength"/>
  </xs:complexType>

  <xs:simpleType name="withMax">
    <xs:restriction base="xs:string">
      <xs:enumeration value="max"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="ncharInteger">
    <xs:restriction base="xs:integer">
      <xs:minInclusive value="1"/>
      <xs:maxInclusive value="4000"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:simpleType name="ncharLength">
    <xs:union memberTypes="ncharInteger withMax"/>
  </xs:simpleType>

  <xs:complexType name="nchar">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attributeGroup ref="withStringDefault"/>
    <xs:attribute name="length" 
          use="optional" type="ncharLength"/>
  </xs:complexType>

  <xs:simpleType name="GUID">
    <xs:annotation>
      <xs:documentation xml:lang="en">
        The representation of a GUID.
      </xs:documentation>
    </xs:annotation>
    <xs:restriction base="xs:string">
      <xs:pattern value="\{[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]
                            {4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}\}"/>
    </xs:restriction>
  </xs:simpleType>
  
  <xs:complexType name="uniqueidentifier">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attribute name="default" 
            use="optional" type="GUID"/>
  </xs:complexType>

  <xs:complexType name="parameterlessStringType">
    <xs:attributeGroup ref="withDefaultExpression"/>
    <xs:attributeGroup ref="withStringDefault"/>
  </xs:complexType>
  
  <xs:complexType name="column">
    <xs:choice minOccurs="1" maxOccurs="1">
      <!--Exact numerics-->
      <xs:element name="bigint" type="bigint"/>
      <xs:element name="numeric" type="decimal"/>
      <xs:element name="numericScale0" type="decimalScale0"/>
      <xs:element name="bit" type="bit"/>
      <xs:element name="smallint" type="smallint"/>
      <xs:element name="decimal" type="decimal"/>
      <xs:element name="decimalScale0" type="decimalScale0"/>
      <xs:element name="smallmoney" type="smallmoney"/>
      <xs:element name="int" type="int"/>
      <xs:element name="tinyint" type="tinyint"/>
      <xs:element name="money" type="money"/>

      <!--Approximate numerics-->
      <xs:element name="float" type="float"/>
      <xs:element name="real" type="real"/>

      <!--Date and time-->
      <xs:element name="date" type="parameterlessStringType"/>
      <xs:element name="datetimeoffset" type="variablePrecisionTime"/>
      <xs:element name="datetime2" type="variablePrecisionTime"/>
      <xs:element name="smalldatetime" type="parameterlessStringType"/>
      <xs:element name="datetime" type="parameterlessStringType"/>
      <xs:element name="time" type="variablePrecisionTime"/>

      <!--Character strings-->
      <xs:element name="char" type="char"/>
      <xs:element name="varchar" type="char"/>
      <xs:element name="text" type="parameterlessStringType"/>

      <!--Unicode character strings-->
      <xs:element name="nchar" type="nchar"/>
      <xs:element name="nvarchar" type="nchar"/>
      <xs:element name="ntext" type="parameterlessStringType"/>

      <!--Binary strings-->
      <xs:element name="binary" type="binary"/>
      <xs:element name="varbinary" type="binary"/>
      <xs:element name="image" type="parameterlessType"/>

      <!--Other data types-->
      <xs:element name="rowversion" type="parameterlessType"/>
      <xs:element name="hierarchyid" type="parameterlessStringType"/>
      <xs:element name="uniqueidentifier" type="uniqueidentifier"/>
      <xs:element name="sql_variant" type="parameterlessStringType"/>
      <xs:element name="xml" type="parameterlessStringType"/>

    </xs:choice>
    <xs:attribute name="name" 
          use="required" type="xs:string"/>
    <xs:attribute name="allowNulls" 
           use="optional" type="xs:boolean"/>
  </xs:complexType>

  <xs:complexType name="columnList">
    <xs:sequence>
      <xs:element name="column" maxOccurs="unbounded" 
                     minOccurs="1" type="column"/>
    </xs:sequence>
  </xs:complexType>

  <!--Constraints-->
  
  <xs:simpleType name="sortOrder">
    <xs:restriction base="xs:string">
      <xs:enumeration value="Ascending"/>
      <xs:enumeration value="Descending"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:complexType name="constraintColumn">
    <xs:attribute name="name" use="required" type="xs:string"/>
    <xs:attribute name="sortOrder" 
            use="optional" type="sortOrder"/>
  </xs:complexType>

  <xs:complexType name="constraint">
    <xs:sequence>
      <xs:element name="column" maxOccurs="unbounded" 
            minOccurs="1" type="constraintColumn"/>
    </xs:sequence>
    <xs:attribute name="name" use="required" type="xs:string"/>
    <xs:attribute name="clustered" use="required" type="xs:boolean"/>
    <xs:attribute name="fillFactor" use="optional" type="xs:integer"/>
    <xs:attribute name="padIndex" use="optional" type="xs:boolean"/>
  </xs:complexType>

  <xs:complexType name="primaryKey">
    <xs:sequence>
      <xs:element name="key" maxOccurs="1" 
             minOccurs="0" type="constraint"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="uniqueConstraints">
    <xs:sequence>
      <xs:element name="constraint" 
            maxOccurs="unbounded" minOccurs="0" 
            type="constraint"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="index">
    <xs:complexContent>
      <xs:extension base="constraint">
        <xs:attribute name="unique" 
              use="optional" type="xs:boolean"/>
      </xs:extension>
    </xs:complexContent>
  </xs:complexType>

  <xs:complexType name="indexes">
    <xs:sequence>
      <xs:element name="index" maxOccurs="unbounded" 
              minOccurs="0" type="index"/>
    </xs:sequence>
  </xs:complexType>

  <!--Relationships-->

  <xs:complexType name="relationshipColumn">
    <xs:attribute name="name" 
              use="required" type="xs:string"/>
  </xs:complexType>

  <xs:complexType name="foreignKeyColumns">
    <xs:sequence>
      <xs:element name="column" maxOccurs="unbounded" 
             minOccurs="1" type="relationshipColumn"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="primaryKeyTable">
    <xs:sequence>
      <xs:element name="column" maxOccurs="unbounded" 
            minOccurs="1" type="relationshipColumn"/>
    </xs:sequence>
    <xs:attribute name="name" 
            use="required" type="xs:string"/>
  </xs:complexType>

  <xs:complexType name="relationship">
    <xs:sequence>
      <xs:element name="foreignKeyColumns" 
          maxOccurs="1" minOccurs="1" 
          type="foreignKeyColumns"/>
      <xs:element name="primaryKeyTable" maxOccurs="1" 
             minOccurs="1" type="primaryKeyTable"/>
    </xs:sequence>
    <xs:attribute name="name" 
                use="required" type="xs:string"/>
  </xs:complexType>

  <xs:complexType name="relationships">
    <xs:sequence>
      <xs:element name="relationship" maxOccurs="unbounded" 
                 minOccurs="0" type="relationship" />
    </xs:sequence>
  </xs:complexType>
  
  <xs:complexType name="table">
    <xs:sequence>
      <xs:element name="columns" maxOccurs="1" 
           minOccurs="1" type="columnList"/>
      <xs:element name="primaryKey" maxOccurs="1" 
           minOccurs="1" type="primaryKey"/>
      <xs:element name="uniqueConstraints" maxOccurs="1" 
           minOccurs="1" type="uniqueConstraints"/>
      <xs:element name="indexes" maxOccurs="1" 
           minOccurs="1" type="indexes"/>
      <xs:element name="relationships" maxOccurs="1" 
           minOccurs="1" type="relationships"/>
    </xs:sequence>
    <xs:attribute name="name" 
          type="xs:string" use="required"/>
  </xs:complexType>

  <xs:complexType name="tables">
    <xs:sequence>
      <xs:element name="table" maxOccurs="unbounded" 
            minOccurs="0" type="table"/>
    </xs:sequence>
  </xs:complexType>

  <!--Stored procedures-->
  <xs:simpleType name="platform">
    <xs:restriction base="xs:string">
      <xs:enumeration value="SQLServer"/>
    </xs:restriction>
  </xs:simpleType>

  <xs:complexType name="procedure">
    <xs:attribute name="name" 
           type="xs:string" use="required"/>
    <xs:attribute name="path" 
           type="xs:string" use="required"/>
    <xs:attribute name="platform" 
           type="platform" use="optional"/>
  </xs:complexType>

  <xs:complexType name="procedures">
    <xs:sequence>
      <xs:element name="procedure" maxOccurs="unbounded" 
             minOccurs="0" type="procedure"/>
    </xs:sequence>
  </xs:complexType>

  <xs:complexType name="database">
    <xs:sequence>
      <xs:element name="tables" maxOccurs="1" 
            minOccurs="1" type="tables"/>
      <xs:element name="procedures" maxOccurs="1" 
            minOccurs="1" type="procedures"/>
    </xs:sequence>
    <xs:attribute name="name" 
           type="xs:string" use="required"/>
    <xs:attribute name="ExampleFolderPath" 
           type ="xs:string" use="optional"/>
    <xs:attribute name="SQLServerOutputPath" 
           type ="xs:string" use="optional"/>
  </xs:complexType>

  <xs:element name="database" type="database"/>
</xs:schema>

我不会详细说明 XSD 中的每一个设计决策,但总的来说,我使用 SQL Server Management Studio 作为我的层级结构的指南,将其围绕表进行构建,并将索引和关系附加在其上。这是一个用新格式定义的一个非常简单的数据库架构。

<?xml version="1.0" encoding="utf-8" ?>

<database xmlns="http://olduwan.com/DatabaseSchemaModel.xsd" name="Simple">
  <tables>
    <table name="Customer">
      <columns>
        <column name="CustomerId">
          <int>
            <identity/>
          </int>
        </column>
        <column name="Surname">
          <nchar length="50"/>
        </column>
      </columns>
      <primaryKey>
        <key name="PK_Customer" clustered="true">
          <column name="CustomerId"/>
        </key>
      </primaryKey>
      <uniqueConstraints>        
      </uniqueConstraints>
      <indexes>
        <index name="IX_Customer_Surname" clustered="false">
          <column name="Surname" sortOrder="Ascending"/>
        </index>
      </indexes>
      <relationships>
      </relationships>
    </table>
    <table name="Order">
      <columns>
        <column name="OrderId">
          <int>
            <identity/>
          </int>
        </column>
        <column name="Details">
          <nvarchar length="max"/>
        </column>
        <column name="Complete">
          <bit default="false"/>
        </column>
        <column name="CustomerId">
          <int/>
        </column>
      </columns>
      <primaryKey>
        <key clustered="true" name="PK_Order">
          <column name="OrderId"/>
        </key>
      </primaryKey>
      <uniqueConstraints>
      </uniqueConstraints>
      <indexes>
      </indexes>
      <relationships>
        <relationship name="FK_Order_Customer">
          <foreignKeyColumns>
            <column name="CustomerId"/>
          </foreignKeyColumns>
          <primaryKeyTable name="Customer">
            <column name="CustomerId"/>
          </primaryKeyTable>
        </relationship>
      </relationships>
    </table>
  </tables>
  <procedures>
  </procedures>
</database> 

这里是一个简化的数据库图,描述了它。

SimpleDatabase.PNG

希望这种格式相当自明,如果您熟悉 SQL Server 的基本概念。我之所以选择使用 XSD,是因为我对此已经很熟悉,并且我知道它在 Visual Studio 中得到了很好的支持。只要 Visual Studio 知道在哪里可以找到 XSD(例如,XSD 文件存在于项目中),那么在编辑文件时,智能感知就会为您提供指导。经过一些思考,您可能无需任何参考就可以使用此格式定义自己的数据库。XSD 确实有两个局限性,这些局限性可以在其他 XML 架构格式(例如 RelaxNG)中解决,但要在 Visual Studio 中获得可用的智能感知是一个很高的优先级,所以我选择接受它们。

首先,无法根据属性的值来定义类型有效的属性/子元素。请注意,在列的定义中,类型是一个嵌套元素。

<column name="CustomerId">
  <int>
    <identity/>
  </int>
</column>

可以说,将类型作为列的属性会更直观。

<column name="CustomerId" type="int">
  <identity/>
</column>

但这样做的话,column类型就必须支持描述任何类型所需的所有参数。例如,int可以有标识符规范,但decimal不行。使column类型如此通用将严重影响其在智能感知方面的可用性。为了使这种方法奏效,我需要能够在我的架构中表达这个语句:

"column类型可以有一个identity子元素,如果type属性是集合 [int, bigint, smallint, tinyint, decimalScale0] 中的一个"。

可能存在一些允许这种灵活性的 XML 架构格式,但我不知道,而且它可能在 Visual Studio 中支持不佳。

当我想验证关系中的表名和列名时,我遇到了 XSD 的第二个限制。在上面的示例中,您可以看到订单表和客户表之间的关系:FK_Order_Customer。在column元素中,name属性必须是“外键”表(即包含此relationships元素的表)中列的名称。在 XSD 中无法指定这一点。同样的情况也适用于primaryKeyTablename属性,它必须是表之一的名称。虽然这可以在转换时进行验证,但如果 Visual Studio 在编辑文件时提供表/列名称的自动完成列表,并在无效条目下显示红色波浪线,那就更好了。

列类型

如前所述,列的类型用嵌套元素表示,在 XSD 中表示如下:

<xs:complexType name="column">
  <xs:choice minOccurs="1" maxOccurs="1">
    <!--Exact numerics-->
    <xs:element name="bigint" type="bigint"/>
    <xs:element name="numeric" type="decimal"/>
    <xs:element name="numericScale0" type="decimalScale0"/>
    <xs:element name="bit" type="bit"/>
    <xs:element name="smallint" type="smallint"/>
    <xs:element name="decimal" type="decimal"/>
    <xs:element name="decimalScale0" type="decimalScale0"/>
    Many more types...

这决定了一个类型为 column 的元素必须包含恰好 1 个元素,该元素可以是列出的任何类型。因此,每个 SQL Server 类型都匹配 XSD 中的一个复杂类型。每个适用于一个或多个 SQL Server 类型的参数集都有一个复杂类型。请注意,这不是一对一的对应关系,因为许多 SQL Server 类型共享相同的参数集。但是,我已尽可能尝试使参数具体化和验证。例如,这是 XSD 中的int类型。

<xs:complexType name="int">
  ...
  <xs:attribute name="default" use="optional" type="xs:int"/>
</xs:complexType>

请注意,default属性(将转换为生成 SQL 中列的字面默认值)被强类型化,仅允许int值。编辑文件时,这将有助于快速发现复制粘贴错误。其他类型的default属性尽可能被强类型化。有时这需要定义进一步的简单类型;例如,SQL Server 中的money类型存在一些内置 XSD 类型无法表达的限制。

<xs:simpleType name="moneydefault">
  <xs:restriction base="xs:decimal">
    <xs:totalDigits value="10"/>
    <xs:fractionDigits value="4"/>
    <xs:minInclusive value="-922337203685477.5808"/>
    <xs:maxInclusive value="922337203685477.5807"/>
  </xs:restriction>
</xs:simpleType>

<xs:complexType name="money">
  <xs:attributeGroup ref="withDefaultExpression"/>
  <xs:attribute name="default" use="optional" type="moneydefault"/>
</xs:complexType>

在这里,moneyDefault基于decimal。这意味着decimal类型的所有限制都适用,但更多限制也适用,例如 SQL Server 中money类型的最小和最大有效值。

有时,默认列值是表达式而不是“字面量”。这通过defaultExpression属性来表示,它只是一个字符串。在适当的时候,default将在生成的 SQL 中被引用;但是,defaultExpression是按原样复制的。对单个列同时使用defaultdefaultExpression没有意义。

在上面的money类型的 XSD 中,您可以看到我使用了一个名为withDefaultExpressionattributeGroup元素。这为money类型添加了defaultExpression属性。属性组很有用,因为它允许您模块化设计和重用代码。您可以通过继承实现类似的效果,但 XSD 不支持多重继承,因此属性组是模拟它的好方法。

结论

如果您不熟悉 XSD,那么其中一些可能没有太多意义。如果是这样,我很抱歉,但重要的是您实际上不需要知道 XSD 如何工作就可以从中受益。如果您创建一个包含 XSD 和单个 XML 文件的 Visual Studio 项目,然后将第一个示例复制粘贴到 XML 中,那么您应该会发现,当您更改示例时,Visual Studio 会告诉您是否犯了错误。或者,您可以解压缩示例代码并使用我之前制作的解决方案。显然,这不会让您满意太久,因为您现在还不能真正用它做什么。但在下一篇文章中,我将介绍我如何开发了一个映射到新文件格式的 C# 代码模型,使您能够以简单且类型安全的方式以编程方式检查它。

© . All rights reserved.