第 4 章:存储

 

简介
第 1 章:“长角”应用模型
第 2 章:生成“长角”应用程序
第 3 章:控件和 XAML

第 4 章:存储

布伦特校长
Wise Owl Consulting

2004 年 1 月

更新:尽管此内容中可能有说明,但“WinFS”不是 Longhorn 操作系统附带的功能。但是,“WinFS”将在将来的某个日期在 Windows 平台上提供,这就是为什么继续提供本文来提供信息的原因。

目录

什么是 WinFS?
WinFS 编程模型
使用 WinFS API 和 SQL
总结

在某些方面, 个人计算机 的名称不充分。 大多数人不使用个人计算机进行计算。 他们使用计算机通过电子邮件或即时消息) (进行通信,并存储和组织其个人数据 (,如电子邮件、文档、图片和数字音乐) 。 遗憾的是,虽然计算机目前存储的数据非常出色,但它在允许你整理信息以便以后找到这些信息方面做得相对较差。

在过去十年中,磁盘容量以每年约 70% 的速度增长。 目前,可以购买存储超过 250 GB (GB) 的驱动器。 未来几年可能会有 500 GB 的驱动器可用,并且许多系统将有多个磁盘驱动器。 我刚刚在编写本章的计算机上快速检查,在仅 200 GB 磁盘空间的 114,129 个文件夹中有 283,667 个文件。 当我忘记文件放置位置时,可能需要相当长的时间才能再次找到它。 在最坏的情况下,我必须搜索每个磁盘的全部内容。 几年后,人们将能够存储数百万个文件,其中大多数,如果没有什么改进,他们再也看不到了。

人们难以在计算机上查找信息的一个原因是用户组织数据的能力有限。 目前对文件夹和文件的文件系统支持最初效果很好,因为它是大多数人熟悉的范例,并且文件数量相对较少。 但是,它并不容易允许您存储您的同事 Bob 在 2007 年当地公园的公司野餐中打垒球的图像,并在搜索文档时找到该图像:

  • 提及 Bob
  • 涉及运动
  • 与公司事件相关
  • 与公园或其周边地区相关
  • 创建于 2007 年

如果要以多种方式对数据进行分类,分层文件夹结构效果不佳。 因此,我们今天有一个问题,因为我们有很多东西要存储,没有好的方法对其进行分类。 除了对信息进行分类(许多人将其与向数据附加一组固定关键字相关联)外,人们还需要关联数据。 例如,我可能想要将图片与公司野餐相关联,或者我想将图片关联到 Bob,Bob 也是我作为联系人向其捐赠时间和精力的组织的成员。

另一个问题是,我们将相同内容以多种格式存储在多个位置。 开发人员花费大量时间和精力为日常信息(例如人员、地点、时间和事件)创建自己独特的存储抽象。 例如,Microsoft Outlook 具有联系人的定义。 Microsoft Windows 通讯簿也有其自己的联系人定义。 每个即时消息应用程序都有另一个。 每个应用程序将其联系人的定义存储在唯一的隔离信息孤岛中。

当前数据存储方法存在许多问题,包括:

  • 开发人员反复重塑基本数据抽象。
  • 多个应用程序无法轻松共享通用数据。
  • 相同的信息位于多个位置。
  • 用户重复输入相同的信息。
  • 单独的数据副本变得不同步。
  • 没有数据更改通知。

什么是 WinFS?

WinFS 是 Longhorn 中的新存储系统。 它通过三种方式改进了 Microsoft Windows 平台。 首先,它允许你以多种方式对信息进行分类,并将一个信息项与另一个信息项相关联。 其次,它为日常收集的信息提供通用的存储格式,例如处理人员、地点、图像等的信息。 第三,它促进跨多个供应商的多个应用程序共享通用信息的数据。

WinFS 是一个存储平台

WinFS 是用于组织、搜索和共享各种信息的活动存储平台。 此平台定义了一个丰富的数据模型,允许你使用和定义存储平台可以使用的丰富数据类型。 WinFS 包含许多描述真实实体的架构,如图像、文档、人员、地点、事件、任务和消息。 这些实体可能相当复杂。 例如,一个人可以有多个姓名、多个物理地址和电子邮件地址、当前位置等。

独立软件供应商 (ISV) 还可以定义自己的新数据类型,并为 WinFS 提供其架构。 通过允许 WinFS 管理复杂的存储问题,ISV 可以专注于开发其独特的应用程序逻辑,并利用 WinFS 更丰富的存储设施来获取其日常和自定义数据。

WinFS 包含一个关系引擎,可用于使用功能强大的关系查询查找存储类型的实例。 WinFS 允许你使用关系以有意义的方式合并这些存储实体。 一个联系人可以是组织的“员工”组的成员,同时是特定地址的“家庭”组的成员。 ISV 自动获得在其唯一数据类型以及预定义的 Windows 数据类型之间搜索、复制、保护和建立关系的能力。

此结构允许用户向系统提出问题,并要求其查找信息,而不是要求系统单独搜索文件夹。 例如,可以要求 WinFS 查找即时通讯伙伴列表中没有电话号码的人员的所有电子邮件。 使用关系查询,可以查找特定员工在当月过生日的所有家庭成员。

WinFS 还支持多种灵活的编程模型,这些模型允许你为任务选择适当的应用程序编程接口 (API) 。 可以通过使用结构化查询语言的传统关系查询 (SQL) 来访问存储。 或者,可以使用 .NET 类和对象来访问数据存储。 还可以在数据存储上使用基于 XML 的 API。 WinFS 还支持通过传统的 Microsoft Win32 文件系统 API 访问数据。 甚至可以混合和匹配,即对单个任务使用多个 API。 但是,在大多数情况下,开发人员将使用托管类 API 更改 WinFS 存储中的数据。 与使用对象 API 相比,使用原始 SQL 语句进行更新通常要复杂得多。

此外,WinFS 提供一组用于监视、管理和操作数据的数据服务。 可以注册以在特定数据项更改时接收事件。 可以计划 WinFS 将数据复制到其他系统。

WinFS 是一个文件系统

对于基于文件的传统数据(如文本文档、音轨和视频剪辑),WinFS 是新的 Windows 文件系统。 通常,将文件(文件流)的main数据存储为 NTFS 卷上的文件。 但是,每当调用更改或添加具有 NTFS 文件流部分的项的 API 时,WinFS 将从流中提取元数据,并将元数据添加到 WinFS 存储。 此元数据描述有关流的信息,例如其路径,以及 WinFS 可以从流中提取的任何信息。 根据文件内容,此元数据可以是文档) 的作者 (、音频文件) 的流派 (、PDF 文件中) (关键字等。 WinFS 同步 NTFS 驻留文件流和 WinFS 驻留元数据。 新的 Longhorn 应用程序还可以选择将其文件流直接存储在 WinFS 中。 可以使用现有的 Win32 文件系统 API 或新的 WinFS API 访问文件流。

WinFS 不仅仅是一个文件系统

文件系统管理文件和文件夹。 虽然 WinFS 管理文件和文件夹,但它还管理所有类型的非基于文件的数据,例如个人联系人、事件日历、任务和电子邮件。 WinFS 数据可以是结构化的、半结构化的或非结构化的。 结构化数据包括一个架构,该架构还定义了数据的用途以及应如何使用这些数据。 由于 WinFS 在一定程度上是关系系统,因此它在语义、事务和约束方面强制实施数据完整性。

WinFS 也不仅仅是一个关系系统。 它同时支持分层存储和关系存储。 它支持将数据作为结构化类型和对象(类型加行为)返回。 可以将 WinFS 视为分层、关系型、面向对象的数据存储系统,尽管它实际上包含每个传统存储系统的某些方面。 WinFS 超越了传统的文件系统和关系数据库系统。 它是最新 Windows 平台上所有类型的数据的存储。

WinFS 和 NTFS

可以将文件存储在传统的 NTFS 文件系统或新的 WinFS 数据存储中,就像可以将文件存储在 FAT32 中、CD-ROMs或 NTFS 中一样。 通常,存储在 NTFS 中的文件在 WinFS 中不可见。 使用新 WinFS API 的 Longhorn 应用程序可以访问存储在 WinFS 或 NTFS 中的数据。 此外,Longhorn 应用程序可以继续使用 Win32 API 访问存储在 NTFS 文件系统中的数据。

文件提升

文件是否位于 WinFS 中。 具有文件流部件的任何项都可以参与提升/降级,我们通常称之为 元数据处理。 WinFS 升级文件时,它会从已知的 NTFS 文件内容中提取元数据,并将元数据添加到 WinFS 数据存储。 文件的实际数据流保留在 NTFS 文件系统中。 然后,可以查询有关元数据的 WinFS,就像文件本机驻留在 WinFS 中一样。 WinFS 还会检测 NTFS 文件中的更改,并根据需要更新 WinFS 数据存储中的元数据。

文件导入和导出

还可以将文件从 NTFS 导入到 WinFS,并将文件从 WinFS 导出到 NTFS。 导入和导出文件可移动文件内容和元数据。 导入或导出后,新文件完全独立于原始文件。

WinFS 编程模型

WinFS 编程模型包括数据访问、数据操作、WinFS 数据类扩展性、数据同步、数据更改通知和事件优先级。 数据访问和数据操作允许创建、检索、更新和删除存储在 WinFS 中的数据,并执行特定于域的行为。 数据类扩展性使你能够使用自定义字段和自定义类型扩展 WinFS 架构。 数据同步允许在 WinFS 存储之间以及 WinFS 与非 WinFS 存储之间同步数据。

WinFS 数据模型层次结构的顶部是 WinFS 服务,它只是 WinFS 的实例。 服务层次结构中的一个级别是 。 卷是项的最大自治容器。 每个 WinFS 实例都包含一个或多个卷。 在卷中是

WinFS 将项作为一致性和操作的新单元引入,而不是文件。 存储系统存储项。 你对项具有丰富的查询能力。 项实际上是存储系统的基类型。 因此,项具有一组数据属性,并提供基本的查询功能。

人员通常根据一些在给定域中有意义的系统来组织现实世界中的数据。 所有此类系统将数据分区为命名组。 WinFS 使用 文件夹的概念对此概念进行建模。 文件夹是一种特殊类型的项目。 有两种类型的文件夹: 包含文件夹虚拟文件夹

包含文件夹是包含指向其他项目的链接的项,并模拟文件系统文件夹的常见概念。 只要至少有一个包含链接的项引用它,项就存在。 请注意,包含文件夹不直接包含文件夹中逻辑存在的项,而是包含指向这些项目的链接。 这允许多个包含文件夹 包含 同一项。

虚拟文件夹是项的动态集合。 它是一组命名的项。 可以显式枚举集,也可以指定返回集成员的查询。 查询指定的虚拟文件夹非常有趣。 向存储中添加符合虚拟文件夹查询条件的新项时,新项目将自动成为虚拟文件夹的成员。 虚拟文件夹本身是一项。 从概念上讲,它表示一组指向项目的非扣留链接,如图 4-1 所示。

图 4-1。 WinFS 数据模型层次结构

有时,你需要对高度约束的包含概念进行建模,例如,从某种意义上说,电子邮件中嵌入的 Microsoft Word文档比文件夹中包含的文件更紧密地绑定到其容器。 WinFS 通过使用 嵌入的项来表达此概念。 嵌入项是引用另一项的项 (名为 Embedded Link) 中的一种特殊链接。 引用的项只能在包含项的上下文中绑定到或以其他方式操作。

最后,WinFS 提供 类别 的概念作为对项进行分类的一种方式。 可以将一个或多个类别与 WinFS 中的每个项相关联。 实际上,WinFS 会将类别名称标记到项上。 然后,可以在搜索中指定类别名称。 WinFS 数据模型允许定义类别层次结构,从而启用类似树的数据分类。

组织信息

所有这些功能共同允许通过五种方式在 WinFS 中组织信息:

  • 基于文件夹的分层组织。 使用此方法时,仍具有传统的分层文件夹和项组织结构。 WinFS 数据存储中的所有项都必须驻留在容器中,其中一种容器类型是文件夹。
  • 基于类型的组织。 项始终属于特定类型。 例如,你有人员项目、照片项目、组织项目和许多其他可用类型。 甚至可以创建新类型并将其存储在 WinFS 数据存储中。
  • 基于项属性的组织。 可以查看将一个或多个属性设置为指定值的项。 实际上,这是一个虚拟文件夹视图,其中包含一个查询,该查询返回具有指定属性的指定值的项目。
  • 基于关系的组织。 可以根据项目与其他项的关系检索项,例如,人员可以是组织的成员,可以根据此关系来组织或搜索其中一个。
  • 基于类别的组织。 可以创建任意数量的用户定义的关键字并将其与项相关联。 随后,可以检索具有关联关键字 (keyword) 特定值的项。 但是,你将无法创建分类分类,因此此组织技术不像前面的方法那样强大。

WinFS API

WinFS 提供三个数据访问 API:托管的 WinFS API、ADO.NET API 和 Win32 API。 WinFS API 是一种强类型的“高级”API。 ADO.NET 提供了一个较低级别的 API,用于将数据作为 XML 或表或行处理。 使用 ADO.NET,可以使用Transact-Structured查询语言 (T-SQL) 访问存储在 WinFS 中的数据,并在需要时使用 T-SQL 的 FOR XML 功能检索 XML 中的数据。 Win32 API 允许访问存储在 WinFS 中的文件和文件夹。

你可能更喜欢使用多种访问模式来解决问题。 例如,可以发出 T-SQL 查询,该查询将一组联系人作为 WinFS 联系人类型的托管对象返回。 无论使用哪种 API,每个 API 最终都会使用 T-SQL 操作 WinFS 存储中的数据。

在许多情况下,你更喜欢使用托管 WinFS API。 这些.NET Framework类自动执行在面向对象的编程构造之间进行转换所需的对象关系映射,并执行必要的 T-SQL 来实现 WinFS 数据访问。

使用托管 WinFS 类

WinFS 托管类驻留在 System.Storage 命名空间及其嵌套命名空间中。 许多应用程序还将使用命名空间中的 System.Storage.Core WinFS 类型定义。 此外,还可以使用更专用命名空间中的类型。 例如,操作 Contact 的系统定义的托管类驻留在 命名空间中 System.Storage.Contact 。 为简单起见,本章中的所有代码示例都将使用以下一组 using 声明:

using System.Storage;
using System.Storage.Core;
using System.Storage.Contact;

ItemContext

WinFS 存储由组织到文件夹和分类的项组成。 使用 WinFS 的第一步是标识要使用的项集。 我们调用此过程 绑定,项集可以是以下任一项:

  • 整个卷 (也称为 根文件夹)
  • 给定卷中项的可识别子集,例如,特定包含文件夹或虚拟文件夹
  • 单个项
  • WinFS 共享 (,用于标识卷、文件夹、虚拟文件夹或单个项)

若要绑定到一组项,请创建一个 System.Storage.ItemContext 对象并将其连接到 WinFS 数据存储。 使用静态 System.Storage.ItemContext.Open 帮助程序方法创建 ItemContext 对象。

以下代码创建一个 ItemContext 连接到默认本地 WinFS 卷的 。 默认值为 \\local-computer-name\DefaultStore 共享:

System.Storage.ItemContext ctx = System.Storage.ItemContext.Open ();
§
ctx.Close();

或者,可以将字符串传递给构造函数,以将项上下文连接到特定的 WinFS 存储。 以下代码创建一个项上下文,该上下文连接到由 \\machine\Legal Documents 共享标识的 WinFS 共享:

ItemContext ctx = null;
try {
ctx = ItemContext.Open (@"\machine\Legal Documents");
  §
}
finally {
  if (ctx != null) ctx.Dispose();
}

请确保在完成上下文对象的使用后立即关闭或释放上下文对象,而不考虑异常。 使用 ItemContext 大量非托管资源(例如与存储的连接),应及时释放这些资源。 为了尽可能方便结束上下文,类 ItemContext 实现了 IDisposable 接口。 因此,可以使用 C# using 语句(如以下示例中所示)释放这些资源:

using (ItemContext ctx = ItemContext.Open (@"D:\MyStore")) {
§
}

在 WinFS 数据存储中存储新项

WinFS 数据存储中的每个项都必须是存储区文件夹的成员。 通过调用命名非常良好的静态方法 获取文件夹层次结构的根。System.Storage.Folder.GetRootFolder 但是,也有几个系统定义的容器用于存储特定于应用程序的数据。 通常使用 类上的 UserDataFolder 静态方法之一来检索一个文件夹,然后将新项目放入其中。

获取文件夹

在以下示例中,我将找到当前用户的“个人联系人”文件夹(如果存在),并在不存在时创建该文件夹。 请注意,这是一个有点阴谋的示例(如果用户首次登录到系统时,系统会自动创建用户的“个人联系人”文件夹,如果该文件夹不存在),但它使我有机会演示如何在不存在时创建预期文件夹。

ItemContext ctx = ItemContext.Open ();
WellKnownFolder contactsFolder =
          UserDataFolder.FindUsersWellKnownFolderWithType (ctx,
                         GeneralCategories.PersonalContactsFolder);

if (contactsFolder == null) {
    //create the Personal Contacts folder
    Folder userDataFolder = UserDataFolder.FindMyUserDataFolder (ctx);
    WellKnownFolder subFolder = new WellKnownFolder (ctx);
    CategoryRef category = new CategoryRef (ctx,
                            GeneralCategories.PersonalContactsFolder);

    // Associate the PersonalContactsFolder category to the folder
    subFolder.FolderType = category;
    userDataFolder.AddMember (subFolder);
    ctx.Update();
}

前面的代码执行许多有趣的操作。 首先,我尝试查找用户个人数据文件夹层次结构中包含的现有文件夹。 我不是以已知名称查找文件夹。 相反,我在用户的个人数据树中查找文件夹,该文件夹以前已与已知类别 PersonalContactsFolder相关联。 选择“我的联系人”时,shell 会显示此文件夹。

此文件夹通常已存在,但如果不存在,则检索用户数据层次结构的根文件夹。 创建类型 WellKnownFolder为 的新项,然后创建对已知类别 (类别)的 PersonalContactsFolder 引用。 然后,将新文件夹的类型设置为 PersonalContactsFolder 类别类型,最后,将新文件夹添加到其包含文件夹-用户的个人数据根文件夹。 WinFS 不会保存对数据存储的任何更改,直到你调用 Update 项上下文 (我经常忘记) 。

当然,这是查找“个人联系人”文件夹的详细方法。 我想告诉你事情是如何工作的。 通常,我将改用以下代码。 方法 FindMyPersonalContactsFolder 查找现有文件夹。

WellKnownFolder userDataFolder =
         UserDataFolder.FindMyPersonalContactsFolder (ctx);

创建新项

由于我现在有“个人联系人”文件夹,因此在文件夹中创建新联系人似乎很合适。 在以下示例中,我将创建多个 Person 联系人并将其添加到 文件夹中:

Person[] CreateFriends (ItemContext ctx) {
  string[] GivenNames = { "Monica", "Rachel", "Chandler",
                          "Joey",   "Phoebe", "Ross"};
  string[] SurNames = { "Uchra",    "Emerald",  "Ranier",
                         "Fibonacci", "Smorgasbord", "Uchra"};
  Person[] Friends = new Person [GivenNames.Length];
  
  for (int index = 0; index < GivenNames.Length; index++) {
    string linkName = GivenNames[index] + " " + SurNames[index];
    Person p = Person.CreatePersonalContact (ctx, linkName);
    Friends[index] = p;

    p.DisplayName = linkName;
    FullName fn = p.GetPrimaryName ();
    fn.GivenName = GivenNames[index];
    fn.Surname = SurNames[index];
  }
  ctx.Update ();
}

前面的代码使用静态 Person.CreatePersonalContact 方法。 此方法

  • 在指定的项上下文中创建新的 Person 项
  • 使用引用 Person 的指定名称创建一个新 FolderMember 关系
  • FolderMember 关系添加到 PersonalContactsFolderRelationship 集合

随后,我更新了 DisplayNamePerson 项的 、 GivenNameSurname 属性。 与往常一样,我在项上下文上调用 Update ,以将更改保存到数据存储区。

让我们更仔细地看看 CreatePersonalContact 方法。 它等效于以下命令:

// Find the PersonalContacts folder
WellKnownFolder contactsFolder =
           UserDataFolder.FindUsersWellKnownFolderWithType (ctx,
                             GeneralCategories.PersonalContactsFolder);
// Create a new Person item
Person p = new Person (ctx);

// Need a folder relationship that references the new Person
FolderMember fm = new FolderMember (p, linkName);
folder.Relationships.Add (fm);
ctx.Update ();

关系项

WinFS 定义了一个关系数据模型,使你能够将项彼此关联。 定义数据类型的架构时,可以将零个或多个关系定义为架构的一部分。 例如,Folder 架构定义 FolderMember 关系。 组织架构定义关系 Employee 。 对于每个此类定义的关系,都有一个表示关系本身的类。 此类派生自 类, Relationship 包含特定于关系类型的成员。 还有一个强类型的“虚拟”集合类。 此类派生自 VirtualRelationshipCollection 并允许创建和删除关系实例。

关系将源项与目标项关联。 在前面的示例中,“个人联系人”文件夹是源项,“人员”项是目标项目。 关系 FolderMember 基本上指示“人员”项与作为文件夹成员的个人联系人文件夹相关。

定义关系时,可以定义该关系是使目标项存在( 保持关系)还是使目标项不存在( 引用关系)。 创建与目标项的保留关系时,WinFS 会递增目标项的引用计数。 当 WinFS 删除保留关系时,它会递减目标项上的引用计数。 当项的引用计数达到零时,该项不再存在于存储中。 创建或销毁与目标的引用关系时,WinFS 永远不会更改目标的引用计数。 因此,当目标项的引用计数达到零并且关系可能引用不再存在的项时,目标项可能会从存储中消失。

WinFS 将 FolderMember 关系定义为持有关系。 大多数其他关系类都是引用关系。

文件夹项

现在,你已了解“链接项”,我可以优化对文件夹项的描述。 文件夹是具有链接项集合的 WinFS 项。 集合中每个链接项的目标都是 文件夹的成员。 属性 Folder.Members 表示此链接集合。

请注意,这与传统文件系统文件夹相比,WinFS 文件夹具有更大的灵活性。 文件夹的成员可以是文件项和非文件项。 指向特定项的多个链接可以同时驻留在多个文件夹中。 换句话说,多个文件夹可以 包含 同一项。

其他项类型

通常,在 WinFS 存储中创建其他项类型,就像在前面的示例中所做的那样。 每种类型偶尔都有其自己的特殊使用模式。 例如,我们可以将组织作为“个人联系人”文件夹的成员,因此让我们创建一个:

Organization cp = FindOrCreateOrganization (ctx, "Main Benefit");
§
Organization FindOrCreateOrganization (ItemContext ctx, string orgName) {
  Organization o =
    Organization.FindOne (ctx, "DisplayName='" + orgName + "'");
  if (o == null) {
    Folder Pcf = UserDataFolder.FindMyPersonalContactsFolder (ctx);

    o = new Organization (ctx);
    o.DisplayName = orgName;

    Folder pcf = UserDataFolder.FindMyPersonalContactsFolder (ctx);

    pcf.AddMember (o, o.DisplayName.ToString ());
    ctx.Update ();
  }
  return o;
}

现在,让我们向该组织添加一名员工:

enum Names { Monica, Rachel, Chandler, Joey, Phoebe, Ross }
§
Person[] Friends = CreateFriends (ctx);
Organization cp = FindOrCreateOrganization (ctx, "Main Benefit");
AddEmployeeToOrganization (ctx, Friends [(int)Names.Rachel],
  cp);
§
void AddEmployeeToOrganization (ItemContext ctx, Person p, Organization o) {
  EmployeeData ed = new EmployeeData (ctx);

  ed.Name = p.DisplayName;
  ed.Target_Key = p.ItemID_Key;
  o.Employees.Add (ed);
  ctx.Update ();
}

同样,我们可以在“个人联系人”文件夹中创建家庭。 请注意,家庭并不意味着家庭。 一个家庭可能是一群室友。 WinFS 为家庭提供了其他架构,但我会将其保留为读者练习。

CreateHousehold (ctx, Friends [(int) Names.Chandler],
                      Friends [(int) Names.Joey]);
CreateHousehold (ctx, Friends [(int) Names.Monica],
                      Friends [(int) Names.Rachel]);
§
void CreateHousehold (ItemContext ctx, Person p1, Person p2) {
  Household h = new Household (ctx);
  h.DisplayName = p1.GetPrimaryName().GivenName + " and " +
                  p2.GetPrimaryName().GivenName + " household";

  Folder pcf = UserDataFolder.FindMyPersonalContactsFolder (ctx);
  pcf.AddMember (h, h.DisplayName.ToString ());

  // Add first person to the household
  HouseholdMemberData hhmd = new HouseholdMemberData (ctx);
  hhmd.Name = p1.DisplayName;
  hhmd.Target_Key = p1.ItemID_Key;
  h.HouseholdMembers.Add (hhmd);

  // Add second person to the household
  hhmd = new HouseholdMemberData (ctx);
  hhmd.Name = p2.DisplayName;
  hhmd.Target_Key = p2.ItemID_Key;
  h.HouseholdMembers.Add (hhmd);
}

前面的示例使用了一个我尚未讨论的概念。 请注意在 ItemID_Key 以下代码行中使用 属性:

  hhmd.Target_Key = p1.ItemID_Key;

基本上, ItemID_Key 值是另一种引用 WinFS 存储中项的方法,因此让我们看一下在存储区中查找项的方法。

如何查找项

当然,如果在数据存储区中放置项无法轻松找到它们,则不会有什么好处。 类 ItemContext 包含可用于检索 WinFS 数据存储中的项的实例方法。 指定要查找的项类型以及返回的项必须满足的任何特殊约束。 此外,每个项类(例如 、PersonFileFolder等)还包含静态方法,可用于查找该特定类型的项。

方法 FindAll 返回一个或多个与指定条件匹配的项。 实例 ItemContext.FindAll 方法要求指定要查找的项的类型。 此外,还可以选择指定搜索条件以缩小搜索范围。 例如,以下代码查找具有 DisplayName 其值以“Brent”开头的属性的所有 Person 项。

FindResult res = ctx.FindAll (typeof(Person), "DisplayName='Brent%'");
foreach (Person p in res) {
    // Use the Person item somehow
}

或者,可以使用 类的静态 FindAll 方法, Person 如下所示:

FindResult res = Person.FindAll (ctx, "DisplayName='Brent%'");
foreach (Person p in res) {
    // Use the Person item somehow
}

在这两个示例中, FindAll 方法始终返回与类型和指定条件匹配的项的集合。 此集合可能不包含任何项,但你不会收到 针对 的 FindResult空引用。 因此,始终循环访问集合以获取找到的项。

如果知道只有单个项将匹配请求的类型和指定的筛选条件,则可以使用 FindOne 方法。 但是,请注意 , FindOne 方法在找到与请求匹配的多个项时引发异常。

Person p = Person.FindOne (ctx, "DisplayName='Brent Rector'");

第二个字符串参数是一个筛选器表达式,可用于指定返回的项必须满足的其他约束。 筛选器表达式的基本格式是格式为“”<propertyName> <operator> <propertyValue>的字符串。

WinFS 将表达式 OPath 调用表达式。 语法与用于标识 XML 文档中的项的 XPath 表达式语法相似,但并不完全相同。 此代码片段返回文件扩展名为“doc”或“txt”的文件的所有文件项:

FindResult Files = File.FindAll (ctx, "Extension='doc' || Extension='txt'");

这些表达式可能相当复杂。 例如,以下语句返回代表“主要权益”的雇主 DisplayName 雇员的所有 Person 项目:

string pattern = "Source(EmployeeOf).DisplayName='Main Benefit'";
FindResult result = Person.FindAll (ctx, pattern);

这是另一个。 我想要姓氏不是“Ranier”且电子邮件地址不以“.edu”结尾的 Person 项。

string filter = "PersonalNames[Surname!='Ranier'] &&
                 !(PersonalEmailAddresses[Address like '%.edu'])");
FindResult result = Person.FindAll (ctx, filter);

标识特定项

经常需要创建对 WinFS 存储中项的引用。 最终,使用这些引用来查找相应的项。 在本章前面部分中,我介绍了如何使用链接引用项。 链接使用基于字符串的友好标识作为引用,并且此字符串名称在链接的包含文件夹中必须是唯一的。 换句话说,需要文件夹及其包含的链接之一来标识引用的项。

但是,只要将链接添加到不同的文件夹,就可以创建多个具有相同友好字符串名称的链接,以便单个文件夹中的所有名称保持唯一。 请注意,这些具有相同友好文本名称的多个链接实际上不必引用相同的目标项。 他们可以,但他们不必。

在这种情况下,使用 FindAll搜索具有特定友好文本名称的所有链接 (,例如,) 将返回多个结果。 然后,需要检查每个链接的源以确定包含的文件夹,然后确定哪个链接引用了所需项。

我们需要一种方法来引用存储中的任何任意项,例如,假设我想要应用商店中的第 3,287 个项。 幸运的是,你可以完全做到这一点。

值ItemID_Key 查找项

WinFS 为每个新创建的项分配一个基于 GUID 的标识号,称为其 ItemID_Key 属性。 实际上,值很可能在所有 WinFS 卷中是唯一 ItemID_Key 的;但是,WinFS 仍然将此标识符视为仅在卷中是唯一的。 可以使用此卷唯一值来标识 WinFS 卷中的任何项。

Item GetItem (ItemContext ctx, SqlBinary itemID_Key) {
   // Convert itemID_Key to a string for use in the OPath filter 
   string hexItemID_Key = BitConverter.ToString (itemID_Key.Value);
   hexItemID_Key = "'0x" + hexItemID_Key.Replace ("-", String.Empty) + "'";

   // Build an opath filter expression.
   string query = "ItemID_Key=" + hexItemID_Key;

   return Item.FindOne (ctx, query);
}

常用功能

WinFS API 跨整个数据类范围提供多个功能。 这些功能包括

  • 异步
  • 事务
  • 通知
  • Blob/流支持
  • 游标和分页

异步

WinFS API 允许异步运行查询。 WinFS API 使用 .NET 标准异步编程模型模式。

事务

WinFS 存储是事务存储。 因此,WinFS 允许使用 BeginTransaction对象上的 ItemContextCommitTransactionAbortTransaction 方法对存储进行事务更新,如以下示例所示:

using (ItemContext ctx = ItemContext.Open()) {
  using (Transaction t = ctx.BeingTransaction()) {
    Person p = Person.FindOne (ctx,
        "PersonalNames[GivenName='Chandler' And SurName='Bing']" );
    Household h = Household.FindOne (ctx,
        "DisplayName = 'Chandler and Joey Household'");
    p.PersonalEAddresses.Add (new TelephoneNumber ("202", "555-1234"));
    p.Save ();
    h.Members.Add (p);
    h.Save ();
    t.Commit ();
  }
}

通知

WinFS 通知服务使用短期和长期订阅的概念。 短期订阅将持续到应用程序取消订阅或应用程序退出为止。 应用程序重启后,长期订阅将幸存下来。 WinFS API 观察程序 是一组类,允许应用程序有选择地收到 WinFS 存储中更改的通知,并提供应用程序可以保留的状态信息以支持暂停/恢复方案。

Watcher 可以通知应用程序对 WinFS 对象不同方面的更改,包括以下内容:

  • 项更改
  • 嵌入项更改
  • 项扩展更改
  • 关系更改

当观察程序引发事件时,它会发送带有事件通知的观察程序状态数据。 应用程序可以存储此状态数据,供以后检索。 随后,可以使用此观察程序状态数据向 WinFS 指示你希望接收生成状态后发生的所有更改的事件。

观察程序编程模型还允许禁用添加、修改和删除事件的任意组合。 还可以将其配置为引发初始事件,以模拟添加所有现有项、项扩展、关系等。

WinFS 观察程序设计分为下表中所述的类。

用途/说明
WatcherOptions 用于将初始范围和粒度选项指定为 的类 StoreWatcher
StoreWatcher 用于监视 WinFS 项、嵌入项、项扩展和关系的 quintessential 类
WatcherState 可用于初始化 的不透明对象 StoreWatcher
ChangedEventHandler 定义要调用的事件处理程序的类 StoreWatcher
ChangedEventArgs 作为参数传递给的类 ChangedEventHandler
ItemChangeDetail 为项事件提供精细更改详细信息的基类
ItemExtensionChangeDetail 派生自 ItemChangeDetail 的类,它提供特定于项扩展事件的其他更改详细信息
RelationshipChangeDetail 派生自 ItemChangeDetail 的类,提供特定于关系事件的其他更改详细信息

使用 StoreWatcher 类为 WinFS 存储中的某个项创建观察程序。 当 StoreWatcher 指定项发生更改时,实例将引发事件。 可以指定要watch的项类型和层次结构。 默认情况下,观察程序

  • 不引发初始事件来建立当前状态
  • 监视项和层次结构 (包括) 任何更改的直接子级
  • 引发此项目或整个层次结构中任何子项的添加、删除和修改事件
  • 引发此项或整个层次结构中任何子项的项扩展的添加、删除和修改事件
  • 为此项目或整个层次结构中的任何子级是关系源的关系引发添加、删除和修改事件

由于观察程序默认监视指定项及其后代中的更改,因此可能需要将 指定 WatchItemOnly 为观察程序选项。 以下示例仅监视所定位的 Person 项的更改:

Person p = Person.FindOne (ctx,
            "PersonalNames[GivenName='Rachel' and Surname='Emerald'");
StoreWatcher w = new StoreWatcher ( p, WatcherOptions.WatchItemOnly );

文件夹只是另一个 WinFS 项。 watch文件夹中的更改,就像对人员所做的那样:

Folder f = · · ·
StoreWatcher w = new StoreWatcher (f, <WatcherOptions>);

也可以watch项的指定关系中的更改:

Person p = · · ·
StoreWatcher w = new StoreWatcher (p, typeof(HouseholdMember),
                                   <WatcherOptions> );
w.ItemChanged += new ChangedEventHandler (ItemChangedHandler);
w.Enabled = true;

// Change notifications now arrive until we unsubscribe from the event
  §
// Now we unsubscribe from the event
w.ItemChanged -= new ChangedEventHandler (ItemChangedHandler);
w.Dispose ();
§

// The change notification handler
void ItemChangedHandler (object source, ChangedEventArgs args) {
  foreach (ItemChangeDetail detail in args.Details) {
    switch (typeof(detail)) {
      case ItemExtensionChangeDetail:
        // handle added + modified + removed events for Item Extension
        break;

      case RelationshipChangeDetail:
        // handle added + modified + removed events for Relationship
        break;

      default:
      case ItemChangeDetail:
        // handle added + modified + removed events for Item or Embedded Item
        HandleItemChangeDetail (detail);
        break;
    }
  }
|

void HandleItemChangeDetail (ItemChangeDetail detail) {
  switch (detail.ChangeType) {
    case Added:          // handle added event
      break;

    case Modified:       // handle modified event
      break;

    case Removed:        // handle modified event
                break;
  }
}

Blob 和流支持

在撰写本文时,Blob 和流支持 API 仍在变化。 请查看文档,获取有关如何访问 WinFS 存储中的 Blob 和流的最新信息。

光标和分页

WinFS 类中的各种 Find 方法可以返回 (可能) 大型对象集合。 此集合等效于数据库世界中的行集。 传统的数据库应用程序使用 分页游标 在大型行集中高效导航。 此游标引用单个行 (一个精简游标) 或一组行 (页面游标) 。 其思路是应用程序一次检索一页的行数;它们还可以查明页面中的一行,以便进行定位的更新和删除。 WinFS API 为开发人员提供了类似的抽象,用于处理大型集合。

默认情况下,查找操作在返回的集合上提供只读、可滚动的动态游标。 应用程序可以具有消防软管光标,以获得最佳性能。 消防软管光标是仅向前游标。 应用程序可以一次检索一页行,但下一个检索操作将从后续的行集开始,它无法返回并重新检索行。 从某种意义上说,行从商店流向应用程序,就像消防软管中的水一样,因此得名。

CursorType类中的 FindParameters 属性将允许应用程序在消防软管和可滚动光标之间进行选择。 对于 fire hose 和可滚动游标,应用程序可以使用 类的 FindParameters 属性设置页面大小PageSize。 默认情况下,页面大小设置为 1。

数据绑定

可以在数据绑定环境中将 WinFS 数据类用作数据源。 WinFS 类为单个对象实现 IDataEntity () , IDataCollection 为集合) 接口实现 (。 接口 IDataEntity 向数据绑定目标提供对数据源对象中属性的更改的通知。 接口 IDataCollection 允许确定多态集合中对象的基类型。 它还允许检索 , System.Windows.Data.CollectionManager它浏览集合的数据实体并提供视图 (例如集合的排序顺序或筛选) 。 我在第 5 章中详细介绍了数据绑定。

安全性

WinFS 安全模型基本上通过以下方式向 Principal 上的 Item 授予一组 Rights

  • 安全性设置在 的 Items级别。
  • 可以向 上的安全原则授予一 Item组权限。 此集包括:READ、WRITE、DELETE、EXECUTE () 、CREATE_CHILD、管理和审核的所有项。 (可授予对文件夹项的其他权限。)
  • 用户和应用程序是安全原则。 应用程序权限取代用户权限。 当应用程序没有删除联系人的权限时,无论用户的权限如何,用户都无法通过应用程序删除该联系人。
  • 安全性是使用规则设置的;每个规则都是 , Grant 适用于三元: (<ItemSet, PrincipalSet, RightSet>) 。
  • 规则本身存储为 Items

获取项的权限

每个 WinFS 项类都有一个名为 GetRightsForCurrentUser的方法,该方法返回当前用户对指定项拥有的一组权限(READ、WRITE、DELETE 等)。 此外,方法返回 WinFS 允许用户执行的方法集。

设置项的权限

WinFS 使用特殊的项类型 SecurityRule来存储有关 Items的权限信息。 因此,设置和更改权限与在 WinFS 中操作任何其他 Item 权限没有什么不同。 下面是一个代码示例,演示如何设置对文件夹项的权限:

using (ItemContext ctx = ItemContext.Open("\\localhost\WinFS_C$")) {
  SecurityRule sr = new SecurityRule (ctx);
  sr.Grant = true;
  // set permission on items under folder1 including folder1
  sr.AppliesTo = <folder1's Identity Key>; 
  sr.Condition = acl1;   // a DACL
  sr.Save();
}

扩展 WinFS API

每个内置 WinFS 类都包含标准方法,例如 Find* 和 具有用于获取和设置字段值的属性。 这些类和关联的方法构成了 WinFS API 的基础,使你能够了解如何使用一个类,并通常了解如何使用许多其他 WinFS 类。 但是,尽管标准行为很有用,但每个特定数据类型都需要其他特定于类型的行为。

域行为

除了这些标准方法外,每个 WinFS 类型通常都有一组特定于该类型的唯一域方法。 (WinFS 文档通常将类型定义称为 架构,反映 WinFS 的数据库遗产。) WinFS 将特定于类型的方法称为 域行为。 例如,下面是联系人架构中的一些域行为:

  • 确定电子邮件地址是否有效
  • 给定文件夹,获取文件夹的所有成员的集合
  • 给定项 ID,获取表示此项的对象
  • 给定一个人,获取其在线状态
  • 使用帮助程序函数创建新联系人或临时联系人

Value-Added行为

具有域行为的数据类构成了应用程序开发人员构建的基础。 但是,数据类不可能也不可取地公开与该数据相关的每一种可想见的行为。

可以提供新的类来扩展 WinFS 数据类提供的基本功能。 为此,可以编写一个类,其方法将一个或多个 WinFS 数据类作为参数。 在以下示例中, OutlookMainServicesWindowsMessageServices 是使用标准 WinFS MailMessagePerson 类的假设类:

MailMessage m = MailMessage.FindOne (…);
OutlookEMailServices.SendMessage(m); 
 
Person p = Person.FindOne (…);
WindowsMessagerServices wms = new WindowsMessagerServices(p);
wms.MessageReceived += new MessageReceivedHandler (OnMessageReceived);
wms.SendMessage("Hello");

然后,可以使用 WinFS 注册这些自定义类。 注册数据将与 WinFS 为每个已安装的 WinFS 类型维护的架构元数据相关联。 WinFS 将架构元数据存储为 WinFS 项;因此,可以像更新、查询和检索所有其他 WinFS 项一样对其进行更新、查询和检索。

指定约束

WinFS 数据模型允许对类型进行值约束。 将项添加到存储区时,WinFS 会评估并强制实施这些约束。 但是,有时需要验证输入数据是否满足其约束,而不会造成往返服务器的开销。 WinFS 允许架构/类型作者决定类型是否支持客户端约束检查。 当类型支持客户端验证时,该类型将具有一个验证方法,你可以调用来验证对象是否满足指定的约束。 请注意,无论开发人员是否调用 Validate 方法,WinFS 仍会检查存储中的约束。

使用 WinFS API 和 SQL

WinFS API 使开发人员能够使用熟悉的公共语言运行时 (CLR) 概念来访问 WinFS 存储。 在本章中,我使用以下编码模式进行 WinFS 访问:

  1. 绑定到 ItemContext
  2. 查找所需的项。
  3. 更新项。
  4. 将所有更改保存回存储。

步骤 2 实质上是对存储的查询。 WinFS API 使用基于 OPath 的筛选器表达式语法来指定这些查询。 在许多情况下,对于大多数任务,使用筛选器表达式应该已足够。 但是,在某些情况下,开发人员需要使用 SQL 的全部功能和灵活性。

SQL 中存在以下功能,但在使用筛选器表达式时它们不可用:

  • 聚合 (Group By、、 HavingRollup)
  • 投影 (包括计算选择表达式、distinct、 IdentityColRowGuidCol)
  • 对于 XML
  • Union
  • 选项
  • 右/完全/交叉联接
  • 嵌套选择
  • 联接到非 WinFS 表

因此,WinFS 开发人员必须在 SQLClient API 和 WinFS API 之间无缝转换,在代码中的不同位置使用其中一个或另一个。

使用 SQL 聚合和分组,然后使用 WinFS API

一位小企业主乔想确定他的前 10 位客户是谁,并向他们发送礼品篮。 假设 Customer 是架构化的项类型。 这意味着 ISV 已经为 WinFS 提供了客户类型的架构,因此,这也意味着 WinFS 存储现在可以包含客户项。 客户项具有指向架构化订单项类型的保留链接。 订单项具有行订单的嵌入集合,如下所示:

1. using (ItemContext ctx = ItemContext.Open()) {
2. 
3.  SqlCommand cmd = ctx.CreateSqlCommand();
4.  cmd.CommandText = 
5.   "select object(c) from Customers c inner join (" +
6.     "select top 10 C.ItemId, sum(p.price) " +
7.     "from Customers C" +
8.     "inner join Links L on L.SourceId = C.ItemId" +
9.   "inner join Orders O on L.TargetId = O.ItemId" + 
10.    "cross join unnest(O.LineOrders) " + 
11.    "group by C.ItemId" +
12.    "order by sum(p.price)) t ON c.ItemId = t.ItemId"; 
13.
14.  SqlDataReader rdr = cmd.ExecuteReader();
15. 
16.  GiftBasketOrder gbOrder = new GiftBasketOrder(Ö);
17. 
18.  while (rdr.Read()) {
19.   Customer c = new Customer((CustomerData) rdr.GetValue(0));
20.   // add the customer to gbOrder's recipient collection
21.   gbOrder.Recipients.Add(c);
22.  }
23.
24.  // send the order. The ISV's GiftBasketOrder can easily pull out 
25.  // customer info such as shipping address from the Customer object
26.  gbOrder.Send();
27. }                                             

在本示例的第 1 行中,我打开了系统卷根的上下文。 在行 3 中,我创建了一个 SQL 命令对象,随后使用该对象对 WinFS 存储执行 SQL 查询。 此命令对象重用项上下文使用的连接。 第 4 行到第 12 行构造查询,第 14 行执行查询。 查询按以下方式返回前 10 个客户:第 6 行到第 12 行的 SELECT 语句生成包含每个客户订单总值的分组表:第 12 行的 ORDER BY 子句与第 6 行中的 TOP 10 修饰符相结合,仅选择此分组表中的前 10 个客户。

GiftBasketOrder 是使用 WinFS API Customer 对象的自定义类。 我在第 16 行创建 的 GiftBasketOrder 实例。

第 19 行使用 SQLDataReader 读取返回行集的第一列,并将其 CustomerData 强制转换为 对象。

在 WinFS 中定义新类型 (称为创建新架构) 时,实际上定义了两种类型:托管类和 WinFS 存储的类的永久性格式。 WinFS 始终将 数据 后缀添加到类的名称,以创建存储类型的名称。 因此,例如,当你定义驻留在 WinFS 存储中的新客户类型时,WinFS 会创建并行 CustomerData 的 WinFS 用户定义类型 (UDT) 。

行集的第一列包含存储的 CustomerData 对象。 我将此对象传递给 类的 Customer 构造函数,构造函数从 CustomerData 对象初始化新对象。 此示例是使用存储 UDT 构造 WinFS API 对象的典型示例。

第 24 行将客户添加到 Recipients 的集合中 GiftBasketOrder

最后,我在 Send gbOrder 上使用 方法“发送”此订单。

假设你想要查找 10 年的平均工资 (,) 我的投资组合中每家公司的首席执行官。 使用以下假设:

  • 我有一个名为“我的项目组合中的公司”的文件夹,其中包含“组织”类型的项目。
  • EmployeeData 是一种基于链接的关系,其具有 YearlyEmploymentHistory 年份和该年度的工资。
1. using (ItemContext ctx = ItemContext.Open(@"Companies In My Portfolio")) {
2.
3.  SqlCommand cmd = ctx.CreateCommand();
4.  cmd.CommandText = 
5.   "select avg( Salary ) from Links l cross apply " +
6.   "( select Salary from unnest( convert(" + 
7.   "EmployeeData,l.LinkCol)::YearlyEmploymentHistory )" +
8.   "where Year >= '1993' ) where l.LinkID = @LinkID";
9.
10. SqlParameter param = new SqlParameter ("@LinkID", SqlDbType.BigInt);
11. cmd.Parameters.Add (param);
12. 
13. Folder f = Folder.FindByPath (ctx, ".");
14. 
15. FindResult orgs = f.GetMembersOfType (typeof(Organization));
16. foreach (Organization o in orgs) {
17.   EmployeeData ed = EmployeeData.FindEmployeeInRole (o,
18.               Organization.Categories.CeoRole);
19.   param.Value = ed.Link.LinkID;
20.   SqlDataReader rdr = cmd.ExecuteReader ();
21.   rdr.Read ();
22.   Console.WriteLine ("{0} ${1}",   
23.    ((Person)ed.Target).PersonalNames[0].FullName, rdr.GetFloat(0) );
24.   rdr.Close ();
25. }
26. }

第 1 行为“我的投资组合中的公司”WinFS 共享打开上下文。 第 3 行到第 11 行创建可在文件夹上下文中使用的参数化 SQL 查询。 此查询返回由 参数) 表示 @LinkID 的给定员工 (的平均工资。 第 10 行和第 11 行指定 @LinkID 为 类型的 BigInt参数。 我在示例中稍后的第 20 行执行此查询。

第 13 行获取一个 Folder 对象,该对象表示创建上下文时指定的共享所指示的文件夹。 第 15 行和第 16 行设置了用于遍历此文件夹中对象的集合的 Organization 循环。

对于每个组织,第 17 行获取 EmployeeData CEO 的对象。

第 19 行准备查询,并将 参数的值设置为相应的 LinkID,然后第 20 行执行参数化 SELECT。

第 21 行读取查询结果中的下一行和唯一行,第 22 行和第 23 行打印 CEO 的姓名和 10 年平均工资。

总结

WinFS 数据存储提供比传统文件系统更丰富的数据存储模型。 由于它支持数据、行为和关系,因此很难将 WinFS 分类为文件系统、关系数据库或对象数据库。 这是一个产品中的所有技术。 WinFS 提供了通用的通用信息定义,该信息全局可见,并且可供在 Longhorn 上运行的所有应用程序使用。 应用程序可以利用 WinFS 的查询、检索、事务更新和筛选功能;因此,开发人员花费更少的时间来开发数据访问和存储代码,并花更多的时间处理独特的应用程序功能。

继续学习第 5 章:数据绑定