本地数据存储:SQLite的使用方法

发表时间: 2023-10-08 14:30

拥有关系数据时,在 SQLite 中存储数据十分有用。 例如,假设正在生成用于管理图书馆的应用程序。 图书馆中的每本书都有一位或多位作者,而一名作者可以写多本书。 可在 SQLite 数据库中轻松地为这种关系建模。

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

什么是 SQLite?

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

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

备注:如果想要下载 SQLite.NET,可在此处找到它:
https://www.nuget.org/packages/sqlite-net


什么是 SQLite.NET?

C++学习资料→CC++入门到高级资料

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

SQLite.NET 是对象关系映射器。 它通过让我们能够将在项目中定义的模型用作架构,帮助简化定义数据库架构的过程。 以下面的代码片段为例:

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

借助对象关系映射器,可使用此初始“User” 类,并让其自动创建包含“Id”和“Username”列的名为“User”的数据库表。

SQLite.NET 作为 NuGet 包提供。 将它添加到 Xamarin.Forms 的每个项目中。

如何连接到 SQLite 数据库

SQLite.Net 通过“SQLiteConnection”对象建立与 SQLite 数据库的连接。 实例化此对象时,必须传入数据库文件的文件名。 然后,它将打开文件(如果存在文件)或创建文件(如果不存在文件)。

下面是其用法示例:

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

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

如何创建表

回想一下,SQLite.NET 是对象关系映射器,这意味着可以通过 C# 类生成数据库架构。 返回 User 类的上一个示例。

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

SQLite.NET 可通过此 C# 类生成数据库架构,但许多属性可供添加到类中,用于对架构进行修改。

以下是可用属性的一些示例:

  • 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# 类后,需要让 SQLite.NET 创建表。 为此,请在“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。

插入数据后,通常需要从表中检索数据。 借助 SQLite.NET,可使用“Table”方法轻松地在表中检索所有行。 以下是如何使用它的示例:

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

“Table”方法返回“TableQueryT”。 若要获取“List”,请使用“ToList”方法。

使用 LINQ 执行 SQLite 查询

虽然可使用“Table”方法在表中检索所有行,但并不总是想要这样做。 有时,想要仅返回行的子集或运行更复杂的查询。 对于这些任务,请将 LINQ 与 SQLite.NET 配合使用。

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

  • Where
  • Take
  • Skip
  • OrderBy
  • OrderByDescending
  • ThenBy
  • ElementAt
  • First
  • FirstOrDefault
  • ThenByDescending
  • Count

通过这些方法,你可以使用扩展方法语法或 LINQ C# 语法。 例如,下面是可供使用 LINQ C# 语法按用户名获取用户的代码片段:

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

定义 SQLite.NET 实体

首先,创建用于定义数据库架构的模型类。

  1. 将名为“Models”的新文件夹添加到“People”.NET 标准库中。
  2. 在“Models”文件夹中,创建名为“Person”的新类。 确保将其标记为“公开”。
namespace People.Models{   public class Person   {   }}

3.添加名为“ID”的 int 属性。

4.添加名为 Name 的字符串属性。

namespace People.Models{   public class Person   {      public int Id { get; set; }      public string Name { get; set; }   }}

添加 SQLite.NET 属性

现在已创建了模型,接下来可添加一些属性用于帮助 SQLite.NET 将类映射到表中。

  1. 为 SQLite 命名空间添加 using 指令。 借助该指令,可使用 SQLite.NET 属性。
  2. 使用 [Table] 属性批注“Person”类,并将名称指定为“people”。
  3. 将“Id”属性指定为主键。 使用 [PrimaryKey] 和 [AutoIncrement] 属性对其进行批注。
  4. 向“Name”属性添加注释,以向数据添加一些约束。 将其 [MaxLength] 指定为 250。 指定列中的每个值都应为 [Unique]。
using SQLite;namespace People.Models{   [Table("people")]   public class Person   {      [PrimaryKey, AutoIncrement]      public int Id { get; set; }      [MaxLength(250), Unique]      public string Name { get; set; }   }}

5.生成应用程序,确保其能正常编译。


添加存储库类

接下来,需要为跨平台项目添加一些预生成的代码。 已提供此代码。 您可以在练习存储库的克隆或下载副本的文件夹中找到它。

  1. 转到“exercise1”“start”文件夹。
  2. 打开“People.sln”解决方案。
  3. 将“assets”文件夹中的“PersonRepository.cs”C# 源文件添加到“People”共享项目中。 可将其从文件夹拖放到解决方案资源管理器中的项目根节点。
  4. 检查存储库类。 它具有一些预先提供的方法和少量 TODO 标记,将在其中添加一些功能用于访问数据库。

连接到数据库

现在,将在“SQLiteConnection”上创建一个实例,然后创建一个“Person”表。

  1. 打开“PersonRepository.cs”源文件。
  2. 在构造函数中,初始化新的“SQLiteConnection”。 将其分配给名为“conn”的字段。
  3. 使用“conn.CreateTable”方法创建一个表,用于存储“Person”数据。
private SQLiteConnection conn;...public PersonRepository(string dbPath){   conn = new SQLiteConnection(dbPath);   conn.CreateTable<Person>();}

向数据库中插入行

创建“Person”表后,即可开始插入数据。 实现“AddNewPerson”方法,使用户能够插入新人员。

  1. 在“PersonRepository.cs”中,找到“AddNewPerson”方法。
  2. 添加代码以插入新的“Person”对象。 在“SQLiteConnection”对象上使用“Insert”方法。 将返回值分配给已在方法中定义的“result”变量。
public void AddNewPerson(string name){   int result = 0;   try   {      //basic validation to ensure a name was entered      if (string.IsNullOrEmpty(name))            throw new Exception("Valid name required");      result = conn.Insert(new Person { Name = name });      ...   }   ...}

从数据库中读取

此时,可以向“Person”表添加新行。 尝试使用“Table”方法读取它们。

  1. 在“PersonRepository.cs”中,找到“GetAllPeople”方法。
  2. 使用 Table<T> 方法检索所有记录。
  3. 无法直接返回枚举器,因为预期为 List<Person>。 使用“ToList()”扩展方法将其转换为有效类型。
  4. 可根据需要,通过将代码包装到 try-catch 块中来包含错误处理流程。 如果存在错误,请将“StatusMessage”属性设置为异常的“消息”并返回空的 。
public List<Person> GetAllPeople(){   try   {      return conn.Table<Person>().ToList();   }   catch (Exception ex)   {      StatusMessage = string.Format("Failed to retrieve data. {0}", ex.Message);   }   return new List<Person>();}

连接到 UI

最后一步是添加 UI,以调用在上一步中实现的方法。 使用练习所用的“Assets”文件夹中包含的预生成的 UI。

  1. 将“People”共享项目中的“MainPage.xaml”和“MainPage.xaml.cs”替换为资产中的副本。 将它们直接复制到文件夹结构中并替换现有文件。
  2. 打开 .NET Standard 项目中的“App.xaml.cs”文件,然后找到构造函数。
  3. 将传入的参数重命名为 dbPath(如果它不是该名称)。
  4. 删除“MainPage”对象上的“Text”属性资源库。 你不再拥有该属性,因为刚刚替换了 UI。
  5. 添加名为“PersonRepo”的公共静态属性,用于保存“PersonRepository”对象。
  6. 通过创建“PersonRepository”的实例来初始化构造函数中的“PersonRepo”属性,并将“dbPath”参数传入存储库构造函数。

运行应用程序

生成解决方案,并运行应用程序。 通过在文本框中键入名称并选择“添加人员”,将人员添加到数据库中。

之后,可选择“获取所有人员”,以从数据库中提取名称并将在列表中显示它们。 此外,尝试重启应用程序,查看数据在应用程序启动期间是否还在。


如果以同步方式对数据库运行查询,可能会导致性能问题。 SQLite.NET 具有一个异步 API,可用于使应用程序始终保持响应。

本单元将介绍如何使用 SQLite.NET 的异步 API 来确保应用程序保持高度响应。

了解异步查询

到目前为止,所做的一切均已在 UI 线程上执行。 但是,若要生成响应速度极快的移动应用程序,需要以略有不同的方式执行操作。 如果在 UI 线程上运行数据库操作,则可能会导致 UI 在操作需要很长时间才能完成时冻结。

为了解决此问题,SQLite.NET 通过“SQLiteAsyncConnection”类包含了一个异步 API。 例如,若要异步创建表,可使用

var conn = new SQLiteAsyncConnection(dbPath);await conn.CreateTableAsync<User>();

SQLite.NET 不是线程安全的。 这意味着如果多个线程使用同一个连接,你可能会遇到问题。 因此,最好有一个负责管理“SQLite”连接的存储库类。

使用 SQLite.NET 执行异步操作

“SQLiteAsyncConnection”公开与同步对应类相同的操作。 但是,操作均基于任务,以便在后台使用。

下面列出了一些常见的异步操作:

  • CreateTableAsync:根据指示的类创建表
  • DropTableAsync:删除与指示的类关联的表
  • GetAsync:获取表中与指示的类关联的记录,并匹配传入构造函数的主键
  • InsertAsync:使用传入构造函数的项插入新记录
  • UpdateAsync:使用传入构造函数的项更新现有记录
  • DeleteAsync:删除表中映射到指示类的记录,并匹配传入构造函数的主键
  • QueryAsync:运行直接 SQL 查询并返回对象
  • ExecuteAsync:运行直接 SQL 查询并返回受影响的行数。
  • ExecuteScalarAsync:运行直接 SQL 查询并返回单个结果
  • ToListAsync:异步执行“Table”方法

以下是使用“ToListAsync”方法异步检索记录的示例:

SQLiteAsyncConnection conn;ObservableCollection<User> userList;  // Bound to UI...public async Task AddAllUsersAsync(){    List<User> users = await conn.Table<User>().ToListAsync();    // Must be on UI thread here!    foreach (var u in users)        userList.Add(u);}

使用“ToListAsync”方法从数据库异步获取所有用户。 如果使用此方法,即使数据库中有大量用户,UI 也能保持响应。

异步使用 SQLite

本单元将把 Person 存储库应用程序从同步 SQLite.NET API 转换为异步版本。 这样一来,无论对数据库执行多少次查询,应用程序将始终能够保持响应。

创建异步连接

首先将“PersonRepository”更改为使用“SQLiteConnection”的异步版本。 现在,可以与数据库进行异步交互。

  1. 打开“People”.NET Standard 项目中的“PersonRepository.cs”文件。
  2. 将“SQLiteConnection”conn 属性更改为“SQLiteAsyncConnection”。 若要进行此更改,必须更新属性和实例化。
  3. 在构造函数中,请注意,对“CreateTable”的调用不再有效。 将此调用替换为“CreateTableAsync”。
private SQLiteAsyncConnection conn;public PersonRepository(string dbPath){   conn = new SQLiteAsyncConnection(dbPath);   conn.CreateTableAsync<Person>().Wait();}

以异步方式向表中插入项

现在使用的是“SQLiteAsyncConnection”,可与数据库进行异步交互。 接下来了解如何以异步方式插入新项。

修改“AddNewPerson”方法,以通过异步插入方式插入新的“Person”。

using System.Threading.Tasks;...public async Task AddNewPersonAsync(string name){   int result = 0;   try   {      //basic validation to ensure a name was entered      if (string.IsNullOrEmpty(name))            throw new Exception("Valid name required");      // TODO: insert a new person into the Person table      result = await conn.InsertAsync(new Person { Name = name });      StatusMessage = string.Format("{0} record(s) added [Name: 事件处理)", result, name);   }   catch (Exception ex)   {      StatusMessage = string.Format("Failed to add {0}. Error: 事件处理", name, ex.Message);   }}

异步获取表中的所有项

最后,从数据库中异步检索“People”。

  1. 修改“GetAllPeople”方法,以使用异步调用返回结果。
public async Task<List<Person>> GetAllPeopleAsync(){   try   {      return await conn.Table<Person>().ToListAsync();   }   catch (Exception ex)   {      StatusMessage = string.Format("Failed to retrieve data. {0}", ex.Message);   }   return new List<Person>();}

2.在“MainPage.xaml.cs”文件中,修改两个“Button.Click”事件处理程序,以使用“PersonRepository”类中的异步方法。 使用 async 和 await 关键字。

3.运行该程序,验证它是否仍能正常运行。

总结

在移动设备本地存储数据对于提高性能十分有用。 可以在本地存储重要数据并快速检索,而不是通过不断地调用远程服务器来获取数据。

可用的存储选项因拥有的数据类型而异。 处理本质上为关系数据的数据时,数据库是最佳选项。

可使用 SQLite 在 Xamarin 应用程序中创建本地数据库。 SQLite.NET 是 SQLite 的 C# 包装器。 它公开异步 API,以帮助确保应用程序的 UI 始终保持响应。