65.9K
CodeProject 正在变化。 阅读更多。
Home

SQLite Helper:SQLite数据库的微型ORM

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.97/5 (11投票s)

2024年10月5日

MIT

9分钟阅读

viewsIcon

41412

downloadIcon

231

本项目展示了SQLiteHelper如何简化数据库交互,让开发人员能够更多地专注于应用程序的逻辑,而不是SQL查询编写的复杂性。

SQLite Helper:SQLite数据库的微型ORM

NuGet Version NuGet Downloads

引言

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 之间的区别以及如何选择。

依赖

SQLite Helper 的主要功能

SQLite Helper 提供了一系列旨在让您与 SQLite 数据库的交互尽可能顺畅的功能。

  1. 管理连接字符串: SQLiteHelper 仅需数据库文件路径即可建立连接,大大简化了此过程。
  2. 自动打开和关闭连接: SQLiteHelper 使用一个辅助类来管理数据库连接,无需手动跟踪连接状态,并确保写入操作后正确释放连接。
  3. 对象映射到数据库类: SQLiteHelper 通过直接将对象映射到数据库类,简化了数据库的单方法读写操作,从而简化了数据操作任务。
  4. 处理来自不同数据库源的查询: SQLiteHelper 能够处理来自各种数据库源的查询,提供了管理多个数据库所需的灵活性。
  5. 实用工具函数: SQLiteHelper 包含诸如 ClearTableGetPrimaryKeysGetTableSchema 等实用方法,所有这些方法都设计有防 SQL 注入的保护措施——这是新手常见的疏忽。

总之,SQLiteHelper 是处理 SQLite 数据库的开发者的必备工具。它简化了数据库交互,使开发人员能够将更多精力集中在应用程序逻辑上,而不是 SQL 查询的复杂性。SQLiteHelper 有效地提高了中小型项目的开发效率。

SQLiteHelper 的结构

  • SQLiteHelper (抽象):包的主类。它包含了数据库读写操作所需的所有方法。
  • SQLiteDatabaseHandler (抽象):这是从 SQLiteHelper 派生的子类。它继承了 SQLiteHelper 类所有功能,并且还能够切换远程和本地数据库,以及从远程源同步数据到本地缓存副本。
  • SQLiteDataReaderEx (扩展)SQLiteDataReader 的扩展类,处理 get value 方法的 null 检查。
  • SQLAttribute:用于表映射的属性基类。

使用 SQLiteHelper

作为一个辅助类,SQLiteHelper 中的大多数方法都被指定为受保护的,因为不期望派生类的用户直接与数据库层交互。所有与数据库相关的操作都应该对用户在 API 和应用程序级别都隐藏。

创建 SQLite 数据库类

创建继承自 SQLiteHelper 类的项目特定数据库类。SetSQLPath 具有可选的 readOnly 参数,当设置为 true 时,将以只读模式打开数据库文件。

public class MyDatabase : SQLiteHelper
{
    public MyDatabase(string databaseFilePath): base()
    {
        SetSQLPath(databaseFilePath);
    }
}

处理忙碌连接

正确处理重试和超时对于确保事务成功完成且不影响用户体验至关重要。参数 SQLStepRetriesSQLBusyTimeout 是定义重试忙碌(锁定数据库)连接的迭代次数和延迟的两个重要参数。

基本查询功能

在 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;}
}
  1. 传统方式,使用 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
        }
    });
}
  1. 上述实现可以使用带类的查询 - ReadFromDatabase - 进一步简化。
public Employee[] ReadEmployeeData()
{
    return ReadFromDatabase<Employee>().ToArray();
}

将数据写入数据库

要更新或写入新数据到数据库,您可以使用 WriteToDatabase 方法。虽然对于简单的表结构,使用 ExecuteNonQuery 方法也可以执行相同的操作,但 WriteToDatabase 方法能够处理更复杂的数据库结构,这将在下一节中介绍。

public void WriteEmployeeData(Employee[] newDatas)
{
    WriteToDatabase(newDatas);
}

读写复杂表(ORM)

ReadFromDatabaseWriteToDatabase 方法可以轻松地将代码中的对象与数据库中的表关联起来。它们与具有关系(子表)的表配合良好,并能通过简单的命令处理多个数据库。让我们仔细看看它们的功能。

这些方法遵循“快速失败”原则,这意味着它们会在您第一次使用时快速检查您的对象结构是否与您的数据库表结构匹配。此检查是为了确保所有列都匹配。为避免与旧版本出现问题,您的数据库表可以有对象中不存在的额外列,但反之则不然。

SQLite 写入选项

“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, DateTimeTEXT
int, long, boolINTEGER
double, decimal, floatREAL

SQLDataType 属性可用于显式定义数据库中值的存储类型。例如,可以通过应用 SQLDataType 属性将 EnumDateTime 类型存储为整数,其中 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

唯一约束

SQLUniqueSQLUniqueMultiColumn 属性用于标记具有唯一约束的列。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 属性,确保每个电子邮件地址都是唯一的。UsernamePhoneNumber 列被标记为 SQLUniqueMultiColumn 属性,确保 UsernamePhoneNumber 的组合在表中是唯一的。

建议与反馈

我们希望本文档为您提供了清晰有用的信息来使用此工具。您的反馈对我们至关重要,因为它有助于提高我们工作质量和文档清晰度。请分享您的建议、评论或在使用本指南时遇到的任何困难。您的输入将帮助我们更好地改进我们的资源并更有效地支持像您一样的用户。感谢您的关注和贡献。

© . All rights reserved.