相关文章推荐
乐观的冰棍  ·  Electron ...·  1 年前    · 
忧郁的硬盘  ·  mqtt - MQTTnet: ...·  1 年前    · 

SQLite 数据库引擎允许 Xamarin.Forms 应用程序在共享代码中加载和保存数据对象。 示例应用程序使用 SQLite 数据库表来存储 todo 项。 本文介绍如何使用共享代码中的 SQLite.Net 在本地数据库中存储和检索信息。

按照以下步骤将 SQLite.NET 集成到移动应用中:

  • 安装 NuGet 包
  • 配置常量
  • 创建数据库访问类
  • 访问 中的数据 Xamarin.Forms
  • 高级配置
  • 安装 SQLite NuGet 包

    使用 NuGet 包管理器搜索 sqlite-net-pcl 并将最新版本添加到共享代码项目。

    许多 NuGet 包都有着类似的名称。 正确的包具有以下属性:

  • ID: sqlite net pcl
  • 作者:SQLite-net
  • 所有者:praeclarum
  • NuGet 链接: sqlite-net-pcl
  • 不管包名称,即便在 .NET Standard 项目中也使用 sqlite-net-pcl NuGet 包

    SQLite.NET 是 praeclarum/sqlite-net 存储库 支持的第三方库。

    配置应用常量

    示例项目包括一个提供常见配置数据的 Constants.cs 文件:

    public static class Constants
        public const string DatabaseFilename = "TodoSQLite.db3";
        public const SQLite.SQLiteOpenFlags Flags =
            // open the database in read/write mode
            SQLite.SQLiteOpenFlags.ReadWrite |
            // create the database if it doesn't exist
            SQLite.SQLiteOpenFlags.Create |
            // enable multi-threaded database access
            SQLite.SQLiteOpenFlags.SharedCache;
        public static string DatabasePath
                var basePath = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData);
                return Path.Combine(basePath, DatabaseFilename);
    

    常量文件指定用于初始化数据库连接的默认 SQLiteOpenFlag 枚举值。 枚举 SQLiteOpenFlag 支持以下值:

  • Create:连接将自动创建数据库文件(如果不存在)。
  • FullMutex:连接在序列化线程模式下打开。
  • NoMutex:连接在多线程模式下打开。
  • PrivateCache:即使已启用共享缓存,连接也不会参与共享缓存。
  • ReadWrite:连接可以读取和写入数据。
  • SharedCache:连接将参与共享缓存(如果已启用)。
  • ProtectionComplete:设备锁定时文件已加密且不可访问。
  • ProtectionCompleteUnlessOpen:文件在打开前会进行加密,但即使用户锁定设备,该文件仍可访问。
  • ProtectionCompleteUntilFirstUserAuthentication:在用户启动并解锁设备之前,文件将加密。
  • ProtectionNone:数据库文件未加密。
  • 可能需要根据数据库的使用方式指定不同的标志。 有关 的详细信息 SQLiteOpenFlags,请参阅在 sqlite.org 上打开新的数据库连接

    创建数据库访问类

    数据库包装类从应用的其余部分提取数据访问层。 此类集中了查询逻辑并简化了数据库初始化的管理,使得随着应用的增长,可以更轻松地重构或扩展数据操作。 Todo 应用为此定义类 TodoItemDatabase

    延迟初始化

    使用 TodoItemDatabase 由自定义 AsyncLazy<T> 类表示的异步延迟初始化来延迟数据库的初始化,直到首次访问数据库:

    public class TodoItemDatabase
        static SQLiteAsyncConnection Database;
        public static readonly AsyncLazy<TodoItemDatabase> Instance = new AsyncLazy<TodoItemDatabase>(async () =>
            var instance = new TodoItemDatabase();
            CreateTableResult result = await Database.CreateTableAsync<TodoItem>();
            return instance;
        public TodoItemDatabase()
            Database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
        //...
    

    字段 Instance 用于为 TodoItem 对象创建数据库表(如果尚不存在),并返回 TodoItemDatabase 作为单一实例的 。 类型 Instance 为 的 AsyncLazy<TodoItemDatabase> 字段是在首次等待时构造的。 如果多个线程尝试同时访问字段,它们都将使用单个构造。 然后,在构造完成后,所有操作都 await 完成。 此外,由于值可用,因此构造完成后的任何 await 操作都会立即继续。

    数据库连接是一个静态字段,可确保在应用的整个生命周期内使用单一数据库连接。 与在单个应用会话期间多次打开和关闭连接相比,使用永久性静态连接的性能更好。

    异步延迟初始化

    为了启动数据库初始化,避免阻塞执行,并有机会捕获异常,示例应用程序使用异步延迟初始化,由 AsyncLazy<T> 类表示:

    public class AsyncLazy<T>
        readonly Lazy<Task<T>> instance;
        public AsyncLazy(Func<T> factory)
            instance = new Lazy<Task<T>>(() => Task.Run(factory));
        public AsyncLazy(Func<Task<T>> factory)
            instance = new Lazy<Task<T>>(() => Task.Run(factory));
        public TaskAwaiter<T> GetAwaiter()
            return instance.Value.GetAwaiter();
    

    AsyncLazy将 和 Task<T> 类型组合Lazy<T>在一起,以创建一个延迟初始化的任务,该任务表示资源的初始化。 传递给构造函数的工厂委托可以是同步的,也可以是异步的。 工厂委托将在线程池线程上运行,并且不会 (多次执行,即使多个线程尝试同时启动它们) 也是如此。 工厂委托完成后,延迟初始化值可用,等待实例的任何方法 AsyncLazy<T> 都会收到该值。 有关详细信息,请参阅 AsyncLazy

    数据操作方法

    TodoItemDatabase 包括四种类型的数据操作方法:创建、读取、编辑和删除。 SQLite.NET 库提供了一个简单的对象关系映射 (ORM) ,使你无需编写 SQL 语句即可存储和检索对象。

    public class TodoItemDatabase
        // ...
        public Task<List<TodoItem>> GetItemsAsync()
            return Database.Table<TodoItem>().ToListAsync();
        public Task<List<TodoItem>> GetItemsNotDoneAsync()
            // SQL queries are also possible
            return Database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
        public Task<TodoItem> GetItemAsync(int id)
            return Database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
        public Task<int> SaveItemAsync(TodoItem item)
            if (item.ID != 0)
                return Database.UpdateAsync(item);
                return Database.InsertAsync(item);
        public Task<int> DeleteItemAsync(TodoItem item)
            return Database.DeleteAsync(item);
    

    访问中的数据 Xamarin.Forms

    TodoItemDatabase 公开 Instance 字段,通过该字段可以调用 类中的数据 TodoItemDatabase 访问操作:

    async void OnSaveClicked(object sender, EventArgs e)
        var todoItem = (TodoItem)BindingContext;
        TodoItemDatabase database = await TodoItemDatabase.Instance;
        await database.SaveItemAsync(todoItem);
        // Navigate backwards
        await Navigation.PopAsync();
    

    SQLite 提供了一个可靠的 API,其功能比本文和示例应用中介绍的功能更多。 以下部分介绍对可伸缩性非常重要的功能。

    有关详细信息,请参阅有关 sqlite.org 的 SQLite 文档

    预写日志记录

    默认情况下,SQLite 使用传统的回滚日志。 未更改的数据库内容的副本将写入单独的回滚文件,然后将更改直接写入数据库文件。 删除回滚日志时,将发生 COMMIT。

    Write-Ahead日志记录 (WAL) 首先将更改写入单独的 WAL 文件。 在 WAL 模式下,COMMIT 是追加到 WAL 文件的特殊记录,允许在单个 WAL 文件中发生多个事务。 WAL 文件在称为 检查点的特殊操作中合并回数据库文件。

    对于本地数据库来说,WAL 速度更快,因为读取器和写入器不会相互阻止,因此允许读取和写入操作并发。 但是,WAL 模式不允许更改 页面大小、向数据库添加其他文件关联以及添加额外的 检查点 操作。

    若要在 SQLite.NET 中启用 WAL,请在 实例上SQLiteAsyncConnection调用 EnableWriteAheadLoggingAsync 方法:

    await Database.EnableWriteAheadLoggingAsync();
    

    有关详细信息,请参阅 SQLite Write-Ahead sqlite.org 上的日志记录

    复制数据库

    在某些情况下,可能需要复制 SQLite 数据库:

  • 数据库已随应用程序一起提供,但必须复制或移动到移动设备上的可写存储。
  • 需要创建数据库的备份或副本。
  • 需要对数据库文件进行版本控制、移动或重命名。
  • 通常,移动、重命名或复制数据库文件的过程与任何其他文件类型相同,但需注意一些其他事项:

  • 在尝试移动数据库文件之前,应关闭所有数据库连接。
  • 如果使用 预写日志记录,SQLite 将创建共享内存访问 (.shm) 文件和 (预写日志) (.wal) 文件。 确保也对这些文件应用任何更改。
  • 有关详细信息,请参阅 中的Xamarin.Forms文件处理

  • Todo 示例应用程序
  • SQLite.NET NuGet 包
  • SQLite 文档
  • 将 SQLite 与 Android 配合使用
  • 将 SQLite 与 iOS 配合使用
  • AsyncLazy
  •