http://msdn.microsoft.com/zh-cn/magazine/ff898405.aspx
|
为了与本刊主题保持一致,现在应该回过头来介绍一下 sql 和关系数据库本质方面的内容。很自然地,我们似乎应该写一些有关 sql Server 的内容,即有关它的新功能集或性能改进之类的内容,但这并不是我的风格。请不要误解我的意思,sql Server 是一种优秀的大型数据库,因而强烈建议在那些“巨无霸式”企业级方案中使用它,但用一位朋友的话说,并不是每个问题都需要借助“航母级的集中式数据库”来解决。
事实上,开发人员长期以来使用关系数据库仅仅是将其作为“存放内容以备下次使用”的位置,诸如配置选项、用户设置、国际化值这样的内容。虽然有时将这些内容存放在集中式 sql Server 实例十分方便,但在某些情况下,具体来说是在富客户端方案(特别是 Microsoft Silverlight 或 Windows Phone 7 富客户端方案)中,保持与 sql Server 实例的固定连接并非切实可行的,通常就是根本无法实现。
开发人员不一定需要放弃关系数据库的强大功能和灵活性,但即使是 sql Server Express 有时安装规模也过大。那我们应该怎么办?
当然是轻装上阵:准确的说,是使用 sqlite。
sqlite 简介
sqlite 的网站 (sqlite.org) 对它进行了如下描述:“sqlite 是一个可实现独立、无服务器、零配置、事务性 sql 数据库引擎的软件库”。这句话中的关键要素都围绕着“库”这一名词。与使用客户端程序集向服务器发送请求以供分析和执行的 sql Server 不同的是,sqlite 完全驻留在客户端进程中,这使之成为一种“嵌入式”数据库。在使用期间,sqlite 数据库的运行空间是一个存储在客户端文件系统的某一位置中的单个文件,并且安装空间通常也相当小。
尽管如此,sqlite 数据库的功能却是极其丰富的,因为它支持大部分 sql-92 规范,只是去除了 RIGHT 和 FULL OUTER JOIN、ALTER TABLE、某一触发器支持、GRANT/REVOKE 以及写入 VIEW 等几项内容(更详细的说明请参见 sqlite 网站)。令人印象深刻的是支持的功能数量,包括事务和各种数据类型。虽然不加修改就将 sql Server 数据库方案全盘迁移到 sqlite 是不足为信的,但有一点是合乎情理的,即在迁移非常简单(即未利用 sql Server 特定的类型或功能)的方案时几乎不会遇到什么麻烦。这使 sqlite 十分适合只需“轻型 sql”的情形。
为了消除人们对其适用性或稳定性的担心,sqlite 正慢慢地融入各种“轻型”环境中,它已出现在 Mozilla Firefox 浏览器(用于支持 HTML 5)以及 Symbian、iOS 和 Android 等环境中。换句话说,这就是“另一半”的开发领域(即不以 Microsoft 为中心)实现轻型数据库的方式。sqlite 正在不断地发展和修复错误,使得这种微型 sql 引擎有了足够的安全保障。
当然,数据库必须具有某种管理员界面才是完整的,而 sqlite 也不例外。sqlite 数据库具有一个用于对其进行访问和操作的命令行控制台工具,但您的系统管理员可能对它没有太多兴趣。庆幸的是,开源社区提供了许多 sqlite 工具(在 sqlite 网站上提供了这些工具的完整列表),但如果只需类似查询分析器的快速工具,请尝试使用 sqlite Administrator,这是一个可从以下位置下载的免费工具:sqliteadmin.orbmu2k.de。
本机开发
从一开始,sqlite 就立足于面向本机代码开发人员的数据库,这就是它为何作为本机 C/C++ DLL 实施的原因。sqlite 这种本机特色的利弊鲜明:有利的是,它从执行指定 sql 语句所需的总时间中省去了许多开销(例如穿过网络到达服务器,然后重新返回);而弊端在于,由于原始 sqlite 数据库是本机 C/C++ DLL,因此从基于 Microsoft .NET Framework 的应用程序访问它会是一项不小的挑战。
庆幸的是,技术精湛的 .NET Framework 开发人员认识到访问本机 DLL 实际上只是练习使用 P/Invoke 声明,而围绕 sqlite DLL 中公开的本机声明创建包装类则相对比较容易。事实上,对于基本功能来说,就像开源社区中提供的众多内容一样,它们也已经实现;导航到switchonthecode.com/tutorials/csharp-tutorial-writing-a-dotnet-wrapper-for-sqlite,我们会发现已创建好的 P/Invoke 声明的工作集,如图 1中所示。
图 1P/Invoke 声明
namespace sqliteWrapper { public class sqliteException : Exception { public sqliteException(string message) : base(message) { } } public class sqlite { const int sqlITE_OK = 0; const int sqlITE_ROW = 100; const int sqlITE_DONE = 101; const int sqlITE_INTEGER = 1; const int sqlITE_FLOAT = 2; const int sqlITE_TEXT = 3; const int sqlITE_BLOB = 4; const int sqlITE_NULL = 5; [DllImport("sqlite3.dll",EntryPoint = "sqlite3_open")] static extern int sqlite3_open(string filename,out IntPtr db); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_close")] static extern int sqlite3_close(IntPtr db); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_prepare_v2")] static extern int sqlite3_prepare_v2(IntPtr db,string zsql,int nByte,out IntPtr ppStmpt,IntPtr pzTail); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_step")] static extern int sqlite3_step(IntPtr stmHandle); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_finalize")] static extern int sqlite3_finalize(IntPtr stmHandle); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_errmsg")] static extern string sqlite3_errmsg(IntPtr db); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_column_count")] static extern int sqlite3_column_count(IntPtr stmHandle); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_column_origin_name")] static extern string sqlite3_column_origin_name( IntPtr stmHandle,int iCol); [DllImport("sqlite3.dll",EntryPoint = "sqlite3_column_type")] static extern int sqlite3_column_type(IntPtr stmHandle,EntryPoint = "sqlite3_column_int")] static extern int sqlite3_column_int(IntPtr stmHandle,EntryPoint = "sqlite3_column_text")] static extern string sqlite3_column_text(IntPtr stmHandle,EntryPoint = "sqlite3_column_double")] static extern double sqlite3_column_double(IntPtr stmHandle,int iCol); } }
面向 C/C++ 的 P/Invoke API 具有非常高的保真性,这使得该过程变得相对简单;sqlite API 使用原始指针来表示数据库本身,这在 P/Invoke 中通过 System.IntPtr 实现,并且 sqlite API 经常会将指向 int 的指针作为参数,这样可以使用 C#“out”关键字修改 P/Invoke 描述的内容。(有关 P/Invoke 的详细信息,请参阅pinvoke.codeplex.com)。
若要了解有关如何使用 sqlite API 的大部分详细信息,建议您访问 sqlite 网站;但若要快速了解如何打开数据库,执行查询,然后关闭数据库这一过程,请参阅图 2,其中显示了类似的内容。
static void NativeMain() { // Open the database--db is our "handle" to it IntPtr db; if (sqliteNative.sqlite3_open(@"cities.sqlite",out db) == sqliteNative.sqlITE_OK) { // Prepare a simple DDL "CREATE TABLE" statement string query = "CREATE TABLE City " + "(name TEXT,state TEXT,population INTEGER)"; IntPtr stmHandle; if (sqliteNative.sqlite3_prepare_v2(db,query,query.Length,out stmHandle,IntPtr.Zero) != sqliteNative.sqlITE_OK) { // Something went wrong--find out what var err = sqliteNative.sqlite3_errmsg(db); } if (sqliteNative.sqlite3_step(stmHandle) != sqliteNative.sqlITE_DONE) { // Something went wrong--find out what var err = sqliteNative.sqlite3_errmsg(db); } if (sqliteNative.sqlite3_finalize(stmHandle) != sqliteNative.sqlITE_OK) { // Something went wrong--find out what var err = sqliteNative.sqlite3_errmsg(db); } // ... Now that we've created a table,we can insert some // data,query it back and so on // Close the database back up sqliteNative.sqlite3_close(db); } }