科技行者

行者学院 转型私董会 科技行者专题报道 网红大战科技行者

知识库

知识库 安全导航

至顶网软件频道基础软件从 Pocket Access 向 SQL Server CE 进行数据库迁移

从 Pocket Access 向 SQL Server CE 进行数据库迁移

  • 扫一扫
    分享文章到微信

  • 扫一扫
    关注官方公众号
    至顶头条

本文讨论有关将用 eMbedded Visual Basic 编写的 Pocket PC 应用程序迁移到 .NET Compact Framework 的问题。迁移包括从使用 ADO CE 访问 Pocket Access 数据库中的数据更改为使用 ADO.NET 访问 SQL Server CE 中的数据。本文提供了 Visual Basic 和 C# 两种版本的示例代码。

作者:Christian Forsberg 来源:MSDN 2007年9月1日

关键字: Pocket Access SQL Server 迁移

  • 评论
  • 分享微博
  • 分享邮件

简介

无论您是已经决定将应用程序和数据从以前的 Pocket PC 工具(Microsoft eMbedded Visual Basic 和 Pocket Access)迁移到目前的技术(Microsoft .NET Compact Framework 和 Microsoft SQL Server CE),还是正在考虑这一决定,您都需要研究这一迁移的一些困难的理由。

第一个理由是您的设备将提高性能。在设备处理器(即使是最新的最优秀的处理器)上,性能无疑非常重要。在 SQL Server CE 中正确地设置数据库(通过适当的密钥、索引等)以后,它的性能将完全超过 Pocket Access — 有时可以超过好多倍。

第二个理由是 SQL Server CE 中更为丰富的类型系统将使您的设备能够更有效地存储数据。您可以存储在 Pocket Access 中的所有内容都可以存储在 SQL Server CE 中(不止如此)。因为该类型系统是 SQL Server 2000 的类型系统的干净子集,所以同步也会更加容易。

第三个理由是当前的数据访问中间件 (ADO.NET) 将为您提供更多的功能,以使您除了操作数据的定义以外,还可以读取和操作数据本身。新增的且本来断开的模型(由 DataSet 表明)使您在数据的使用方式方面具有更多的自由。可以将 DataSet 发送到 XML Web 服务(当您通过 ASP.NET 创建 XML Web 服务时,DataSet 类型将受到天然支持)。您可以将 DataSet 作为 XML(包含 XML 架构信息,或者在单独的文件中)存储到文件系统中(可以将 XML 从相同的文件重新读取到 DataSet 中)。

最后一个理由是,除了数据操作以外,更加丰富的 SQL 语法还使您在读取数据时具有更多的选择(使用子查询、外部联接等)。以前,您需要在检索 Recordset 之后的代码中实现大量逻辑,而现在您可以直接在它所属的 SQL 命令中完成该工作。在以前的代码中,完成诸如通过 LTRIMCASE...WHEN...THEN...ELSE...END 语句格式化 SELECT 语句中的列的工作要困难得多,并且通常会更慢。

当从以前的 Pocket PC 应用程序进行的迁移同时涉及到数据和代码的迁移时,您可以在下列三个不同的级别执行迁移:

数据库

数据库中间件

源代码

在数据库级别,可以将数据和代码从 Pocket Access 迁移到 SQL Server CE。主要差异与受支持的数据类型有关。当您迁移数据访问代码时,数据库中间件中的差异主要与受支持的对象以及使用这些对象的语法有关。通常,当您迁移源代码时,差异同时与不同的语言以及受支持的类库有关。下列各部分将描述不同级别之间的差异。

 

从 Pocket Access 迁移到 SQL Server CE

通常,以前的 Pocket PC 应用程序中的数据源自桌面计算机的 Microsoft Access 数据库。当需要进行桌面计算机复制时,通常会借助于 Microsoft ActiveSync 中内置的同步功能在桌面计算机和设备之间复制这些数据。当数据迁移到 SQL Server CE 时,第一步是将现有的 Access 数据库迁移到 SQL Server 2000。

您可以从 Access 内部执行该迁移,方法是通过连接到 SQL Server 2000 数据库的开放式数据库连接 (ODBC) 数据源来导出表。然后,您可以通过使用远程数据访问 (RDA) 将 SQL Server 2000 中的数据复制到设备上的 SQL Server CE。有关如何完成该复制的详细信息,请参阅本文中的“同步”一节。

表 1 显示了每个数据库支持的不同数据类型的映射。

表 1. 数据类型映射
Access 数据类型 Pocket Access 类型 SQL Server 2000 类型 SQL Server CE 类型

Text

varchar

nvarchar

nvarchar

Memo

text

ntext

ntext

LongInt

integer

int

int

Int, Byte

smallint

smallint

smallint

Double, Single

double

float

float

Replication ID

varbinary

uniqueidentifier

uniqueidentifier

Date/Time

datetime

datetime

datetime

Currency

double

money

money

AutoNumber

integer

int

int

YesNo

boolean

bit

bit

OleObject

varbinary

image

image

HyperLink

text

ntext

ntext

Lookup

varchar

nvarchar

nvarchar

SQL Server 2000 支持所有 Access 数据类型,并且因为 SQL Server CE 支持(不用转换)导出中生成的所有类型,所以迁移过程中不会丢失任何信息。

 

从 ADO CE 迁移到 ADO.NET

ActiveX Data Objects for Windows CE (ADO CE) 是作为 ADO 的设备版本创建的,并且第一个版本 (3.0) 只支持 Pocket Access。在创建 SQL Server CE 时,更新了 ADO CE 以支持它。尽管很多以前的 Pocket PC 应用程序使用 ADO CE(3.0) 的第一个版本来访问 Pocket Access 数据库,但本节中的信息只与最新版本 (3.1) 有关。不过,下列代码示例也应该适用于 ADO CE .0。

ADO CE 支持下列对象:Recordset、Field(和集合)、Connection 和 Error(和集合)。但是,ADO CE 不支持完整(桌面)ADO 实现中包含的 Command 对象。作为 ADO CE 的扩展,ADO XCE 包含管理数据库的功能,如操作表、字段和其他数据库对象。

.NET Compact Framework 中的 ADO.NET 是完整 .NET Framework 数据访问中间件的设备版本。您可以在“System.Data.SqlServerCe”命名空间中找到 ADO.NET for SQL Server CE,它通常被称为 SQL Server CE 的托管提供程序。该托管提供程序中最重要的对象是:SqlCeConnection(用于连接到数据库)、SqlCeDataAdapter(用于访问数据库中的数据)、SqlCeCommandBuilder(用于生成 SQL 命令以便在数据库中保存数据)、SqlCeCommand(用于向数据库发出 SQL 命令)、SqlCeDataReader(用于从数据库中快速地顺序读取数据)、SqlCeEngine(用于管理数据库)以及 SqlCeException(用于错误处理)。有关 ADO CE 和 ADO.NET 的详细信息,请参阅 Microsoft Visual Studio .NET 帮助文件。

在 ADO CE 中执行的多个重要操作可以通过 ADO.NET 来完成。首先,您需要打开数据库连接。在 ADO CE 中,您可以按如下方式打开数据库。

Dim connection AS ADOCE.Connection
Set connection = CreateObject("ADOCE.Connection.3.1")
connection.ConnectionString = "Data Source=\MyDatabase.cdb"
connection.Open

您还可以将连接字符串作为连接的 Open 方法的参数提供。调用 Connection 对象上的 Close 方法可以关闭该连接。

如果您要使用 ADO.NET 和 Visual Basic .NET,则您可以按如下方式打开数据库连接。

Dim connection As SqlCeConnection
connection = New SqlCeConnection("Data Source=\MyDatabase.sdf")
connection.Open()

与 ADO CE 类似,该连接的 Close 方法可以关闭该连接。

在 C# 中,相同的代码如下所示。

SqlCeConnectionconnection
connection = new SqlCeConnection(@"Data Source=\MyDatabase.sdf");
connection.Open();

因为越早考虑错误处理越好,所以您可以查看 ADO CE 中是如何处理错误的。首先,您需要按如下方式设置错误处理程序:

On Error Resume Next
Err.Clear

在应用程序执行每个数据库命令之后,Connection 对象上的错误集合 (Errors) 包含已经发生的任何错误。您可以通过使用以下代码让应用程序报告任何错误。

If (Err.Number <> 0) Then
  For i = 1 To connection.Errors.Count
    MsgBox connection.Errors(i).Number & " - " & _
      connection.Errors(i).Description, vbCritical, "Error"
  Next i
  Err.Clear
End If

对于每个错误,都会显示一个消息框,其中带有错误号和错误说明。当您使用 Pocket Access 数据库时,ADO CE 中的错误对象还支持本机错误 (NativeError) 和错误源 (Source) 的属性。

在 Visual Basic .NET 中,您可以通过使用 Try Catch 结构处理错误。下面是用于捕获和报告 ADO.NET 错误的 Visual Basic .NET 代码。

Try
' Database code
Catch ex As SqlCeException
  If Not ex.InnerException Is Nothing Then
      MessageBox.Show("Inner Exception: " +         ex.InnerException.ToString())
  End If
  
  Dim s As StringBuilder = New StringBuilder() 
  Dim error As SqlCeError
  For Each error In ex.Errors
      s.Append("Error Code: " + error.HResult.ToString("X"))
      s.Append(vbCrLf + "Message   : " + error.Message)
      s.Append(vbCrLf + "Minor Err.: " + error.NativeError)
      s.Append(vbCrLf + "Source    : " + error.Source)
  
      Dim numericErrorParameter As Integer
      For Each numericErrorParameter In error.NumericErrorParameters
          If numericErrorParameter <> 0 Then
              s.Append(vbCrLf + "Num. Par. : " +                 numericErrorParameter)
          End If
      Next
  
      Dim errorParameter As String
      For Each errorParameter In error.ErrorParameters
          If errorParameter.Length > 0 Then
              s.Append(vbCrLf + "Err. Par. : " + errorParameter)
          End If
      Next
  
      MessageBox.Show(s.ToString())
      s.Remove(0, s.Length)
  Next
End Try

在 ADO.NET 中,发生的异常(错误)包含的信息要比 ADO CE 提供的信息多得多。该信息帮助开发人员更快地查找程序错误,并且还在处理每个异常方面提供了更多的控制。首先,应用程序在消息框中报告任何内部异常,然后,应用程序在每个错误的消息框中报告(通过详细信息)异常对象 (ex) 上的集合 (Errors) 中的所有错误。

在 C# 中,代码如下所示。
try
{
  // Database code
}
catch (SqlCeExceptionex)
{
  if(ex.InnerException != null) 
      MessageBox.Show("Inner Exception: " +         ex.InnerException.ToString());

  StringBuilder s = new StringBuilder();
  foreach (SqlCeError error in ex.Errors) 
  {
      s.Append("Error Code: " + error.HResult.ToString("X"));
      s.Append("\nMessage   : " + error.Message);
      s.Append("\nMinor Err.: " + error.NativeError);
      s.Append("\nSource    : " + error.Source);

      foreach (int numericErrorParameter in
                        error.NumericErrorParameters) 
          if(numericErrorParameter != 0)
              s.Append("\nNum. Par. : " + numericErrorParameter);
              
      foreach (string errorParameter in error.ErrorParameters) 
          if(errorParameter.Length > 0)
              s.Append("\nErr. Par. : " + errorParameter);

      MessageBox.Show(s.ToString());
      s.Remove(0, s.Length);
  }
}

既然您具有连接并且可以处理返回的错误,那么您就可以开始与数据库交互了。最重要的操作是在数据库中查询数据。在 ADO CE 中,可以用下列两种方式查询数据库:通过使用 Recordset 对象上的 Open 方法,或者通过使用 Connection 对象上的 Execute 方法。

使用 Recordset 对象的代码如下所示。

Dim rs As ADOCE.Recordset
Dim sql As String
sql = "SELECT* FROM Customers"
Set rs = CreateObject("ADOCE.Recordset.3.1")
rs.Open sql, connection, adOpenDynamic, adLockOptimistic

如果您从前使用 Connection 对象,则您可以用以下行替换最后两行。

Set rs = connection.Execute(sql)

使用数据适配器的相应 Visual Basic .NET 代码如下所示。

Dim sql As String = "SELECT* FROM Customers"
Dim da As New SqlCeDataAdapter(sql, connection)
Dim ds As New DataSet
da.Fill(ds, "Customers")

在前面的代码中,通过将数据库 (SQL) 命令和 Connection 对象作为构造函数的参数进行传递,创建了一个新的数据适配器对象 (da)。然后,通过使用该数据适配器上的 Fill 方法为 DataSet (ds) 填充了数据。Fill 方法的第二个参数设置了 DataSet 中刚刚检索的表的名称。在后台,数据适配器使用 SqlCeCommand 对象(在它的 SelectCommand 属性中)查询数据库。

在 C# 中,代码如下所示。

string sql = "SELECT* FROM Customers";
SqlCeDataAdapterda = new SqlCeDataAdapter(sql, connection);
DataSetds = new DataSet;
da.Fill(ds, "Customers");

ADO.NET 中提供的另一种读取数据的方式是 SqlCeDataReader。您可以使用它以一种非常高效的方式来顺序读取数据。当性能很重要以及数据量很高时,您应该考虑该选项。以下代码显示了一个有关如何使用 SqlCeDataReader 的示例。

Dim sql As String = "SELECT* FROM Customers"
Dim cmd As New SqlCeCommand(sql, connection)
Dim dr As SqlCeDataReader= cmd.ExecuteReader()
While dr.Read
  ListBox1.Items.Add(dr(1))
End While

当从命令对象创建读取器时,使用第二个列 (dr(1)) 向列表框 (ListBox1) 中添加行。

在 C# 中,代码如下所示。

string sql = "SELECT* FROM Customers";
SqlCeCommandcmd = new SqlCeCommand(sql, connection);
SqlCeDataReaderdr = cmd.ExecuteReader();

在 ADO CE 中,唯一受支持的执行不返回任何数据的命令的方式是使用 Connection 对象上的 Execute 方法,然后通过使用以下代码忽略返回值。

connection.Execute("DELETE Customers WHERE CustomerID=1")

在 Visual Basic .NET 中,您可以使用 SqlCeCommand 对象执行命令,如下所示。

Dim cmd As SqlCeCommand= connection.CreateCommand()
cmd.CommandText = "DELETE Customers WHERE CustomerID=1"
cmd.ExecuteNonQuery()

在 C# 中,代码如下所示。

SqlCeCommandcmd = connection.CreateCommand();
cmd.CommandText = "DELETE Customers WHERE CustomerID=1";
cmd.ExecuteNonQuery();

您还可以通过向构造函数提供命令文本和连接来创建命令对象。

该命令对象具有 ExecuteScalar 方法,当您只需要返回单个值时,可以使用该方法。如果该命令只返回一个带有一个列的行,则您可以使用以下代码。

Dim cmd As SqlCeCommand= connection.CreateCommand()
cmd.CommandText = "SELECTCOUNT(*) FROM Customers"
Dim numberOfCustomers As Integer = cmd.ExecuteScalar()

在 C# 中,代码如下所示。

SqlCeCommandcmd = connection.CreateCommand();
cmd.CommandText = "SELECTCOUNT(*) FROM Customers";
int numberOfCustomers = (int)cmd.ExecuteScalar();

该命令对象还支持参数(但是,它不支持命名参数),您可能发现这在多次使用命令时非常有用,因为参数化查询避免了重复计算查询计划的需要,因而显著提高了多次发出的命令的总体性能。

您还可以用 ADO CE 中的 Connection 对象上的 Execute 方法,通过使用 SQL insertupdatedelete 命令来执行数据操作。您可以用 ADO.NET 中的 SqlCeCommand 对象,通过使用上述命令来操作数据,从而获得良好的性能。但是,您必须手动处理 SQL 语法,这有时可能不太方便。ADO CE 和 ADO.NET 都已经轻松地使用结构来执行数据操作。

在 ADO CE 中,您需要以特定的方式打开 Recordset,以便进行数据操作。您提供的命令可能只包含一个表名,并且您需要提供最后一个特定参数。下面是一个在 ADO CE 中添加、更新和删除行的代码示例。

Dim rs As ADOCE.Recordset
Set rs = CreateObject("ADOCE.Recordset.3.1")
rs.Open "Customers", connection, adOpenDynamic, _
  adLockOptimistic, adCmdTableDirect
' INSERT
rs.AddNew
rs("CustomerID").Value = 1
rs("CompanyName").Value = "My Company"
rs.Update
' UPDATE
rs.Find "CustomerID=2"
rs("CompanyName").Value = "Modified Name"
rs.Update
' DELETE
rs.Find "CustomerID=3"
If Not rs.EOF Then rs.Delete
rs.Close

应用程序用直接表操作 (adCmdTableDirect) 的参数打开一个包含 Customers 表的 Recordset。然后,您可以通过使用用于添加新行 (AddNew)、更新已添加或已更改的行 (Update) 和移除行 (Delete) 的 Recordset 内置功能来操作数据。

Visual Basic .NET 中的相应代码如下所示。

Dim da As New SqlCeDataAdapter("SELECT* FROM Customers", _
  connection)
Dim cb As New SqlCeCommandBuilder(da)
da.InsertCommand = cb.GetInsertCommand()
da.UpdateCommand = cb.GetUpdateCommand()
da.DeleteCommand = cb.GetDeleteCommand()
Dim ds As DataSet= New DataSet
da.Fill(ds, "Customers")
' INSERT
Dim dr As DataRow = ds.Tables("Customers").NewRow()
dr("CustomerID") = 1
dr("CustmerName") = "My Company"
ds.Tables("Customers").Rows.Add(dr)
' UPDATE
dr = ds.Tables("Customers").Select("CustomerID=2")(0)
dr("CustomerName") = "Modified Name"
' DELETE
dr = ds.Tables("Customers").Select("CustomerID=3")(0)
dr.Delete()
da.Update(ds, "Customers")

这里,应用程序首先通过向构造函数传递 select 命令和连接,初始化一个新的 SqlCeDataAdapter。您可以使用 SqlCeCommandBuilder 对象,根据数据适配器中的 select 命令生成需要的数据操作命令(insertupdatedelete)。然后,这些命令在数据适配器中得到设置;稍后,当需要在数据库中保存对 DataSet 中的行所做的更改时,数据适配器将使用这些命令来操作数据。请注意 ADO.NET 模型是如何通过数据适配器达到更高的断开程度的;它充当完全断开的 DataSet 和数据库之间的智能链接。DataSet 不需要任何指向数据库的连接,并且当您用数据填充 DataSet 以后,您可以关闭数据适配器。然后,您可以操作 DataSet;当您完成该操作以后,您可以创建一个新的数据适配器来更新数据库。由于有了新的数据操作模型,因此通过 ADO.NET 可以完成的工作比通过 ADO CE 可能完成的工作多得多。

在 C# 中,相同的代码如下所示。

SqlCeDataAdapterda = new SqlCeDataAdapter(
  "SELECT* FROM Customers", connection);
SqlCeCommandBuildercb = new SqlCeCommandBuilder(da);
da.InsertCommand = cb.GetInsertCommand();
da.UpdateCommand = cb.GetUpdateCommand();
da.DeleteCommand = cb.GetDeleteCommand();
DataSetds = new DataSet();
da.Fill(ds, "Customers");
// INSERT
DataRow dr = ds.Tables["Customers"].NewRow();
Dr["CustomerID"] = 1;
Dr["CustomerName"] = "My Company";
ds.Tables["Customers"].Rows.Add(dr);
// UPDATE
dr = ds.Tables["Customers"].Select("CustomerID=2")[0];
dr["CustomerName"] = "Modified Name";
// DELETE
dr = ds.Tables["Customers"].Select("CustomerID=3")[0];
dr.Delete();
da.Update(ds, "Customers");

迄今为止,本文已经讨论了读取和操作数据的方式。接下来,本文将讨论两个数据库中间件实现中的 SQL 支持中的一些差异。实际上,ADO CE 在以前的工具中实现了 SQL 支持,而即使 ADO.NET 对 SQL 有一些支持,SQL Server CE 本身也支持大多数 SQL 语法。

作为 Pocket PC 程序员,您很快就会发现新工具中 SQL 支持的丰富性 — 包括 SQL 的数据操作语言 (DML) 和数据定义语言 (DDL) 这两个部分。例如,在 ADO CE 中,当您需要知道表中的行数时,您必须打开一个带有从该表中加载的所有实际行的 Recordset 对象。在 ADO.NET 中,您可以简单地查询数据库表的行数 (SELECT COUNT(*) FROM Customers),并且在单个值中获得结果(通过 SqlCeCommand 对象上的 ExecuteScalar 方法)。

为了帮助您直观地了解新工具对于 SQL 的扩展支持,图 1 列出了 ADO CE 和 SQL Server CE 中的保留字。


1. ADO CE SQL Server CE 中的 SQL 保留字。

左边一列包含 ADO CE 中的保留字,右边的六个列包含 SQL Server CE 中的保留字。有关详细信息,请浏览 SQL Reference in the SQL Server CE Helpfile(称为联机图书),或者浏览 Visual Studio .NET Help 中的这一内容。

 

从 eMbedded Visual Basic 迁移到 Visual Basic .NET 和 C#

Moving from eMbedded Visual Basic to Visual Basic .NET 一文讨论了有关从 eMbedded Visual Basic 迁移到 Visual Basic .NET 的大多数常规问题。该文章的“Working with Databases”部分包含的示例代码描述了在使用 SQL Server CE 时,如何将代码从 eMbedded Visual Basic 迁移到 Visual Basic .NET。

它介绍了 Visual Basic .NET 和 C# 这两个版本的迁移代码。这样做的主要原因是让更多身为开发人员的读者能够立即从示例代码中受益。而且,如果您是传统的 eMbedded Visual Basic 开发人员并且要开始使用 .NET Compact Framework,则您还应该考虑 C#。考虑 C# 的主要原因是:在 .NET 中有如此之多的新功能,以至于您可能发现可以使用它来获得一个全新的开始,而无需使用 Visual Basic 遗产。

 

同步

很多以前的 Pocket PC 应用程序借助于 ActiveSync 同步桌面计算机和设备的数据。ActiveSync 包含一些支持,以便您同步数据库以及选择要在同步操作中包括的表。在 ActiveSync 中,不包含对于将 Microsoft Access 或 SQL Server 2000 数据库与设备上的 SQL Server CE 数据库进行同步的固有支持。但是,您可以使用 ActiveSync 使设备可以借助于传递连接来连接到桌面计算机。

对于 SQL Server CE,您需要使用不同的模型。SQL Server 2000 将同步功能作为 Web 服务器的插件予以支持。在 SQL Server CE Server Tools 安装过程中,在 Internet 信息服务 (IIS) 中设置了一个虚拟目录。该虚拟目录包含一个称为 SQL Server CE Server Agent 的文件 (sscesa20.dll),您可以通过 HTTP 请求来调用它,以便与 SQL Server 2000 同步数据。在客户端,可以使用 SQL Server CE Client Agent,它包含用于合并复制(设置数据订阅)和远程数据访问 (RDA) 的功能。有关合并复制的详细信息,请参阅 SQL Server CE Help(联机图书);该文章集中讨论了如何通过使用 RDA 来同步数据。

即将问世的示例(本文中的大多数代码示例都以它为基础)使用一个源自 Access 的数据库。Access 数据库被逐个表地导出到 SQL Server 2000,而 Access 中提供的功能被导出到 ODBC 数据源 — 在本例中,为 SQL Server 2000 数据库。(有关如何导出 Access 数据库的详细信息,请参阅 Access 帮助或 How To Convert an Access Database to SQL Server。)下一步是通过使用 RDA 中的 Pull 方法,将表从 SQL Server 2000 下载到 SQL Server CE 数据库,如下面的代码所示。

Dim localDatabase As String = "\MobileSales.sdf" 
Dim localConnectionString As String = "Data Source=" + localDatabase 
Dim remoteConnectionString As String = "Provider=sqloledb;" + _
  "Data Source=(local);Initial Catalog=MobileSales;" + _
  "Integrated Security=SSPI;" 
Dim tables() As String =
  New String() {"Customers", "Orders", "Products"}

If File.Exists(localDatabase) Then
  File.Delete(localDatabase)
End If

Dim engine As New SqlCeEngine(localConnectionString)
engine.CreateDatabase()
engine.Dispose()

Dim rda As New SqlCeRemoteDataAccess()
rda.InternetUrl = "http://server/ssce/sscesa20.dll"
rda.LocalConnectionString = localConnectionString

Dim table As String
For Each table In tables
  Try
      rda.Pull(table, "SELECT* FROM [" + table + "]", _
          remoteConnectionString, _
          RdaTrackOption.TrackingOffWithIndexes, "ErrorTable")
  Catch ex As SqlCeException
      MessageBox.Show(ex.Message);
  End Try
Next
rda.Dispose()

本地连接字符串指向将要创建 SQL Server CE 数据库文件的位置,而远程连接字符串则指向应用程序将从中检索数据的 SQL Server 2000 数据库 (MobileSales)。如果数据库文件已经存在,则应用程序会删除它,然后,应用程序通过使用 SqlCeEngine 对象创建数据库。应用程序设置 RDA (SqlCeRemoteDataAccess) 对象,以指向服务器或桌面计算机中驻留 SQL Server CE Server Agent (sscesa20.dll) 的虚拟目录 (ssce)。应用程序使用表数组将表从 SQL Server 2000 拉到刚刚创建的本地 SQL Server CE 数据库中。

请注意,虚拟目录使用匿名访问,并且,通过指定的连接字符串给予 IIS 中的匿名用户(例如,IUSR_MACHINENAME)访问 SQL Server 2000 中数据库的权利。在实际方案中,虚拟目录很可能需要指定的用户,并且随后在 RDA 对象(在它的 InternetLoginInternetPassword 属性中)上设置凭据。

还要注意,应用程序指定用于下载索引和关闭跟踪的选项 (TrackingOffWithIndexes)。如果指定了跟踪,则应用程序以后可以使用 RDA 对象的 Push 方法将表推送回服务器,以便将更改添加到 SQL Server 2000 数据库中。有关详细信息,请参阅 SQL Server CE Help(联机图书)。

在 C# 中,代码如下所示。

string localDatabase = @"\MobileSales.sdf";
string localConnectionString = "Data Source=" + localDatabase;
string remoteConnectionString = @"Provider=sqloledb;" +
  "Data Source=(local);Initial Catalog=MobileSales;" +
  "Integrated Security=SSPI;";
string[] tables = new string[] { "Customers", "Orders", "Products" };

if(File.Exists(localDatabase))
  File.Delete(localDatabase); 
using(SqlCeEngineengine = new SqlCeEngine(localConnectionString))
  engine.CreateDatabase();

using(SqlCeRemoteDataAccessrda = new SqlCeRemoteDataAccess())
{
  rda.InternetUrl = "http://server/ssce/sscesa20.dll";
  rda.LocalConnectionString = localConnectionString;

  foreach (string table in tables)
  {
      try
      {
          rda.Pull(table, "SELECT* FROM [" + table + "]",             remoteConnectionString,
              RdaTrackOption.TrackingOffWithIndexes, "ErrorTable");
      }
      catch (SqlCeExceptionex)
      {
          //MessageBox.Show(ex.Message);
          showErrors(ex);
      }
  }
}

您也可以不使用上述代码,而是继续与桌面计算机上的 Access 数据库进行同步,或者用另一个数据库进行复制。常规解决方案是在服务器(或桌面计算机)上设置 XML Web 服务。该 Web 服务可以发布方法,以供在数据库中执行数据操作的设备使用。

下面的代码示例从 XML Web 服务(作为 Visual Studio .NET 中的 ASP.NET Web 服务项目创建)中使用原始的 Access 数据库。

 <WebMethod> _ 
Public Function Sync(ByVal newCustomerNames() As String) As DataSet

  ' Set up connection and data adapter
  OleDbConnection cn = New OleDbConnection(+
      "Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;" +
      "Data Source=C:\Mobile Sales.mdb")
  cn.Open()

  ' Set up command and insert new customers
  Dim cmd As New OleDbCommand(
      "INSERT INTO Customers (CustomerName) VALUES(?)", cn) 
  cmd.Parameters.Add("?", DbType.String) 
  For Each newCustomerName As String In newCustomerNames 
      cmd.Parameters(0).Value = newCustomerName 
      cmd.ExecuteNonQuery 
  Next

  '  Get all customers
  Dim da As New OleDbDataAdapter("SELECT* FROM Customers", cn)
  Dim ds As DataSet=  New DataSet() 
  da.Fill(ds, "Customers")

  ' Send back all customers
  Return ds
End Function

XML Web 服务方法(Web 方法)采用新客户的名称数组作为参数。在设置了连接和命令对象以后,应用程序使用该数组创建新的客户行。请注意代码是如何使用参数来提高插入多个客户的速度的。还要注意,使用设备上的命令对象(通过 .NET Compact Framework 中的 ADO.NET)完成该操作的语法是非常类似的。然后,应用程序再次查询数据库,并且将所有客户名称(包括新名称)返回到设备。注意这就是同步功能,并且需要将所有客户返回到该设备,因为当该设备断开连接时,其他设备可能已经添加了客户。

在 C# 中,代码如下所示。

[WebMethod]
public DataSetSync(string[] newCustomerNames)
{
  // Set up connection and data adapter
  OleDbConnection cn = new OleDbConnection( +
      "Provider=Microsoft.Jet.OLEDB.4.0;User ID=Admin;" +
      @"Data Source=C:\Mobile Sales.mdb");
  cn.Open();

  // Set up command and insert new customers
  OleDbCommand cmd = new OleDbCommand(
      "INSERT INTO Customers (CustomerName) VALUES(?)", cn);
  cmd.Parameters.Add("?", DbType.String);
  foreach(string newCustomerName in newCustomerNames)
  {
    cmd.Parameters[0].Value = newCustomerName;
    cmd.ExecuteNonQuery();
  }

  // Get all customers
  OleDbDataAdapter da = new OleDbDataAdapter(
      "SELECT* FROM Customers", cn);
  DataSetds = new DataSet();
  da.Fill(ds, "Customers");

  // Send back all customers (including new)
  return ds;
}

采用类似的方式,应用程序可以访问任何在服务器上具有 ADO.NET 的符合 OLE DB 的数据源 — 或其他具有自己的托管提供程序的数据库(例如,Oracle)。

 

Mobile Sales .NET 示例

Mobile Sales .NET 示例演示了如何将用 eMbedded Visual Basic 编写并且使用 ADO CE 访问 Pocket Access 数据库中的数据的应用程序迁移到 .NET Compact Framework,以便它使用 ADO.NET 访问 SQL Server CE 数据库中的数据。该示例随附在 Larry Roof 于 2001 年撰写的文章 Data To Go 中。

Mobile Sales .NET 示例是用 Visual Studio.NET2003 创建的。它由单个窗体组成,如图 2 所示。


2. Mobile Sales .NET

其方案是一个访问很多个客户的送货司机。在每个站点,他都会取走一份定单。对于每份定单,他都会选择产品,输入数量,然后点击 Add。如果该司机出了错,则他会选择项行,然后点击 Delete。要保存定单,该司机可以点击 Save this order 菜单命令;然后,他可以接着处理下一份定单。当该司机保存了所有客户定单时,他退出应用程序。该示例的工作方式与原始的 eMbedded Visual Basic 示例 (Data To Go) 相同。

 

代码演练

当应用程序启动时,它运行以下代码。在原始 eVB 示例中,这些代码位于窗体的 Load 事件中。

Dim cnMobileSalesAs ADOCE.Connection
Private Sub Form_Load()

' Set up an error handler.
' NOTE: eMbedded Visual Basicsupports only the On Error Resume
' Next method.
  On Error Resume Next
  Err.Clear

' Build the menu bar.
  ConstructMenuBar

' Make a connection to the database.
  Set cnMobileSales= CreateObject("ADOCE.Connection.3.0")
  cnMobileSales.Open "data source=" & App.Path & "\Mobile Sales.cdb"

' Was the connection successful?
  If cnMobileSales.Errors.Count > 0 Then
      MsgBox "Error connecting to database." & vbCrLf & _
                      "Error " & Err.Number & vbCrLf & _
                      Err.Description, _
                      vbCritical, "Open Database"
      Set cnMobileSales= Nothing
      On Error GoTo 0
      App.End
  End If

' Load the customer and product information.
  LoadComboBox cmbCustomers, "Customers", "CustomerID", _
              "CustomerName", True
  LoadComboBox cmbProducts, "Products", "ProductID", _
              "Description", False

' Set up the selected product's list view control.
  FormatListView

End Sub

如上述代码的第一行所示,连接 (cnMobileSales) 被声明为类型 ADOCE.Connection 的窗体变量。应用程序创建了一个 ADO CE Connection 对象并且打开了 (Open) 该对象。连接字符串的数据源设置为与应用程序位于同一目录中的 Pocket Access 数据库文件。然后,应用程序检查在试图打开数据库的过程中是否发生了错误。如果发生了错误(在 Connection 对象上的 Errors 集合中枚举),则会在消息框中显示第一个错误,并且该应用程序将关闭。然后,两个方法调用 (LoadComboBox) 将数据加载到两个 ComboBox 控件中。

请注意,两个方法调用从属于窗体的设计:MenuBar 控件 (ConstructMenuBar) 的设计以及 ListView 控件 (FormatListView) 的设计。有关详细信息,请参阅原始的 eMbedded Visual Basic 示例代码。

在迁移之后,窗体的 Load 事件的 Visual Basic .NET 代码如下所示。

Private cn As SqlCeConnection
Private Sub MainForm_Load(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles MyBase.Load
  Try
      ' Make the connection to the database
      cn = New SqlCeConnection( _
          "Data Source=" + appPath + "\MobileSales.sdf")
      cn.Open()
  Catch ex As SqlCeException
      MessageBox.Show(ex.Message, Me.Text)
      cn.Close()
      Me.Close()
  End Try

  ' Load the customer and product information.
  loadComboBox(customerComboBox, "Customers", "CustomerID", _
                          "CustomerName", True)
  loadComboBox(productComboBox, "Products", "ProductID", _
                          "Description", False)
End Sub

上述代码中的错误处理具有高度的结构性。如果在应用程序尝试连接到数据库的过程中发生了错误,则会引发异常,就像在 .NET Compact Framework 中处理任何其他错误一样。如果您需要有关该异常的详细信息,则可以分析该异常对象 (ex) 上的错误集合 (Errors)(有关详细信息,请参阅前面的“ADO CE to ADO.NET”部分)。上述代码的第一行将该集合 (cn) 声明为类型 System.Data.SqlServerCe.SqlCeConnection 的类成员变量。应用程序将连接字符串传递给 Connection 对象的构造函数;该构造函数还打开指向数据库的连接。请注意,MainMenu(添加菜单选项)控件和 ListView(添加列)控件的设计是在窗体设计器中进行的;而在以前的工具中,需要大量的代码。

在 C# 中,相同的代码如下所示。

private SqlCeConnectioncn;
private void MainForm_Load(object sender, System.EventArgs e)
{
  try
  {
      // Make the connection to the database
      cn = new SqlCeConnection(
          @"Data Source=" + appPath + @"\MobileSales.sdf");
      cn.Open();
  }
  catch (SqlCeExceptionex)
  {
      MessageBox.Show(ex.Message, this.Text);
      cn.Close();
      this.Close();
  }

  // Load the customer and product information
  loadComboBox(customerComboBox, "Customers", "CustomerID",
      "CustomerName", true);
  loadComboBox(productComboBox, "Products", "ProductID",
      "Description", false);
}

在原始的 eMbedded Visual Basic 示例中,用于将数据加载到 ComboBox 控件中的代码如下所示。

Private Sub LoadComboBox(cmbObject As ComboBox, _
              ByVal strTableName As String, _
              ByVal strIDColumn As String, _
              ByVal strNameColumn As String, _
              ByVal bolSelect As Boolean)
              
  Dim rsTable As ADOCE.Recordset
  Dim strSQLAs String

' Set up error handling.
  On Error Resume Next
  Err.Clear

' Build a RecordSet of all the information.
  strSQL= "SELECT* FROM " & strTableName
  Set rsTable = CreateObject("ADOCE.Recordset.3.0")
  rsTable.Open strSQL, cnMobileSales, adOpenDynamic, _
                      adLockOptimistic
  If (Err.Number) Then
      MsgBox "Error retreiving " & strTableName & _
                      " information. ", vbCritical, "Fill ComboBox"
      On Error GoTo 0
      Exit Sub
  End If

' Place the information into the combo box.
  Do While Not (rsTable.EOF)

' Place name in combo box.
      cmbObject.AddItem rsTable(strNameColumn).Value

' Place the ID number into the itemdata element for the
' name that was just added.
      cmbObject.ItemData(cmbObject.NewIndex) = _
          CLng(rsTable(strIDColumn).Value)

' Attempt to move to the next record.
      rsTable.MoveNext
  Loop

' Select the first item in the list.
  If (cmbObject.ListCount > 0) And (bolSelect = True) Then
      cmbObject.ListIndex = 0
  End If

' Close the recordset.
  rsTable.Close
  Set rsTable = Nothing
  
  On Error GoTo 0
  
End Sub

在设置错误处理以后,应用程序将表 (strTableName) 中的所有行加载到 ADOCE.RecordSet (rsTable) 中。然后,应用程序遍历 RecordSet 中的行,并且对于每个行,应用程序都会在 ComboBox 控件 (cmbObject) 中插入一个项。所插入的每个项还都获得附加到 ItemData 属性的标识。

在迁移以后,相同的 Visual Basic .NET 代码如下所示。

Private Sub loadComboBox(ByVal comboBox As ComboBox, _
      ByVal tableName As String, ByVal valueColumnName As String, _
      ByVal displayColumnName As String, ByVal selectFirst As         Boolean)

  Try
      Dim sql As String = "SELECT" + valueColumnName + ", " + _
                      displayColumnName + " FROM " + tableName + _
                      " ORDER BY " + displayColumnName
      Dim da As New SqlCeDataAdapter(sql, cn)
      Dim ds As New DataSet
      da.Fill(ds, tableName)
      comboBox.DisplayMember = displayColumnName
      comboBox.ValueMember = valueColumnName
      comboBox.DataSource = ds.Tables(tableName)
  Catch ex As SqlCeException
      MessageBox.Show("Could not load " + tableName + " (" + _
              ex.Message + ")!", "Fill ComboBox")
  End Try
End Sub

.NET Compact Framework 中的方法稍有不同。尽管您可以向 ComboBox 控件中添加自定义项,但更常见的方法是使用数据绑定。数据绑定使用 SqlCeDataAdapter 对象 (da) 加载带有表 (tableName) 的行的 DataSet 对象 (ds)。然后,您可以设置 ComboBox 属性,以选择要显示表中的哪个列 (DisplayMember),以及选择表中的哪个列以用于检索当前选择的值 (ValueMember)。最后,ComboBox 绑定到 DataSet 中的唯一表。

在 C# 中,相同的代码如下所示。

private void loadComboBox(ComboBoxcomboBox, string tableName,
  string valueColumnName, string displayColumnName,
  bool selectFirst)
{
  try
  {
      string sql = "SELECT" + valueColumnName + ", " +
                displayColumnName + " FROM " + tableName +
                " ORDER BY " + displayColumnName;
      SqlCeDataAdapterda = new SqlCeDataAdapter(sql, cn);
      DataSetds = new DataSet();
      da.Fill(ds, tableName);
      comboBox.DisplayMember = displayColumnName;
      comboBox.ValueMember = valueColumnName;
      comboBox.DataSource = ds.Tables[0];
  }
  catch (SqlCeExceptionex)
  {
      MessageBox.Show("Could not load " + tableName + " (" +
          ex.Message + ")!", "Fill ComboBox");
  }
}

既然您已经设置了窗体并且加载了 ComboBox 控件,那么您就可以向定单中添加项了。在原始的 eMbedded Visual Basic 示例中,用于向 ListView 控件中添加定单项的代码如下所示。

Private Sub cmdAdd_Click()
  Dim curProductCost As Currency
  Dim lngProductID As Long
  Dim objDetails

' Make sure the user has selected a product.
  If (cmbProducts.ListIndex < 0) Then
      MsgBox "You must select a product before adding it" & _
                      " to the order.", _
                      vbInformation, _
                      "Add Product"
      Exit Sub
  End If

' Make sure the quantity is valid.
  If (CInt(txtQuantity.Text) <= 0) Then
      MsgBox "You must enter a quantity of 1 or more" & _
                      " before adding it to the order.", _
                      vbInformation, _
                      "Add Product"
      Exit Sub
  End If

' Add the current item to the list.
  Set objDetails = lvwOrder.ListItems.Add(, , _
                             cmbProducts.List(cmbProducts.ListIndex))

' Get information about the current product and
' add this information to the current row (the row
' that was just added).
  GetProductInfo cmbProducts.ItemData(cmbProducts.ListIndex), _
                                curProductCost
  objDetails.SubItems(1) = curProductCost
  objDetails.SubItems(2) = txtQuantity.Text
  objDetails.SubItems(3) =     cmbProducts.ItemData(cmbProducts.ListIndex)

' Reset the product fields.
  cmbProducts.ListIndex = -1
  txtQuantity.Text = "0"
  hscQuantity.Value = 0
  
End Sub

应用程序进行检查以确保已经在产品的 ComboBox 控件 (cmbCustomer) 中选择了产品,并且输入了有效的数量。然后,应用程序向 ListView 控件中添加一个项,并且将产品名称作为第一列。应用程序通过使用方法调用 (GetProductInfo) 检索所选产品的价格,然后更新新的 ListView 项的其他列(价格、数量和产品标识)。最后,应用程序重置窗体控件,以便它做好输入下一个定单项的准备。

在迁移以后,相同的 Visual Basic .NET 代码如下所示。

Private Sub addButton_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles addButton.Click

  ' Make sure the user has selected a product.
  If productComboBox.SelectedIndex < 0 Then
      MessageBox.Show("You must select a product before adding it" + _
          " to the order.", "Add Product")
      Return
  End If

    ' Create a new list row.
  Dim lvi As ListViewItem= New ListViewItem(productComboBox.Text)

  ' Get information about the current product and
  ' add this information to the new row.
  Dim curProductCost As Double = getProductInfo( _
      Convert.ToInt32(productComboBox.SelectedValue))
  lvi.SubItems.Add(String.Format("{0:##0.00}", curProductCost))
  lvi.SubItems.Add(quantityNumericUpDown.Value.ToString())
  lvi.SubItems.Add(productComboBox.SelectedValue.ToString())

  ' Add the new row to the list.
  orderListView.Items.Add(lvi)

  ' Reset the product fields.
  productComboBox.SelectedIndex = -1
  quantityNumericUpDown.Value = 1
End Sub

您可以使用 .NET Compact Framework 中的 ListView 控件向列表中添加项 (ListViewItem)。因此,首先需要创建一个项,添加所有列,然后才能将其添加到 ListView 控件中。请注意,quantity 字段的初始值设置为 1(与原始的 eMbedded Visual Basic 示例代码相反),因为该值更有可能出现。

在 C# 中,相同的代码如下所示。

private void addButton_Click(object sender, System.EventArgs e)
{
  // Make sure the user has selected a product
  if(productComboBox.SelectedIndex < 0)
  {
      MessageBox.Show("You must select a product before adding it" +
                                  " to the order.", "Add Product");
      return;
  }

  // No need to make sure the quantity is valid because the
  // NumericUpDown control takes care of that

  // Create a new list row
  ListViewItemlvi = new ListViewItem(productComboBox.Text);

  // Get information about the current product and
  // add this information to the new row
  double curProductCost = getProductInfo(Convert.ToInt32(
      productComboBox.SelectedValue));
  lvi.SubItems.Add(string.Format("{0:##0.00}", curProductCost));
  lvi.SubItems.Add(quantityNumericUpDown.Value.ToString());
  lvi.SubItems.Add(productComboBox.SelectedValue.ToString());

  // Add the new row to the list
  orderListView.Items.Add(lvi);

  // Reset the product fields
  productComboBox.SelectedIndex = -1;
  quantityNumericUpDown.Value = 1;
}

在原始的 eMbedded Visual Basic 示例中,用于获取产品信息的代码如下所示。

Private Sub GetProductInfo(lngProductID As Long, _
                                      curProductCost As Currency)

      Dim rsProduct As ADOCE.Recordset
      Dim strSQLAs String

' Setup an error handler.
      On Error Resume Next
      Err.Clear

' Find the record of the specified product.
      strSQL= "SELECTCost FROM Products " & _
              "WHERE ProductID = " & lngProductID
      Set rsProduct = cnMobileSales.Execute(strSQL)

' Check to see if an error occurred.
      If (Err.Number <> 0) Then
      MsgBox "An error was encountered while attempting" & _
                      " to retrieve the product info." & vbCrLf & _
                      "Error #" & Err.Number & ", " & _
                      Err.Description, vbCritical, "Database Error"
        curProductCost = 0
      Else
        curProductCost = rsProduct.Fields("Cost")
      End If

' Clean up before exiting.
      rsProduct.Close
      Set rsProduct = Nothing
      On Error GoTo 0

End Sub

应用程序检索产品 ADOCE.Recordset (rsProduct),并且如果没有发生任何错误,则应用程序会返回该产品的价格。

在迁移以后,相同的 Visual Basic .NET 代码如下所示。

Private Function getProductInfo(ByVal productID As Integer) As Double
  Dim curProductCost As Double
  Try
      ' Find the record of the specified product.
      Dim sql As String = "SELECTCost FROM Products" + _
                      " WHERE ProductID = " + productID.ToString()
      Dim cmd As SqlCeCommand= New SqlCeCommand(sql, cn)
      curProductCost = Convert.ToDouble(cmd.ExecuteScalar())
  Catch ex As SqlCeException
      MessageBox.Show("An error was encountered while attempting" + _
                      " to retrieve the product info." + vbCrLf + _
                      "Error: " + ex.Message + "!", "Database Error")
      curProductCost = 0
  End Try
  Return curProductCost
End Function

ADO.NET 可以使用 SqlCeCommand 对象获得只包含一个值的结果。应用程序使用该结构 (ExecuteScalar) 获得产品价格。

在 C# 中,相同的代码如下所示。请注意,在 Visual Basic .NET 代码中,由函数而不是更新的参数返回成本,从而使得代码更易于阅读和理解。

private double getProductInfo(int productID)
{
  double curProductCost;
  try
  {
      // Find the record of the specified product
      string sql = "SELECTCost FROM Products" +
                   " WHERE ProductID = " + productID.ToString();
      SqlCeCommandcmd = new SqlCeCommand(sql, cn);
      curProductCost = Convert.ToDouble(cmd.ExecuteScalar());
  }
  catch (SqlCeExceptionex)
  {
      MessageBox.Show("An error was encountered while attempting" +
                  " to retrieve the product info.\r\n" +
                  "Error: " + ex.Message + "!", "Database Error");
      curProductCost = 0;
  }
  return curProductCost;
}

尽管上述代码中未显示,但还可以从定单中移除项。有关详细信息,请参阅 deleteButtonClick 事件中的示例代码。

既然已经在定单中添加(也可能移除)了项,那么您就可以保存该定单了。在原始的 eMbedded Visual Basic 示例中,用于保存新定单的代码如下所示。

Private Sub SaveOrder()
' This routine saves this current order to the database.

  Dim curProductCost As Currency
  Dim intCounter As Integer
  Dim intErrors As Integer
  Dim lngProductID As Long
  Dim strSQLAs String

' Set up an error handler.
  On Error Resume Next
  Err.Clear

' Store the individual items for this order.
  For intCounter = 1 To lvwOrder.ListItems.Count

' Place the information into the database.
      strSQL= "INSERT INTO Orders (CustomerID, ProductID," & _
           " Quantity) VALUES (" &              cmbCustomers.ItemData(cmbCustomers.ListIndex) & ", " & _
           lvwOrder.ListItems(intCounter).SubItems(3) & ", " & _
           lvwOrder.ListItems(intCounter).SubItems(2) & ")"
      cnMobileSales.Execute strSQL

' Check to see if an error occurred.
      If (Err.Number <> 0) Then
          For intErrors = 1 To cnMobileSales.Errors.Count
              MsgBox cnMobileSales.Errors(intErrors).Number & _
                " - " & _
                cnMobileSales.Errors(intErrors).Description, _
                vbCritical, _
                "Save Order"
          Next intErrors
          Err.Clear
          On Error GoTo 0
          Exit Sub
      End If
  Next intCounter

' Clean up before exiting.
  lvwOrder.ListItems.Clear
  cmbCustomers.RemoveItem cmbCustomers.ListIndex
  cmbCustomers.Refresh
  If (cmbCustomers.ListCount > 0) Then
      cmbCustomers.ListIndex = 0
  Else
      MsgBox "All customers have been processed.", _
                      vbInformation, "Save Order"
      App.End
  End If
  cmbProducts.ListIndex = -1
  txtQuantity.Text = "0"
  MsgBox "The order has been saved.", vbInformation, _
                "Save Order"
  On Error GoTo 0

End Sub

对于 ListView 中的每个列,应用程序都通过使用连接对象 (cnMobileSales) 来执行 SQL 插入语句 (Execute),以便插入新的定单项。应用程序从 ComboBox 控件中检索客户标识,从 ListView 中的每个行中检索产品标识和数量。在将所有定单项添加到数据库中以后,应用程序从 ComboBox 控件中移除客户,并且重置窗体以便输入下一份定单。在输入最后一份定单以后,应用程序关闭。

在迁移以后,相同的 Visual Basic .NET 代码如下所示。

Private Sub saveMenuItem_Click(ByVal sender As System.Object, _
      ByVal e As System.EventArgs) Handles saveMenuItem.Click

  ' Store the individual items for this order.
  Dim lvi As ListViewItem
  For Each lvi In orderListView.Items
      Try
          Dim sql As String = "INSERT INTO Orders (CustomerID," + _
                                " ProductID, Quantity) VALUES (" + _
              customerComboBox.SelectedValue.ToString() + ", " + _
              lvi.SubItems(3).Text + ", " + _
              lvi.SubItems(2).Text + ")"
          Dim cmd As New SqlCeCommand(sql, cn)
          cmd.ExecuteNonQuery()
      Catch ex As SqlCeException
          MessageBox.Show("An error was encountered while attempting" + _
              " to save the order." + vbCrLf + _
              "Error: " + ex.Message + "!", "Save Order")
      End Try
  Next
  ' Clean up before exiting.
  orderListView.Items.Clear()
  Dim dt As DataTable= (CType(customerComboBox.DataSource, DataTable))
  dt.Rows(customerComboBox.SelectedIndex).Delete()
  dt.AcceptChanges()
  If dt.Rows.Count > 0 Then
      customerComboBox.SelectedIndex = 0
  Else
      MessageBox.Show("All customers have been processed.", _
          "Save Order")
      Me.Close()
      Return
  End If
  productComboBox.SelectedIndex = -1
  quantityNumericUpDown.Value = 1
  MessageBox.Show("The order has been saved.", "Save Order")
End Sub

.NET Compact Framework 使用 SqlCeCommand 对象的方法来执行 SQL 语句。该方法不返回任何结果 (ExecuteNonQuery)。请注意,在 .NET Compact Framework 中,DataTable 对数据进行管理,而不是依赖于用户界面作为数据存储;在原始的 eMbedded Visual Basic 实现中也是如此。让 DataTable 管理数据可以使代码健壮得多,因为它将数据存储区与数据显示分开。

在 C# 中,相同的代码如下所示。

private void saveMenuItem_Click(object sender, System.EventArgs e)
{
  // Store the individual items for this order
  foreach(ListViewItemlvi in orderListView.Items)
  {
      try
      {
          string sql = "INSERT INTO Orders (CustomerID, ProductID," +
              " Quantity) VALUES (" +
              customerComboBox.SelectedValue.ToString() + ", " +
              lvi.SubItems[3].Text + ", " +
              lvi.SubItems[2].Text + ")";
          SqlCeCommandcmd = new SqlCeCommand(sql, cn);
          cmd.ExecuteNonQuery();
      }
      catch (SqlCeExceptionex)
      {
          MessageBox.Show("An error was encountered while attempting" +
              " to save the order.\r\n" +
              "Error: " + ex.Message + "!", "Save Order");
      }
  }
  // Clean up before exiting
  orderListView.Items.Clear();
  DataTabledt = ((DataTable)customerComboBox.DataSource);
  dt.Rows[customerComboBox.SelectedIndex].Delete();
  dt.AcceptChanges();
  if(dt.Rows.Count > 0)
      customerComboBox.SelectedIndex = 0;
  else
  {
      MessageBox.Show("All customers have been processed.",
          "Save Order");
      this.Close();
      return;
  }
  productComboBox.SelectedIndex = -1;
  quantityNumericUpDown.Value = 1;
  MessageBox.Show("The order has been saved.", "Save Order");
}

正如已经说明的那样,您可以使用标准 SQL 插入语句将行插入到数据库中。对于 SQL updatedelete 语句也是这样。但是,ADOCE.Recordset 对象在更新数据库之前生成需要的 SQL 语句。尽管在原始的 eMbedded Visual Basic 示例中未包含以下代码,但它显示了一种通过使用 ADO CE 在 eMbedded Visual Basic 中添加新行的替代方式。

Dim rs As ADOCE.Recordset
Set rs = CreateObject("ADOCE.Recordset.3.0")
rs.Open "Orders", cnMobileSales, adOpenDynamic, _
  adLockOptimistic, adCmdTableDirect
rs.AddNew
rs("CustomerID").Value = cmbCustomers.ItemData(cmbCustomers.ListIndex)
rs("ProductID").Value = lvwOrder.ListItems(intCounter).SubItems(3)
rs("Quantity").Value = lvwOrder.ListItems(intCounter).SubItems(2)
rs.Update
rs.Close

要更新 Recordset,您需要指定具有特殊参数 (adCmdTableDirect) 的表名称(不是 SQL 语句)。

等效的 Visual Basic .NET 代码如下所示。

Dim da As New SqlCeDataAdapter("SELECT* FROM Orders", cn)
Dim cb As New SqlCeCommandBuilder(da)
da.InsertCommand = cb.GetInsertCommand()
da.UpdateCommand = cb.GetUpdateCommand()
da.DeleteCommand = cb.GetDeleteCommand()
Dim ds As DataSet= New DataSet
da.Fill(ds, "Orders")
Dim dr As DataRow = ds.Tables("Orders").NewRow()
dr("CustomerID") = Convert.ToInt32(customerComboBox.SelectedValue)
dr("ProductID") = Convert.ToInt32(lvi.SubItems(3).Text)
dr("Quantity") = Convert.ToInt32(lvi.SubItems(2).Text)
ds.Tables("Orders").Rows.Add(dr)
da.Update(ds, "Orders")

ADO.NET 命令生成器 (SqlCeCommandBuilder) 包含 ADOCE.Recordset 对象中内置的数据操作逻辑。应用程序使用命令生成器 (cb) 对象,根据提供给数据适配器 (da) 的选择命令 (SELECT * FROM Orders) 生成需要的命令(insertupdatedelete)。生成的命令如下所示:

INSERT INTO Orders (CustomerID,ProductID,Quantity) VALUES (?,?,?)
UPDATE Orders SET CustomerID=?,ProductID=?,Quantity=? WHERE OrderID=?
DELETE FROM Orders WHERE OrderID=?

命令生成器将 select 命令与从数据库中检索的信息(主键)一起使用,以生成其他命令。

在 C# 中,相同的代码如下所示。

SqlCeDataAdapterda = new SqlCeDataAdapter("SELECT* FROM Orders", cn);
SqlCeCommandBuildercb = new SqlCeCommandBuilder(da);
da.InsertCommand = cb.GetInsertCommand();
da.UpdateCommand = cb.GetUpdateCommand();
da.DeleteCommand = cb.GetDeleteCommand();
DataSetds = new DataSet();
da.Fill(ds, "Orders");
DataRow dr = ds.Tables["Orders"].NewRow();
dr["CustomerID"] = Convert.ToInt32(customerComboBox.SelectedValue);
dr["ProductID"] =  Convert.ToInt32(lvi.SubItems[3].Text);
dr["Quantity"] =  Convert.ToInt32(lvi.SubItems[2].Text);
ds.Tables["Orders"].Rows.Add(dr);
da.Update(ds, "Orders");

在该示例的迁移过程中,移除了很多行代码,因为 Visual Studio .NET 开发环境中的窗体设计器现在处理了很多以前必须在 eMbedded Visual Basic 中实现的任务。.NET Compact Framework 中的错误处理具有更高结构性的这一事实也节省了很多行代码。

 

小结

将以前的 Pocket PC 应用程序迁移到当前工具集的原因很多。该工具集还为开发人员和应用程序用户带来了新的机会。本文可能为您提供足够的理由,以便让您的股东相信这一迁移不仅是可能的,而且可以带来相当高的投资回报率。

查看本文来源

    • 评论
    • 分享微博
    • 分享邮件
    邮件订阅

    如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。

    重磅专题
    往期文章
    最新文章