.NET MAUI 本地数据库

浏览示例。 浏览示例

SQLite 数据库引擎允许 .NET Multi-platform App UI (.NET MAUI) 应用在共享代码中加载和保存数据对象。 可以通过以下步骤将 SQLite.NET 集成到 .NET MAUI 应用中,以在本地数据库中存储和检索信息:

  1. 安装 NuGet 包
  2. 配置常量
  3. 创建数据库访问类
  4. 访问数据
  5. 高级配置。

本文使用 sqlite-net-pcl NuGet 包提供对表的 SQLite 数据库访问权限,以存储待办事项。 另一种方法是使用 Microsoft.Data.Sqlite NuGet 包,该包是 SQLite 的轻型 ADO.NET 提供程序。 Microsoft.Data.Sqlite 用于实现连接、命令和数据读取器等功能的常见 ADO.NET 抽象。

安装 SQLite NuGet 包

使用 NuGet 包管理器搜索 sqlite-net-pcl 包,并将最新版本添加到 .NET MAUI 应用项目。

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

  • ID: sqlite net pcl
  • 作者:SQLite-net
  • 所有者:praeclarum
  • NuGet 链接:sqlite-net-pcl

不论包名称如何,都可以在 .NET MAUI 项目中使用 sqlite-net-pcl NuGet 包。

重要

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

安装 SQLitePCLRaw.bundle_green

除了 sqlite-net-pcl,还需要临时安装在每个平台上公开 SQLite 的基础依赖项:

  • ID:SQLitePCLRaw.bundle_green
  • 版本:>= 2.1.0
  • 作者:Eric Sink
  • 所有者:Eric Sink
  • NuGet 链接:SQLitePCLRaw.bundle_green

配置应用常量

配置数据(如数据库文件名和路径)可以存储为应用中的常量。 示例项目包括一个 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 =>
        Path.Combine(FileSystem.AppDataDirectory, DatabaseFilename);
}

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

  • Create:连接将自动创建数据库文件(如果不存在)。
  • FullMutex:连接在序列化线程模式下打开。
  • NoMutex:连接在多线程模式下打开。
  • PrivateCache:即使连接已启用,连接也不会参与共享缓存。
  • ReadWrite:连接可以读取和写入数据。
  • SharedCache:如果启用了共享缓存,则连接将参与共享缓存。
  • ProtectionComplete:设备锁定时,文件会被加密且不可访问。
  • ProtectionCompleteUnlessOpen:文件会被加密,直到文件打开,但之后即使用户锁定设备,也可以访问该文件。
  • ProtectionCompleteUntilFirstUserAuthentication:文件会被加密,直到用户启动并解锁设备。
  • ProtectionNone:数据库文件不会被加密。

可能需要根据数据库的使用方式指定不同的标记。 有关 SQLiteOpenFlags 的详细信息,请参阅 sqlite.org 上的打开新数据库连接

创建数据库访问类

数据库包装类从应用的其余部分抽取数据访问层。 该类集中查询逻辑并且简化数据库初始化的管理,使得应用增长时重构或扩展数据操作更容易。 为此,示例应用将定义 TodoItemDatabase 类。

延缓初始化

TodoItemDatabase 使用异步延缓初始化,将数据库的初始化延迟到首次访问时,类中的每个方法都会调用简单的 Init方法:

public class TodoItemDatabase
{
    SQLiteAsyncConnection Database;

    public TodoItemDatabase()
    {
    }

    async Task Init()
    {
        if (Database is not null)
            return;

        Database = new SQLiteAsyncConnection(Constants.DatabasePath, Constants.Flags);
        var result = await Database.CreateTableAsync<TodoItem>();
    }
    ...
}

数据操作方法

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

下列示例演示示例应用中的数据操作方法:

public class TodoItemDatabase
{
    ...
    public async Task<List<TodoItem>> GetItemsAsync()
    {
        await Init();
        return await Database.Table<TodoItem>().ToListAsync();
    }

    public async Task<List<TodoItem>> GetItemsNotDoneAsync()
    {
        await Init();
        return await Database.Table<TodoItem>().Where(t => t.Done).ToListAsync();

        // SQL queries are also possible
        //return await Database.QueryAsync<TodoItem>("SELECT * FROM [TodoItem] WHERE [Done] = 0");
    }

    public async Task<TodoItem> GetItemAsync(int id)
    {
        await Init();
        return await Database.Table<TodoItem>().Where(i => i.ID == id).FirstOrDefaultAsync();
    }

    public async Task<int> SaveItemAsync(TodoItem item)
    {
        await Init();
        if (item.ID != 0)
            return await Database.UpdateAsync(item);
        else
            return await Database.InsertAsync(item);
    }

    public async Task<int> DeleteItemAsync(TodoItem item)
    {
        await Init();
        return await Database.DeleteAsync(item);
    }
}

访问数据

如果使用依赖项注入,TodoItemDatabase 类可以注册为在整个应用中使用的单一实例。 例如,可以在 MauiProgram.cs 中使用 AddSingletonAddTransient 方法,将页面和数据库访问类注册为 IServiceCollection 对象上的服务:

builder.Services.AddSingleton<TodoListPage>();
builder.Services.AddTransient<TodoItemPage>();

builder.Services.AddSingleton<TodoItemDatabase>();

然后,这些服务可以自动注入到类的构造函数中,并可被访问:

TodoItemDatabase database;

public TodoItemPage(TodoItemDatabase todoItemDatabase)
{
    InitializeComponent();
    database = todoItemDatabase;
}

async void OnSaveClicked(object sender, EventArgs e)
{
    if (string.IsNullOrWhiteSpace(Item.Name))
    {
        await DisplayAlert("Name Required", "Please enter a name for the todo item.", "OK");
        return;
    }

    await database.SaveItemAsync(Item);
    await Shell.Current.GoToAsync("..");
}

也可以创建数据库访问类的新实例:

TodoItemDatabase database;

public TodoItemPage()
{
    InitializeComponent();
    database = new TodoItemDatabase();
}

有关 .NET MAUI 应用中依赖项注入的详细信息,请参阅依赖项注入

高级配置

SQLite 提供一个强大的 API,其功能远超本文和示例应用所涵盖的范围。 下列部分介绍对可扩展性非常重要的功能。

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

预写日志

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

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

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

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

await Database.EnableWriteAheadLoggingAsync();

有关详细信息,请参阅 sqlite.org 上的 SQLite 预写日志

复制数据库

在有几种情况下,可能需要复制 SQLite 数据库:

  • 数据库已随应用程序一起提供,但必须复制或移动到移动设备上的可写存储。
  • 需要创建数据库的备份或副本。
  • 需要对数据库文件进行版本控制、移动或重命名。

通常说来,移动、重命名或复制数据库文件的过程与任何其他文件类型相同,但需要注意一些其他注意事项:

  • 尝试移动数据库文件之前,应关闭所有数据库连接。
  • 如果使用预写日志,SQLite 将创建一个共享内存访问 (.shm) 文件和一个(预写日志)(.wal) 文件。 请确保也为这些文件应用了更改。