Xamarin.Forms 复制本地SQLite数据库

 

Xamarin.Forms 复制本地SQLite数据库

 

        2022年元旦的三天假期,闲暇无事。

       想到一个多月以前浏览博客园收藏的两则新闻。一是2021年11月9日 Microsoft 发布了迄今为止最快的 .NET 版本---- .NET6 ,二是号称宇宙最强IDE的Visual Studio 2022 正式版也于同日正式发布。一时技痒,就想用Xamarin.Forms来做一个移动端应用程序。

Xamarin.Forms 复制本地SQLite数据库

引子:关于年过半百的老李头

       何谓一时技痒呢?本人是一名年过半百(1971年出生)的编程爱好者,92年左右开始接触电脑,当时的电脑叫286或386,内存大多是32K或者64K。存储介质是一种叫软盘的东西,现在的计算机为什么从C盘开始,好像就是因为当年的A盘和B盘都是软盘驱动器,两个软盘驱动器一大一小,小的三寸盘1.44M,大的五寸盘容量512K。当年最高光的时刻就是用BASIC语言,使用一种叫DBASIC III的工具编写了一套可打印的单位工资表。

Xamarin.Forms 复制本地SQLite数据库

       90年代中期开始接触编程,使用Pascal语言、Delphi3和SQL Server6.5数据库来开发Windows桌面应用程序,曾经获得过省部级的科技成果三等奖。到了20年代初期,ASP等脚本语言逐渐流行起来,开发方式也分为C/S方式和B/S方式。谁也想不到当年毫不起眼的JavaScript和mysql等今天能如此流行。Borland公司也在和Microsoft公司的大战中逐渐失利,最主要的是由于一些个人原因就没有再跟进学习。一晃二十年过去了。当年读过一本书叫《Borland传奇》记录了好多当年的技术大牛传奇故事,不知今天的开发者又有多少人读过。以史为鉴,谁会是今天的Borland?谁又会是明天的Microsoft?三十年后,谁在河东?谁在河西?这,也许是程序员的一种悲哀。

Xamarin.Forms 复制本地SQLite数据库Xamarin.Forms 复制本地SQLite数据库

        为什么要做移动端应用?由于从来没有做过网页开发,不会任何前端开发语言,一直听说微软有一个宏大的理想,要用.NET一统江湖,对我而言,就是能用C#写网页,也就一直比较关注。不久前,微软推出一个新的 UI 框架Blazor ,知乎上说:“blazor是基于C#的,主要的开发工具是C#(无需使用Javascript), 这对于.net 程序员来说非常友好,可以无需为了前端学习各种不同的技术,同时基于C#的代码也可以很好的和原有的Javascript代码进行沟通。”实验了一下,纷繁复杂的各种前端技术还是给我整蒙了。想到也许今天的网站开发也许就是二十年前的WinForm,于是就想直接挑战一下跨平台移动端应用开发。

        从零开始,Hello World。

Xamarin.Forms 复制本地SQLite数据库 

问题:如何复制本地数据库到目标环境

        如果您向我一样,是一个零基础移动端的开发者,那么按照Xamarin.Forms 快速入门(链接地址)的示例指引,那么应该在30分钟左右,就会生成第一个Xamarin的应用,您也可以直接下载示例(链接地址)。链接数据库和创建数据表的方法如下,挺简单吧?

        public NoteDatabase(string dbPath)
        {
            database = new SQLiteAsyncConnection(dbPath);
            database.CreateTableAsync<Note>().Wait();
        }

 Xamarin.Forms 复制本地SQLite数据库

        但我希望App可以提供查询功能,查询我已经准备好的数据库中的数据。于是对于我这样零基础初学者的坑来了:

        --------尝试将本地数据库直接复制到虚拟机中,把查询的功能做完。按照传统的WinForm思维,要直接复制数据库到目标环境中中,需要先知道虚拟机中创建数据库的位置。于是安装了Android的SDK,但没有在android目录中找到DDMS工具。各种ADB shell 在控制台中敲了若干命令行,还是没有在/data/user/0/com.companyname.notes/files/目录下看到App创建的Notes.db3数据库。最终明白了所谓应用沙盒,应用程序可以使用的专有区域。在默认情况下,除操作系统本身外,其他任何应用程序都无法访问此区域。于是放弃。

        --------尝试将数据直接INSERT到已经创建好的数据库中。写一个数据库初始化类,直接将初始化的数据Insert进去。少量数据这也许是一个折中(偷懒)的方法,但对于近万条的初始数据,即使能Insert程序启动时也不能等这么久啊。于是放弃。

        以上两个尝试消耗了三天假期中的两天。

        继续查找微软的Xamarin文档,在“Xamarin.Forms 本地数据库”(链接地址)页面中写道:在多种情况下,可能需要复制 SQLite 数据库:数据库随应用程序一起提供,但必须复制或移动到移动设备上的可写存储。说的挺明白,但没找到后续操作的明确指引。

Xamarin.Forms 复制本地SQLite数据库

方法:通过文件流的方式来完成复制操作

       在阅读了大量博客园里的文章之后(其中大部分是在第一次尝试直接复制数据库到Android虚拟机中时阅读的),看到denniswang 博主在2011年10月18日《Android中asset文件夹和raw文件夹区别》(链接地址)文章中提到的思路。

Xamarin.Forms 复制本地SQLite数据库

        虽然感觉这篇十年前的文章应该是采用DSK的原生Android开发,但有两点明确的启示:一是Android中Resource/Raw下的文件在打包后会原封不动的保存在apk包中,不会被编译成二进制。二是读取Resource/Raw下的文件资源,通过获取输入流来的方式来进行写操作。曙光初现,假期还有一天,继续挑战,最终具体步骤如下:

        一、准备本地SQLite 数据库文件。首先在Notes.Android项目的Resources目录下新建文件夹raw,然后分别将本机包含初始数据的Notes.db3在VS中复制到Notes.Android项目下的raw和Notes.iOS项目的Resources目录下。然后重新生成解决方案,让复制到项目中的资源可编译。

Xamarin.Forms 复制本地SQLite数据库 

        二、新建ISQLite 接口并分别实现。1、在Notes项目下新建类ISQLite.cs。

using SQLite;

namespace Notes
{
    public interface ISQLite
    {
        SQLiteAsyncConnection GetConnection();
    }
}

         2、在Notes.Android项目下打开MainActivity.cs。添加属性如下:internal static MainActivity Instance { get; private set; }

Xamarin.Forms 复制本地SQLite数据库

        3、在Notes.Android项目下新建类SQLite_Android.cs

using System;
using Notes.Droid;
using Xamarin.Forms;
using System.IO;
using SQLite;

[assembly: Dependency(typeof(SQLite_Android))]

namespace Notes.Droid
{
    public class SQLite_Android : ISQLite
    { 
        public SQLiteAsyncConnection GetConnection()
        {
            var sqliteFilename = "Notes.db3";
            string documentsPath = System.Environment.GetFolderPath(System.Environment.SpecialFolder.Personal); 
            var path = Path.Combine(documentsPath, sqliteFilename);
            Console.WriteLine(path);
            if (!File.Exists(path))
            {
                var readStream = MainActivity.Instance.Resources.OpenRawResource(Resource.Raw.Notes);  
                FileStream writeStream = new FileStream(path, FileMode.OpenOrCreate, FileAccess.Write);
                int Length = 256;
                Byte[] buffer = new Byte[Length];
                int bytesRead = readStream.Read(buffer, 0, Length);
                while (bytesRead > 0)
                {
                    writeStream.Write(buffer, 0, bytesRead);
                    bytesRead = readStream.Read(buffer, 0, Length);
                }
                readStream.Close();
                writeStream.Close();
            }
            var conn = new SQLiteAsyncConnection(path);
            return conn;
        }
    }
}

         4、在Notes.iOS项目下新建类SQLite_iOS.cs

using System;
using Notes.iOS;
using Xamarin.Forms;
using System.IO;
using SQLite;

[assembly: Dependency(typeof(SQLite_iOS))]

namespace Notes.iOS
{
    public class SQLite_iOS : ISQLite
    {
        public SQLite_iOS()
        {
        }
        public SQLiteAsyncConnection GetConnection()
        {
            var sqliteFilename = "Notes.db3";
            string documentsPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); 
            string libraryPath = Path.Combine(documentsPath, "..", "Library"); 
            var path = Path.Combine(libraryPath, sqliteFilename);

            // This is where we copy in the prepopulated database
            Console.WriteLine(path);
            if (!File.Exists(path))
            {
                File.Copy(sqliteFilename, path);
            }
            var conn = new SQLiteAsyncConnection(path);
            return conn;
        }
    }
}

        三、修改 Notes项目下NoteDatabase.cs类的构造函数public NoteDatabase(string dbPath)。

        readonly SQLiteAsyncConnection database;

        //public NoteDatabase(string dbPath)
        //{
        //    database = new SQLiteAsyncConnection(dbPath);
        //    database.CreateTableAsync<Note>().Wait();
        //}
        public NoteDatabase()
        {
            database = Xamarin.Forms.DependencyService.Get<ISQLite>().GetConnection();
            database.CreateTableAsync<Note>().Wait();
        }

        四、修改 Notes项目下的App.xaml.cs

        public static NoteDatabase Database
        {
            get
            {
                if (database == null)
                {
                    //database = new NoteDatabase(Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Notes.db3"));
                    database = new NoteDatabase();
                }
                return database;
            }
        }

        假期三天,就做了这么件事。无所谓好坏和成败。只是感悟,记录于此,希望能给有相同困扰的开发者一点参考。

-------------------图片来源于网页截图,如有侵权,告知即删除-------------------

上一篇:21天养成好习惯第一期-20


下一篇:初始化数据库