一、場景描述
在智慧裝置(Smart Device)應用程式和智慧客戶端(Smart Client)應用程式的部署階段,我們需要對離線資料進行初始化,即將後臺資料庫伺服器中的一些資料,匯入到離線資料庫中。通常採用兩種方式對離線資料進行初始化,第一種是在程式第一次執行時,通過資料同步的方式,把資料從後臺下載下來;第二種是將預先準備好的離線資料隨應用程式一起部署。
對於 SQL Server Compact Edition (SQL CE 3.1) 資料庫,第一種方式通常可以利用 Remote Data Access (RDA), Merge Replication, Sync Services for ADO.NET (SQL CE 3.5 中新增) 或者自己實現基於 Web Service 的資料同步機制來實現。RDA 和 Merge Replication 最大的缺點是隻能連到 SQL Server 資料庫,如果 SQL CE 需要跟 Oracle 和 DB2 等資料庫進行資料同步,需要 SQL Server 做“中介”。另外,RDA 沒有衝突處理機制,並且每次必須重新下載全部資料;Merge Replication 配置太繁瑣了。Sync Services for ADO.NET 目前還在 beta 階段,beta1 還不支援智慧裝置應用程式,只支援桌面應用程式。Orcas beta2 剛剛釋出,目前還沒有下載完畢,不知道有沒有效能方面的提升和增加對智慧裝置應用程式的支援。暫時先對 Sync Services for ADO.NET 保留意見,等我用上 beta2 了再詳細介紹。自己實現基於 Web Service 的資料同步機制需要考慮大資料量如何分批次傳輸和效能問題。總的來說,第一種方式的實現途徑很多,如果初始化資料量比較大,並且客戶端數量比較多的話,那麼將有可能帶來漫長的部署過程和一筆巨大的無線網路流量的費用。
第二種方式可以利用 SQL CE 3.1 對桌面應用程式的支援,預先將 SQL Server, Oracle, DB2, MySQL 等等各種資料庫的資料匯入到 SQL CE 中,然後通過 ActiveSync 批量將 SQL CE 的資料庫檔案(*.sdf)拷貝到裝置或機器上。以後客戶端再通過 Web Service 的方式下載新增/修改/刪除的資料來更新本地的離線資料。這樣可以節約大量的部署時間和網路通訊成本。
當然並不是第二種方式一定比第一種方式好,這個看具體的實施環境。本文主要介紹的是第二種方式。
二、技術選擇
既然 SQL CE 3.1 支援桌面應用程式,那麼我們可以通過三種方式來準備離線資料:第一,利用 RDA 資料同步;第二,利用 Merge Replication 資料同步;第三,用 ADO.NET 直接從讀寫資料。第一和第二種方式需要額外的安裝和配置,而且只支援 SQL Server 資料庫。如果非要在第一和第二種方式中選擇的話,我會選擇 RDA,因為它配置工作量更少,效能更好,更加靈活。本文選擇第三種方式,因為它離兩個資料庫的距離最近,而且支援多種資料庫。
三、實現原理
資料匯入程式實現起來很簡單,不過需要考慮效能。從源資料庫讀取資料要考慮速度和記憶體衝擊,可以採用 DataSet 或者 DataReader,毫無疑問我們選擇 DataReader。將資料寫入 SQL CE,通常大家會想到編寫一個 SqlCeCommand,然後給 SqlCeCommand 的 CommandText 屬性賦上 Insert SQL 語句“insert into Products values(@ProductID, @ProductName)”,接著一邊讀取資料,一邊給引數賦值並寫入 SQL CE 資料庫中……大家冷落了一個叫 SqlCeResultSet 的物件,它是 SQL Mobile 增加的資料訪問物件。SqlCeResultSet 提供了一個功能的組合:DataSet 的可更新性和可滾動性以及與 SqlCeDataReader 類似的效能。SqlCeResultSet 類繼承了 SqlCeDataReader 類,因此它擁有 SqlCeDataReader 類所有的特性。利用 SqlCeResultSet 可以實現高效能的資料讀取和寫入。
四、程式碼和分析
/// 將源資料庫表的資料複製到 SQL Server Compact Edition 資料庫的表中。
/// </summary>
/// <param name="srcConnection">源資料庫連線接物件。</param>
/// <param name="destConnection">目標 SQL Server Compact Edition 資料庫連線物件。</param>
/// <param name="queryString">源資料的查詢語句。</param>
/// <param name="destTableName">目標資料庫表名稱。</param>
/// <remarks>本方法假設目標 SQL Server Compact Edition 資料庫的表已經存在。</remarks>
public static void CopyTable(
IDbConnection srcConnection,
SqlCeConnection destConnection,
string queryString,
string destTableName)
{
IDbCommand srcCommand = srcConnection.CreateCommand();
srcCommand.CommandText = queryString;
SqlCeCommand destCommand = destConnection.CreateCommand();
destCommand.CommandType = CommandType.TableDirect; //基於表的訪問,效能更好
destCommand.CommandText = destTableName;
try
{
IDataReader srcReader = srcCommand.ExecuteReader();
SqlCeResultSet resultSet = destCommand.ExecuteResultSet(
ResultSetOptions.Sensitive | //檢測對資料來源所做的更改
ResultSetOptions.Scrollable | //可以向前或向後滾動
ResultSetOptions.Updatable); //允許更新資料
object[] values;
SqlCeUpdatableRecord record;
while (srcReader.Read())
{
// 從源資料庫表讀取記錄
values = new object[srcReader.FieldCount];
srcReader.GetValues(values);
// 把記錄寫入到目標資料庫表
record = resultSet.CreateRecord();
record.SetValues(values);
resultSet.Insert(record);
}
srcReader.Close();
resultSet.Close();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.ToString());
}
}
由於 CopyTable 函式的源資料庫連線引數採用的是 IDbConnection 介面,所以該方法可以支援多種源資料庫。程式碼中還利用 IDataReader.GetValues(object[] values) 和 SqlCeUpdatableRecord.SetValues(object[] values) 更方便的讀取和寫入資料。
在使用 CopyTable 函式之前必須預先建立好 SQL CE 資料庫的表結構,並且 SQL CE 資料庫的表結構必須跟 queryString 引數(select SQL 語句)的查詢結果的表結構對應。
通過下面的程式碼使用 CopyTable 函式:
string srcConnString = "Data Source=(local);Initial Catalog=Northwind;Integrated Security=True";
SqlConnection srcConnection = new SqlConnection(srcConnString);
// 建立目標 SQL Server Compact Edition 資料庫連線物件
string destConnString = @"Data Source=C:\Northwind.sdf";
SqlCeConnection destConnection = new SqlCeConnection(destConnString);
VerifyDatabaseExists(destConnString); //建立資料庫結構
srcConnection.Open();
destConnection.Open();
// 複製資料
CopyTable(srcConnection, destConnection, "SELECT * FROM Products", "Products");
CopyTable(srcConnection, destConnection, "SELECT * FROM Employees", "Employees");
srcConnection.Close();
destConnection.Close();
五、建立資料庫結構
上面說到在使用 CopyTable 函式之前 SQL CE 資料庫必須存在並且表結構都建立好。我們現在就來編寫建立資料庫結構的程式碼。首先建立一個名為 DbSchema.sql 的檔案,並編寫 Northwind 資料庫中的 Products 和 Employees 表的建立指令碼:
ProductID int NOT NULL CONSTRAINT PK_Products PRIMARY KEY,
ProductName nvarchar(40) NOT NULL,
SupplierID int NULL,
CategoryID int NULL,
QuantityPerUnit nvarchar(20) NULL,
UnitPrice money NULL,
UnitsInStock smallint NULL,
UnitsOnOrder smallint NULL,
ReorderLevel smallint NULL,
Discontinued bit NOT NULL
)
GO
CREATE TABLE Employees(
EmployeeID int NOT NULL CONSTRAINT PK_Employees PRIMARY KEY,
LastName nvarchar(20) NOT NULL,
FirstName nvarchar(10) NOT NULL,
Title nvarchar(30) NULL,
TitleOfCourtesy nvarchar(25) NULL,
BirthDate datetime NULL,
HireDate datetime NULL,
Address nvarchar(60) NULL,
City nvarchar(15) NULL,
Region nvarchar(15) NULL,
PostalCode nvarchar(10) NULL,
Country nvarchar(15) NULL,
HomePhone nvarchar(24) NULL,
Extension nvarchar(4) NULL,
Photo image NULL,
Notes ntext NULL,
ReportsTo int NULL,
PhotoPath nvarchar(255) NULL
)
GO
這段 SQL 語句不能直接在 SQL CE 上執行的,我們需要進行一些字串的處理。現在將該檔案新增到 Visual Studio 2005 的專案資源中。
並新增執行這段 SQL 建立資料庫表結構的方法:
{
using (SqlCeConnection connection = new SqlCeConnection(connectionString))
{
if (! File.Exists(connection.Database))
{
using (SqlCeEngine engine = new SqlCeEngine(connection.ConnectionString))
{
engine.CreateDatabase();
string[] commands = Properties.Resources.DbSchema.Split(
new string[] { "GO" }, StringSplitOptions.RemoveEmptyEntries);
SqlCeCommand command = new SqlCeCommand();
command.Connection = connection;
connection.Open();
for (int i = 0; i < commands.Length; i++)
{
command.CommandText = commands[i];
command.ExecuteNonQuery();
}
}
}
}
}
六、總結
效能測試的結果會因為環境的不同而有一些出入,我想留給大家去做會更有意義。不過我相信這是當前效能比較好的向 SQL CE 匯入資料的方法之一。目前需要預先建立好 SQL CE 的表結構是美中不足的地方,我會在後續文章中實現一個自動根據查詢結果生成建立 SQL CE 表結構的 SQL 語句的程式碼,其實並不難。
參考:
ADO.NET Generic Copy Table Data Function
Creating your SQL Server Compact Edition database and schema in code
示例程式碼下載:sqlce_data_import.rar
作者:黎波
部落格:http://upto.cnblogs.com/
日期:2007年7月29日