在Pocket PC中使用Web Service连接数据库
前言
微软的移动开发者大会在六月的北京举行了,国内的移动应用软件虽然是刚刚起步,但是这个前景宽广的领域已经受到越来越多软件厂商的关注了。移动设备上的商业应用尽管刚刚起步,但已经显示出巨大的发展潜力。
在微软移动开发挑战赛中,我的作品《饕餮元年无线餐饮管理系统》获得了商业应用的三等奖。为了实现Pocket PC与后台数据库服务器的连接,我的作品中使用了.Net CompactFramework和Web Service技术,我将核心部分的实现原理拿出来与大家分享。在本文中没有涉及《饕餮元年》的代码,我用另外一个程序“SQL查询分析器”作为演示的范例。
在嵌入式数据库领域,世界各大数据库厂商都提供了自己的移动解决方案,比如微软的SQL Server CE、Sybase的iAnywhere、IBM的DB2 Everyplace等。虽然各家厂商都提供了数据同步的解决方案,而且实现原理大同小异,但是,每种数据同步方案都针对自己的数据库,不能兼容其他的数据库产品。
那么有没有什么办法,可以通过一种机制来访问多种数据库呢?微软的Mobile 2003已经完全支持.net Compact Framework开发,而.net对Web Service提供了很好的支持,开发者可以很容易的开发Web Service的服务程序和客户端应用。
本文讨论的是在Windows Mobile2003平台上通过Web Service来访问多种数据库的解决方案。这肯定不会是移动设备访问多种远程数据库的最优解,但我希望这是利用现有技术实现的比较优秀的解决方案。
系统需求:Visual Studio .NET 2003
Pocket PC 2003 模拟器
IIS 5.0
第一个Web Service
目前Web Service受到了广泛的关注,主流的开发工具都为Web Service开发提供了很好的支持。我们在这里使用Visual Studio . NET 2003来做一个简单的Web Service实例,让不了解Web Service开发的程序员更好的进入后续话题的讨论。
我们首先来创建一个ASP.NET Web Service工程:打开File菜单,选择New,然后是Project。在New Project对话框中,Project Types选择“Visual C# Projects”,Templates中选择“ASP.NET Web Service”。Location中选择你的Web Service的位置和名称,由于我的机器上配置了IIS服务,所以我将Web Service直接部署在本机上。请见图一。
ASP.NET Web Service工程和ASP.NET十分相似,只是页面文件的扩展名为asmx,而不是aspx。接下来,我们要为Web Service 创建一个WebMethod。打开Service1.asmx.cs文件,在class Service1中添加两个WebMethod:YourName和welcom,代码如下(粗体字为添加的代码):
namespace WebService1
{
public class Service1 : System.Web.Services.WebService
{
……
[WebMethod]
public string YourName()
{
return "My Name is wolf!我就是老狼!";
}
[WebMethod]
public string welcom(string yourname)
{
string str = yourname+",欢迎使用wolf的Web Service";
return str;
}
}
}
这里需要提醒大家注意的是,每个WebMethod都必须是public的,而且需要在函数前
加上[WebMethod]的声明。YourName函数返回一个包含英文和中文的string对象,主要是想测试一下Web Service的字符串能否在支持Unicode的WindowsCE平台上正常现实。Welcom函数增加了一个参数,目的也是为了测试Web Service和WindowsCE平台间是否存在字符串不能正常显示的问题。
然后我们选择Debug菜单中的Start命令(或者按F5)运行这个Web Service,出现如下界面:(见图二)
可以看到,我们刚才编写的两个函数的名称被显示了出来。大家可以点击函数名称,来查看WebMethod的SOAP和HTTP POST等信息,还可以直接点击Invoke按钮,查看WebMethod返回的结果是否正确。
如果您的Web Service工作一切正常,接下来,我们创建一个Smart Device应用程序,在Pocket PC 2003环境下调用我们的Web Service。如果您对如何在Visual Studio .NET 2003下如何创建Smart Device应用程序还不是很了解,希望您参考下面的说明。
打开一个新的VS.NET的IDE环境,打开File菜单,选择New,然后是Project。在New Project对话框中,Project Types选择“Visual C# Projects”,Templates中选择“Smart Device Application”,然后点击OK。
在Smart Device Application Wizard对话框中,有两个列表框。上边一个列表框选择程序的目标平台,根据你开发使用机器上所安装的SDK的不同可以显示出不同的选项。其中Pocket PC中包括Pocket PC 2002和Pocket PC 2003;Windows CE中包括其他支持.NET Compact Framework的WindowsCE平台。如果您安装了SmartPhone的SDK,这里也会显示出SmartPhone的选项。
下面的对话框选择的是所创建的应用程序类型,包括Windows应用程序、类库、Non-graphical应用程序和空工程。这里的选项根据您选择平台的不同也会略有差异。还有一点需要说明的是,如果您安装了Mobile Internet Toolkit,您也可以通过这个向导来创建目标平台为Mobile设备的ASP.NET Web程序。
我们在这里选择“Pocket PC”和“Windows Application”,点击OK。我们就可以开始移动开发之旅了。
我们先来看一下IDE环境的全景:其实和WinForm程序的开发环境差不多吧。但请大家注意上图中的下拉菜单,做过WindowsCE开发的朋友们一定很熟悉吧。对了,这就是选择应用程序输出设备的菜单。我们在这里选择“CHS Pocket PC 2003 – SDK Emulator”,我们可以选择下拉菜单旁边的Connect To Device按钮来启动模拟器,也可以等到程序运行时再启动模拟器。
请注意:Visual Studio .NET 2003会默认安装Pocket PC 2002的SDK,您需要另外安装Pocket PC 2003 SDK和中文映像才能够看到这一项。不过您在Pocket PC 2002的模拟器下调试下面的应用程序也是没有问题的。
下面,我们来添加Web References,选择Project菜单下的“Add Web References”,或者在Solution Explorer中右击Web References,在弹出菜单中选择“Add Web References”。我们就会看到下面的对话框:
在URL里要填写Web Service的URL,还记得我们运行Web Service时IE地址栏中的URL吗?http://localhost/WebService1/Service1.asmx。对吗?如果是WinForm程序,这样写是正确的,但在Smart Device应用程序中,这样写就是错的。请大家注意,下面讨论的话题十分重要:Windows CE设备的模拟器尽管运行在你的PC上,但它实际上是作为一台远程设备连接到你的PC上的。所以,如果写“localhost”或者“127.0.0.1”,那么程序访问的将是模拟器,而不是你用来开发的PC机。所以,在这里你必须填写你的PC在网络中的实际机器名或者IP地址。所以这里应该填写http://yourname/WebService1/Service1.asmx(yourname表示你实际的机器名)。这里如果您填写的是localhost,开发环境也将为你找到Web References,但在调用Web Service时将出现异常。好了,填写好URL,按GO按钮,如果URL正确,则会显示上图的信息,然后点击Add References按钮。这样我们就可以在项目中使用这个Web Service了。
我们打开界面编辑器,添加两个Button和一个TextBox,用来调用两个WebMethod。代码如下:
private void button1_Click(object sender, System.EventArgs e)
{
yourname.Service1 myService = new yourname.Service1();
label1.Text = myService.YourName();
MessageBox.Show(myService.YourName());
}
private void button2_Click(object sender, System.EventArgs e)
{
yourname.Service1 myService = new yourname.Service1();
MessageBox.Show(myService.welcom(textBox1.Text));
}
yourname是Web Service的名称,相当于命名空间,所以我们必须创建一个yourname中的Service1类的对象,然后调用Service1中的方法。大家可以看到,在C#程序中调用Web Server的方法和创建本地类并调用其方法的过程是十分类似的。另外需要说明的是,由于网络因素的影响,调用Web Service不一定成功,所以在商业软件的开发中需要加入异常处理的代码,这里为了程序的简洁就省略了。程序运行结果如下:
SQL查询分析器
在了解了基本的Web Service的开发与调用之后,我们可以进入核心内容的讨论了。Web Service是基于XML语言的,正是这种特性才使Web Service成为连接各种异构系统之间的桥梁。那么有没有一种可以在跨越不同数据库来传递数据的简单方式呢?ASP.NET Web Service是支持返回DataSet对象的,而DataSet又能够被各种数据表示控件所支持,比如DataGrid等,DataSet就是我们要找的桥梁。
在这一部分里,我将创建一个SQL的查询分析器,与其他查询分析器不同的是,我可以在调用时再指定连接的数据库类型,这样就可以实现连接不同数据库了。我们首先来创建SQL查询分析器的Web Service部分:
先来看一下SQL Service的UML图。我们沿用了上面SQL Service中的Service1类,并为它添加了五个WebMethod,其名称和功能介绍见下表:
名称
功能
SetDatabaseType
设置连接数据库的类型
SetDbConnectionString
设置数据库的连接字符串
Create
创建数据库操作对象
ExecuteNonQuery
执行不返回结果的SQL语句
ExecuteDataSet
执行SQL语句,并返回一个DataSet
在Web Service的Service1类中,我们首先来声明三个变量用来分别存储数据库操作对象、数据库连接字符串和数据库类型。注意,我们将变量声明为static,是为了让客户端执行不同的WebMethod时,可以访问到相同的数据库操作对象和字符串对象。DBOperater是数据库操作类的基类,而等一下我们创建的是DBOperater派生类的实例。
private static DBOperater m_dbOperater;
private static string m_DatabaseType;
private static string m_DbConnectionString;
SetDatabaseType和SetDbConnectionString两个WebMethod是为了设置数据库类型和数据库连接字符串而提供的方法。在这里需要特别提醒的是,将数据库的连接字符串作为Web Service参数,以明文的方式在网络上传递是一种十分危险的做法,这里只是为了示例程序的简单才这样实现的。强烈建议读者在实现自己的Web Service时,用更好的方法来保护自己的数据库连接字符串。
[WebMethod]
public bool SetDatabaseType(string DatabaseType)
{
if(DatabaseType == "")
return false;
m_DatabaseType = DatabaseType;
return true;
}
[WebMethod]
public bool SetDbConnectionString(string DbString)
{
if(DbString == "")
return false;
m_DbConnectionString = DbString;
return true;
}
下面的Create方法,用来根据m_DatabaseType来创建相应的DBOperater派生类的对象。这里需要说明的是,设置数据库类型(SetDatabaseType)用的是string作为参数,而没有使用enum类型或者其他类型,是为了再以后添加对新的数据库支持时不必修改已有的客户端源代码。
[WebMethod]
public bool Create()
{
if(m_DatabaseType == "SQL Server")
{
m_dbOperater = new DBSqlServerOperater(m_DbConnectionString);
return true;
}
if(m_DatabaseType == "Access")
{
m_dbOperater = new DBAccessOperater(m_DbConnectionString);
return true;
}
return false;
}
而剩下的两个WebMethod只是简单的调用了DBOperater派生类对象的相应方法,而实际的数据库操作则是在DBOperater派生类中具体实现的。这样可以更好地实现数据库操作与客户端操作的分离。
[WebMethod]
public bool ExecuteNonQuery(string sql)
{
return m_dbOperater.ExecuteNonQuery(sql);
}
[WebMethod]
public DataSet ExecuteDataSet(string sql)
{
return m_dbOperater.ExecuteDataSet(sql);
}
我们将对数据库操作的具体方法封装到了DBOperater类中,并由DBOperater类派生出了DBSqlServerOperater和DBAccessOperater类,分别用来进行对Sql Server数据库和Access数据库的操作。DBOperater类暴露了两个虚方法:ExecuteNonQuery()和ExecuteDataSet(),由派生类负责重载实现。而派生类也各自暴露了自己的构造函数,用来获取连接数据库字符串。
下面是DBOperater类的实现代码,可以看到DBOperater类中的两个方法使用了virtual的关键字,声明为虚函数。
// 数据库操作基类
public class DBOperater
{
public virtual bool ExecuteNonQuery(string sql)
{
return true;
}
public virtual DataSet ExecuteDataSet(string sql)
{
return null;
}
}
在Web Service的体系结构中,真正进行数据库操作的只有DBOperater的派生类,我们下面看到的DBSqlServerOperater类,使用了System.Data.SqlClient命名空间中的数据库操作类,对SQL Server数据库进行存储操作。DBAccessOperater类对OleDB数据进行操作,使用了System.Data.Oledb命名空间中的类。
// SQL Server 操作类
public class DBSqlServerOperater : DBOperater
{
public DBSqlServerOperater(string DBConnectionString)
{
m_SqlConnection = new SqlConnection(DBConnectionString);
}
public override bool ExecuteNonQuery(string sql)
{
if(sql == "")
return false;
m_SqlConnection.Open();
SqlCommand command = m_SqlConnection.CreateCommand();
command.CommandText = sql;
command.ExecuteNonQuery();
m_SqlConnection.Close();
return true;
}
public override DataSet ExecuteDataSet(string sql)
{
if(sql == "")
return null;
m_SqlConnection.Open();
SqlDataAdapter da = new SqlDataAdapter(sql,m_SqlConnection);
DataSet ds = new DataSet();
da.Fill(ds);
m_SqlConnection.Close();
return ds;
}
private SqlConnection m_SqlConnection;
}
// OleDb 操作类
……
这里省略了与SQL Server操作类基本相同的Access操作类代码。关于C#中重载和继承的实现方式和注意事项,请参阅相关书籍。
由于对数据库的操作已经在Web Service端做了封装,所以客户端的代码实现起来就相对简单了。SmartSQLClient包含两个属性页,第一个属性页为Setting,设置须查询的数据库类型和数据库连接字符串,设置完毕后,点击Setting按钮,程序会将数据库类型和连接字符串保存起来,并自动切换到第二个属性页中。
第二个属性页Query,包括一个textBox,用来书写SQL查询语句,一个DataGrid,用来显示Web Service传来的数据库,两个Button分别执行返回DataSet的SQL查询和不返回结果集的操作。
现在我们来看一下ExecDataSet按钮的响应函数,该函数首先创建一个Web Service访问对象的实例,然后根据Setting中的设置,设置数据库类型和数据库连接字符串,然后创建相应数据库操作类的对象实例,并调用Web Service的ExecuteDataSet方法,将一个有效的SQL查询语句传递给Web Service,Web Service会返回一个DataSet对象,我们的客户端程序会将DataSet中的第一个表显示到DataGrid控件中。
private void ExecDsBtn_Click(object sender, System.EventArgs e)
{
wolf.Service1 service = new SQLSmartClient.wolf.Service1();
service.SetDatabaseType(m_DatabaseType);
service.SetDbConnectionString(m_connectionString);
service.Create();
DataSet ds = service.ExecuteDataSet(textBox1.Text);
dataGrid1.DataSource = ds.Tables[0];
}
好了,到这里,我们已经实现了一个可以在PDA端操作远程SQL Server和Access数据库的SQL查询分析器了。随着.Net数据库操作组件的日益丰富,您完全可以实现对Oracle、DB2等流行的关系型数据库的支持,并在此基础上,实现更多基于移动设备的应用程序。
与SQL SERVER CE交互
我们上面实现的SQL查询分析器的意义不仅在于可以在移动设备上方便地对不同类型的数据库进行检索,而且这种Web Service的方式还有更现实的意义。
众所周知,在Windows CE平台上主流的数据库是SQL Server CE,SQL Server CE与服务器交换数据的方式主要有两种:Remote Data Access(RDA)和Replication。这两种数据同步的方式,不但需要搭建专门的IIS环境,而且只能与SQL Server数据库进行数据同步。这就大大增加了使用其他类型数据库的系统集成商开发移动设备应用程序的难度,同时也不利于Windows CE平台被更广泛的领域所接受。
下面的时间,我将在上面Web Service的基础上,演示如何利用Web Service实现SQL Server CE与Access之间的数据同步。当然,我的实现还无法达到RDA可以将表结构同步复制的功能。
为了完成这部分的代码,我们在SQLSmartClient中再添加一个属性页,命名为SQLServerCE。最上面的textBox和CreateDB按钮是为了创建SQL Server CE数据库和数据库中的表所准备的,稍后我们将介绍他们的代码实现。而下面的Fill按钮完成的任务包括:将第二个textBox中的SQL语句提交给Web Service,由Web Service负责提供包含相应数据的DataSet。然后解析DataSet,在组合成相应的SQL语句,插入到SQL Server CE数据库中。
我们先来介绍一下在.Net Compact Framework中对SQL Server CE的操作。在.Net CF中针对SQL Server CE的操作类全部在System.Data.SqlServerCe命名空间中,其中类的名称与操作方式和.Net Framework中System.Data.SqlClient类似。我们先来看一下,如何创建SQL Server CE数据库:
// 创建SQL Server CE数据库
private void CreateDBBtn_Click(object sender, System.EventArgs e)
{
if (File.Exists ( DBNametextBox.Text) )
File.Delete ( DBNametextBox.Text);
string connString = "Data Source = "+ DBNametextBox.Text;
SqlCeEngine engine = new SqlCeEngine (connString);
engine.CreateDatabase();
// Create Table
SqlCeConnection conn = new SqlCeConnection(connString);
conn.Open();
SqlCeCommand command = conn.CreateCommand();
command.CommandText = "CREATE TABLE Human (ID int PRIMARY KEY, Name ntext,Age int ,Address ntext)";
command.ExecuteNonQuery();
conn.Close();
MessageBox.Show("数据库创建成功");
}
在这段代码中,我们首先检查sdf文件是否存在,如果存在则删除已存在的数据库文件(扩展名sdf)。然后根据给出的sdf文件名构造数据库连接字符串,创建SqlCeEngine对象,并调用SqlCeEngine的CreateDatabase方法创建数据库。最后一部分的代码使用SqlCeConnection和SqlCeCommand创建了名为Human的表。
好了,创建好SQL Server CE数据库了,该将Web Service上的数据取下来了。我们来看Fill按钮的实现代码。
第一步是创建Web Service的访问对象,向Web Service请求DataSet数据库。不多做解释了。
private void FillDsBtn_Click(object sender, System.EventArgs e)
{
// Get Web Service DataSet
wolf.Service1 service = new SQLSmartClient.wolf.Service1();
service.SetDatabaseType(m_DatabaseType);
service.SetDbConnectionString(m_connectionString);
service.Create();
DataSet ds = service.ExecuteDataSet(SqlTextBox.Text);
第二步,我们根据上面创建好的SQL Server CE数据库,组成数据库连接字符串,并创建SqlCeConnection和SqlCeCommand对象,看起来很眼熟,不是吗?
// Get Sql Server Ce Database DataSet
string connString = "Data Source = "+ DBNametextBox.Text;
SqlCeConnection conn = new SqlCeConnection(connString);
conn.Open();
SqlCeCommand command = conn.CreateCommand();
第三步,我们从DataSet的第一个DataTable对象中得到DataRow数组,如果DataRow的数量不小于1,则表示DataRow数组中有数据。我们通过C#语言的foreach关键字来循环得到每一个字段的值,然后将其存储到相应的string对象中去,再由这些string对象组成SQL语句。最后用SqlCeCommand对象来执行这些SQL语句。
// Build Sql Command
String sql,Id,Name,Age,Address;
DataRow[] currRows = ds.Tables[0].Select(null, null, DataViewRowState.CurrentRows);
if( currRows.Length < 1)
return;
foreach(DataRow myRow in currRows)
{
Id = myRow["id"].ToString();
Name = myRow["name"].ToString();
Age = myRow["age"].ToString();
Address = myRow["address"].ToString();
sql = "INSERT INTO Human (id,Name,Age,Address) VALUES ("+Id+",'"+Name+"',"+Age+",'"+Address+"')";
// Execute Sql Command
command.CommandText = sql;
command.ExecuteNonQuery();
}
conn.Close();
MessageBox.Show("转换完成");
}
最后关闭SqlCeConnection的连接,整个数据同步过程完成。我们通过SQL Server CE自带的查询分析器,查看一下同步过来的数据,和Access表中的完全相同。当然,我们也可以将SQL Server CE中的数据同步到远程的数据库中去,这里就不再重复实现了。
写在最后
好了,我们已经完成了有些漫长的Web Service体验之旅。从第一个Web Service的实现,到连接不同数据库的SQL查询分析器,最后我们还发现,我们的SQL Server CE竟然可以连接Access数据库了,当然也可以是任何一种你想要的数据库。
带给我们这些奇妙体验所花费的仅仅是短短的代码。看起来把最流行的Web Service和DataSet带到你的程序中去并不是件难事,对吗?
那么试着把Web Service带到你的下一个移动应用的开发中去吧。
关于作者
马宁,程序员。目前正在从事Windows CE.NET平台上的应用开发,熟悉Embedded Visual C++和Visual Studio.NET的移动开发,常混迹于CSDN的嵌入开发版块。