使用 SQL Server 数据服务开发功能强大且可扩展的应用程序
2008-10-26 10:09:30 来源:WEB开发网本文使用了以下技术:
SQL Server
本文将介绍以下内容:
SSDS 数据模型
管理实体、容器和颁发机构
创建示例 Web 应用程序
类序列化和反序列化
本专栏基于 SQL Server 数据服务的预发布版本撰写而成。文中包含的所有信息均有可能发生变更。
目录
SSDS 数据模型
构建分类广告系统
添加城市
添加类别
更新和删除实体
添加和删除列表架构
分类 Web 应用程序
类反序列化
使用自定义列表架构
展望未来
Microsoft 数据平台由一组丰富的产品和技术组成,它包括所有数据类型并涵盖了整个领域范围,从一端的非结构化二进制数据到另一端的高度结构化在线分析处理 (OLAP) 多维数据集。虽然这些技术可以解决您将面临的任何应用场合,但通常必须要在时间和硬件资源方面进行一些前期投资,然后才能开始对解决方案进行编码。
对于应用程序开发人员而言,通常很难估计某个应用程序在其整个生命周期过程中所需的存储和处理要求。如果您面对的是新兴的创业公司,其资金比较紧张而且用来制定硬件投资决策的数据不足,则此问题可能尤为明显。为解决有关这些前期投资的问题,Microsoft 最近在其已经很强大的数据平台中又增添了一项新技术:SQL Server® 数据服务 (SSDS)。
SSDS 并非传统意义上的盒装软件产品术语,而是一个强大的、无尺度的数据服务,它在内部使用可靠的 SQL Server 技术并依据行业标准 Web 服务接口来公开其功能。SSDS 可提供易用而灵活的数据模型,可通过开放的行业标准协议对其进行访问。
不论是使用 Microsoft® .NET Framework、Java 还是其他技术进行开发,都可以使用 SSDS 进行数据存储。此外,SSDS 的设计使开发人员能够迅速置备帐户并立即开始对其进行开发。
SSDS 为开发人员提供了一种数据后端,它不受可用驱动器托架或支架空间的限制。通过使用 SSDS 作为数据平台,您的应用程序可以随意使用所需数量的数据。无论是 1GB 字节的存储还是 1PB 字节的存储,您只需支付这些资源的费用即可使用。
许多网站和应用程序都具有循环访问模式,在这种模式下,您需要具有足够的容量来容纳峰值负载(即使这些负载是瞬态的)。通过使用 SSDS 作为数据后端,您可以将精力集中在应用程序上,而不是集中在数据平台容量规划上。
在本文中,我将为您介绍有关围绕 SSDS 开发数据解决方案的基础知识。首先,我将制订 SSDS 使用的数据模型。然后,通过向您展示如何使用 SSDS 构建简单的在线分类广告系统来深入介绍开发细节,具体如图 1 所示。
图 1 SSDS 托管的分类应用程序示例
SSDS 数据模型
SSDS 提供了一种灵活的基于实体的数据模型。此模型包含三个主要元素,即颁发机构、容器和实体,这些统称为 ACE 概念(请参见图 2)。SSDS 颁发机构可与关系型领域的数据库相关。置备了颁发机构后,SSDS 将会为您创建访问该颁发机构的 DNS 名称。例如,如果您置备了一个名为 ssdsdemo 的颁发机构,则可以通过以下 URI 访问它:
复制代码 |
ssdsdemo.data.beta.mssds.com |
图 2基本 SSDS 组件
颁发机构还可以是地理位置单元,这意味着创建的 DNS 名称将会映射到您托管数据的指定 Microsoft 数据中心。如果您创建了两个颁发机构 americasdemo.data.beta.mssds.com 和 europedemo.data.beta.mssds.com,则每一个都将分别与距离用户最近的不同数据中心建立关联。
颁发机构包含容器的集合,而容器则类似于关系型数据库中的表。主要差别在于您需要将架构附加到数据库表中以使表中的所有行都属于同类。SSDS 中的容器不需要架构,因此您可以将异类实体存储在一个方便的位置。它只是一个实体集合。在当前发布的 SSDS 中,所有查询都被控制在单一容器内。
还需重点指出的是,每个容器都位于 SSDS 群集的不同节点中。在 SQL Server 中,要获得额外的性能,应将不同的表放在不同的心轴上以最大程度地发挥读/写功能。这与 SSDS 非常相似,只不过每个容器都将位于单独的计算机中。通过将数据分割到多个容器中并将请求分为多个线程,您可以获得额外的性能提升,因为读和写将不再受到单一计算机的限制。
关于容器最后要注意的一点是,虽然每个容器都被放置在 SSDS 群集中的单独节点上,但出于灾难恢复目的,还应将数据复制到多个其他节点上。如果容器所在的计算机发生故障,系统会自动提升一个备份副本以确保您的应用程序不会损失任何数据或性能。
实体可以看作是关系型数据库中表的一行。实体只是名称/值对的属性包。这些名称/值对可被分组为两个类别:可分辨系统属性和灵活属性。
可分辨系统属性是所有实体共有的,包括 ID、Kind 和 Version 等。ID 用于唯一标识实体。ID 在其所在的容器中必须是唯一的,但不同容器中的实体可以具有相同的 ID。Kind 用于将类似的实体分类在一起。实体上没有附加任何架构,因此具有相同 Kind 的实体并不能保证其结构也相同,Version 用于标识当前的实体版本。此值在每次操作后都会进行更新。
灵活属性是开发人员在其中存储应用程序数据的位置。灵活属性支持简单的类型:String、decimal、bool、datetime 和 binary。每种灵活属性的前 256 个字节都被编制了索引。
构建分类广告系统
为了说明 SSDS 的功能并概要介绍开发体验,我将带您实现一个在线分类系统 Contoso Classifieds 示例。此示例包含两个独立的应用程序:一个是 Windows® Forms 应用程序(用于管理系统),一个是用户将要使用的 ASP.NET 应用程序(Contoso Classifieds 主站点)。此应用程序通过 SOAP 接口(在 Windows Forms 应用程序中)和具像状态传输 (REST) 接口(在 ASP.NET 应用程序中)来访问 SSDS。
注册了 SSDS 帐户后,您将会收到一个用户名和密码。在这里,您可以决定是否开始构建颁发机构、容器和实体。对于 Contoso Classifieds,我将建立颁发机构 contosoclassifieds,在测试版 SSDS 中服务 URI 端点是 data.data.beta.mssds.com。
contosoclassifieds 颁发机构包括两个容器:Categories 和 Cities。Categories 包含与 Listing 类别相关的实体。Cities 包含与系统中定义的每个城市相对应的实体。这些实体只是指向特定城市的颁发机构的指针。各城市特定的颁发机构在每个列表类别中都对应一个容器(请参见图 3)。
图 3 Contoso Classifieds 元素
我之所以采用这种设计是因为考虑到几个原因。我选择按城市来实现颁发机构,以便可以确保颁发机构被置备在从地理位置而言最接近其服务人口的数据中心。我选择每个 listing 类别对应一个容器,这样做不但可以简化查询模式,而且还可以将负载分布到群集中的多个计算机中(请记住,容器会扩展到后端群集中的指定节点上)。图 4 显示了 Contoso Classifieds Administration 客户端的用户界面。
图 4 分类应用程序管理客户端
如前所述,开发人员需要自行决定是否创建应用程序要使用的任何颁发机构、容器和实体。在此示例应用程序中,所有工作都是通过 Perform Initial Setup(执行初始设置)按钮的单击事件实现的。(包括错误处理在内的全部代码都包括在本文的代码下载中。)
置备 SSDS 颁发机构非常简单。在本示例代码中,我使用的是 SitkaSoapServiceClient,先前我曾为它向 SSDS SOAP 接口添加了一个服务引用:
using (SSDSClient.SitkaSoapServiceClient ssdsProxy =
new SSDSClient.SitkaSoapServiceClient())
您已看到了一个 Authority 对象,但 SSDS 的作用域是什么?作用域对象在 SSDS 中被用来提供对象在 SOAP 服务中的寻址方式,类似于 URI 在 REST 服务中的使用方式。
您首先应该做的是设置服务的用户名和密码:
ssdsProxy.ClientCredentials.UserName.UserName =
txtUserName.Text;
ssdsProxy.ClientCredentials.UserName.Password =
txtPassword.Text;
您需要创建一个颁发机构,它应该处于 ACE 模型的最高级别,以便先创建一个空作用域:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
现在创建 Contoso Classifieds 颁发机构。它将容纳系统中的常规配置信息
SSDSClient.Authority contosoAuth =
new SSDSClient.Authority();
contosoAuth.Id = "contosoclassifieds";
并将创建任务提交到 SSDS
ssdsProxy.Create(serviceScope, contosoAuth);
现在已创建了主 Contoso Classifieds 颁发机构并将您的作用域指向了它,接下来要创建容器以容纳要为其提供分类广告的城市并将容器的创建任务提交到 SSDS:
serviceScope.AuthorityId = contosoAuth.Id;
SSDSClient.Container citiesContainer = new SSDSClient.Container();
citiesContainer.Id = "Cities";
ssdsProxy.Create(serviceScope, citiesContainer);
创建容器的过程与创建颁发机构的过程类似。唯一的区别在于您创建的是 Container 对象而非 Authority 对象,而且您更新的是指向颁发机构(要在其中创建容器)的作用域,而非空作用域。
在应用程序中创建容器以容纳您要支持的所有 Header 和 Listing 类别:
SSDSClient.Container categoriesContainer =
new SSDSClient.Container();
categoriesContainer.Id = "Categories";
ssdsProxy.Create(serviceScope, categoriesContainer);
颁发机构创建完毕后,您可以使用 HTTP 或 HTTPS 将 Web 浏览器指向它,而且可以使用 REST 接口查看容器的内容。设置颁发机构时只需使用 SSDS 创建的新 DNS 名称即可。在本例中,URL 为:
http://contosoclassifieds.data.data.beta.mssds.com/v1
将此 URL 输入到浏览器之后,系统将提示您进行身份验证。身份验证一经通过,您即可在浏览器中看到如下内容:
<s:Authority xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:x="http://www.w3.org/2001/XMLSchema">
<s:Id>contosoclassifieds</s:Id>
<s:Version>11</s:Id>
</s:Authority>
颁发机构名称采用的是小写字母,因为在 SSDS 为颁发机构创建 DNS 条目后,您必须遵守 DNS 命名约定。
容器创建完毕后,如果您刷新浏览器并以 ?q="" 形式在 URL 中添加一个空查询,将会看到 EntitySet 中会返回单一的 Container 对象。EntitySet 只是作为查询响应返回的实体的集合。初始设置完成后,示例应用程序将创建一个颁发机构 (contosoclassifieds) 和两个容器(Categories 和 Cities):
<s:EntitySet xmlns:s="http://schemas.microsoft.com/sitka/2008/03/"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:x="http://www.w3.org/2001/XMLSchema">
<s:Container>
<s:Id>Categories</s:Id>
<s:Version>1</s:Id>
</s:Container>
<s:Container>
<s:Id>Cities</s:Id>
<s:Version>1</s:Id>
</s:Container>
</s:EntitySet>
现在初始设置已经完成,让我们继续添加用于城市的功能。
添加城市
如前所述,Contoso Classifieds 将每个城市的列表都放在其本身的颁发机构中,以便您可以选择它所在的数据中心。(试用版 SSDS 当前并不支持此功能,但在产品最终发布时会提供支持。)
用来添加城市的代码与用来进行初始设置的代码相似。使用 SOAP 代理来设置凭据、创建空作用域、设置颁发机构 ID 并发出创建命令:
SSDSClient.Authority cityAuth =
new SSDSClient.Authority();
cityAuth.Id = cityAuthorityName;
ssdsProxy.Create(serviceScope, cityAuth);
至此已创建了新的城市颁发机构,接下来向主 contosoclassifieds 颁发机构中的 Cities 容器添加一个指针实体:
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Cities";
//Create the City Entity, and set its properties appropriately
SSDSClient.Entity cityEntity = new SSDSClient.Entity();
cityEntity.Id = cityAuthorityName;
cityEntity.Kind = "CityServed";
cityEntity.Properties = new Dictionary<string, object>();
cityEntity.Properties["AuthorityUri"] =
string.Format("{0}.data.data.beta.mssds.com/v1/", cityAuthorityName);
cityEntity.Properties["Name"] = txtCity.Text;
//Issue the create to SSDS
ssdsProxy.Create(serviceScope, cityEntity);
让我们简单梳理一下。我已经创建了一个主 contosoclassifieds 颁发机构,用于存储系统范围的所有数据。在该颁发机构中有一个 Cities 容器,应用程序针对的各个城市所对应的实体都包含在其中。该实体包含城市的显示名称以及指向包含所有列表的颁发机构的指针。
添加类别
至此您已添加了颁发机构、容器和实体。下一步是实现列表类别功能。为此需要使用 SSDS 的查询、更新和删除功能。
Contoso Classifieds 将支持类别标题,并使用其下方的子类别来存储用户实际发布的内容。如果打算对此数据使用关系型模型,通常应使用数据的标题/行模式。但是,SSDS 使用的灵活实体数据模型允许您对数据采用您喜欢的任何形式。在 Contoso Classifieds 应用程序中,我选择使用单个实体来表示每个类别标题以及属于它的所有子类别(请参见图 5)。
图 5 应用程序使用的实体数据模型
请注意,我使用此模式只是为了显示实体所具有的灵活本质,并说明尽管多个实体可以具有相同的 Kind,但这并不能使实体属于同类。实体可以采用您希望的任何形式。
对于此方案而言,更合理的方法应该是使用两个不同的 Kind 实体(Category 和 Listing Category),并使 CategoryID 在所有实体中都具有 flex 属性。这将允许您发出如下查询:
from e in entities where e["CategoryID"] == "For Sale" select e
请注意我说的是 flex 属性。如前所述,容器中的实体必须具有唯一的 ID,这可以限制您使用可分辨系统属性 ID。还要注意我发出的查询语法。SSDS 使用类似 LINQ 的查询语法,大多数 .NET Framework 开发人员对它都应该很熟悉。
如果您回过头来看一下图 4 中所示的管理应用程序,会注意到一个表示列表类别的树视图以及两个用于添加类别标题和列表类别的文本框。让我们深入了解一下添加类别标题背后的实现过程。
与上面的代码一样,您使用 SitkaSOAPServiceClient、设置凭据并将作用域设置为 contosoclassifieds 颁发机构和 Categories 容器。现在您只需创建实体即可:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
SSDSClient.Entity categoryHeaderEntity = new SSDSClient.Entity();
categoryHeaderEntity.Id = CategoryID;
categoryHeaderEntity.Kind = "Category";
您还可以添加单个 flex 属性,其中将包含类别名称。请注意,这只会向 flex 属性集合中添加单个属性:
categoryHeaderEntity.Properties =
new Dictionary<string, object>();
categoryHeaderEntity.Properties["CategoryName"] = CategoryName;
下一步是添加列表类别。由于 Contoso Classifieds 在类别标题中使用单个实体来表示整个列表类别集合,因此您需要检索类别实体、添加附加列表类别(作为附加 flex 属性添加)并将更新提交到 SSDS。
要检索类别实体,请将作用域设置为直接指向它然后再调用 Get 方法,这将直接检索实体而不会发出查询:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = CategoryID;
//Retrieve the Category Entity
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
现在只需确定新属性的索引、添加它作为附加 flex 属性并发出更新命令即可(请参见图 6)。
图 6:添加 Flex 属性
//Determine whether this is the first listing
//category being added to this header
if (categoryEntity.Properties.Count > 2) {
propCount = ((categoryEntity.Properties.Count - 1) / 2);
propCount++;
}
else {
propCount = 1;
}
//Create the FlexProperties for the new listing category
string listingIdPropName = string.Format ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format ("ListingCategoryName{0}", propCount);
categoryEntity.Properties[listingIdPropName] = ListingCategoryID;
categoryEntity.Properties[listingNamePropName] = ListingCategoryName;
//Issue the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);
更新和删除实体
更新列表类别与添加附加 flex 属性一样简单。对于每个更新,您都需要检索实体、进行本地更新并提交,以便将更新的实体及作用域传递给 Update 方法。
首先将服务作用域设置为主 contosoclassifieds 颁发机构。同时,将其指向 Categories 容器以及要更新的类别所对应的实体:
SSDSClient.Scope serviceScope = new SSDSClient.Scope();
serviceScope.AuthorityId = "contosoclassifieds";
serviceScope.ContainerId = "Categories";
serviceScope.EntityId = currentHdrNode.Name;
获取要进行更新的实体:
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
现在,您可以重新初始化 flex 属性集合并重新添加列表类别的所有 flex 属性。此外,您本来应该可以对每个 flex 属性进行循环并加以更新,但由于此实体的结构过于简单,因此如图 7 所示重新创建实体会更为容易一些。
图 7 更新属性
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = currentHdrNode.Text;
int propCount = 1;
if (e.Node.Parent != null) {
//Loop through each node and add its category ID and
//category name flex property.
foreach (TreeNode node in e.Node.Parent.Nodes) {
string listingIdPropName = string.Format ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format ("ListingCategoryName{0}", propCount);
categoryEntity.Properties[listingIdPropName] = node.Name;
categoryEntity.Properties[listingNamePropName] = node.Text;
//if we are re-adding the updated category, the tree view
//will still have its old value, so set the flex property
//to the updated value
if (node.Name == e.Node.Name) {
categoryEntity.Properties[listingIdPropName] = m_OldSelectNode.Name;
categoryEntity.Properties[listingNamePropName] = e.Label;
}
propCount++;
}
}
//Submit the update to SSDS
ssdsProxy.Update(serviceScope, categoryEntity);
要删除某个实体或容器,请将 ServiceScope 指向该项目并调用 SitkaSoapServiceClient 的 Delete 方法。如果是 Header,则删除整个实体:
if (m_OldSelectNode.Parent == null) {
ssdsProxy.Delete(serviceScope);
}
对于其他实体,与处理更新时一样,您可以重新创建 flex 属性集合并加以重新构建,然后对已删除节点以外的所有节点执行添加操作。
SSDSClient.Entity categoryEntity = ssdsProxy.Get(serviceScope);
categoryEntity.Properties = new Dictionary<string, object>();
categoryEntity.Properties["CategoryName"] = CategoryName;
int propCount = 1;
foreach (TreeNode node in entityNode.Nodes) {
string listingIdPropName = string.Format ("ListingCategoryID{0}", propCount);
string listingNamePropName = string.Format ("ListingCategoryName{0}", propCount);
if (node.Text != m_OldSelectNode.Text) {
categoryEntity.Properties[listingIdPropName] = node.Name;
categoryEntity.Properties[listingNamePropName] = node.Text;
propCount++;
}
}
最后,将更新命令发给 SSDS 并从树视图中删除相应节点:
ssdsProxy.Update(serviceScope, categoryEntity);
tvCategories.Nodes.Remove(m_OldSelectNode);
添加和删除列表架构
试用版 SSDS 当前不支持架构。虽然计划在未来加入对架构的支持,但其实在 SSDS 中自行添加架构是一项很简单的任务。唯一需要注意的是您必须从应用程序中管理架构,因为此时 SSDS 不会强制执行架构。
对于 Contoso Classifieds,由管理员针对某些列表类别定义自定义的架构是一种不错的方法。您可以在管理客户端内定义架构,随后在实现网站时使用它。
截至现在,同一容器内没有任何异类实体。由于每个实体都具有 Kind 属性,因此您可以利用这一点将不同的实体类型存储在同一容器中,并可以查询特定类型(在本例中为 Kind)的实体。虽然没有与 Kind 关联的架构约定,但利用此机制很容易将相似的数据进行分组。现在,我将新实体 Kind ListingSchema 添加到 Categories 容器中。
添加列表架构的对话框非常简单,如图 8 所示。管理员可以使用它来定义与某个列表关联的附加字段,也可以将某些字段标识为必填字段。稍后我将对此进行详细介绍,同时为您演示如何实现实际的网站。
图 8 自定义列表架构
用于添加 ListingSchema 实体的过程与您刚刚看到的过程相同,但这一次将使用不同的实体 (Kind)。图 9 显示了如何添加 ListingSchema 的代码。针对 For Sale Cars 类别添加了列表架构之后,可注意到 Categories 容器的内容包括两个不同的实体类型,而且同一容器中的数据完全不同。
图 9 添加架构
listingSchemaEntity.Kind = "ListingSchema";
listingSchemaEntity.Properties = new Dictionary<string, object>();
listingSchemaEntity.Properties["ListingID"] = m_ListingID;
listingSchemaEntity.Properties["ListingName"] = m_ListingName;
int propCount = 0;
bool required = false;
foreach (DataGridViewRow row in dataGridView1.Rows) {
if (row.IsNewRow) continue;
propCount++;
listingSchemaEntity.Properties[string.Format ("Property{0}Name",propCount)] =
row.Cells["colFieldName"].Value.ToString();
listingSchemaEntity.Properties[string.Format ("Property{0}DataType", propCount)] =
row.Cells["colDataType"].Value.ToString();
if (row.Cells["colRequired"].Value == null) {
required = false;
}
else {
required = Convert.ToBoolean(row.Cells ["colRequired"].Value.ToString());
}
listingSchemaEntity.Properties[string.Format ("Property{0}Required", propCount)] = required;
}
if (propCount > 0) {
using (SSDSClient.SitkaSoapServiceClient ssdsProxy = new SSDSClient.SitkaSoapServiceClient()) {
//Set username and password for the service
...
//Set service scope to the main contoso classifieds
//authority. Point it to the categories container
...
//If this entity doesn't exist, create it
if (listingSchemaID == "" || listingSchemaID == null) {
ssdsProxy.Create(serviceScope, listingSchemaEntity);
}
//Otherwise update it
else {
serviceScope.EntityId = listingSchemaID;
ssdsProxy.Update(serviceScope, listingSchemaEntity);
}
}
MessageBox.Show("Custom Schema Saved", "Custom Schema Saved",
MessageBoxButtons.OK, MessageBoxIcon.Information);
this.Hide();
}
管理客户端的最后一项工作是在列表类别被删除时删除自定义列表架构。我之前讨论过删除实体和容器,但那时我知道要删除的实体或容器的 ID。删除架构意味着首先要发出 LINQ 查询以检索附加到列表类别的所有列表架构,然后再将其删除。查询内容如下所示:
string.Format(@"from e in entities
where e.Id == ""{0}"" select e", CategoryID);
此查询将返回 ListingSchema 实体的列表,此时您需要做的就是将其删除。图 10 显示了完整的 DeleteListingSchema 方法。
图 10 DeleteListingSchema
//Retrieve the custom schema entity for this listing
string linqQuery = string.Format(
@"from e in entities where e.Id == ""{0}"" select e", CategoryID);
List<SSDSClient.Entity> entities =
ssdsProxy.Query(serviceScope, linqQuery);
foreach (SSDSClient.Entity entity in entities) {
//If more than 1 flex property on the entity, the
//header has listing categories attached to it.
if (entity.Properties.Count > 1) {
//Calculate the number of Listing Categories
int propCount = ((entity.Properties.Count - 1) / 2);
for (int x = 1; x < propCount + 1; x++) {
//Delete the listing schema for each listing
//in this category
DeleteListingSchema(entity.Properties["ListingCategoryID" +
x.ToString()].ToString());
}
}
}
此时,Contoso Classifieds Administration 客户端已完成,我可以继续使用 SSDS 的 REST 接口来实现网站。
分类 Web 应用程序
如前所述,您可以通过 SSDS 的 REST 接口和 SOAP 接口对其进行访问(就像我对 Contoso Classifieds Administration 客户端所做的那样)。无论选择哪个接口,概念和功能都是相同的,但使用 REST 会直接调用 HTTP(或 HTTPS)。
在开始之前,先回顾一下图 1 中所示的 Web 应用程序主界面。有一个代表列表类别的树视图、一个主内容区域以及一个由当前受应用程序支持的所有城市组成的 Datalist。仔细看一下各个部分,我将向您展示基于 SSDS 来开发 ASP.NET 应用程序是多么容易。
如果您看一下树视图的 ASPX 代码,就会发现它其实非常简单:
<h3>Groups</h3>
<asp:TreeView ID="TreeView1" runat="server"
onselectednodechanged="TreeView1_SelectedNodeChanged">
</asp:TreeView>
奇妙的事情发生在隐藏的代码中,如图 11 所示。您面对的第一件事情就是构建 URI,即指向 Categories 容器的指针。由于此数据并不是特定于城市的,因此我选择将其存储在主 contosoclassifieds 颁发机构中的 Categories 容器内。
图 11 构建树视图
TreeView1.Nodes.Clear();
appDataUri = string.Format(@"http://{0}.{1}{2}",
conSSDSAuthName, conSSDSUri, "Categories");
query = @"from e in entities where e.Kind == ""Category"" select e";
UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));
string xmlResults =
HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(),
new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));
XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//Category");
int nodeIndex = 0;
foreach (XmlNode node in nodeList) {
if (node.ChildNodes.Count > 3) {
int propCount = ((node.ChildNodes.Count - 1) / 2);
TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText,
node.ChildNodes[0].InnerText);
TreeView1.Nodes.Add(tn);
for(int x=1;x<propCount;x++) {
tn = new TreeNode(node.ChildNodes[(x*2)+2].InnerText,
node.ChildNodes[(x*2)+1].InnerText);
TreeView1.Nodes[nodeIndex].ChildNodes.Add(tn);
}
}
else {
TreeNode tn = new TreeNode(node.ChildNodes[2].InnerText, node.ChildNodes[0].InnerText);
TreeView1.Nodes.Add(tn);
}
nodeIndex++;
}
之后,我继续使用 LINQ 查询来检索具有 Kind 类别的所有实体,然后再使用 UriBuilder 对象将其放在一起并添加必要的 HTTP 转义。接下来,我调用 GetHTTPWebRequest 方法,并使用 SSDS 用户名和密码传入转义的 URI 和 NetworkCredential 对象。GetHTTPWebRequest 返回字符串形式的 EntitySet 以及满足查询谓词的所有实体。
GetHTTPWebRequest 是一种静态辅助方法,可隐藏某些 HTTP 细节:
WebRequest request =
HttpWebRequest.Create(Uri.EscapeUriString(serviceUri));
request.Credentials = requestCredential;
request.Method = "GET";
request.ContentType = XmlContentType;
// Get the response and read it in to a string.
using (HttpWebResponse response =
(HttpWebResponse)request.GetResponse()) {
return ReadResponse(response);
}
现在需要做的就是创建一个新 WebRequest 对象、设置传递给该对象的凭据、设置相应的 HTTP 动词、将 ContentType 设置为 XML 以及调用 GetResponse 方法。接下来调用 ReadResponse,这是另一个静态辅助方法,它使用流读取器读取 HTTPResponse 并将其返回给调用方。隐藏的代码随后将获取返回的 XML、将其加载到 XmlDocument 中并加载其中的树视图。
类反序列化
在此之前,代码一直只是手动操作 XML,但也可以很容易地获取实体并将其反序列化为一个类。在本例中,应用程序将使用 CityServed 类。
基本 Entity 类是一个相当初级的类。该类同时包含 ID 和 Version 的属性,而这些属性为所有实体所共有。此外,它还包含序列化类时所必需的一些属性。
CityServed(如图 12 所示)继承自基本 Entity 类。泛型 Query 方法将返回 CityServed 类型实体的列表:
foreach (CityServed i in HTTPHelper.Query<CityServed>(
appDataUri, query, new System.Net.NetworkCredential(
conSSDSUsername, conSSDSPassword)))
图 12 CityServed
[XmlRoot(ElementName = "CityServed", Namespace = "")]
public class CityServed : Entity {
[XmlElement(ElementName = "AuthorityUri")]
public object AuthorityUriField;
[XmlElement(ElementName = "Name")]
public object NameField;
public override string ToString() {
return Name;
}
[XmlIgnore]
public string AuthorityUri {
get { return (string)AuthorityUriField; }
set { AuthorityUriField = value; }
}
[XmlIgnore]
public string Name {
get { return (string)NameField; }
set { NameField = value; }
}
}
Query 方法包括在本文的代码下载中,它只是一种泛型静态辅助方法,用于调用我们之前使用过的 GetHTTPWebRequest 方法。图 13 中显示了 Serialize 和 Deserialize 方法。Query 方法与 Serialize 和 Deserialize 方法相结合,允许您使用强类型化 .NET 对象并将其保存在 SSDS 中。
图 13 序列化和反序列化实体
private static T Deserialize<T>(Stream stm, string xmlPayload) {
XmlSerializer ser = new XmlSerializer(typeof(T));
T flex = (T)ser.Deserialize(stm);
XmlDocument xDom = new XmlDocument();
xDom.LoadXml(xmlPayload);
return flex;
}
public static T Deserialize<T>(String xmlPayload) {
using (MemoryStream stm = new MemoryStream()) {
Encoding encoding = new UTF8Encoding(false);
stm.Write(encoding.GetBytes(xmlPayload), 0, encoding.GetByteCount(xmlPayload));
stm.Position = 0;
return Deserialize<T>(stm, xmlPayload);
}
}
public static string Serialize<T>(T flex) {
using (MemoryStream stm = new MemoryStream()) {
Serialize(stm, flex);
stm.Position = 0;
using (StreamReader reader = new StreamReader(stm)) {
return reader.ReadToEnd();
}
}
}
private static void Serialize<T>(Stream stm, T flex) {
XmlSerializer ser = new XmlSerializer(typeof(T));
Encoding encoding = new UTF8Encoding(false);
XmlWriterSettings settings = new XmlWriterSettings();
settings.CloseOutput = false;
settings.ConformanceLevel = ConformanceLevel.Document;
settings.Encoding = encoding;
settings.Indent = true;
settings.OmitXmlDeclaration = true;
using (XmlWriter writer = XmlWriter.Create(stm, settings)) {
ser.Serialize(writer, flex);
}
}
使用自定义列表架构。
如前所述,SSDS 实体非常灵活,因为每个实体都可以具有您需要的任何形式。例如,Contoso Classifieds Administration 客户端为您提供了向列表类别添加架构的能力。您还可以从 SSDS 中抽出自定义架构并动态创建可用来向系统中添加列表的输入窗体。
第一步创建包含以下四列的 DataTable:Field、Value、DataType 和 Required。此表将存储管理员为列表定义的字段。接着我将执行查询以了解是否已为列表定义了自定义架构:
string appDataUri = string.Format(@"http://{0}.{1}{2}",
conSSDSAuthName, conSSDSUri, "Categories");
string query = string.Format(
@"from e in entities where e[""ListingID""] == ""{0}"" select e",
listingCategoryID);
UriBuilder newUri = new UriBuilder(appDataUri);
newUri.Query = String.Format("q='{0}'", Uri.EscapeDataString(query));
string xmlResults = HTTPHelper.GetHTTPWebRequest(newUri.Uri.ToString(),
new System.Net.NetworkCredential(conSSDSUsername, conSSDSPassword));
XmlDocument categoriesDoc = new XmlDocument();
categoriesDoc.LoadXml(xmlResults);
XmlNodeList nodeList = categoriesDoc.SelectNodes("//ListingSchema");
然后我可以将返回的 XML 加载到 DataTable 中并将其绑定到 DataList。
在 REST 接口中,您需要构建实体的 XML 表示并向要将实体插入其中的容器发出 HTTP POST。构建实体非常容易,您只需使用样板实体并对每个字段执行循环,然后将其作为节点添加到 XML 文档中即可:
foreach (DataListItem fieldItem in dlAddFields.Items) {
Label lblField = (Label)fieldItem.FindControl("lblAddField");
TextBox tbItem = (TextBox)fieldItem.FindControl("txtAddValue");
Label lblFieldType = (Label)fieldItem.FindControl("lblAddFieldType");
entity = entity + String.Format(
@" <{0} xsi:type='x:{1}'>{2}</{0}>", lblField.Text.Replace(" ", ""),
lblFieldType.Text, tbItem.Text);
}
最后,构建 URI 以指向要执行 POST 操作的容器并发出 POST:
string serviceUri = string.Format(@"http://{0}.{1}{2}",
postingAuthority, conSSDSUri, postingContainer);
HTTPHelper.PostHTTPWebRequest(serviceUri, entity,
new System.Net.NetworkCredential(
conSSDSUsername, conSSDSPassword));
展望
SSDS 很容易进行开发。我想重申的一点是:SSDS 是构建在可靠的 SQL Server 和 Windows Server® 技术之上的。团队决定仅在 SSDS 的初始试用版中公开有限的功能集,实际上其功能远不止这些。现在可供使用的功能子集将帮助解决客户需要处理的许多问题。
此外,您有望看到对 SSDS 的支持被添加到 SQL Server 产品和工具套件的其他产品中。这样,作为一名开发人员,无论您是使用 C#、Visual Basic®、Java、Ruby,还是使用 Microsoft Office Access®,自带开放协议支持的 SSDS 都是您存储应用程序数据的理想选择。要了解更多内容,请访问 SSDS 网站 microsoft.com/sql/dataservices 并注册下载试用版。如果您需要了解本产品的其他附加功能,请通过 david.robinson@microsoft.com 与我联系。
David Robinson 是 Microsoft SQL Server 数据服务团队的高级项目经理。他的任务是向产品中加入一些引人注目的新功能。他还喜欢参加社区活动,并且非常希望收到有关 SSDS 的客户反馈。
- ››使用脚本恢复WinXP系统的用户登录密码
- ››SqlCommand对象
- ››SqlDataAdapter用法
- ››使用phpMyadmin创建数据库及独立数据库帐号
- ››使用Zend Framework框架中的Zend_Mail模块发送邮件...
- ››使用cout标准输出如何控制小数点后位数
- ››使用nofollow标签做SEO的技巧
- ››使用 WebSphere Message Broker 的 WebSphere Tra...
- ››SQL分页方法存储过程和游标存储过程
- ››SQL Server事件探查器的提示和技巧
- ››SQL Server高级性能调优策略
- ››使用SQL Server事件探查器做应用程序的性能分析
更多精彩
赞助商链接