UML软件工程组织

Windows Powered Pocket PC Phone Edition 的数据库开发
Andy Sjostrom
businessanyplace

(转载 Microsoft)

 

适用于:
     Pocket PC Phone Edition

摘要:学习如何使用 Pocket PC Phone Edition 工具集快速创建无线应用程序。本文将为您展示一个使用 Visual C# 和 SQL Server CE 2.0 创建的 Pocket PC Phone Edition 应用程序。如果您刚开始接触 .NET Framework 精简版,建议您访问 .NET Compact Framework Overview 白皮书(英文)。

目录

一个新的移动计算时代开始了

移动解决方案系统结构的各个部分正在快速地变为现实,并逐渐融合。更重要的是,许多公司越来越清楚地意识到移动解决方案能够带来很高的效率和新的商业机会。消费者也开始在日常生活中使用各种移动设备。移动设备硬件,特别是各种新式的 Pocket PC,正在引领时尚潮流,不断满足企业用户和消费者的需要。将各种事物紧密联系的关键是:新的软件和开发工具。

在过去几个月中,我使用了新的 Visual Studio .NET、Smart Device Extensions 和新的 SQL Server ™CE 2.0。将这些软件组件应用到各种 Pocket PC(如 Pocket PC Phone Edition)上,不但提高了开发效率,而且还促进了许多新的移动应用程序的开发。

SQL Server CE 的新功能

从表面上看来,SQL Server CE 带来了某种矛盾。作为用于移动设备的本地数据库,SQL Server CE 支持断开连接的方案。在大多数情况下,在断开连接的 Pocket PC 上运行的 Pocket PC 应用程序都需要进行本地数据存储。SQL Server CE 还支持连接的方案,而且不论是从开发角度还是从带宽角度来看,它都提高了与远程服务器之间的数据传输效率。

以下是新版本的 SQL Server CE 的一些“闪光点”(当然,不是全部):

  • 集成了新的 .NET Framework 精简版类,可用于本地 SQL Server CE 管理以及远程 SQL Server 连接。
  • 提供了 Connectivity Setup Wizards,可以简化 SQL Server CE 组件的安装。
  • 提供了大量新的内部函数,包括期待已久的 NEWID、CHAR、CHARINDEX、UNICODE、LEN、LTRIM、RTRIM、SPACE、SUBSTRING、IDENTITY、DATALENGTH 等等。
  • UNION (SELECT * FROM Orders UNION SELECT * FROM OldOrders)。
  • 使用 Remote Data Access Pull 请求下载远程服务器表索引的能力。
  • 显著改进的 SQL Server Query Analyzer。

新的 SQL Server CE 2.0 Data Access Architecture 依赖于命名空间中的类:

  • System.Data.SqlServerCE(使用 Merge Replication 和 Remote Data Access 管理本地数据库和远程服务器连接)
  • System.Data.SqlClient(管理远程数据库并包含对 TSQL 和存储过程的支持)

由于 Data Access Architecture 已移至 .NET Framework 精简版中,其中包含的组件得以改进而且便于使用。例如,前面的 Merge Replication Initialize、Run 和 Terminate 架构已被一种方法替代,即 System.Data.SQLServerCE.Replication.Synchronize。此方法可以创建架构并在首次同步时下载数据,然后它强制上载修改后的数据,并在随后的同步过程中将其下载。

新的 Remote Data Access 类也有所改进,包括请求下载远程表索引的能力,以及为 Push 方法定义批处理模式的能力。我先为您介绍一个高尔夫记分牌应用程序示例,这个应用程序是用 SQL Server CE 2.0、Remote Data Access 和 Visual C# 创建的,然后再看一下它的代码。

示例应用程序:走过高尔夫球场

示例应用程序 Golf Anyplace 能够在各种标准的 Pocket PC 上运行,而在实际的高尔夫球场中,由于 Pocket PC Phone Edition 内嵌了连接功能,因此具有更大的优势。Golf Anyplace 实际上就是一个数字记分牌,它可以跟踪您和其他球员的比分结果。其思想是每个高尔夫球员都使用一个 Pocket PC Phone Edition 来跟踪分数。由于每个人都能够将自己的分数强制上载到远程服务器,同时可以请求下载其他人的分数,因此可以持续关注比赛的进程。

这个应用程序的外观如下所示(抱歉,我不是用户界面设计人员):

主窗体用于输入您自己的分数,也可用于查看其他球员的分数。

图 1:输入您的分数

Synchronize 命令将本地数据强制上载到远程服务器,然后再请求下载所有的远程分数数据。

图 2:同步分数

您可以使用 View 窗体来查看比赛的详细信息和概要信息。

图 3:跟踪所有高尔夫球员

示例应用程序:预演代码

让我们看一下代码。在代码的某些位置,您将看到我用不同的方法解决同一个问题。比如,如何进行类的初始化、是使用 DataReader 还是使用 DataSet、如何填充 ListView、是否使用 SQL Server CE 包装,等等。我希望这会对您有所帮助,因为在不同的情况下,各种解决方案各有千秋。请注意,您可以下载 Golf Anyplace 的示例代码(英文)。

启动

Golf Anyplace 的启动对象是 GolfAnyplace.RDAGolf。以下就是构造函数逻辑在应用程序启动时所执行的操作:

public RDAGolf()
{
InitializeComponent();

// 确保数据库存在!
SQLServerCEWrapper SSCEWrapper = new SQLServerCEWrapper();
bool NewDatabase = SSCEWrapper.CreateDatabase();

// 如果创建了新数据库,则执行第一次请求下载操作!
if(NewDatabase==true)
{
// 调用 Pull 而不保留本地数据! (没有任何数据!)
SSCEWrapper.Pull(false);
}

// 填充组合框
for(int iCounter=1; iCounter < 19; iCounter++)
this.cmbHole.Items.Add(iCounter.ToString());

// 将第一洞作为默认设置
this.cmbHole.SelectedIndex = 0;

}

您可以看到,我使用了一个 SQL Server CE 包装。这样做的目的是将与数据库相关的代码组织到一个位置。使用包装可以帮助我管理和使用本地数据库以及远程服务器同步。

以下是包装的前几行代码。

using System;
using System.Data;
using System.Windows.Forms;
using System.Collections;
using System.Data.Common;
using System.Data.SqlServerCe;
using System.Data.SqlClient;

namespace GolfAnyplace
{

public class SQLServerCEWrapper
{

public string InternetServer = "http://servername/directory/sscesa20.dll";
public string InternetUser ="DOMAIN\\user";
public string InternetPassword = "password";
public string RemoteConnection = "Provider=sqloledb;Data Source=MySQLServer;
Initial Catalog=GolfAnyplace;User Id=user;Password=password";
public string LocalDatabase = "\\My Documents\\ga.sdf";
public string LocalConnection = "Provider=Microsoft.SQLSERVER.OLEDB.CE.2.0;
Data Source=\\My Documents\\ga.sdf";
public string LocalTableName = "Result";
public string RemoteTableName = "Result";

我将 RDA 属性设置为公有,只是因为当我需要说明如何在应用程序的其他部分使用 DataReader 时,会用到该属性。其实 RDA 属性应当是私有的,这样就能在应用程序的其余部分保持一致,而且能从包装传递一个 DataSet 来代替它。

以下是包装的 CreateDatabasePull 方法:

public bool CreateDatabase()
{
// 确保数据库存在!
// 如果创建了新数据库,则返回 true。
// 如果未创建新数据库,则返回 false。
// 已知错误:
// 版本     日期   人员 注释
// 00.00.000 020808 ASJ  创建
// *****************************

if(System.IO.File.Exists(LocalDatabase) == false)
{
System.Data.SqlServerCe.Engine SQLCEEngine = new System.Data.
SqlServerCe.Engine(LocalConnection);
SQLCEEngine.CreateDatabase();
return true;
}
else
{
return false;
}

如果数据库不存在,CreateDatabase 方法将创建一个新的数据库。

public void Pull(bool KeepLocalData)
{
// 将表请求下载到本地数据库中。
// 已知错误:
// 版本     日期   人员 注释
// 00.00.000 020808 ASJ  创建
// *****************************
string SQL;
SqlCeConnection cn;
SqlCeCommand cmd;
RemoteDataAccess RDA = null;

// 创建并启动新的 RDA 对象
RDA = new RemoteDataAccess(InternetServer, InternetUser, 
InternetPassword, LocalConnection);

// 是否保留本地数据?如果要保留,请先强制上载这些数据!
if(KeepLocalData)
{
RDA.Push(LocalTableName, RemoteConnection, RdaBatchOption.BatchingOff);
}

// 在 Pull 之前,必须先删除本地表
// 打开本地数据库连接
cn = new SqlCeConnection(LocalConnection);
cn.Open();

// 删除本地表
SQL = "DROP TABLE " + LocalTableName;
cmd = new SqlCeCommand(SQL, cn);

// 如果该表不存在,则出错。
// 如果出错,可以置之不理。
try
{
cmd.ExecuteNonQuery();
}
catch{ }

// 关闭连接
cn.Dispose();

// 最后,请求下载远程表!
SQL = "SELECT PlayerName, Hole, Result FROM " + RemoteTableName;
RDA.Pull(LocalTableName, SQL, RemoteConnection, RdaTrackOption.
TrackingOnWithIndexes, "RDAErrors");

// 清理
RDA.Dispose();

}

请注意,我将一个布尔型变量传递给 Pull 方法。由于无法将内容从远程服务器表请求下载到现有的本地 SQL Server CE 表中,所以在请求下载操作之前必须有一条 DROP TABLE 语句。布尔型变量用来控制在首次强制上载到服务器之前,是否保留本地数据。在 Push 方法中您能看到新的批处理参数。在本例中,我使用了 BatchingOff,这就意味着我没有将那些强制上载到服务器中的行看成一批数据,这些行要么全部通过要么全部不通过。另一个有效的设置为 BatchingOn。正如您所看到的,我用 SqlCeCommand 来执行 DROP TABLE。

我想强调一下,我在 Pull 语句中指定了字段名。请您也务必在 Pull 语句和常规的 SELECT 语句中指定字段名。这样做可以确保您处理的数据是绝对必要的数据。此外,您还可以避免在查找某些不能请求下载的字段时出现问题,例如 rowguidcolint identitytimestamp 字段。

服务器上有什么?

在进一步研究 Pocket PC 应用程序的源代码之前,让我们先了解一下服务器端有什么东西。远程服务器是 SQL Server 2000,其上运行了名为 GolfAnyplace 的数据库。数据库中只有一个表,即 Result 表。下面是表的数据定义:

CREATE TABLE [dbo].[Result] (
[PlayerName] [nvarchar] (50) NOT NULL ,
[Hole] [smallint] NOT NULL ,
[Result] [smallint] NOT NULL 
) ON [PRIMARY]
GO

ALTER TABLE [dbo].[Result] WITH NOCHECK ADD 
CONSTRAINT [PK_Result] PRIMARY KEY CLUSTERED 
(
[PlayerName],
[Hole],
[Result]
) ON [PRIMARY]

我还通过一些方便的向导助手对 SQL Server CE 2.0 服务器代理进行了配置。

填充 ListView

GolfAnyplace 项目介绍了两种填充 ListView 的方法:使用 DataSet 和使用 DataReader。由于我之前选择了实现 SQL Server CE 的包装,因此这时就适合使用 DataSet。DataSet 可以断开连接,而且还能在这种断开连接的状态下在类之间传递数据(封送处理)。由于 DataReader 需要有效的数据库连接,因此应该尽可能在打开和关闭连接时使用。第一段示例代码显示如何通过遍历 DataSet 来填充 ListView。

以下是填充 ListView 的代码:

private void UpdateResultListView()
{
// 更新列表视图。
// 已知错误:
// 版本     日期   人员 注释
// 00.00.000 020808 ASJ  创建
// *****************************
DataSet ds = null;
SQLServerCEWrapper SSCEWrapper = new SQLServerCEWrapper();

// 获取 DataSet
ds = SSCEWrapper.GetPlayerResult(txtPlayerName.Text);

// 清除 ListView
lvwResult.Items.Clear();

// 遍历 DataSet
foreach (DataRow dr in ds.Tables[0].Rows)
{
ListViewItem lviItem = new ListViewItem(dr["Hole"].ToString());
lviItem.SubItems.Add(dr["Result"].ToString());
lvwResult.Items.Add(lviItem);
}
}

以下是包装类中相应的代码:

public DataSet GetPlayerResult(string PlayerName)
{
// 从本地数据库获取数据。作为 DataSet 返回。
// 已知错误:
// 版本     日期   人员 注释
// 00.00.000 020808 ASJ  创建
// *****************************
string SQL;
SqlCeConnection cn;
SqlCeCommand cmd;
DataSet ds;
SqlCeDataAdapter da;

// 初始化新的连接
cn = new SqlCeConnection(LocalConnection);

// 打开连接
cn.Open();

// 生成 SQL
SQL = "SELECT PlayerName, Hole, Result FROM " + LocalTableName 
+ " WHERE PlayerName = '" + PlayerName + "' ORDER BY Hole";

// 初始化新的命令
cmd = new SqlCeCommand(SQL,cn);

// 初始化新的 DataSet
ds = new DataSet();
da = new SqlCeDataAdapter(SQL,cn);

// 通过适配器,使用数据来填充 DataSet
da.Fill(ds, LocalTableName);

// 清理
cn.Dispose();
cmd.Dispose(true);

// 返回 DataSet
return ds;

}

以下示例代码显示如何使用 DataReader 方法完成同样的操作。

private void UpdateDetailedResultListView()
{
// 使用 DataReader 来更新详细结果。
// 已知错误:
// 版本     日期   人员 注释
// 00.00.000 020808 ASJ  创建
// *****************************
SQLServerCEWrapper SSCEWrapper = new SQLServerCEWrapper();
string SQL;

// 用于获取详细 ListView 的 SQL!
SQL = "SELECT PlayerName, Hole, Result FROM Result 
ORDER BY PlayerName, Hole";

SqlCeConnection cn = new SqlCeConnection();
cn.ConnectionString = SSCEWrapper.LocalConnection;

// 打开连接
cn.Open();

// 启动命令并执行读取器。
SqlCeCommand cmd = new SqlCeCommand(SQL,cn);
SqlCeDataReader dtr = cmd.ExecuteReader();

// 清除 ListView
this.lvwResultDetailed.Items.Clear();

// 开始读取!
while (dtr.Read())
{

ListViewItem lviItem = new ListViewItem(dtr["PlayerName"].ToString());
lviItem.SubItems.Add(dtr["Hole"].ToString());
lviItem.SubItems.Add(dtr["Result"].ToString());
lvwResultDetailed.Items.Add(lviItem);

}

// 关闭 DataReader
dtr.Close();

// 清理
cmd.Dispose(true);
cn.Dispose();

}

最后请注意,可供下载的示例代码使用了一些错误处理。我还在项目中保留了 Options 窗体(见图 4),当然它需要更多的代码支持。

图 4:SQL Server CE Options 窗体

提示和技巧

以下是一些提示和技巧,供您参考:

  • 请一位用户界面设计人员。
  • 在 SQL Server CE 代码中包含适当的错误处理,可以使用 Try、Catch 和 Finally 结构。
  • 如果没有可用于开发的 Pocket PC 或者您的 Pocket PC 没有以太网适配器,您可以使用 Pocket Emulator。
  • 信任 SQL Server CE Connectivity Wizards,或至少在开始工作时使用它们。
  • 使用服务器端的 SQL Profiler 或者使用 Merge Replication 来监视数据强制上载和请求下载时的情况。即使没有其他作用,您也可以看到何时或是否发生了什么情况。
  • 了解如何通过 Internet 查找编码问题的答案。下面列出了我喜欢的一些站点。

小结

Visual Studio .NET、Visual C# 或 Visual Basic .NET 与 SQL Server CE 2.0 之间的协作非常好。希望您能从我的文章中获得一些有益的启示。

更多信息

 

版权所有:UML软件工程组织