Dare Obasanjo
Microsoft Corporation

摘要:Dare Obasanjo 介绍了 C-Omega 编程语言,这种语言是由 Microsoft Research 创建的,方法是增加 C# 的构造,使之能够更好地处理诸如 XML 和关系数据等信息。

简介
XML 异军突起成为信息交换的通用语言,一个主要的原因是其不同于以前的数据交换格式,XML 可以很容易地表示严格结构化的表格数据(关系数据或序列化对象)和半结构化数据(office 文档)。前者往往是强类型数据,通常使用 Object<->XML 映射技术进行处理,而后者往往是非类型化数据,通常使用传统的 XML 技术(例如 DOM、SAX 和 XSLT)进行处理。然而,在这两种情况下,开发人员使用传统的面向对象的编程语言处理 XML 时,都会感到有些脱节。

在使用 Object<->XML 映射技术处理强类型 XML 的情况下,编程语言对象与 XML 架构语言(比如 DTD 或 W3C XML 架构)之间会出现“阻抗不匹配”的现象。诸如元素和属性之间的区别、文档顺序以及指定元素选择的内容模型等等,所有这些概念都是 XML 架构语言本身具有的,但在传统的面向对象编程中却没有相应的概念。将 XML 映射到对象时,这些不匹配的方面常常会导致某些歪曲和数据丢失。使用诸如 XSLT 或 DOM 这样的技术处理非类型化 XML 文档时,会出现一些不同的问题。在使用 XSLT 或其他 XML 的特定语言(比如 Xquery)的情况下,开发人员必须系统学习另一种语言,以便有效地处理 XML 以及他们所选择的编程语言。通常,主语言集成开发环境的所有好处(比如编译时检查和智能感知)都无法在处理 XML 时得到利用。在用 API(比如 DOM 或 SAX)处理 XML 的情况下,开发人员常常抱怨说,他们必须编写的代码往往变得很难使用。

当用于执行特定任务的设计模式和应用程序编程接口 (API) 得到越来越广泛的使用时,它们有时会融合到编程语言中。类似 C# 的编程语言已经提升了其他语言中作为设计模式和 API 调用而存在的概念,比如本机字符串类型、使用垃圾回收的内存管理,以及该语言中针对核心构造的事件处理。现在这种演化过程已经开始涉及 XML。当 XML 变得越来越流行时,某些团体已经开始将创建和操作 XML 的构造集成到主流的编程语言中。他们期望让 XML 处理这些编程语言的本机部分,这样使用传统 XML 处理技术的开发人员所面临的一些问题将变得简单。

将 XML 集成到传统编程语言中有两个最值得注意的例子,一个是由 Microsoft Research 创作的 C-Omega(C-Omega),它是 C# 的扩展;一个是由 ECMA International 创作的 ECMAScript for XML (E4X),它是 ECMAScript 的扩展。本文将概述 C-Omega 的 XML 功能,后续的文章会对 E4X 语言进行深入探索。本文首先讨论 C-Omega 中对 C# 类型系统进行的更改,接着介绍添加到 C# 语言中以使之可以更容易地处理关系数据和 XML 数据的运算符。

C-Omega 类型系统
C-Omega 类型系统的目的是,通过创建一种组合所有三种数据模型的类型系统,弥合关系数据、对象数据和 XML 数据访问之间的间隙。C-Omega 类型系统所支持的方法不是将内置 XML 或关系类型添加到 C# 语言中,而是将某些通用更改添加到 C# 类型系统中,使之更有利于对结构化的关系数据和半结构化的 XML 数据进行编程。

C-Omega 中对 C# 进行的许多更改都使之更有利于对强类型 XML(特别是使用 W3C XML 架构约束的 XML)进行编程。XML 和 XML 架构中的一些概念在 C-Omega 中有相似的特征。C-Omega 中有这样一些概念,例如文档顺序、元素和属性(包含多个字段,这些字段具有相同的名称但是具有不同的值)之间的区别,以及为特定字段指定类型选择的内容模型。其中许多概念都是用传统的 Object<->XML 映射技术进行处理的,但这常常会带来很多麻烦。C-Omega 旨在使强类型 XML 编程与传统编程语言中对数组或字符串进行的编程一样简单自然。


C-Omega 中的流类似于 XQuery 和 XPath 2.0 以及 System.Collections.Generic.IEnumerable 类型<T>,它将存在于 .NET Framework 2.0 版中。有了流的存在,C-Omega 就可以将零个或多个项目的排序同源集合概念提升到编程语言构造中。流是 C-Omega 类型系统的基本方面,对 C# 进行的许多其他扩展都依赖于它。

通过将运算符“*”追加到变量声明的类型名中声明流。通常使用迭代程序函数 生成流。迭代程序函数是一种返回值的排序序列(通过使用 yield 语句依次返回每个值)的函数。在产生值时,保持迭代程序函数的状态并允许调用方执行。当下一次调用迭代程序时,它将从上次的状态继续产生下一个值。C-Omega 中的迭代程序函数的工作方式类似于为 C# 2.0 计划的迭代程序函数。C-Omega 中的迭代程序函数与 C# 中的迭代程序函数之间最明显的区别在于,C-Omega 迭代程序返回流 (T*),而 C# 2.0 迭代程序返回枚举器 (IEnumerator)。然而,在与流或枚举器进行交互时,它们在行为上有一些细微的区别。更重要的不同在于,就像 XQuery 中的序列一样,C-Omega 中的流无法包含其他的流。相反,当组合多个流时,结果会被压缩成单一流。例如,将流 (4,5) 追加到流 (1,2,3) 中会产生一个包含 5 项的流 (1,2,3,4,5),而不是 4 项的流 (1, 2, 3, (4, 5))。虽然在 C# 2.0 中可以创建枚举器的枚举器,但是不可以用这样的方式组合多个枚举器。

下面的迭代程序函数返回一个流,它包含 Lord of the Rings 三部曲中的三本书。

 public string* LoTR(){

    yield return "The Fellowship of the Ring";
    yield return "The Two Towers";
    yield return "The Return of the King";

  }

可以使用传统的 C# foreach 循环(如下所示)处理上述函数的结果。

  public void PrintTrilogyTitles(){

    foreach(string title in LoTR())
      Console.WriteLine(title);
  }

C-Omega 流强大的功能在于,可以在流中调用方法,然后将其转换为流中每一项的后续方法调用。下面的方法显示了正在使用的这种功能的示例:

  public void PrintTrilogyTitleLengths(){

    foreach(int size in LoTR().Length)
      Console.WriteLine(size);
  }

以上方法调用将导致 Length 属性的值被 PrintTrilogyTitles() 方法返回的每个字符串调用。以这样一种聚合方式访问流内容属性的能力使得可以通过对象图形编写 XPath 样式的查询。

还有一种应用于所有表达式 构造的概念,它使得可以将匿名方法直接应用于流的每个成员。这些匿名方法可能包含特殊变量 it,该变量被绑定到迭代流的每个连续元素。下面是 PrintTrilogyTitles() 方法(使用应用于所有表达式 构造)的替代实现。

  public void PrintTrilogyTitles(){
 
    LoTR().{Console.WriteLine(it)};
  }

选择类型和可为空值的类型
C-Omega 的选择类型非常类似于 C 和 C++ 编程语言中的联合类型、DTD 中的‘|’运算符,以及 W3C XML 架构中的 xs:choice 元素。下面是一个使用选择类型的类的示例:

public class NewsItem{

    string title;
    string author;
    choice {string pubdate; DateTime date;};
    string body;   
}

在这个示例中,NewsItem 类的实例可以有 System.String 类型的 pubdate 字段,也可以有 System.DateTime 类型的 date 字段,但是两者不能同时存在。应该注意到,C-Omega 编译器强制选择类型的每个字段都有不同的名称,否则在访问该字段时预定的类型会具有二义性。在 C-Omega 中访问选择类型字段的方法不同于 C 和 C++ 中的联合类型。在 C/C++ 中,编程人员必须知道特定联合类型中的值表示什么类型,因为编译器不进行静态类型检查。但编译器静态检查 C-Omega 的联合类型,这使得可以对它们进行如下声明:

  choice{string;DateTime;} x =  DateTime.Now;
  choice{string;DateTime;} y =  "12/12/2004";

然而,还会有一个问题,那就是如何静态声明一个可能依赖于选择类型中并不存在的字段的类型。例如,在上面的示例中,如果用 System.DateTime 的实例初始化变量,那么 x.Length 就不是有效的属性访问,而当用字符串“12/12/2004”进行初始化时,将返回 10。这就是可为空值的类型的用武之地。

在 W3C XML 架构和关系数据库领域中,所有的类型都可以有值为 null 的实例。一些语言(例如 Java)和 C# 的现有版本都不允许将 null 赋值给整型或浮点型的值类型。然而,当使用 XML 或关系数据时,能够声明 null 是某种类型的有效值非常重要。在这些情况下,不希望对值的属性访问导致引发 NullReferenceExceptions。通过将属性访问返回的所有值映射为 null 值,可为空值的类型解决了这个问题。下面是一些使用可为空值的类型的示例。

   string? s = null;
   int? size = s.Length;  // returns null instead of throwing 
                          // NullReferenceException
 
   if(size == null)
     Console.WriteLine("The value of size is NULL");

   choice{string;DateTime;} pubdate =  DateTime.Now;
   int? dateLen  = pubdate.Length; //works since it returns null because
                                   //Length is a property of System.String
   int dateLen2  = (int) pubdate.Length; //throws NullReferenceException

该功能类似(但不同)于 C# 2.0 中的可为空值的类型。C# 2.0 中可为空值的类型是 Nullable 的实例,它包含一个值和一个指示(不论指示值是否为 null)。从本质上讲,这是一些值类型(比如不可为 null 的整型和浮点型)的包装。C-Omega 采取了更进一步的措施,当访问值为 null 的可为空值的类型的字段或属性时,返回 null,而不是引发 NullReferenceException。

匿名结构
匿名结构类似于 W3C XML 架构中的 xs:sequence 元素。匿名结构使得能够建模 C-Omega 中某些以 XML 为中心的概念(比如文档顺序和一个元素可能有多个名称相同但值不同的子元素的事实)。匿名结构类似于 C# 中的常规结构,但有几个关键的不同之处:

1. 匿名结构没有显式类型名。
 
2. 匿名结构中的字段是排序的,这使得可以通过数组索引运算符访问它们。
 
3. 匿名结构中的字段不必有名称。它们可以只有类型。
 
4. 匿名结构可以包含具有相同名称的多个字段。在这种情况下,通过名称访问这些字段将导致返回流。
 
5. 具有相似结构的匿名结构(即相同顺序中的相同成员类型)是兼容的,而且这种结构的变量可以来回赋值。
 

以下示例突出显示了 C-Omega 中匿名结构的多种特征。

  struct{ int; string;
          string; DateTime date;
     string;} x =          new {47, "Hello World",
                 "Dare Obasanjo", date=DateTime.Now,
                 "This is my first story"};
    Console.WriteLine(x[1]);
    DateTime pubDate = x.date;

    struct{ long; string; string;
            DateTime date; string;} newsItem = x;    
    Console.WriteLine(newsItem[1] + " by " + newsItem[2] + " on " + newsItem.date);
   
    struct {string field;
            string field;
       string field;} field3 = new {field="one",
                field="two",
                field="three"};

    string* strField = field3.field;
    //string strField = field3.field doesn't work since field3.field returns a stream   

    struct {int value; string value;} tricky = new {value=10, value="ten"};
    choice {int; string;}* values = tricky.value;    

内容类
内容类是一种使用 struct 关键字将其成员按不同的单元进行分组的类。在某种程度上,内容类类似于 XML 中的 DTD。考虑下面 Books 对象的内容类:

public class Books{
 
  struct{
    string title;
    string author;
    string publisher;
    string? onloan;
  }* Book;     

}

这个示例类似于下面的 DTD:

<!ELEMENT Books (Book*)>
<!ELEMENT Book  (title, author,publisher, onloan?)>
<!ELEMENT title  (#PCDATA)>
<!ELEMENT author (#PCDATA)>
<!ELEMENT publisher (#PCDATA)>
<!ELEMENT onloan (#PCDATA)>

以及下面的 XML 架构:

<xs:schema XMLns:xs="http://www.w3.org/2001/XMLSchema">

  <xs:element name="Books">
    <xs:complexType>
      <xs:sequence minOccurs="0" maxOccurs="0">
   <xs:element name="Book">
     <xs:complexType>
       <xs:sequence>
         <xs:element name="title" type="xs:string" />
         <xs:element name="author" type="xs:string" />
         <xs:element name="publisher" type="xs:string" />
         <xs:element name="onloan" type="xs:string" minOccurs="0"/>
       </xs:sequence>
     </xs:complexType>
   </xs:element>
      </xs:sequence>
    </xs:complexType>
  </xs:element>
</xs:schema>

下面的代码示例显示了如何使用 Books 对象:

using Microsoft.Comega;
using System;

public class Books{
 
  struct{
    string title;
    string author;
    string publisher;
    string? onloan;
  }* Book;     

  public static void Main(){

    Books books = new Books();

    books.Book = new struct {title="Essential.NET", author="Don Box",
        publisher="Addison-Wesley", onloan =  (string?) null};

    Console.WriteLine((string) books.Book.author + " is the author of " +
        (string) books.Book.title);
  }
}

C-Omega 中的查询运算符
C-Omega 将两大类查询运算符添加到 C# 语言中:

• 基于 XPath 的运算符,用于根据名称或类型查询对象的成员变量。
 
• 基于 SQL 的运算符,用于执行包括一个或多个对象数据的投影、分组、联接的复杂查询。
 

基于 XPath 的运算符
有了流以及可以有名称相同的多个成员的匿名结构的存在,即使 C-Omega 中用“.”运算符进行的普通直接成员访问也可以认为是一种查询操作。例如,前一部分的 books.Book.title 操作将返回 Books 类中包含的所有 Book 对象的标题。这等效于 XPath 查询 '/Books/Book/title',它返回包含在 Books 元素中的 Book 元素的所有标题。

通配符成员访问运算符“.*”可以用于检索某种类型的所有字段。该运算符等效于 XPath 查询 child::*,它返回当前节点的主要节点类型的所有子节点。例如,操作 books.Book.* 返回一个流容器,所有 Book 对象的所有成员都包含在 Books 类中。这等效于 XPath 查询 '/Books/Book/*',它返回 Books 元素中包含的 Book 元素的所有子元素。

C-Omega 还支持使用“...”运算符的可传递成员访问,这类似于 XPath 中的 descendent-or-self 轴或“//”缩写路径。操作 books...title 以递归方式返回一个流,其中包含 Books 类(或者其任何内容的成员字段)中包含的所有 title 成员字段。这等效于 XPath 查询 '/Books//title',它返回所有的 title 元素(它们是 Books 元素的子代)。也可以使用可传递成员访问运算符匹配以 '...typename::*' 形式的语法将其限制于特定类型的节点。例如,操作 books...string::* 以递归方式返回一个流,它包含 Books 类(或者其任何内容的成员字段)中包含的 System.String 类型的所有成员字段。这类似于 XPath 2.0 查询 /Books//element(*, xs:string),它匹配 xs:string 类型的 Books 元素的任何子代。

筛选操作可以以相同方式应用于可传递成员访问的结果,谓词可以用于筛选 XPath 查询。与在 XPath 中一样,可以使用放置在查询后的 '[expression]' 运算符将 C-Omega 筛选器应用于查询操作。正如应用于所有表达式 这种情况,筛选器可以包含特殊变量 it,该变量被绑定到迭代流的每个连续元素。下面是一个示例,它查询匿名结构中 System.Int32 类型的所有字段,然后筛选其值大于 8 的结果。

 struct {int a; int b; int c;} z = new {a=5, b=10, c= 15};
 int* values = z...int::*[it > 8];

 foreach(int i in values){
    Console.WriteLine(i + " is greater than 8");
 }

基于 SQL 的运算符
C-Omega 包含 SQL 语言中的许多构造并将其作为关键字。C-Omega 中内置了用投影、筛选、排序、分组和联接进行选择的运算符。SQL 运算符既可以应用于内存中的对象,也可以应用于关系存储,后者可以使用 ADO.NET 进行访问。当应用于关系数据库时,可以通过基础存储将 C-Omega 查询运算符转换为 SQL 查询。在 C-Omega 语言中使用 SQL 运算符的主要优点在于,查询语法和结果可以在编译时检查而不是在运行时检查,同样可以使用传统关系 API 在字符串中嵌入 SQL 表达式。

要用 C-Omega 连接到 SQL 数据库,必须将其公开为托管程序集(即 .NET 库文件),然后才能由应用程序引用。使用 Visual Studio 中的 sql2comega.exe 命令行工具或 Visual Studio 中的 Add Database Schema... 对话框,可以将关系数据库公开给 C-Omega 作为托管程序集。C-Omega 使用 Database 对象来表示由服务器宿主的关系数据库。Database 对象有一个用于数据库中的每个表或视图的公共属性和一个用于数据库中的每个表值函数的方法。要查询关系数据库,必须将表、视图或表值函数指定为一个或多个基于 SQL 的运算符的输入。

下面的示例程序和输出显示了 C-Omega 中使用基于 SQL 的运算符查询关系数据库的一些功能。这个示例中使用的数据库是 Microsoft SQL Server 附带的 Northwind 数据库示例。示例中使用的名称 DB 引用 Northwind.dll 程序集(使用 sql2comega.exe 生成)的 Northwind 命名空间中 Database 对象的全局实例。

using System;
using System.Data.SqlTypes;
using Northwind;

class Test {
   static void Main() {

   // The foreach statement can infer the type of the
   // iteration variable 'row' statically evaluating
   // the result type of the select expression
     foreach( row in select ContactName from DB.Customers ) {
       Console.WriteLine("{0}", row.ContactName);
     }
   }
}

下面的示例程序和输出显示了 C-Omega 中使用基于 SQL 的运算符查询内存中对象的一些功能。

Code Sample
using Microsoft.Comega;
using System;

public class Test{

  enum CDStyle {Alt, Classic, HipHop}

  static struct{ string Title; string Artist; CDStyle Style; int Year;}* CDs =
    new{new{ Title="Lucky Frog", Artist="Holly Holt", Style=CDStyle.Alt, Year=2001},
   new{ Title="Kamikaze", Artist="Twista", Style=CDStyle.HipHop, Year=2004},
   new{ Title="Stop Light Green", Artist="Robert O'Hara", Style=CDStyle.Alt, Year=1981},
   new{ Title="Noctures", Artist="Chopin", Style=CDStyle.Classic, Year=1892},
   new{ Title="Mimosa!", Artist="Brian Groth", Style=CDStyle.Alt, Year=1980},
   new {Title="Beg For Mercy", Artist="G-Unit", Style=CDStyle.HipHop, Year=2003}  
    };

  public static void Main(){

    struct { string Title; string Artist;}* results;
   
    Console.WriteLine("QUERY #1: select Title, Artist from CDs where Style == CDStyle.HipHop");
    results = select Title, Artist from CDs where Style == CDStyle.HipHop;
    results.{ Console.WriteLine("Title = {0}, Artist =  {1}", it.Title, it.Artist); };

    Console.WriteLine();

    struct { string Title; string Artist; int Year;}* results2;

    Console.WriteLine("QUERY #2: select Title, Artist, Year from CDs order by Year");
    results2 = select Title, Artist, Year from CDs order by Year;
    results2.{ Console.WriteLine("Title = {0}, Artist =  {1}, Year = {2}", it.Title, it.Artist, it.Year); };   
  }
}
Output
QUERY #1: select Title, Artist from CDs where Style == CDStyle.HipHop
Title = Kamikaze, Artist =  Twista
Title = Beg For Mercy, Artist =  G-Unit

QUERY #2: select Title, Artist, Year from CDs order by Year
Title = Noctures, Artist =  Chopin, Year = 1892
Title = Mimosa!, Artist =  Brian Groth, Year = 1980
Title = Stop Light Green, Artist =  Robert O'Hara, Year = 1981
Title = Lucky Frog, Artist =  Holly Holt, Year = 2001
Title = Beg For Mercy, Artist =  G-Unit, Year = 2003
Title = Kamikaze, Artist =  Twista, Year = 2004

在 C-Omega 中,许多需要冗长嵌套循环的操作都可以直接使用类似 SQL 的声明性运算符简单地进行处理。下面简要描述了 C-Omega 中包含的 SQL 运算符的主要类别。

投影
选择表达式的投影是紧跟 select 关键字的表达式列表。对 from 子句指定的每一行都执行一次投影。投影的作用是将结果数据行构形为只包含所需列的行。选择命令最简单的形式由四部分组成,一个 select 关键字;后面是一个或多个投影表达式列表,用于标识源的列;其后紧跟一个 from 关键字;然后是一个表达式,用于标识查询的源。下面是一个示例:

    rows = select ContactName, Phone from DB.Customers;
    
    foreach( row in rows ) {
     Console.WriteLine("{0}", row.ContactName);
    }

在这个示例中没有指定选择查询的结果的类型指示器。C-Omega 编译器可以自动推断正确的类型。单个结果行的实际类型是 C-Omega tuple 类型。可以直接使用 tuple 类型(即匿名结构)和星号 (*) 来指定结果类型,以便指定结果流。例如:

   struct{SqlString ContactName;}* rows =
                  select ContactName from DB.Customers;

   struct{SqlString ContactName; SqlString Phone;}* rows =
                  select ContactName, Phone from DB.Customers;

筛选
可以使用三个关键字 — distinct、top 和 where — 中的一个来筛选选择表达式的结果。distinct 关键字用来限制结果行只有唯一值。top 关键字用来限制查询产生的总行数。top 关键字后面是一个常量表达式,它指定返回的行数。还可以创建 distinct top 选择,它限制查询返回的唯一行的总数。where 子句用来指定用于筛选查询源返回的行的布尔表达式。保留表达式求值为 true 的行,而丢弃剩余行。下面的示例显示了正在使用的所有三种筛选运算符:

  select distinct top 10 ContactName from DB.Customers where City == "London";

排序
可以使用 order by 子句对选择表达式的结果行进行分类。order by 子句总是选择表达式的最后一个子句(如果完全指定)。order by 子句由两部分组成,两个关键字 order by;后面是由逗号分隔的表达式列表,它们定义确定顺序的值。列表中的第一个表达式定义具有最高优先级的排序标准。也可能需要指定应该按照升序还是降序对每个表达式进行排序。所有的表达式默认为按照升序排序。下面的示例显示了正在使用的 order by 子句:

   rows = select ContactName, Phone from DB.Customers
            order by ContactName desc, Phone asc;

分组
可以使用 group by 子句和内置的聚合函数跨多行对值进行聚合。group by 子句使得可以指定不同行的实际关联方式,这样就可以将它们组合在一起。然后,可以将聚合函数应用于各个列以计算整个组的值。聚合函数是一种根据一系列输入计算单值的函数;例如计算许多数的总和或平均值。C-Omega 中内置了 6 个聚合函数。它们是 Count、Min、Max、Avg、Sum 和 Stddev。要在查询中使用这些函数,必须首先导入 System.Query 命名空间。下面的示例显示了如何使用 group by 子句和内置的聚合函数。

    rows = select Country, Count(Country) from DB.Customers
      group by Country;

这个示例使用聚合函数来产生所有国家的集合以及每个国家的顾客数。Count() 合计出组中的项数。

可以在 group by 子句后求值的所有子句中使用聚合。即使以前在查询中进行了指定,也要在 group by 子句后对投影列表进行求值。这样做的后果是无法将聚合函数应用于 where 子句。然而,仍然可以使用 having 子句筛选经过分组的行。having 子句的作用与 where 子句一样,唯一的不同之处在于它是在 group by 子句后进行计算的。下面的示例显示了如何使用 having 子句。

    rows = select Country, Count(Country) from DB.Customers
           group by Country
           having Count(Country) > 1;

联接
选择查询可以用于组合来自多个表的结果。SQL 联接是一个或多个表的笛卡尔乘积,其中一个表的每一行要与另一个表的每一行进行配对。全部笛卡尔乘积由所有这样的配对组成。要选择多个应该联接其数据以执行查询的源,from 子句实际上可以包含一个由逗号分隔的源表达式列表,其中每个表达式都有自己的迭代别名。下面的示例将所有的 Customer 行与它们对应的 Order 行进行配对,并产生一个表,列出顾客的名字和定单的配送日期。

   rows = select c.ContactName, o.ShippedDate
            from c in DB.Customers, o in DB.Orders
            where c.CustomerID == o.CustomerID;

C-Omega 还使用对应的关键字支持来自 SQL 领域的更复杂的表联接语法,包括 inner join、left join、right join 和 outer join。有关各种联接的语法说明,可以在 W3Schools tutorial on SQL JOIN 中找到。下面的示例显示了使用 inner join 关键字的选择表达式。

   rows = select c.ContactName, o.ShippedDate
          from c in DB.Customers
          inner join o in DB.Orders
          on c.CustomerID == o.CustomerID;

数据修改
C-Omega 的关系数据访问能力并不限于查询数据。也可以用 insert 命令将新行插入到表中,用 update 命令修改表中现有的行,或者用 delete 命令从表中删除行。

insert 命令是一个表达式,它计算由于执行该命令而成功插入到表中的次数。下面的示例将新的顾客插入到 Customer 表中。

   int n = insert CustomerID = "ABCDE", ContactName="Frank", CompanyName="Acme"
           into DB.Customers;

使用匿名结构而不是直接设置每个字段可以获得相同的效果,如下所示:

   row = new{CustomerID = "ABCDE", ContactName="Frank", CompanyName="Acme"};
   int n = insert row into DB.Customers;

update 命令是一个表达式,它计算由于执行该命令而成功修改的行数。下面的示例显示了如何对城市“London”的所有拼写错误的引用进行全局替换。

  int n = update DB.Customers
          set City = "London"
          where Country == "UK" && City == "Lundon";

也可以忽略 where 子句而修改表中的所有行。delete 命令是一个表达式,它计算由于执行该命令而成功删除的行数。下面的示例删除了 London 的顾客的所有定单。

 int n = delete o from c in DB.Customers
         inner join o in DB.Orders
         on c.CustomerID == o.CustomerID
         where c.City == "London";

大多数使用 insert、update 和 delete 表达式的应用程序也都使用事务来保证对数据库的一次或多次更改的 ACIDity(原子性、一致性、隔离性和持久性)。C-Omega 语言包含 transact 语句,它提升了编程语言功能中初始化和退出事务的概念。transact 语句是一个绑定与数据库事务相关的代码块的语句。如果代码成功执行并试图退出代码块,则事务将自动提交。如果由于引发异常而退出代码块,则事务将自动中止。开发人员也可以使用 commit 和 rollback 处理程序指定,一旦退出事务块,就执行某些用户定义的处理程序。下面的示例显示了正在使用的一些事务:

transact(DB) {

      delete from DB.Customers where CustomerID == "ALFKI";

   }

   commit {

      Console.WriteLine("commited");

   }

   rollback {

      Console.WriteLine("aborted");

   }

在 C-Omega 中使用 XML
在 C-Omega 中,可以使用 XML 语法构造对象实例。在诸如 XQuery 和 XSLT 这样的语言有能力构造元素之后,就可以对此功能进行建模。诸如 XQuery 和 XSLT,XML 这样的语言可以包含用于构造值的嵌入代码。然而,由于 C-Omega 是静态类型,所以在编译时必须知道成员和类型的名称,并且无法进行动态构造。

可以使用许多构造来控制 XML 的形式。可以使用成员声明中的 attribute 修饰符指定将 XML 中的字段视为属性。同样,可以将选择类型和匿名结构视为映射到内容类的 XML 元素的子元素。下面的示例显示了如何初始化 XML 中的内容类。

using Microsoft.Comega;

using System;
public class NewsItem{
  attribute  string title;

  attribute  string author;

  struct { DateTime date; string body; } 
  public static void Main(){
    NewsItem news = 

                      {DateTime.Now}

                      I am the first post of the New Year.

                    ;
    Console.WriteLine(news.title + " by " + news.author + " on " + news.date);

    

  }

}

XML 旨在使构造强类型 XML 的过程更简单。考虑下面的 XQuery 示例(摘自 W3C XQuery 用例文档)。它循环访问包含许多书的书目。对于书目中的每本书,它都列出标题和作者,并在 result 元素中进行分组。

for $b in $bs/book
return
 
   {$b/title}
   {$b/author}
 

用 C-Omega 执行等效的任务的代码如下所示。

foreach (b in bs.book){

  yield return
         {b.title}
         {b.author}
       
}

小结
C-Omega 语言是一种有趣的尝试,旨在为典型的企业开发工作在跨越关系、面向对象和 XML 领域时所涉及的“阻抗不匹配”搭建桥梁。该语言的许多思想已经为学术界和 Microsoft 内部接受,并且在 Anders Hejlsberg 对 C# 3.0 的发展方向所作的评论中得到证明。从那以后,开发人员就可以通过下载 C-Omega 编译器或 Visual Studio.NET 插件来研究用更加面向数据的编程语言进行编程意味着什么。

致谢
非常感谢 Don Box、Mike Champion、Denise Draper 和 Erik Meijer 在我写这篇文章时提供的想法和反馈。

Dare Obasanjo 是 MSN Communication Services Platform 小组的程序经理。他以极大的热情解决 XML 问题,以便构建 MSN Messenger、MSN Hotmail 和 MSN Spaces 小组可以利用的服务器基础结构。

有关本文的任何问题或意见,欢迎张贴到 GotDotNet 上的 Extreme XML 留言板。