使用 SQLite 在本地存储数据

已完成

当你拥有关系数据时,SQLite 十分有用。 假设你正在生成一个社交媒体应用。 你需要存储有关应用订阅者的信息。 此数据包括每个用户的唯一 ID 以及用户的名称。 可以轻松地在 SQLite 数据库中为此类关系建模。

本单元将介绍如何通过 SQLite-net 在 .NET MAUI 应用程序中使用 SQLite。

什么是 SQLite?

SQLite 是轻型跨平台本地数据库,其已成为移动应用程序的行业标准。 SQLite 不需要服务器。 数据库存储在设备文件系统上的单一磁盘文件中。 所有读写操作都直接针对 SQLite 磁盘文件运行。

默认情况下,SQLite 本机库内置于 Android 和 iOS 中;但引擎仅支持 C/C++ API。 对于想要 SQLite 和 .NET 通过某种方式进行交互的 .NET 开发人员来说,此方案并不理想。

什么是 SQLite-net?

本机 SQLite 引擎有多个 C# 包装器可供 .NET 开发人员使用。 许多 .NET 开发人员使用名为 SQLite-net 的常用 C# 包装器

SQLite-net 是对象关系映射器。 通过支持你将在项目中定义的模型用作架构,它可帮助简化定义数据库架构的过程。

Diagram showing how SQLite-net provides a .NET wrapper and the SQLite C/C++ engine.

例如,请考虑以下为 User 建模的类:

class User
{
    public int Id { get; set; }
    public string Username { get; set; }
    ...
}

通过使用对象关系映射器,可以采用此初始 User 类,并创建一个名为 User 的数据库表,它在此类中包含 IdUsername 字段的列。

SQLite-net 是作为 NuGet 包提供的。 必须将 sqlite-net-pcl 包添加到应用才能使用它。 在 Visual Studio 中使用 NuGet 包管理器。 此外,如果要在 Android 上运行应用,还必须添加 SQLitePCLRaw.provider.dynamic_cdecl 包。

如何连接到 SQLite 数据库

可以通过 SQLiteConnection 对象从应用建立与 SQLite 数据库的连接。 此类与 SQLite 提供的其他类型和方法一起都是在 SQLite 命名空间中定义的。 实例化此对象时,需传入数据库文件的文件名。 然后,构造函数将打开文件(如果存在文件)或创建文件(如果不存在文件)。

请参阅以下代码示例:

using SQLite;
...
string filename = ...
SQLiteConnection conn = new SQLiteConnection(filename);

请注意,filename 应指向应用沙盒中的一个位置。

如何创建表

回想一下,SQLite-net 是对象关系映射器,这意味着可以通过 C# 类生成数据库架构。 SQLite-net 可以从普通 C# 类生成数据库表,但许多属性可供添加到类中,用于提供其他元数据。 此元数据可帮助 SQLite 强制实施唯一性等功能,并将约束应用于数据。

可用的属性包括:

  • Table:如果不想使用类的名称作为表的名称,请指定表的名称
  • PrimaryKey:指定作为主键的列
  • AutoIncrement:指定在插入新行时,列值应自动增加
  • Column:如果不想使用属性名作为列名,请指定列名
  • MaxLength:指定列中可使用的最大字符数
  • Unique:指定列中的值必须与其他所有行不同

下面的代码显示了适用于这些属性的 User 类的更新版本:

[Table("user")]
public class User
{
    // PrimaryKey is typically numeric 
    [PrimaryKey, AutoIncrement, Column("_id")]
    public int Id { get; set; }

    [MaxLength(250), Unique]
    public string Username { get; set; }
    ...
}

定义 C# 类后,对 SQLiteConnection 类调用泛型方法 CreateTable 以在数据库中生成表。 将类指定为类型参数。 下面是一个示例:

SQLiteConnection conn = new SQLiteConnection(filename);
conn.CreateTable<User>();

如果表已存在于数据库中,则 CreateTable 方法将检查架构,以查看是否存在任何更改。 如果有更改,操作会尝试更新数据库架构。

如何执行基本读写操作

创建表后,即可开始与它进行交互。 如果要添加行,请对 SQLiteConnection 实例使用 Insert 方法,并提供用于保存要插入的数据的适当类型的对象。 下面的代码演示了如何向 User 表添加新行:

public int AddNewUser(User user)
{
    int result = conn.Insert(user);
    return result;
}

Insert 方法会返回 int,它表示已插入表中的行数。 在本例中,该数字为 1。

要从表中检索行,请使用 Table 方法。 此方法会返回对象的集合(可能为空):

public List<User> GetAllUsers()
{
    List<User> users = conn.Table<User>().ToList();
    return users;
}

Table 方法将返回 TableQuery\<T> 对象。 要获取 List,请使用上述示例所示的方法 ToList

使用 LINQ 执行 SQLite 查询

Table 方法可从表中检索所有行。 在大多数情况下,你只需要返回与一组指定条件匹配的行的子集。 对于这些任务,请将 LINQ 与 SQLite-net 配合使用。

SQLite-net 支持许多常见的 LINQ 查询,其中包括:

  • 其中
  • Take
  • Skip
  • OrderBy
  • OrderByDescending
  • ThenBy
  • ElementAt
  • First
  • FirstOrDefault
  • ThenByDescending
  • 计数

通过这些方法,你可以使用扩展方法语法或 LINQ C# 语法。 例如,下面的代码片段可用于检索指定用户的详细信息:

public User GetByUsername(string username)
{
    var user = from u in conn.Table<User>()
               where u.Username == username
               select u;
    return user.FirstOrDefault();
}

更新和删除行

你将使用 SQLiteConnection 对象的 Update 方法更新行。 提供一个对象,用于定义要使用其新值更新的行。 Update 方法可修改与提供的对象具有相同主键值的行。 返回的值是更改的行数。 如果此值为零,表明找不到具有匹配主键的行,因此未更新任何内容。 下一个代码片段在操作中演示了此方法:

public int UpdateUser(User user)
{
    int result = 0;
    result = conn.Update(user);
    return result;
}

使用 SQLiteConnection 对象的 Delete 方法从表中移除行。 此方法的最简单形式采用了要删除的项的主键作为参数,如以下示例所示。 这种形式的 Delete 方法是泛型方法,需要一个类型参数。 返回的值是从表中删除的行数:

public int DeleteUser(int userID)
{
    int result = 0;
    result = conn.Delete<User>(userID);
    return result;
}