.NET MAUI 本地数据库
SQLite 数据库引擎允许 .NET Multi-platform App UI (.NET MAUI) 应用在共享代码中加载和保存数据对象。 可以通过以下步骤将 SQLite.NET 集成到 .NET MAUI 应用中,以在本地数据库中存储和检索信息:
- 安装 NuGet 包。
- 配置常量。
- 创建数据库访问类。
- 访问数据。
- 高级配置。
本文使用 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 中使用 AddSingleton
和 AddTransient
方法,将页面和数据库访问类注册为 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) 文件。 请确保也为这些文件应用了更改。