摘要:本文講解怎樣使用Pocket PC Phone Edition工具集迅速建立無線數(shù)據(jù)庫應(yīng)用程序。本文介紹的是使用Visual C# 和SQL Server CE 2.0建立一個Pocket PC Phone Edition應(yīng)用程序。
新的移動計算時代開始了
移動解決方案系統(tǒng)體系結(jié)構(gòu)的一部分正在迅速成為現(xiàn)實。最先是很多公司不斷意識到移動解決方案可以帶來效率的提高和新的商業(yè)機會。顧客正在每天的生活中開始看到移動設(shè)備的使用。移動設(shè)備硬件,特別是新的Pocket PC,正在引領(lǐng)企業(yè)和顧客需求的方向。把每種需求膠合在一起的關(guān)鍵就是新的軟件和開發(fā)工具。
我使用新的Visual Studio .NET、Smart Device Extensions和新的SQL Server? CE 2.0工作了幾個月時間。這些軟件組件與連接的(connected)Pocket PC,例如Pocket PC Phone Edition,提高了開發(fā)效率并且已經(jīng)驅(qū)動了很多新的移動應(yīng)用程序。
SQL Server CE的新特性
總的來說,SQL Server CE反映了一種矛盾。作為移動設(shè)備的本地數(shù)據(jù)庫,SQL Server CE支持不連接的環(huán)境。在大多數(shù)情況中,沒有網(wǎng)絡(luò)連接的Pocket PC上運行的Pocket PC應(yīng)用程序需要本地數(shù)據(jù)存儲。SQL Server CE也支持連接的環(huán)境,并且使數(shù)據(jù)向遠程服務(wù)器或從遠程服務(wù)器傳遞效率很高,無論是從開發(fā)還是從帶寬來看。
下面是SQL Server CE的部分特性:
· 為本地的SQL Server CE管理和遠程的SQL Server連接與.NET緊湊框架組件集成。
· 連接安裝向?qū)筍QL Server CE組件的安裝簡化。
· 大量的內(nèi)部函數(shù),包括很有價值的NEWID、CHAR、CHARINDEX、UNICODE、LEN、LTRIM、RTRIM、SPACE、 SUBSTRING、IDENT99vY、DATALENGTH等等。
· 聯(lián)合(UNION,例如SELECT * FROM Orders UNION SELECT * FROM OldOrders)
· 使用遠程數(shù)據(jù)訪問牽引(Remote Data Access Pull)從遠程服務(wù)器表索引得到數(shù)據(jù)的能力。
· 改良的SQL Server查詢分析器(Query Analyzer)。
新的SQL Server CE 2.0數(shù)據(jù)訪問結(jié)構(gòu)依賴下面名字空間中的類:
· System.Data.SqlServerCE(使用合并復(fù)制和遠程數(shù)據(jù)訪問管理本地數(shù)據(jù)庫和遠程服務(wù)器連接)。
· System.Data.SqlClient(管理遠程數(shù)據(jù)庫并且包含對TSQL和存儲過程的支持)。
隨著數(shù)據(jù)訪問結(jié)構(gòu)轉(zhuǎn)移到.NET緊湊框架組件,它里面的組件得到了改良,并且更容易使用了。例如,先前的合并復(fù)制初始化(Merge Replication Initialize)、運行和終止大綱(schema)被一個單獨的方法System.Data.SQLServerCE.Replication.Synchronize所代替,它在第一次同步的時候建立大綱并下載數(shù)據(jù),接著提交修改過的數(shù)據(jù),并在接下來的同步中下載修改過的數(shù)據(jù)。
新的遠程數(shù)據(jù)訪問(Remote Data Access)類也有一定的改良,包含了從遠程表索引取得數(shù)據(jù)的能力和為Push方法定義批處理模式(batch-mode)的能力。我將介紹一個"高爾夫得分卡"示例應(yīng)用程序,它是使用SQL Server CE 2.0、 遠程數(shù)據(jù)訪問和Visual C#建立的。
示例應(yīng)用程序:界面
示例應(yīng)用程序Golf Anyplace可以運行在標準的Pocket PC上,但是在實際的高爾夫球場上Pocket PC Phone Edition的內(nèi)建連接會有很大的好處。Golf Anyplace本質(zhì)上是一個數(shù)字得分卡,它能跟蹤你的得分和其它玩家的結(jié)果。其想法是每個打高爾夫的人使用Pocket PC Phone Edition跟蹤得分。因為每個人都能把自己的分數(shù)發(fā)送到遠程服務(wù)器并下載他人的得分,所以能夠經(jīng)?吹奖荣惖倪^程。
下面是一些界面:
主窗體用于輸入自己的得分。它也可以用于查看其它玩家的得分。
圖1.輸入自己的得分
Synchronize命令把本地數(shù)據(jù)推入遠程服務(wù)器,接著下載所有的遠程得分數(shù)據(jù)。
圖2.同步分數(shù)
你可以使用View窗體查看細節(jié)和整個比賽的情況。
圖3.保持跟蹤其它的玩家
示例應(yīng)用程序:代碼
下面我們看一看代碼。在代碼的某些位置,你可能注意到我使用不同的方法解決同一個問題。這些例子包括我怎樣著手類的初始化,使用DataReader還是DataSet,填充ListView,是否使用SQL Server CE包裝等等。我希望這能對你有些幫助,某個方案在某種情況下工作得很好,在其它的環(huán)境中可能另一個方案更好。
啟動
Golf Anyplace的啟動對象是GolfAnyplace.RDAGolf。下面是當啟動應(yīng)用程序時構(gòu)造邏輯執(zhí)行的操作:
public RDAGolf() { InitializeComponent(); //確定存在數(shù)據(jù)庫 SQLServerCEWrapper SSCEWrapper = new SQLServerCEWrapper(); bool NewDatabase = SSCEWrapper.CreateDatabase(); //如果新數(shù)據(jù)庫被建立了,執(zhí)行第一次下載數(shù)據(jù) if(NewDatabase==true) { //調(diào)用Pull并且不保存本地數(shù)據(jù) SSCEWrapper.Pull(false); } //填充組合框 for(int iCounter=1; iCounter < 19; iCounter++) this.cmbHole.Items.Add(iCounter.ToString()); //把第一洞設(shè)置為默認的 this.cmbHole.SelectedIndex = 0; }
你可以看到我實現(xiàn)了SQL Server CE包裝。我是通過把與數(shù)據(jù)庫相關(guān)的代碼寫到同一個位置實現(xiàn)的。該包裝幫助我管理并與本地數(shù)據(jù)庫一起工作,以及遠程數(shù)據(jù)庫同步。下面是該包裝的前面幾行代碼:
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屬性共享的唯一原因是當我在應(yīng)用程序的其它部分演示DataReader時需要它。實際上RDA屬性應(yīng)該是私有的,這樣就能與應(yīng)用程序的其它部分保持一致,并且可以自該包裝傳遞一個DataSet。
下面是該包裝的CreateDatabase和Pull方法:
public bool CreateDatabase() { // 確定數(shù)據(jù)庫存在,如果新數(shù)據(jù)庫被建立了就返回true,否則返回false。 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; }
如果數(shù)據(jù)庫不存在,CreateDatabase方法就建立一個新數(shù)據(jù)庫。
public void Pull(bool KeepLocalData) { //把表下載到本地數(shù)據(jù)庫 string SQL; SqlCeConnection cn; SqlCeCommand cmd; RemoteDataAccess RDA = null; //建立和初始化新的RDA對象 RDA = new RemoteDataAccess(InternetServer, InternetUser, InternetPassword, LocalConnection); //保持本地數(shù)據(jù)碼?如果保持,首先上載它。 if(KeepLocalData) { RDA.Push(LocalTableName, RemoteConnection, RdaBatchOption.BatchingOff); } //在下載前,必須刪除本地表 //打開本地數(shù)據(jù)庫連接 cn = new SqlCeConnection(LocalConnection); cn.Open(); //刪除本地表 SQL = "DROP TABLE " + LocalTableName; cmd = new SqlCeCommand(SQL, cn); //如果表不存在,會出現(xiàn)錯誤 try { cmd.ExecuteNonQuery(); } catch{ } //關(guān)閉連接 cn.Dispose(); //最后下載遠程表 SQL = "SELECT PlayerName, Hole, Result FROM " + RemoteTableName; RDA.Pull(LocalTableName, SQL, RemoteConnection, RdaTrackOption.TrackingOnWithIndexes, "RDAErrors"); //清除 RDA.Dispose(); }
注意我給Pull方法傳遞了一個布爾變量。如果本地已經(jīng)存在某個表,那么把遠程服務(wù)器表的內(nèi)容傳遞到本地SQL Server CE表是無法做到的,因此傳遞前必須執(zhí)行一個DROP TABLE語句。該布爾變量用于控制第一次向服務(wù)器傳遞數(shù)據(jù)時是否保持本地數(shù)據(jù)。你也可以看到Push方法的新的批處理參數(shù)。在本例中我使用了BatchingOff,這意味著我不認為提交到服務(wù)器的行是批處理的,而那意味著要么全部完成要么什么也不作。另一個可以的選項是BatchingOn。你可以看到,我使用了SqlCeCommand來執(zhí)行DROP TABLE。
我想強調(diào)我在Pull語句中指定了字段的名稱。記住通常要指定字段名稱,無論是在Pull語句中還是在正常的SELECT語句中。通過這種做法,你可以確保只涉及絕對需要的數(shù)據(jù)。另外,你還可以輕易地防止與查找不能下載的字段(例如rowguidcol、int identity和timestamp字段)相關(guān)的問題。
服務(wù)器上有什么
在我們進一步深入該Pocket PC應(yīng)用程序源代碼前,我將解釋以下服務(wù)器端有什么內(nèi)容。遠程服務(wù)器是SQL Server 2000,運行著一個叫GolfAnyplace的數(shù)據(jù)庫,該數(shù)據(jù)庫只有一個表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] W99vH NOCHECK ADD CONSTRAINT [PK_Result] PRIMARY KEY CLUSTERED ( [PlayerName], [Hole], [Result] ) ON [PRIMARY]
我使用幫助向?qū)渲昧薙QL Server CE 2.0服務(wù)器代理。
填充ListView
GolfAnyplace項目演示了兩種填充listview的方法:使用DataSet和使用DataReader。因為我選擇在SQL Server CE周圍實現(xiàn)包裝,所以最好使用DataSet。DataSet可以不連接,并且在不連接狀態(tài)下在類之間傳遞。因為DataReader要求打開到數(shù)據(jù)庫的連接,因此盡可能在打開和關(guān)閉連接附近使用它。第一個例子代碼顯示了怎樣通過在DataSet循環(huán)填充一個listview。
下面是填充listview的代碼:
private void UpdateResultListView() { //更新listview DataSet ds = null; SQLServerCEWrapper SSCEWrapper = new SQLServerCEWrapper(); //得到DataSet ds = SSCEWrapper.GetPlayerResult(txtPlayerName.Text); //清除listview lvwResult.Items.Clear(); //在DataSet中循環(huán) 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); } }
下面是在包裝類中與之對應(yīng)的代碼:
public DataSet GetPlayerResult(string PlayerName) { //從本地數(shù)據(jù)庫得到數(shù)據(jù),作為DataSet返回 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); //使用數(shù)據(jù)適配器用數(shù)據(jù)填充DataSet da.Fill(ds, LocalTableName); //清除 cn.Dispose(); cmd.Dispose(true); // Return the DataSet return ds; }
下面的示例代碼演示了怎樣使用DataReader作相同的事情:
private void UpdateDetailedResultListView() { //使用DataReader更新詳細結(jié)果 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(); //初始化命令并執(zhí)行閱讀程序 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); } //關(guān)閉DataReader dtr.Close(); //清除 cmd.Dispose(true); cn.Dispose(); }
最后,注意我在項目中使用了一個Options窗體(圖4),需要一些附加的代碼來支持它。
圖4. SQL Server CE Options窗體
技巧
下面是一些我希望與你共享的技巧:
· 很好地設(shè)計界面。
· 不能讓SQL Server CE代碼沒有Try、Catch和Finally等適當?shù)腻e誤處理程序。
· 如果你沒有Pocket PC用于開發(fā)或者Pocket PC沒有網(wǎng)卡,可以使用Pocket模擬器。
· 信任SQL Server CE連接向?qū)В辽匍_始時信任。
· 當上載和下載數(shù)據(jù)時使用服務(wù)器端SQL Profiler或者合并復(fù)制監(jiān)視發(fā)生的情況。如果沒有其它情況發(fā)生,你可以看到什么時候或是否發(fā)生了某事。
· 學習在Internet上在何處查找編碼問題的答案。
結(jié)論
Visual Studio .NET、Visual C#或Visual Basic .NET與SQL Server CE 2.0一起工作時匹配得很好。希望你能從本文我提供的方法中學習到一些好的想法。