.NET MAUI 本地数据库

Browse sample. 浏览示例

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 类注册为可在应用中使用的单一实例。 例如,可以使用 AddSingletonAddTransient 方法在 MauiProgram.cs 中将页面和数据库访问类注册为 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) 文件。 请确保也对这些文件应用任何更改。