目录

介绍

依赖

SQLite Helper的主要功能

SQLiteHelper剖析

创建SQLite数据库类

处理繁忙连接

基本查询函数

从数据库表中读取

将数据写入数据库

读取和写入复杂表(ORM)

SQLite写入选项

表名

列名称

数据类型

索引表

主键

父表和子表

多个数据库源

建议和反馈


介绍

SQLite Helper是一个微对象关系映射(ORM)工具,旨在促进使用SQLite数据库进行应用程序开发。它特别适合中小型应用程序,无需从头开始编写每个SQL查询。

相反,实体框架(EF)是一个全面的ORM,提供一整套功能。然而,更多的功能本身并不等同于优越性。在采用EF之前,权衡 EF的利弊是明智的。

虽然Entity Framework提供了一个具有广泛功能集的健壮的ORM解决方案,但SQLite Helper是为了简单性和方便性而定制的,通过简单的功能实现与SQLite数据库的简化交互。

亚历克斯·沙波瓦洛夫(Alex Shapovalov)撰写的一篇文章Micro ORM与ORM详细解释了Micro ORMORM之间的区别以及如何在它们中进行选择。

依赖

SQLite Helper的主要功能

SQLite Helper带有一组功能,旨在使您与SQLite数据库的交互尽可能顺畅:

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

总之,对于使用SQLite数据库的开发人员来说,SQLiteHelper是一个必不可少的工具。它简化了数据库交互,使开发人员能够更专注于应用程序的逻辑,而不是SQL查询编写的复杂性。SQLiteHelper可有效改善中小型项目的开发过程。

SQLiteHelper剖析

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

使用SQLiteHelper

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

创建SQLite数据库类

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

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

处理繁忙连接

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

基本查询函数

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方法的用法。连接闭合,SQLiteDataReader对象rExecuteQuery方法结束时被释放。

ExecuteQuery(query, (r)=>
{
    while(r.Read())
    {
        //ToDo: Perform readback here...
    }
});

从数据库表中读取

SQLite Helper 提供了2种解决方案,用于将 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
        }
    });
}

2、上面的实现可以进一步简化使用带有类——ReadFromDatabase的查询

public Employee[] ReadEmployeeData()
{
    return ReadFromDatabase<Employee>().ToArray();
}

将数据写入数据库

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

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

读取和写入复杂表(ORM

使用ReadFromDatabase WriteToDatabase 方法可以轻松地将代码中的对象链接到数据库中的表。它们适用于具有关系的表(子表),并且可以使用简单的命令处理多个数据库。让我们仔细看看他们能做什么。

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

SQLite写入选项

SQLiteWriteOption类指定的'WriteOptions'属性使用以下选项设置SQLiteHelper在读取和写入数据时的行为方式:

  • CreateTable:设置为true时,如果不存在,则自动在数据库中创建表。如果表已存在,则不执行任何操作。
  • WriteMode:由WriteToDatabase方法使用来决定要更新的内容。(保留供将来实施,尚不可用)

表名

将类映射到名为 Employee 的数据库表。使用SQLName属性覆盖默认表名。

public class Employee { ... }

[SQLName(<span class="pl-s">"Employee")</span>]
public class Emp { ... }

列名称

所有具有公共gettersetter的公共属性都被视为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文档中的更多详细信息类型关联

对象类型

数据库类型

stringEnumDatetime

TEXT

intlongbool

INTEGER

doubledecimalfloat

REAL

SQLDataType属性可用于显式定义数据库中值的存储类型。例如,可以通过应用SQLDataType属性将EnumDateTime类型存储为整数,而枚举Status可以存储为整数,并且DateTime可以存储为tickslong)。

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

主键

主键属性链接到数据库表中的主键。当使用ID0的项执行该WriteToDatabase方法时,它将在数据库表中创建一个新条目并为其分配一个唯一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,该属性ID充当子表和父表之间的映射。以下示例中的DepartmentID值由SQLite Helper分配。类DepartmentPrimaryKey是强制性的,而类EmployeePrimaryKey是可选的,这取决于设计的需要。

子表必须具有ID属性,该属性用ParentKey属性修饰,用作子表和父表之间的链接。在下面的示例中,父键值DepartmentIDSQLite 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>();
}

建议和反馈

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

本文最初发布于 GitHub - Code-Artist/CodeArtEng.SQLite: SQLIte Helper

https://www.codeproject.com/Articles/5383889/SQLite-Helper-A-Micro-ORM-for-SQLite-Database

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐