65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Mobile 应用开发第五部分:SQL Server CE 入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.81/5 (12投票s)

2009 年 10 月 30 日

Ms-PL

18分钟阅读

viewsIcon

84777

学习如何在移动应用中通过 DataSets 和 SQLCeResultSets 访问数据。

引言

为 Windows Mobile 设备开发应用程序与为桌面开发应用程序类似,尤其是如果您使用 Visual Basic .NET 或 Visual C#。您可以使用相同的开发工具来开发应用程序,尽管为 Windows Mobile 设备开发应用程序和开发桌面应用程序之间也存在差异。在设备上处理数据也是如此。没有哪个真正的应用程序可以脱离数据而存在。要在设备上存储数据,开发者当然可以自由选择自己的策略。在本文中,您将更多地了解 SQL Server 2005 Compact Edition 的使用以及在设备本地存储的数据的访问选项。在本文中,我们将重点关注不同访问方法在执行时间和所需代码量方面的性能。代码示例中使用了 Northwind 示例数据库的 Orders 和 Order_Details 表。本文将重点介绍 Visual Studio 2008 结合 SQL Server 2005 Compact Edition 3.5。

SQL Server 2005 Compact Edition

对于能够在 Windows Mobile 设备上运行的 SQL Server 的不同名称和不同版本,可能会存在一些混淆。在撰写本文时,该产品的命名已更加一致。以下是适用于 Windows Mobile 设备开发人员的最重要的 SQL Server 2005 版本的简要概述。

  • SQL Server Mobile 3.0 - 于 2005 年随 Microsoft Visual Studio 2005 和 SQL Server 2005 一起发布。SQL Server Mobile 3.0 支持设备和 Tablet PC。只要开发工具安装在桌面系统上,SQL Server Mobile 3.0 也可以在该特定桌面计算机上运行。
  • SQL Server 2005 Compact Edition 3.1 – 于 2006 年发布,大部分基于 SQL Server Mobile 3.0,此版本的 SQL Server CE 可在 Windows Mobile 设备以及桌面/笔记本电脑系统上运行,没有任何限制。SQL Server CE 与 SQL Server 版本高度兼容,并提供完整的关系数据库功能,体积小巧。此版本的 SQL Server CE 已安装在 Windows Mobile 6 设备中。
  • SQL Server 2005 Compact Edition 3.5 SP1 – 于 2007 年作为单独下载发布,并集成到 Visual Studio 2008 中。此版本的 SQL Server CE 基于并扩展了 SQL Server CE 3.1 的功能,还允许通过 Microsoft Synchronization Services for ADO.NET 与后端服务器进行同步。

SQL Server 2005 Compact Edition 背景

SQL Server CE 是一个轻量级数据库,与其他版本的 SQL Server 不同,SQL Server CE 运行在使用它的应用程序的进程中。这意味着 SQL Server CE 不是作为单独运行的“真正”服务器实现的。SQL Server CE 数据库存储在单个扩展名为 sdf 的文件中,可以轻松实现桌面和设备之间的同步,例如通过 ActiveSync (Windows XP) 或 Windows Mobile Device Center (Windows Vista)。通过 RDA、Merge Replication 和 Sync Services for ADO.NET 可以实现更复杂的同步选项,以允许后端服务器与多个设备之间进行同步,甚至支持在多个用户更新数据库中的同一数据时进行冲突解决。SQL Server CE 数据库具有不同级别的安全性。除其他外,数据库可以设置密码保护并进行加密。这对于在 Windows Mobile 设备上使用非常有用,因为设备可能相对容易丢失,尤其是在数据库存储在存储卡上时,该存储卡可能会被盗。

在本文的其余部分,我们将重点关注将 SQL Server 2005 Compact Edition 3.5 与托管应用程序结合使用。您将学习以多种不同的方式从托管应用程序内部检索、修改和插入数据,每种方式都有其特定的优缺点。

为了现实地比较每种解决方案,我们将使用 Northwind 示例数据库,显示 Orders 和 Order Details。为了能够使用安装在 C:\Program Files\Microsoft SQL Server Compact Edition\v3.5\Samples 文件夹中的 Northwind 示例数据库(Visual Studio 2008 的默认安装路径),您需要以管理员身份运行 Visual Studio 2008。否则,您将无法创建到数据库的连接。

使用强类型数据集 (Strongly Typed DataSets)

使用强类型 **DataSets** 是访问 SQL CE 数据库的一种简单方法。它为您生成了大量代码,但 DataSet 是内存中的数据缓存,从 SQL Server CE 等数据源检索而来。换句话说,DataSet 包含您正在处理的数据的物理副本。这意味着将数据从数据库加载到 DataSet 需要一些时间,并且对 DataSet 中数据的修改不会自动更新到数据库。一旦 DataSet 初始化,处理数据就非常快,因为您正在处理数据的内存副本。对于大型数据库,您可能会遇到内存不足的情况,即内存中存在两份相同的数据。

MOB4DEVS05/mob05fig1.jpg

图 1 - 强类型数据集

在图 1 中,您可以看到 Visual Studio 2008 有一个 Smart Device 项目,已向 Northwind 示例数据库添加了一个新的数据源。在数据源向导中,选择了两个表要添加到强类型 DataSet 中:Order Details 表和 Order 表。正如您在“数据源”窗口中看到的,Orders 和 Order Details 表之间的关系已自动为您创建。如果您想将主/明细视图作为用户界面的一部分,您可以简单地将控件从“数据源”窗口拖放到您正在创建的窗体上。为了自动维护关系,重要的是从 Orders 表中选择 Order Detail 信息。

MOB4DEVS05/mob05fig2.jpg

图 2 - 通过 Visual Studio 设计器自动生成的窗体

在图 2 中,您可以看到已为您自动生成的窗体。无需任何编码,您就可以显示 Northwind 数据库中的信息。唯一的缺点是数据将被复制到 DataSet 中,因此您需要编写一些额外的代码来确保任何更改都会保存到底层数据库中。Visual Studio 为您生成的用于在 MainForm 上显示数据的代码显示在列表 1 中。Visual Studio 还添加了类似的用于显示摘要信息和填充编辑对话框的代码。在填充用户界面控件之前,您可以修改用于连接到 SQL Server CE 数据库的连接字符串。这非常有意义,尤其对于密码保护的数据库,因为如果您让 Visual Studio 生成包含密码信息的连接字符串,您将遇到安全问题。

public partial class MainForm : Form
{
   public MainForm()
   {
      InitializeComponent();
   }
   private void MainForm_Load(object sender, EventArgs e)
   {
      if (NorthwindDataSetUtil.DesignerUtil.IsRunTime())
      {
          this.order_DetailsTableAdapter.Fill(this.northwindDataSet.Order_Details);
      }
      if (NorthwindDataSetUtil.DesignerUtil.IsRunTime())
      {
          this.ordersTableAdapter.Fill(this.northwindDataSet.Orders);
      }
   }
   private void newMenuItemMenuItem_Click(object sender, EventArgs e)
   {
       ordersBindingSource.AddNew();
       SqlCE35WithDataSets.OrdersEditViewDialog ordersEditViewDialog =
          SqlCE35WithDataSets.OrdersEditViewDialog.Instance(this.ordersBindingSource);
       ordersEditViewDialog.ShowDialog();
   }
   private void ordersDataGrid_Click(object sender, EventArgs e)
   {
       SqlCE35WithDataSets.OrdersSummaryViewDialog ordersSummaryViewDialog = 
          SqlCE35WithDataSets.OrdersSummaryViewDialog.Instance(this.ordersBindingSource);
       ordersSummaryViewDialog.ShowDialog();
   }
}
列表 1 – 用于使用数据集初始化 UI 控件的自动生成代码

MainForm_Load 事件处理程序中,将 Northwind 数据库中的数据复制到 DataSet 中,然后通过数据绑定将数据显示给用户。还添加了额外的代码来显示详细信息并输入新订单记录。要了解通过 DataSet 从 SQL CE 数据库检索数据的性能,您可以在 MainForm_Load 事件处理程序的第一个语句中启动一个 Stopwatch,并在 MainForm_Load 事件处理程序的最后一个语句中停止 Stopwatch,然后您可以在状态栏中显示经过的时间。可以进行类似的性能测量,通过用户按下硬件键到显示在 MainForm 上的一个编辑控件的文本发生变化之间的时间,来测量从一个记录移动到另一个记录时显示详细信息所需的时间。图 3 显示了使用强类型 DataSet 时的性能结果。如您所见,从应用程序初始化到数据显示给用户,在设备模拟器上大约需要 13 秒。从一个记录移动到另一个记录,更新,尤其是详细信息,平均需要 300 毫秒。您还可以看到从第一个记录移动到最后一个记录所需的时间,这与移动到下一个记录的时间大致相同,这得益于 DataSet 完全在内存中运行。通过列表 2 中显示的 L代码,可以测量从数据库中的第一个记录移动到最后一个记录所需的时间。

private void menuGotoLast_Click(object sender, EventArgs e)
{
   ordersBindingSource.MoveFirst();
   sw.Reset();
   sw.Start();
   ordersBindingSource.MoveLast();
   sw.Stop();
   statusBar1.Text = "Goto last row: " + 
                     sw.ElapsedMilliseconds + " msec.";
}
列表 2 - 测量从第一个记录移动到最后一个记录所需的时间。

MOB4DEVS05/mob05fig3.jpg

图 3 - 显示数据和记录间移动的初始时间

由于 DataSet 将所有数据保存在本地内存中,因此在某个时候,有必要将更改提交回数据库。当然,何时提交更改由您决定。一种方法是尽可能长时间地在 DataSet 上本地工作,并在用户关闭应用程序时将更改提交到数据库。然而,尤其是在需要与后端服务器同步数据时,最好在同步之前将更改提交到数据库。此外,当所有数据都保存在 DataSet 等本地数据缓存中时,如果应用程序崩溃,则意味着数据可能会丢失。在典型的应用程序中,您可能希望在多个时间点将更改提交回数据库,例如,当应用程序关闭时、需要同步数据时,以及当应用程序转到后台时。您还可以决定在更改发生后立即将其提交到数据库,但这会使 DataSet 几乎成为一个多余的本地数据缓存。列表 3 显示了您需要提供的将更改提交回数据库的代码。在此示例中,数据在 Closing 事件处理程序中写回数据库。只有在 DataSet 中找到更改时,才有必要将更改写回数据库。

private void MainForm_Closing(object sender, CancelEventArgs e)
{
   if (northwindDataSet.HasChanges())
   {
       ordersTableAdapter.Update(northwindDataSet);
       order_DetailsTableAdapter.Update(northwindDataSet);
   }
}
列表 3 - 在应用程序终止时将更改提交回数据库。

当您使用强类型 DataSet 时,会生成大量代码,并且一旦 DataSet 初始化,检索数据就非常快。基本上,您需要提供的唯一代码是将更改提交回数据库的代码。此外,您可能希望添加代码来导航数据,并提供一种非默认方式来填充 DataSet,也许可以过滤您感兴趣的数据。在此 DataSet 入门介绍中,我们省略了一些更高级的主题,例如从数据库延迟加载数据到 DataSet

使用强类型 SqlCeResultSets

使用强类型 **SqlCeResultSets** 可以相对简单地访问 SQL CE 数据库。它会为您生成一些代码,但不如使用强类型 DataSet 时生成的代码多。SqlCeResultSet 的一个主要优点是它直接在数据库上操作。换句话说,数据不会被复制,这对于内存有限的设备(如 Windows Mobile 设备)来说非常棒。**强类型** **SqlCeResultSet** 结合了 DataSet 的易用性和 DataReader 的性能。为了比较 SqlCeResultSetDataSet 之间的性能,我们将使用一个类似的应用程序,处理 Northwind 数据库的相同表并提供相同的功能。性能再次通过 Stopwatch 对象进行测量。在图 4 的“属性”窗口中,您可以看到我们指定了一个**自定义工具**来生成强类型 SqlCeResultSet 而不是 DataSet。初始方法是相同的。您将一个 DataSource 添加到您的项目,即 Northwind 数据库。添加 DataSource 时,Visual Studio 2008 会自动为您创建一个强类型 DataSet。之后,您在“属性”窗口中更改自定义工具以生成 SqlCeResultSet。您在图 4 中可以看到 Visual Studio 为您创建了两个强类型 SqlCeResultSet,但它们之间没有关系。为了在 MainForm 上实现主/明细视图,您必须自己编写代码来将明细视图绑定到主视图。

Visual Studio 为您生成的用于在 MainForm 上显示数据的代码显示在列表 1 中。请注意,只有包含 Orders 数据的网格是由 Visual Studio 2008 自动填充的。此外,不会生成额外的窗体来显示摘要信息或编辑对话框。

public partial class MainForm : Form
{
   private SqlCe35WithResultSets.NorthwindResultSets.OrdersResultSet 
                                                     ordersResultSet;
   public MainForm()
   {
      InitializeComponent();
   }
   private void MainForm_Load(object sender, EventArgs e)
   {
      ordersResultSet = new SqlCe35WithResultSets.NorthwindResultSets.OrdersResultSet();
      ordersResultSet.Bind(this.ordersResultSetBindingSource);
   }
}
列表 4 - 用于使用 SqlCeResultSet 初始化 UI 控件的自动生成代码。

MOB4DEVS05/mob05fig4.jpg

图 4 - 强类型 SqlCeResultSets

要显示 Order_Details 表的信息并结合从 Orders 表中选择的记录,您需要自己编写一些代码。编写此代码的一种好方法是开始扩展 Order_DetailsResultSet 的功能。您可以通过在解决方案资源管理器中右键单击 Northwind.xsd 文件来完成此操作。Visual Studio 会为您打开一个新的文件 Northwind.cs,您可以在其中扩展 Order_DetailsResultSet 的功能,这得益于设计器生成的代码是以部分类实现的。在列表 5 中,您可以看到添加到 Order_DetailsResultSet 的额外功能。创建了一个新的构造函数,其中包含一个布尔标志,指示是否需要打开 Order Details 表。如果您调用此构造函数并指示您不想打开该表,您可以调用另一个 Open 方法(也在列表 5 中显示)来针对 Order Details 表运行查询。通过这种方式,您可以创建一个只包含匹配查询的记录的 Order_DetailsResultSet。在此白皮书中,我们没有通过提供其他索引来优化数据库,以便对 DataSetSqlCeResultSet 的性能进行真实比较。但是,添加索引可能会带来更好的性能。

public partial class Order_DetailsResultSet
{
   public Order_DetailsResultSet(bool openTable)
   {
      // Create default options
      // 
      resultSetOptions = System.Data.SqlServerCe.ResultSetOptions.Scrollable |
         System.Data.SqlServerCe.ResultSetOptions.Sensitive |
         System.Data.SqlServerCe.ResultSetOptions.Updatable;
      if (NorthwindUtil.DesignerUtil.IsDesignTime())
      {
         // Designtime Connection String
         resultSetConnectionString = 
           "Data Source=C:\\Users\\Maarten\\Documents\\Visual Studio " +
           "2008\\Projects\\SqlCE35Demo\\SqlCE35WithDataSets\\Northwind.sdf";
      }
      else
      {
         // Runtime Connection String
         resultSetConnectionString = ("Data Source =" +
            (System.IO.Path.GetDirectoryName(
            System.Reflection.Assembly.GetExecutingAssembly().GetName().CodeBase) +
            "\\Northwind.sdf;"));
      }
      if (openTable)
          this.Open();
   }
   public void Open(string query)
   {
      System.Data.SqlServerCe.SqlCeCommand sqlCeSelectCommand = null;
      try
      {
          // Open a connection to the database
          // 
          sqlCeConnection = new
              System.Data.SqlServerCe.SqlCeConnection(this.resultSetConnectionString);
          sqlCeConnection.Open();
          // Create the command
          // 
          sqlCeSelectCommand = sqlCeConnection.CreateCommand();
          sqlCeSelectCommand.CommandText = query;
          sqlCeSelectCommand.CommandType = System.Data.CommandType.Text;
          // Generate the ResultSet
          // 
          sqlCeSelectCommand.ExecuteResultSet(
             System.Data.SqlServerCe.ResultSetOptions.Scrollable, this);
      }
      finally
      {
          if ((sqlCeSelectCommand != null))
          {
              sqlCeSelectCommand.Dispose();
          }
      }
   }
}
列表 5 - 扩展 Order_DetailsResultSet

扩展 Order_DetailsResultSet 后,您需要做一些额外的工作。首先要做的是确保您将用于明细视图的用户界面控件绑定到一个新的 BindingSource。在拖放 Order_DetailsResultSet 控件时,它们会尝试绑定到将 OrderResultSet 数据网格拖放到窗体上时生成的绑定源,如图 5 所示。对于 Order ID 字段,这工作正常,因为它是两个表中都使用的键值。然而,将 Order_DetailsResultSet 的其他成员绑定到 ordersResultSetBindingSource 会导致异常,因为这些成员在此数据源中未定义。

MOB4DEVS05/mob05fig5.jpg

图 5 - 将产品 ID 文本框绑定到错误的数据源。

解决此问题的最简单方法是向解决方案添加一个新的**绑定源**,并将其 DataSource 属性设置为 Order_DetailsResultSet,如图 6 所示。完成此操作后,将用户界面控件从 Order_DetailsResultSet 拖放到 MainForm 上,Visual Studio 会询问您要使用哪个绑定源。您现在可以指定要使用刚刚添加的绑定源,这可以解决我们刚刚描述的数据绑定问题。接下来,您需要提供代码来显示当前所选记录的详细信息。这可以通过向 MainForm 添加一些额外代码来完成,利用您在列表 5 中看到的 Order_DetailsResultSet 的扩展。

MOB4DEVS05/mob05fig6.jpg

图 6 - 手动添加绑定源并将其设置为 Order_DetailsResultSet。

每次用户在图 6 所示的数据网格中选择新记录时,您都需要找出当前所选记录的 Order ID。使用该 Order ID,您可以构建一个查询来从 Order Details 表中检索订单详细信息。一旦创建了一个新的 Order_DetailsResultSet,只包含匹配查询的记录,下一步就是将 BindingSource 绑定到新创建的结果集,然后您必须释放原始结果集。列表 6 显示了一个通过代码将 Orders 表连接到 Orders Details 表的方法。每次数据网格中的当前记录更改时都可以调用此代码。

private void ordersResultSetBindingSource_PositionChanged(object sender, EventArgs e)
{
   SqlCE35WithResultSets.NorthwindResultSets.Order_DetailsResultSet orgDetailsRS=
       order_DetailsResultSet;
   GetOrderDetails();
   if (orgDetailsRS != null)
      orgDetailsRS.Dispose();
}
private void GetOrderDetails()
{
   int orderID =
    (int)((RowView)this.ordersResultSetBindingSource.Current).UpdatableRecord["Order ID"];
   string query = "SELECT * FROM [Order Details] WHERE [Order ID] = '" + orderID +"'";
   order_DetailsResultSet = 
     new SqlCE35WithResultSets.NorthwindResultSets.Order_DetailsResultSet(false);
   order_DetailsResultSet.Open(query);
   order_DetailsResultSet.Bind(order_DetailsResultSetBindingSource);
}
列表 6 - 将详细信息视图连接到主视图

当用户选择新记录时,会触发 ordersResultSetBindingSource 上的 PositionChanged 事件。此事件可用于检索当前选定的 Order ID。将释放当前活动的 order_DetailsResultSet,并使用 Order ID 创建一个新的 order_DetailsResultSet,以设置一个查询来仅返回匹配 Order ID 的 Order Details。

当您阅读关于 DataSet 的内容时,您还会注意到设计器可以为您创建摘要和编辑窗体。当 Visual Studio 为您创建 SqlCeResultSet 时,此功能不可用。如果您需要该功能,则必须自己创建窗体并提供自己的代码来填充这些窗体。在本文中,我们省略了 SqlCeResultSet 的此功能,因为我们只关注数据检索过程中的性能。另一方面,如果您进行了更改,您无需像使用 DataSet 时那样显式地将其提交回数据库。由于 SqlCeResultSet 直接在数据库上操作,因此更改会立即为您提交。

要了解通过 SqlCeResultSet 从 SQL CE 数据库检索数据的性能,您可以在 MainForm_Load 事件处理程序的第一个语句中启动一个 Stopwatch,并在 MainForm_Load 事件处理程序的最后一个语句中停止 Stopwatch,然后您可以在状态栏中显示经过的时间。可以进行类似的性能测量,通过用户按下硬件键到显示在 MainForm 上的一个编辑控件的文本发生变化之间的时间,来测量从一个记录移动到另一个记录时显示详细信息所需的时间。此功能与 DataSet 类似。图 7 显示了使用强类型 SqlCeResultSet 时的性能结果。如您所见,从应用程序初始化到数据显示给用户,在设备模拟器上大约需要 300 毫秒。从一个记录移动到另一个记录,更新,尤其是详细信息,平均需要 50 毫秒。您还可以看到从第一个记录移动到最后一个记录所需的时间,这稍微长一些,因为需要评估更多的记录才能从第一个记录移动到最后一个记录。

MOB4DEVS05/mob05fig7.jpg

图 7 - 显示数据和记录间移动的初始时间。

结论

在应用程序中使用强类型 DataSets 非常容易。您几乎不需要提供任何代码,因为大部分代码都可以由 Visual Studio 2008 为您生成。尤其是在主/明细呈现的情况下,这非常方便。然而,由于数据库中的所有数据都在内存中重复,因此初始显示窗体需要更长的时间,并且会占用 Windows Mobile 设备上宝贵的内存。

SqlCeResultSet 提供了更好的性能,尤其是在初始加载窗体时。原因是 SqlCeResultSet 直接在数据库上操作,并且只需要检索 DataGrid 中可见的记录。然而,为了实现主/明细呈现,您需要自己编写更多的代码。通过在数据库上使用索引文件,可以进一步提高 SqlCeResultSet 的速度。如果您需要编辑/摘要屏幕,您必须自己为 SqlCeResultSet 提供它们。然而,提高的性能绝对值得在一些代码上进行投入,以便在您的应用程序中获得该功能。

本系列相关文章

其他资源和参考

请访问 www.myrampup.com 获取更多信息。

© . All rights reserved.