SQLite Helper:SQLite数据库的微型ORM
本项目展示了SQLiteHelper如何简化数据库交互,让开发人员能够更多地专注于应用程序的逻辑,而不是SQL查询编写的复杂性。
SQLite Helper 是一款微型对象关系映射(ORM)工具,旨在简化使用 SQLite 数据库的应用开发。它特别适合中小型应用程序,无需从头开始编写所有 SQL 查询。
相比之下,Entity Framework (EF) 是一个功能全面的 ORM,提供全套功能。然而,更多的功能并不一定意味着更优。在采用 EF 之前,权衡 EF 的优缺点 是明智的。
尽管 Entity Framework 提供了功能丰富的强大 ORM 解决方案,但 SQLite Helper 针对简洁和高效进行了优化,通过简单的函数实现了与 SQLite 数据库的流畅交互。
Alex Shapovalov 撰写的一篇名为 Micro ORM vs ORM 的文章详细解释了 Micro ORM 和 ORM 之间的区别以及如何选择。
- NuGet:System.Data.SQLite.Core
- .NET Framework 4.8
SQLite Helper 提供了一系列旨在让您与 SQLite 数据库的交互尽可能顺畅的功能。
- 管理连接字符串:
SQLiteHelper
仅需数据库文件路径即可建立连接,大大简化了此过程。 - 自动打开和关闭连接:
SQLiteHelper
使用一个辅助类来管理数据库连接,无需手动跟踪连接状态,并确保写入操作后正确释放连接。 - 对象映射到数据库类:
SQLiteHelper
通过直接将对象映射到数据库类,简化了数据库的单方法读写操作,从而简化了数据操作任务。 - 处理来自不同数据库源的查询:
SQLiteHelper
能够处理来自各种数据库源的查询,提供了管理多个数据库所需的灵活性。 - 实用工具函数:
SQLiteHelper
包含诸如ClearTable
、GetPrimaryKeys
、GetTableSchema
等实用方法,所有这些方法都设计有防 SQL 注入的保护措施——这是新手常见的疏忽。
总之,SQLiteHelper
是处理 SQLite 数据库的开发者的必备工具。它简化了数据库交互,使开发人员能够将更多精力集中在应用程序逻辑上,而不是 SQL 查询的复杂性。SQLiteHelper
有效地提高了中小型项目的开发效率。
- SQLiteHelper (抽象):包的主类。它包含了数据库读写操作所需的所有方法。
- SQLiteDatabaseHandler (抽象):这是从
SQLiteHelper
派生的子类。它继承了 SQLiteHelper 类所有功能,并且还能够切换远程和本地数据库,以及从远程源同步数据到本地缓存副本。 - SQLiteDataReaderEx (扩展):
SQLiteDataReader
的扩展类,处理 get value 方法的null
检查。 - SQLAttribute:用于表映射的属性基类。
作为一个辅助类,SQLiteHelper
中的大多数方法都被指定为受保护的,因为不期望派生类的用户直接与数据库层交互。所有与数据库相关的操作都应该对用户在 API 和应用程序级别都隐藏。
创建继承自 SQLiteHelper
类的项目特定数据库类。SetSQLPath
具有可选的 readOnly
参数,当设置为 true 时,将以只读模式打开数据库文件。
public class MyDatabase : SQLiteHelper { public MyDatabase(string databaseFilePath): base() { SetSQLPath(databaseFilePath); } }
正确处理重试和超时对于确保事务成功完成且不影响用户体验至关重要。参数 SQLStepRetries
和 SQLBusyTimeout
是定义重试忙碌(锁定数据库)连接的迭代次数和延迟的两个重要参数。
在 SQLite Helper 中,我们提供了一系列查询方法,这些方法在内部处理数据库连接,无需查找未关闭的连接,以免导致数据库被锁定。
protected void ExecuteQuery(string query, Action<SQLiteDataReader> processQueryResults) protected object ExecuteScalar(string query) protected int ExecuteNonQuery(string query) protected void ExecuteTransaction(Action performTransactions)
下面的示例展示了 ExecuteQuery
方法的使用。连接在 ExecuteQuery
方法结束时被关闭,SQLiteDataReader
对象 r
被处置。
ExecuteQuery(query, (r)=> { while(r.Read()) { //ToDo: Perform readback here... } });
SQLite Helper 提供了两种解决方案,将数据从 Employee 表读入 Employee
类对象,如下所示:
Employee (Table) |- ID, INTEGER, Primary Key |- Name, TEXT |- Department, TEXT |- Salary, INTEGER
public class Employee { public int ID {get; set;} public string Name {get; set;} public string Department {get; set;} public int Salary {get; set;} }
- 传统方式,使用
ExecuteQuery
方法手动从数据库读取数据。
public Employee[] ReadEmployeeData() { List<Employee> results = new List<Employee>(); //Execute Query handle database connection ExecuteQuery("SELECT * FROM Employee", (r) => { //(r) = Delegate call back function with SQLiteDataReader parameter r. //Disposal of r is taken care by ExecuteQuery method. int x; while(r.Read()) { x = 0; Employee e = new Employee(); e.ID = r.GetInt32(x++); e.Name = r.GetStringEx(x++); //Extension method. Handle null value. e.Department = r.GetStringEx(x++); e.Salary = r.GetInt32Ex(x++);z } }); }
- 上述实现可以使用带类的查询 -
ReadFromDatabase
- 进一步简化。
public Employee[] ReadEmployeeData() { return ReadFromDatabase<Employee>().ToArray(); }
要更新或写入新数据到数据库,您可以使用 WriteToDatabase
方法。虽然对于简单的表结构,使用 ExecuteNonQuery
方法也可以执行相同的操作,但 WriteToDatabase
方法能够处理更复杂的数据库结构,这将在下一节中介绍。
public void WriteEmployeeData(Employee[] newDatas) { WriteToDatabase(newDatas); }
ReadFromDatabase
和 WriteToDatabase
方法可以轻松地将代码中的对象与数据库中的表关联起来。它们与具有关系(子表)的表配合良好,并能通过简单的命令处理多个数据库。让我们仔细看看它们的功能。
这些方法遵循“快速失败”原则,这意味着它们会在您第一次使用时快速检查您的对象结构是否与您的数据库表结构匹配。此检查是为了确保所有列都匹配。为避免与旧版本出现问题,您的数据库表可以有对象中不存在的额外列,但反之则不然。
“WriteOptions”属性由 SQLiteWriteOption
类指定,该类设置 SQLiteHelper 在读写数据时的行为,具有以下选项:
CreateTable
:当设置为 true 时,如果数据库中不存在表,则自动创建。如果表已存在,则不做任何操作。WriteMode
:由WriteToDatabase
方法使用,以决定更新什么。(保留供未来实现,尚不可用)
将一个类映射到名为 *Employee* 的数据库表。使用 SQLName
属性来覆盖默认表名。
public class Employee { ... } [SQLName(<span class="pl-s">"Employee")</span>] public class Emp { ... }
所有具有公共 getter 和 setter 的公共属性都被视为 SQL 列。默认情况下,这些属性的名称用作相应列的名称。SQLName
属性可用于覆盖默认列名或表名。
public class Employee { //Database Column: Name = 'Name', Type = TEXT public string Name {get; set;} //Database Column: Name = 'Department', Type = TEXT [SQLName(<span class="pl-s">"Department")</span>] public string Dept {get; set;} //Database Column: Name = 'Salary', Type = INTEGER public int Salary {get; set;} //Database Column: Name = 'Cost', Type = NUMERIC public double Cost {get; set;} //Read only property is not a valid SQL Column public int Age {get;} }
下表显示了对象和数据库之间的默认数据类型映射。确保数据类型匹配对于准确地写入和读取数据至关重要。注意:SQLite 可能会自动将值转换为适当的数据类型。更多详情请参阅 SQLite 文档 类型亲和性。
对象类型 | 数据库类型 |
---|---|
string, Enum, DateTime | TEXT |
int, long, bool | INTEGER |
double, decimal, float | REAL |
SQLDataType
属性可用于显式定义数据库中值的存储类型。例如,可以通过应用 SQLDataType
属性将 Enum
和 DateTime
类型存储为整数,其中 Enum Status
存储为整数,DateTime
存储为刻度(long)。
public enum Status { ... } public class MyTable { [SQLDataType(DataType.INTEGER)] public Status CurrentStatus {get; set;} [SQLDataType(DataType.INTEGER)] public DateTime LastUpdate {get; set;} }
下面的示例表明,UserName
存储在 Employee
表的 NameID
列中作为索引,而实际的字符串值保存在名为 Name
的键值对表中。这种方法有助于高效地检索和管理数据,特别是在多个表中使用相同名称的情况下。
SQLIndexTable
的表名参数是可选的。如果留空,则属性名 UserName
将被用作表名。索引表的值可以被多个表共享。
public class Employee { [SQLIndexTable(<span class="pl-s">"Name")</span>] [SQLName(<span class="pl-s">"NameID")</span>] public string UserName {get; set;} }
Employee (Table) |- NameID, INTEGER | ... Name (Table) |- ID, INTEGER, Primary Key |- Name, TEXT, Unique
主键属性与数据库表中的主键相关联。当执行 WriteToDatabase
方法处理 ID 为 0 的项时,它将在数据库表中创建一个新条目并为其分配一个唯一 ID。如果 ID 不为 0,它将更新具有匹配 ID 的现有行。注意: 主键必须声明为 int
类型。
public class Employee { [PrimaryKey] public int ID {get; set;} ... }
让我们来看下面的示例:在数据库中,Department
(表名:Department)作为父表,List<Employee>
(表名:Employees)作为子表,具有一对多关系,即每个部门可以关联多个员工。换句话说,Department
表中的每个条目都可以对应 Employee
表中的多个条目,每个条目代表属于该部门的一个单独的员工,而每个 Employee
只分配给一个 Department
。
子表必须有一个声明了 ParentKey
属性的 ID 属性,它负责映射子表和父表。下面示例中的 DepartmentID
的值由 SQLite Helper 分配。Department
类的 PrimaryKey
是强制性的,而 Employee
类的 PrimaryKey
是可选的,具体取决于设计需求。
子表必须有一个 ID 属性,用 ParentKey
属性进行装饰,它作为子表和父表之间的链接。在下面的示例中,父键值 DepartmentID
由 SQLite Helper 分配。
public class Department { [PrimaryKey] public int ID { get; set; } public string Name { get; set; } public List<Employee> Employees { get; set; } = new List<Employee>(); ... } public class Employee { public string Name { get; set; } [ParentKey(typeof(Department))] public int DepartmentID { get; set; } ... }
对应的数据库表如下所示:
Department (Table) |- ID, INTEGER, Primary Key |- Name, TEXT Employee (Table) |- Name, TEXT |- DepartmentID, INTEGER
SQLite Helper 还支持多数据库源,允许从存储在不同 SQLite 数据库文件中的表中读取和写入数据。下面的示例显示 Department
表存储在主数据库中,而 Employee
表存储在 **Employee.db** 中。主数据库和子数据库之间的切换由读写方法内部处理。此 SQLDatabase
属性只能与子表一起使用。
public class Department { ... [SQLName(<span class="pl-s">"Employee")</span>] [SQLDatabase(<span class="pl-s">"Employee.db")</span>] public List<Employee> Employees { get; set; } = new List<Employee>(); }
数组表功能允许将示例表 TableWithArray
中的数组属性存储到单独的 SQLite 表中。此过程涉及为每种类型的数组属性创建特定的 SQLite 表,从而实现数组数据的有效存储和检索。以下示例演示了如何将数组属性映射到 SQLite 表。
public class TableWithArray { [PrimaryKey] public int ID { get; set; } ... public string[] ArrayData { get; set; } [SQLName(<span class="pl-s">"ArrayIntValue")</span>] public int[] ItemValue { get; set; } }
ArrayData
是一个字符串数组,映射到具有 TEXT 值的 ArrayTable。
ArrayData (Table) |- ID, INTEGER |- Value, TEXT
ItemValue
是一个整数数组,使用 SQLName 属性映射到 SQLite 表 ArrayIntValue
。
ArrayIntValue (Table) |- ID, INTEGER |- Value, INTEGER
SQLUnique
和 SQLUniqueMultiColumn
属性用于标记具有唯一约束的列。SQLUnique
对单列设置唯一约束,而 SQLUniqueMultiColumn
对多列设置唯一约束。
这些属性的示例如下:
public class User { [PrimaryKey] public int ID { get; set; } [SQLUnique] public string Email { get; set; } public string FirstName { get; set; } public string LastName { get; set; } [SQLUniqueMultiColumn] public string Username { get; set; } [SQLUniqueMultiColumn] public string PhoneNumber { get; set; } }
SQL 表结构
User (Table) |- ID, INTEGER, Primary Key |- Email, TEXT, Unique |- FirstName, TEXT |- LastName, TEXT |- Username, TEXT |- PhoneNumber, TEXT (Unique Username, PhoneNumber)
SQL 架构
CREATE TABLE User ( Id INTEGER PRIMARY KEY AUTOINCREMENT, Email TEXT UNIQUE, FirstName TEXT, LastName TEXT, Username TEXT, PhoneNumber TEXT, UNIQUE (Username, PhoneNumber) );
在此示例中:
Email
列被标记为 SQLUnique
属性,确保每个电子邮件地址都是唯一的。Username
和 PhoneNumber
列被标记为 SQLUniqueMultiColumn
属性,确保 Username
和 PhoneNumber
的组合在表中是唯一的。
我们希望本文档为您提供了清晰有用的信息来使用此工具。您的反馈对我们至关重要,因为它有助于提高我们工作质量和文档清晰度。请分享您的建议、评论或在使用本指南时遇到的任何困难。您的输入将帮助我们更好地改进我们的资源并更有效地支持像您一样的用户。感谢您的关注和贡献。