Windows Mobile 应用开发第五部分:SQL Server CE 入门
学习如何在移动应用中通过 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 初始化,处理数据就非常快,因为您正在处理数据的内存副本。对于大型数据库,您可能会遇到内存不足的情况,即内存中存在两份相同的数据。
在图 1 中,您可以看到 Visual Studio 2008 有一个 Smart Device 项目,已向 Northwind 示例数据库添加了一个新的数据源。在数据源向导中,选择了两个表要添加到强类型 DataSet 中:Order Details 表和 Order 表。正如您在“数据源”窗口中看到的,Orders 和 Order Details 表之间的关系已自动为您创建。如果您想将主/明细视图作为用户界面的一部分,您可以简单地将控件从“数据源”窗口拖放到您正在创建的窗体上。为了自动维护关系,重要的是从 Orders 表中选择 Order Detail 信息。
在图 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();
}
}
在 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.";
}
由于 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);
}
}
当您使用强类型 DataSet
时,会生成大量代码,并且一旦 DataSet
初始化,检索数据就非常快。基本上,您需要提供的唯一代码是将更改提交回数据库的代码。此外,您可能希望添加代码来导航数据,并提供一种非默认方式来填充 DataSet
,也许可以过滤您感兴趣的数据。在此 DataSet
入门介绍中,我们省略了一些更高级的主题,例如从数据库延迟加载数据到 DataSet
。
使用强类型 SqlCeResultSets
使用强类型 **SqlCeResultSets** 可以相对简单地访问 SQL CE 数据库。它会为您生成一些代码,但不如使用强类型 DataSet 时生成的代码多。SqlCeResultSet
的一个主要优点是它直接在数据库上操作。换句话说,数据不会被复制,这对于内存有限的设备(如 Windows Mobile 设备)来说非常棒。**强类型** **SqlCeResultSet** 结合了 DataSet
的易用性和 DataReader
的性能。为了比较 SqlCeResultSet
和 DataSet
之间的性能,我们将使用一个类似的应用程序,处理 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);
}
}
要显示 Order_Details 表的信息并结合从 Orders 表中选择的记录,您需要自己编写一些代码。编写此代码的一种好方法是开始扩展 Order_DetailsResultSet
的功能。您可以通过在解决方案资源管理器中右键单击 Northwind.xsd 文件来完成此操作。Visual Studio 会为您打开一个新的文件 Northwind.cs,您可以在其中扩展 Order_DetailsResultSet
的功能,这得益于设计器生成的代码是以部分类实现的。在列表 5 中,您可以看到添加到 Order_DetailsResultSet
的额外功能。创建了一个新的构造函数,其中包含一个布尔标志,指示是否需要打开 Order Details 表。如果您调用此构造函数并指示您不想打开该表,您可以调用另一个 Open
方法(也在列表 5 中显示)来针对 Order Details 表运行查询。通过这种方式,您可以创建一个只包含匹配查询的记录的 Order_DetailsResultSet
。在此白皮书中,我们没有通过提供其他索引来优化数据库,以便对 DataSet
和 SqlCeResultSet
的性能进行真实比较。但是,添加索引可能会带来更好的性能。
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();
}
}
}
}
扩展 Order_DetailsResultSet
后,您需要做一些额外的工作。首先要做的是确保您将用于明细视图的用户界面控件绑定到一个新的 BindingSource
。在拖放 Order_DetailsResultSet
控件时,它们会尝试绑定到将 OrderResultSet
数据网格拖放到窗体上时生成的绑定源,如图 5 所示。对于 Order ID 字段,这工作正常,因为它是两个表中都使用的键值。然而,将 Order_DetailsResultSet
的其他成员绑定到 ordersResultSetBindingSource
会导致异常,因为这些成员在此数据源中未定义。
解决此问题的最简单方法是向解决方案添加一个新的**绑定源**,并将其 DataSource
属性设置为 Order_DetailsResultSet
,如图 6 所示。完成此操作后,将用户界面控件从 Order_DetailsResultSet
拖放到 MainForm
上,Visual Studio 会询问您要使用哪个绑定源。您现在可以指定要使用刚刚添加的绑定源,这可以解决我们刚刚描述的数据绑定问题。接下来,您需要提供代码来显示当前所选记录的详细信息。这可以通过向 MainForm
添加一些额外代码来完成,利用您在列表 5 中看到的 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);
}
当用户选择新记录时,会触发 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 毫秒。您还可以看到从第一个记录移动到最后一个记录所需的时间,这稍微长一些,因为需要评估更多的记录才能从第一个记录移动到最后一个记录。
结论
在应用程序中使用强类型 DataSets 非常容易。您几乎不需要提供任何代码,因为大部分代码都可以由 Visual Studio 2008 为您生成。尤其是在主/明细呈现的情况下,这非常方便。然而,由于数据库中的所有数据都在内存中重复,因此初始显示窗体需要更长的时间,并且会占用 Windows Mobile 设备上宝贵的内存。
SqlCeResultSet
提供了更好的性能,尤其是在初始加载窗体时。原因是 SqlCeResultSet
直接在数据库上操作,并且只需要检索 DataGrid 中可见的记录。然而,为了实现主/明细呈现,您需要自己编写更多的代码。通过在数据库上使用索引文件,可以进一步提高 SqlCeResultSet
的速度。如果您需要编辑/摘要屏幕,您必须自己为 SqlCeResultSet
提供它们。然而,提高的性能绝对值得在一些代码上进行投入,以便在您的应用程序中获得该功能。
本系列相关文章
- Windows Mobile 应用开发 第 1 部分:创建您的第一个应用程序
正确安装 VS2008 和 Windows Mobile SDK 以创建您的第一个移动应用程序。www.myrampup.com 的第 1 部分(共 7 部分)。
- Windows Mobile 应用开发第二部分:设备模拟器和设备模拟器管理器
使用设备模拟器和蜂窝模拟器测试您的应用程序。
- Windows Mobile 应用开发第三部分:Windows Mobile 设备的 Windows Forms 应用开发基础
学习开发 Windows Mobile 设备上基于 Windows Forms 的应用程序的基础知识。
- Windows Mobile 应用开发第四部分:添加自定义控件并利用 GPS 硬件
学习如何在应用中添加自定义控件并利用 GPS 硬件。
- Windows Mobile 应用开发第六部分:设备安全和应用程序部署
了解设备安全、测试以及在 Windows Mobile 设备上安装应用程序。
- Windows Mobile 应用开发第七部分:移动 Web 开发
学习使用浏览器控件为启用了 AJAX 支持的移动设备创建基于 Web 的应用程序。
其他资源和参考
- Windows Mobile 开发者中心
- 博客文章:SqlCeResultSet 入门
- SQL Server Compact 3.5 教程
- 部署、设置、安全以及您
- 视频:如何使用 Visual Studio 2005 提高数据性能
请访问 www.myrampup.com 获取更多信息。