销售订单示例的实现细节
本主题介绍 Adventure Works 销售订单示例应用程序的一些实现细节和设计决策。此示例是 Microsoft SQL Server 附带的,用于说明以编程方式实现服务器到客户端的合并复制方案的优点。有关详细信息,请参阅销售订单合并复制示例自述文件。
体系结构概述
此应用程序本身是一个使用 Microsoft Visual Studio 2005 开发的基于 Microsoft Windows 窗体的托管客户端。此客户端应用程序使用 Microsoft .NET Framework 2.0 的数据访问工具来访问 SQL Server(所有版本)的本地实例中的销售订单数据。为了在销售人员的计算机和总部后方的主 AdventureWorks 数据库之间维护一组一致的数据,实现了一个合并复制拓扑,并对这些数据进行了分区,以便每位销售人员仅收到为其客户提供支持所需的数据。合并发布是使用复制存储过程以编程方式配置的。首次启动销售订单应用程序时,它会使用 SQL Server 管理对象 (SMO) 以编程方式创建本地数据库,并使用复制管理对象 (RMO) 以编程方式定义合并请求订阅。此订阅是使用 RMO 以编程方式进行维护和监视的。
复制功能
通过使用合并复制拓扑,销售人员在拜访客户时或在旅行期间,无需连接到 AdventureWorks 数据库即可在本地处理他们的销售数据。销售人员会定期连接到 Internet,以将销售订单更改上载到发布服务器并将销售订单更改和更新后的产品信息下载到订阅服务器。此示例的合并复制拓扑已设计为支持运行任意类别的 SQL Server 2005 或更高版本的多个订阅服务器,其中包括 SQL Server Express 和 SQL Server Compact 3.5 SP1。在与移动用户交换数据中更加详细地讨论了这种类型的合并复制拓扑。
参数化行筛选器
在现实操作中,应根据 Employee.LoginID 列中的登录信息使用 SUSER_SNAME 函数对属于每位销售人员的销售数据进行水平筛选。通过筛选可以提高性能,减小初始快照的大小,降低订阅服务器之间发生数据更改冲突的可能性以及简化应用程序逻辑。但是,在示例应用程序中,使用 SUSER_SNAME 函数进行筛选是不合理的。在此示例中改用 HOST_NAME 函数对数据进行分区。利用复制,您还可以重载 HOST_NAME 函数以指定用于对数据进行分区的任何筛选条件;此外,此示例使用 WHERE HumanResources.Employee.LoginID = HOST_NAME
子句定义用于 Employee 表项目的水平筛选器。联接筛选器用来将此参数化行筛选器扩展到其他与销售相关的表。
重要提示: |
---|
如果您使用包含 HOST_NAME() 的参数化筛选器,则需要注意一些安全方面的事项。有关详细信息,请参阅参数化行筛选器中的“使用 HOST_NAME() 筛选”部分。 |
按照此示例中使用的项目筛选选项,您必须为发布的每个订阅提供一个不同的 Hostname
值。以下 Employee.EmployeeID 值可以用在此示例中的 Hostname
键中:
adventure-works\michael9
adventure-works\pamela0
adventure-works\tsvi0
adventure-works\shu0
adventure-works\rachel0
adventure-works\lynn0
在更改现有订阅的 Hostname
值之前,您必须分别在订阅服务器和发布服务器上执行下面的 Transact-SQL 脚本,然后重新运行此示例以重新创建订阅。
准备分区快照
发布配置为允许订阅服务器在初始化订阅时请求生成其分区快照。但是,您也可以在初始化前生成这些快照,从而使得在预定时间可以预测到这种处理的开销。有关详细信息,请参阅如何为带有参数化筛选器的合并发布创建快照(复制 Transact-SQL 编程)。
架构选项
项目的架构选项决定了将数据库对象复制到订阅服务器上的方式。有关详细信息,请参阅发布数据和数据库对象。此示例中使用的架构选项取决于订阅服务器上需要的对象类型和功能。
可编程性对象
- 对于数据访问存储过程和用户定义函数,仅编写对象创建命令(如 CREATE PROCEDURE 和 CREATE FUNCTION)的脚本。
这相当于 @schema_option 的值为 0x00001。
表对象
- 表对象是在订阅服务器上创建的。
- 复制表的聚集索引和非聚集索引。
- 复制检查约束和 FOREIGN KEY 约束。
- 复制唯一键。
- 在订阅服务器上会将用户定义数据类型转换为基类型。
- 不复制表的用户定义触发器。
- 创建订阅服务器上尚不存在的架构。
这相当于常规 @schema_option 的值为 0x8004EF1。
订阅状态
此示例调用 SubscriberMonitor 类来显示订阅状态信息。此类作为此示例的一部分实现,同时,在 SubscriberMonitorUtility 项目中,它还作为一个独立监视器实现。有关详细信息,请参阅使用订阅服务器监视器示例。
Web 同步
在创建发布时为发布启用了 Web 同步。但是,若要使用 Web 同步,您必须运行配置 Web 同步向导并在 Web 服务器和订阅服务器上为 SSL 设置证书。此示例在受保护的 SSL 连接上使用 HTTP 基本身份验证,这是针对 Web 同步的建议做法。有关详细信息,请参阅配置 Web 同步。如果在**“Web 同步选项”**窗体中启用了 Web 同步,并且您指定了用于 HTTP 基本身份验证的登录名和密码,则应用程序会将订阅的 UseWebSynchronization 属性设置为 true,并设置 InternetLogin 和 InternetPassword,以使用 HTTPS 协议进行同步。使用此示例时,您必须指定 Windows 帐户和密码才能使用 Web 同步。这些凭据由 Web 服务器在连接到发布服务器时使用。如果合并代理无法使用 HTTPS 连接到位于 InternetUrl 属性所指定位置的 Web 服务器,则会返回错误。
自定义业务逻辑
由于销售订单通常是在销售人员不在线时下的,因此所下的订单有可能订购延期交货的产品。按照公司政策,只有在能够供应订单中的所有项时才会发出此订单订购的货物。如果无法立即供应订单中订购的货物,SalesOrderHeader 表的 Status 列会设置为值 3,以指示整个订单将延期交货。在这些情况下,应用程序必须尽快通知销售人员延期交货情况,这样他们就能通知客户。
销售订单示例实现了 Microsoft.SqlServer.Replication.BusinessLogicSupport 命名空间的 BusinessLogicModule 类,并通过此实现在同步进程中插入了自定义的业务逻辑。对于发送到发布服务器的每个 SalesOrderDetail 表行更改,此自定义逻辑都会连接到发布服务器并对照 ProductInventory 表检查此项的库存量。如果此应用程序确定订购数量超出库存可用总量,它会将 SalesOrderHeader 表的 Status 列设置为 3。然后,此应用程序会向同步日志写入一条描述此延期交货情况的消息。由于复制提供的业务逻辑框架可用来实现 .NET Framework 支持的任何功能,因此可以利用此自定义业务逻辑引发对话框或向客户发送自动电子邮件,要做到这一点也很容易。
注意: |
---|
使用 Web 同步来同步订阅时,订阅服务器上不显示由此业务逻辑处理程序写入日志的消息。 |
此自定义业务逻辑通过实现 InsertHandler 和 UpdateHandler 方法来处理行的插入和更新。Initialize 方法也得到了实现。初始化此自定义业务逻辑时,复制将传递发布服务器信息,此信息用来以编程方式使用 SqlConnectionStringBuilder 类为 SqlConnection 对象生成连接字符串。
同步状态
复制代理可以从代理作业中启动(异步方式)或直接从您的代码中启动(同步方式)。以异步方式运行复制代理的一大优点是能够使用代理的内置回调功能获取和显示代理状态消息。在销售订单示例中,Status 事件(回调)是通过使用 StatusEventHandler 获取并显示所返回 StatusEventArgs 对象中的状态消息来处理的。还会返回一个指示估计的同步完成百分比的整数,此百分比通过进度栏显示给用户。由于后台同步可导致**“订阅状态”窗体中的信息过时,因此销售人员可以单击“刷新”**按钮来加载最新的会话。
连接时同步
销售订单示例显示了合并复制在已断开连接的环境中同步数据方面的优势。但是,在有些情况下(例如在公司总部举行会议期间)可以很方便地使用 Internet 连接。此示例还可以用来在已连接的环境中连续进行同步。在**“高级选项”中的“连接时同步”选项启用时,将启用 Timer 控件。当此计时器达到此应用程序的配置文件中预设的间隔时,将引发计时器事件并且此应用程序会查询 Windows Management Instrumentation (WMI) 提供程序(使用 System.Management 命名空间)来检查计算机是否已连接。如果已连接,则在后台同步订阅。为避免出现数据输入问题,此示例在“销售订单”以外的窗体具有焦点时不允许进行后台同步。由于此示例使用一个在 Microsoft Windows 2000 中不可用的 WMI 属性,因此在运行 Windows 2000 的计算机上“连接时同步”**菜单选项未启用。
注意: |
---|
在此示例中,在单个线程中实现连接时同步。在理想情况下,此功能应在一个单独的线程中实现。当您在一个单独的线程中运行复制代理时,可以考虑使用 BackgroundWorker 类。 |
部署
所有用来访问订阅数据库中数据的编程对象(如存储过程、用户定义类型和用户定义函数)都只在发布服务器上的发布数据库中创建,并作为项目发布到订阅服务器上。在销售人员的计算机上第一次运行销售订单示例时,此应用程序会连接到发布服务器,下载初始快照,然后应用此快照,此快照包含程序的数据访问编程对象。成功应用此快照后,此应用程序会在销售订单窗体中加载本地数据,然后就可以使用此应用程序输入数据了。
利用新的 Visual Studio 2005 ClickOnce 部署功能,您可以将应用程序安装包发布到网站。订阅服务器可以从此 ClickOnce 部署站点下载此应用程序并安装它,然后从现场外位置通过虚拟专用网络 (VPN) 以远程方式初始化它们的订阅,前提是已经安装了 .NET Framework 2.0。ClickOnce 还可以从网站传递服务更新。有关 ClickOnce 部署的详细信息,请参阅 Visual Studio 2005 文档中的“ClickOnce 部署概述”主题。
有关如何安装和运行此示例的分步说明,请参阅销售订单合并复制示例自述文件。
用户界面元素
销售订单示例实现了以下用户界面元素。
元素名称 | 说明 |
---|---|
“销售订单”窗体 |
这是主用户界面元素和此应用程序的入口点。在此窗体上,销售人员可通过从“客户”组合框中选择客户并从“订单”组合框中选择订单来处理订单。单击“编辑”按钮会在“编辑订单”窗体中显示选择进行编辑的订单。单击“新建”按钮会显示一个空白的“编辑订单”窗体。 |
“编辑订单”窗体 |
销售人员可使用此窗体编辑订单。可以在“订单项”网格中编辑行项,并可以在此网格中追加新项,单击“添加”按钮即可保存追加的新项。可以在此网格中选择项,单击“删除”按钮即可将所选项从此网格中删除。处理完订单后,单击“保存”按钮即可将更改提交至数据库。 |
“同步销售数据”窗体 |
调用此窗体可初始化、同步或重新初始化订阅。来自合并代理的状态消息显示在“同步状态”字段中,并且会显示一个指示已完成百分比的进度栏。 |
“合并订阅服务器监视器”窗体 |
此窗体是在一个单独的程序集中实现的。它是从“高级选项”菜单调用的,并使用 MergeSubscriberMonitor 来显示订阅信息。有关详细信息,请参阅使用订阅服务器监视器示例。 |
“Web 同步选项”窗体 |
此窗体是通过在“高级选项”菜单中选择“Web 同步选项”调用的。它用于管理订阅的 Web 同步设置。它不是在应用程序中管理这些设置,而是使用一个 MergePullSubscription 对象来将 Web 同步选项存储在订阅元数据中以及从中检索 Web 同步选项。在此示例中,您必须提供一个 Windows 登录名和密码,它们使用通过 SSL 连接的 HTTP 基本身份验证传递到 Web 服务器。有关详细信息,请参阅本主题后面部分中“示例注意事项”一节中的“Web 同步”。 |
“登录用户”窗体 |
此窗体是在创建订阅时调用的。如果在创建订阅时 CreateSyncAgentByDefault 设置为 true,则必须为 Mergepullsubscription 提供有效的 Windows 登录名和密码。这是必需的,原因是此示例必须创建与代理相关的元数据来存储 Web 同步设置。CreateSyncAgentByDefault 为 false 时,不创建代理作业,且 Web 同步及其他订阅元数据必须由应用程序来维护。有关详细信息,请参阅本主题后面部分中“示例注意事项”一节中的“Web 同步”。对于 SQL Server Express 订阅服务器,尽管此版本不支持 SQL Server 代理且不会创建代理作业本身,仍然会创建这些元数据。 |
“同步”菜单 |
此菜单中的项用于在连接时启动同步会话并启用同步。 |
“高级选项”菜单 |
此菜单中的项用于重新初始化订阅,启用 Web 同步以及显示“合并订阅服务器监视器”窗体。 |
示例注意事项
本节介绍此示例中存在的设计注意事项、能带来负面影响的因素和潜在的问题。
对 SQL Server Compact 订阅服务器的支持
复制拓扑的一个重要设计注意事项是发布支持 SQL Server Compact 3.5 SP1 订阅服务器。由于 SQL Server Compact 3.5 SP1 仅支持字符模式的数据快照,因此发布存在使用本机模式快照时所没有的一些额外限制,当所有订阅服务器都运行其他版本的 SQL Server 时便可以使用本机模式快照。这些限制包括以下方面:
- 在应用快照前后运行 Transact-SQL 脚本。这会允许在订阅服务器上使用 Transact-SQL 创建本地数据库和任何用户定义类型或架构,而无需在运行时加载 SQL Server 管理对象 (SMO) 程序集。
- 计算列。无法复制 AdventureWorks 销售订单架构中的一些键列。这些键列包括 SalesOrderDetail 表中的 LineTotal 以及 SalesOrderHeader 表中的 TotalDue 和 SalesOrderNumber。应用程序必须执行其自己的计算才能向用户显示这些信息。
- 原本可以使用逻辑记录来确保 SalesOrderDetail 表中的销售项在它们所属的 SalesOrderHeader 表中的行不存在时不会发送到发布服务器。但是,在支持 SQL Server Compact 3.5 SP1 订阅服务器时,无法使用逻辑记录。
合并复制性能
由于 AdventureWorks 示例数据库设计为仿效使用 SQL Server 实现的许多生产数据库,因此其架构相当复杂并且其数据高度规范化。这种程度的规范化可以提高依赖索引的操作的性能,但对合并复制性能可能有负面影响。在此示例中,需要用五个联接筛选器将参数化行筛选器从 Employee 表向下扩展到 SalesOrderDetail 表。复制包含一些可优化参数化行筛选器性能的功能,如预计算分区。但是,在存在合并复制性能问题的情况下,您应使已发布的表非规范化,以将联接筛选器数目减少到三个以内。有关详细信息,请参阅增强合并复制性能。
Web 同步
使用户能通过直接连接到此示例体系结构影响的发布服务器在 Web 同步和常规同步之间无缝切换。此示例使用 MergePullSubscription 类来帮助安全地存储和检索 Web 同步所需的属性,这也简化了我们的应用程序逻辑。若要通过 MergePullSubscription 类实现此功能,在创建订阅时必须将 CreateSyncAgentByDefault 属性设置为 true。与 MSsubscription_properties 表中用来设置 MergePullSubscription 对象的这些属性的行一起使用时,它还会为订阅创建一个代理作业。由于此应用程序始终直接启动同步而不使用 SQL Server 代理,因此此作业从未得到使用。对于 SQL Server Express 订阅服务器,不会创建此代理作业,原因是此版本不支持 SQL Server 代理,但仍会创建所需的元数据。在调用 Create 方法且 CreateSyncAgentByDefault 为 true 时,必须提供 SynchronizationAgentProcessSecurity 信息。因此,在创建订阅时会显示**“登录用户”**窗体。这些凭据由 SQL Server 存储,但从未使用。合并代理启动时使用的则是运行此示例的用户的上下文。
此示例设计为对所有服务器连接都使用集成身份验证,在 Web 同步期间到 Web 服务器的订阅服务器连接除外,此连接使用 HTTP 基本身份验证。我们建议不要对 Web 同步使用集成身份验证。在实际应用场景中,用于 Web 同步的 IIS 服务器部署在发布服务器或分发服务器以外的计算机上。由于 Windows 模拟存在限制,这要求向 Web 服务器提供的基本身份验证凭据等效于在发布服务器上使用的登录名。
注意: |
---|
Web 同步不支持仅上载或仅下载同步。启用 Web 同步时,“仅上载数据”菜单项将禁用。 |
用户定义类型
AdventureWorks 包含许多用户定义类型,这些类型在订阅服务器上作为基类型复制。如果这些类型属公共语言运行时 (CLR) 类型,它们将作为 CLR 类型复制。
已发布的 AdventureWorks 对象
以下 AdventureWorks 表、数据访问存储过程和用户定义函数在合并复制拓扑中作为项目发布。
对象名称 | 对象类型 | 备注 |
---|---|---|
Customer |
表 |
有关详细信息,请参阅Customer 表 (AdventureWorks)。 |
CustomerAddress |
表 |
有关详细信息,请参阅CustomerAddress 表 (AdventureWorks)。 |
Employee |
表 |
仅下载类项目。使用基于 LoginID 的参数化查询来对每位销售人员的订阅数据进行分区。有关详细信息,请参阅Employee 表 (AdventureWorks)。 |
Product |
表 |
仅下载类项目。有关详细信息,请参阅Product 表 (AdventureWorks)。 |
ProductCategory |
表 |
仅下载类项目。有关详细信息,请参阅ProductCategory 表 (AdventureWorks)。 |
ProductDescription |
表 |
仅下载类项目。有关详细信息,请参阅ProductDescription 表 (AdventureWorks)。 |
ProductInventory |
表 |
有关详细信息,请参阅ProductInventory 表 (AdventureWorks)。 |
ProductListPriceHistory |
表 |
仅下载类项目。有关详细信息,请参阅ProductListPriceHistory 表 (AdventureWorks)。 |
ProductModel |
表 |
仅下载类项目。有关详细信息,请参阅ProductModel 表 (AdventureWorks)。 |
ProductModelProductDescriptionCulture |
表 |
仅下载类项目。有关详细信息,请参阅ProductModelProductDescriptionCulture 表 (AdventureWorks)。 |
ProductSubcategory |
表 |
仅下载类项目。有关详细信息,请参阅ProductSubcategory 表 (AdventureWorks)。 |
SalesOrderDetail |
表 |
有关详细信息,请参阅SalesOrderDetail 表 (AdventureWorks)。 |
SalesOrderHeader |
表 |
有关详细信息,请参阅SalesOrderHeader 表 (AdventureWorks)。 |
ShipMethod |
表 |
仅下载类项目。有关详细信息,请参阅ShipMethod 表 (AdventureWorks)。 |
SpecialOffer |
表 |
仅下载类项目。有关详细信息,请参阅SpecialOffer 表 (AdventureWorks)。 |
Store |
表 |
有关详细信息,请参阅Store 表 (AdventureWorks)。 |
StoreContact |
表 |
有关详细信息,请参阅StoreContact 表 (AdventureWorks)。 |
ufnGetAccountingStartDate |
标量值用户定义函数 |
AdventureWorks 函数。 |
ufnGetAccountingEndDate |
表值用户定义函数 |
AdventureWorks 函数。 |
udfGetStatusTextTable |
用户定义函数 |
返回状态值的文本说明的表值函数。 |
uspSalesOrderDetailDeleteCommand |
存储过程 |
在从 SalesOrderDetail 表中删除行时由此应用程序使用。 |
uspSalesOrderDetailInsertCommand |
存储过程 |
在向 SalesOrderDetail 表中插入行时由此应用程序使用。 |
uspSalesOrderDetailSelectCommand |
存储过程 |
在查询 SalesOrderDetail 表 (AdventureWorks)时由此应用程序使用。 |
uspSalesOrderDetailUpdateCommand |
存储过程 |
在更新 SalesOrderDetail 表时由此应用程序使用。 |
uspSalesOrderHeaderDeleteCommand |
存储过程 |
在从 SalesOrderHeader 表 (AdventureWorks)中删除行时由此应用程序使用。 |
uspSalesOrderHeaderInsertCommand |
存储过程 |
在向 SalesOrderHeader 表中插入行时由此应用程序使用。 |
uspSalesOrderHeaderSelectCommand |
存储过程 |
在查询 SalesOrderHeader 表 (AdventureWorks)时由此应用程序使用。 |
uspSalesOrderHeaderUpdateCommand |
存储过程 |
在更新 SalesOrderHeader 表时由此应用程序使用。 |
uspStoreSelectCommand |
存储过程 |
由此应用程序用来查询给定商店的客户。 |
uspCheckProductInventory |
存储过程 |
由此自定义业务逻辑用来将订单状态更改为延期交货并返回记入日志的消息。 |