当前位置:首页 > 问答 > 正文

微软WP7里用SQLite搞本地数据库那些事儿和小技巧分享

主要参考自2010-2012年间MSDN官方博客、开发者论坛如Stack Overflow、以及当时流行的技术博客如“WP7开发笔记”、“Claus的WP7开发之旅”等综合整理)


说起在Windows Phone 7上用SQLite,那可真是一段“先挖渠,后引水”的经历,WP7刚出来那会儿,系统本身是不支持SQLite的,因为它的.NET框架是定制版的Silverlight for Windows Phone,里面压根就没包含ADO.NET那些玩意儿,更别说System.Data.SQLite了,你想在本地存点结构化数据,官方推荐的是用独立的存储空间(Isolated Storage)存XML或者用个简单的对象集合(比如List)再序列化一下,但稍微复杂点的应用,比如要排序、要关联查询,这点手段就完全不够看了。

当时我们这些开发者干的第一件事,等”和“找”,等什么呢?等SQLite官方或者社区出马,编译一个能在WP7的ARM芯片和那个特定版本的.NET Compact Framework上跑的版本,这个过程挺折腾的,因为涉及到本地代码(C++)的移植和托管封装的编写,我记得最早能用的版本是社区里的大佬们折腾出来的,不稳定,功能也有限,后来才慢慢有了相对靠谱的版本,但用起来还是得小心翼翼。

第一个大坑:怎么把数据库文件搞到项目里?

微软WP7里用SQLite搞本地数据库那些事儿和小技巧分享

你不可能让用户一开始就用个空数据库吧?通常我们需要一个预置了基础数据(比如城市列表、产品目录)的数据库,在WP7上,做法很固定:

  1. 先在电脑上用SQLite管理工具创建一个.db.sqlite文件,把表结构和初始数据都弄好。
  2. 把这个数据库文件添加到你的Visual Studio WP7项目中。
  3. 最关键的一步:在解决方案资源管理器里右键点击这个数据库文件,打开属性窗口,把“生成操作(Build Action)”设置为“内容(Content)”,同时确保“复制到输出目录(Copy to Output Directory)”设置为“如果较新则复制(Copy if newer)”。(参考自当时大量的MSDN示例代码和博客教程) 这样做的目的是,编译项目时,这个数据库文件会被原封不动地打包进XAP安装包,但问题来了,安装后这个文件是在安装目录下的,而WP7的应用是没有直接写入安装目录的权限的,只有读取权限,真正的可读写数据库必须在独立存储空间里。

所以第二个小技巧就是:第一次启动时,把数据库从安装目录复制到独立存储空间。

这几乎成了WP7 SQLite开发的标配代码,每个项目开头都得写这么一段:

微软WP7里用SQLite搞本地数据库那些事儿和小技巧分享

// 伪代码风格,参考自当年无数个博客的示例
using (var isoStore = IsolatedStorageFile.GetUserStoreForApplication())
{
    string dbPath = "MyDatabase.sqlite";
    if (!isoStore.FileExists(dbPath))
    {
        // 从程序集资源中读取预置的数据库文件
        StreamResourceInfo sr = Application.GetResourceStream(new Uri(dbPath, UriKind.Relative));
        using (Stream source = sr.Stream)
        {
            using (IsolatedStorageFileStream target = isoStore.CreateFile(dbPath))
            {
                byte[] buffer = new byte[1024];
                int bytesRead;
                while ((bytesRead = source.Read(buffer, 0, buffer.Length)) > 0)
                {
                    target.Write(buffer, 0, bytesRead);
                }
            }
        }
    }
}

这段代码的意思就是检查独立存储里有没有数据库文件,没有的话,就从安装包里把它拷贝过去,以后所有数据库操作,都是针对独立存储里的那个副本。

第三个事儿:连接字符串和数据库操作。

连接字符串超级简单,因为所有东西都是本地的,大概长这样:"Data Source=isostore:/MyDatabase.sqlite;",注意那个isostore:/前缀,这是告诉SQLite引擎,数据库文件在独立存储里。(来源:SQLite for WP7的官方文档或README文件)

微软WP7里用SQLite搞本地数据库那些事儿和小技巧分享

操作数据库呢,就跟在其他平台上用SQLite.Net差不多,无非就是SQLiteConnectionSQLiteCommand这些,但有个小技巧是为了方便,大家普遍会用一些微型的ORM(对象关系映射)库,比如当时很火的“sqlite-net”,这个库特别轻量,你只需要在你的数据模型类上加上[PrimaryKey][AutoIncrement]这样的属性注解,它就能帮你把对象和数据库表映射起来,省去了大量手写SQL语句的麻烦,比如插入一个对象,不用写INSERT INTO ...,直接db.Insert(myObject)就行了,非常符合WP7那种追求简洁快速的开发风格。

第四个小技巧:处理数据库升级(Migration)。

这是最头疼的部分,你的应用发布v1.0后,用户手机里已经有一个数据库了,等你开发v2.0,可能要加新表、新字段,你怎么能保证用户更新应用后,旧数据不丢,新结构又能用上? 当时成熟的方案不多,很多都是自己手动处理,基本思路是:

  1. 在App.xaml.cs的启动代码里,在拷贝数据库之后,设一个版本号(比如存在ApplicationSettings里)。
  2. 检查当前应用版本号是否大于存储的数据库版本号。
  3. 如果大于,就执行升级脚本,比如从v1到v2,可能就需要用ALTER TABLE语句添加新列,这个过程要特别小心,做好异常处理,万一升级失败,应用可能就崩溃了。 后来一些第三方ORM库开始集成简单的升级支持,在WP7上做数据库升级是个需要非常细心和手动干预的活儿,远没有现在一些成熟ORM框架那么自动化。

还有一些零碎的经验:

  • 性能: 批量操作时,记得把操作放在一个事务(Transaction)里,速度能快上几十倍甚至上百倍。
  • 线程安全: WP7的UI线程和后台线程访问数据库要小心,最好通过Deployment.Current.Dispatcher.BeginInvoke来确保数据库操作在UI线程上执行,或者确保你的SQLite连接是线程安全的(通常建议每个线程单独开连接,但WP7场景简单,很多时候单连接也够用)。
  • 文件路径: 别用绝对路径,就用isostore:/加上文件名,最保险。

在WP7上用SQLite,核心就是“社区驱动,自力更生”,它不像现在Flutter、React Native那样有官方钦定的、开箱即用的数据库解决方案,每一步,从引入库文件,到部署初始数据,再到后续升级,都需要开发者自己摸索和搭建一套流程,虽然麻烦,但当时能在一个手机上用上关系型数据库,做出功能强大的应用,那种成就感还是挺足的,这些经验和技巧,也为后来Windows Phone 8/8.1原生支持SQLite奠定了一定的社区基础。