.NET SQLite


1. 项目集成与配置

要在 .NET 项目中通过代码操作 SQLite,核心是安装 NuGet 包。

  • 核心驱动: Microsoft.Data.Sqlite。这是所有操作的基础。
  • ORM 支持: 若使用 Entity Framework Core,则安装 Microsoft.EntityFrameworkCore.Sqlite 即可。
  • 安装方式 :
    • Visual Studio: 右键项目 -> "管理 NuGet 程序包" -> 搜索并安装。
    • .NET CLI: 运行 dotnet add package Microsoft.Data.Sqlite。

2. 基础操作:数据库与表

这是所有数据操作的起点。

2.1 数据库的创建与删除

  • 数据库创建: SQLite 通过代码隐式创建数据库。当您尝试连接一个不存在的文件时,该文件(即数据库)会被自动创建。
C# 复制代码
  using Microsoft.Data.Sqlite;  
  // 如果文件不存在,Open() 方法会自动创建它  
  using (var connection = new SqliteConnection("Data Source=MyDatabase.db"))  
  {  
      connection.Open();  
  }
  • 数据库删除: 删除数据库即删除其对应的文件,使用标准的 .NET 文件操作即可。
C# 复制代码
 using System.IO;  
  using Microsoft.Data.Sqlite;

  string dbFile = "MyDatabase.db";  
  // 确保没有活动的连接,否则文件会被占用  
  SqliteConnection.ClearAllPools();   
  if (File.Exists(dbFile))  
  {  
      File.Delete(dbFile);  
  }

2.2 表的创建与字段类型说明

通过执行 CREATE TABLE 语句来创建表。

C# 复制代码
using (var connection = new SqliteConnection("Data Source=Company.db"))  
{  
    connection.Open();  
    using (var cmd = connection.CreateCommand())  
    {  
        cmd.CommandText = @"  
            CREATE TABLE Employees (  
                EmployeeID   INTEGER PRIMARY KEY AUTOINCREMENT, -- 整数,主键并自增  
                FirstName    TEXT NOT NULL,                     -- 文本  
                Salary       REAL,                              -- 浮点数  
                HireDate     TEXT,                              -- 日期(存为文本)  
                ProfileImage BLOB,                              -- 二进制数据  
                IsActive     INTEGER DEFAULT 1                  -- 布尔值 (0/1)  
            );  
        ";  
        cmd.ExecuteNonQuery();  
    }  
}

2.3 C# 与 SQLite 数据类型映射

您会看到一个 SQLite 类型对应多个 C# 类型,其根本原因在于两者设计哲学的不同。SQLite 使用的是一种更灵活的"类型亲和性"系统,列会倾向于存储某种类型的数据,但并不严格强制。而 C# 是强类型的,每个变量都必须有一个明确的、不可改变的类型。

因此,当 C# 的 Microsoft.Data.Sqlite 驱动从 SQLite 读取数据时,它提供了多种"转换"的可能性。但是,作为开发者,选择最恰当、最安全的数据类型来接收,是保证程序健壮性的关键。 下表将为您提供详细的映射关系和选型建议

SQLite 类型 存储内容 推荐的 .NET/C# 类型 备注
INTEGER 64位有符号整数 long, int, bool 读首选 long: SQLite 的 INTEGER 存储空间是64位的,与 C# 的 long 完全对应。使用 reader.GetInt64() 是最安全、无损的选择。 谨慎使用 int: 只有当您 100% 确定数据库中的值不会超过 int 的最大值 (约21亿) 时,才应使用 reader.GetInt32()。否则,读取一个大数值会抛出 OverflowException。 用于布尔值: 这是一个常见模式 (0=false, 1=true)。可以直接使用 reader.GetBoolean(),它会自动处理转换。
REAL 64位浮点数 double 首选 double: REAL 与 C# 的 double 都是64位浮点数,这是最直接、无损的映射。请使用 reader.GetDouble()。 谨慎使用 float: float 是32位浮点数。使用 reader.GetFloat() 读取 REAL 值可能会导致精度丢失。仅在精度要求不高时使用。 避免直接映射到 decimal: decimal 是高精度的十进制类型,主要用于金融计算。SqliteDataReader 没有 GetDecimal() 方法。如果您需要 decimal 的精度,最佳实践是在 SQLite 中将数据存为 TEXT (如 "199.99") 或缩放后的 INTEGER (如 19999),在 C# 中读取后再解析为 decimal。
TEXT 文本字符串 string, DateTime, Guid 首选 string: 使用 reader.GetString() 是最标准的操作。 用于日期/时间: 推荐在 SQLite 中以 YYYY-MM-DD HH:MM:SS (ISO 8601) 格式存储。在 C# 中先用 GetString() 读取,再用 DateTime.Parse() 或 DateTime.ParseExact() 进行转换,这样可以避免时区和格式问题。 用于 Guid: 同样,先读取为字符串,再用 Guid.Parse() 转换。
BLOB 二进制数据 byte\[\] 这是唯一的映射方式,用于存储图片、文件等二进制内容。使用 reader.GetBytes() 读取。
NULL 空值 null (关键避坑点) NULL 不是一个类型,而是一个值。在调用任何 Get...() 方法(如 GetString, GetInt32)之前,必须先使用 reader.IsDBNull(index) 进行检查。直接对 NULL 值调用这些方法会抛出 InvalidCastException。

3. 核心数据操作 (CRUD)

以下是针对 Employees 表的完整、独立的增删改查操作示例。

3.1 增 (Create - INSERT)

C# 复制代码
using (var connection = new SqliteConnection("Data Source=Company.db"))  
{  
    connection.Open();  
    using (var cmd = connection.CreateCommand())  
    {  
        cmd.CommandText = "INSERT INTO Employees (FirstName, Salary) VALUES (@FirstName, @Salary)";  
        cmd.Parameters.AddWithValue("@FirstName", "张三");  
        cmd.Parameters.AddWithValue("@Salary", 8000.50);  
          
        int rowsInserted = cmd.ExecuteNonQuery(); // 返回受影响的行数 (通常为1)  
        Console.WriteLine($"成功插入了 {rowsInserted} 行数据。");  
    }  
}

3.2 查 (Read - SELECT)

C# 复制代码
using (var connection = new SqliteConnection("Data Source=Company.db"))  
{  
    connection.Open();  
    using (var cmd = connection.CreateCommand())  
    {  
        cmd.CommandText = "SELECT EmployeeID, FirstName, Salary FROM Employees";  
        using (var reader = cmd.ExecuteReader())  
        {  
            while (reader.Read())  
            {  
                var id = reader.GetInt32(0);  
                var name = reader.GetString(1);  
                var salary = reader.GetDouble(2);  
                Console.WriteLine($"ID: {id}, 姓名: {name}, 薪水: {salary}");  
            }  
        }  
    }  
}

3.3 改 (Update - UPDATE)

C# 复制代码
using (var connection = new SqliteConnection("Data Source=Company.db"))  
{  
    connection.Open();  
    using (var cmd = connection.CreateCommand())  
    {  
        cmd.CommandText = "UPDATE Employees SET Salary = @NewSalary WHERE FirstName = @Name";  
        cmd.Parameters.AddWithValue("@NewSalary", 8500.00);  
        cmd.Parameters.AddWithValue("@Name", "张三");

        int rowsUpdated = cmd.ExecuteNonQuery(); // 返回被更新的行数  
        Console.WriteLine($"成功更新了 {rowsUpdated} 行数据。");  
    }  
}

3.4 删 (Delete - DELETE)

C# 复制代码
using (var connection = new SqliteConnection("Data Source=Company.db"))  
{  
    connection.Open();  
    using (var cmd = connection.CreateCommand())  
    {  
        cmd.CommandText = "DELETE FROM Employees WHERE FirstName = @Name";  
        cmd.Parameters.AddWithValue("@Name", "张三");

        int rowsDeleted = cmd.ExecuteNonQuery(); // 返回被删除的行数  
        Console.WriteLine($"成功删除了 {rowsDeleted} 行数据。");  
    }  
}

4. 进阶操作

以下示例将使用更复杂的"学生-课程"模型来演示。

4.1 多表关联查询 (JOIN)

  • 核心概念: 使用 JOIN ... ON ... 语法,根据关联字段将多张表的数据组合在一起。

  • 代码示例:

C# 复制代码
 // 此处省略了建表和插入初始数据的代码,假设已存在 Students, Courses, Enrollments 表  
  using (var connection = new SqliteConnection("Data Source=University.db"))  
  {  
      connection.Open();  
      Console.WriteLine("--- 查询所有选了课的学生及其课程 ---");  
      using (var cmd = connection.CreateCommand())  
      {  
          cmd.CommandText = @"  
              SELECT s.Name, c.CourseName  
              FROM Students s  
              JOIN Enrollments e ON s.StudentID = e.StudentID  
              JOIN Courses c ON e.CourseID = c.CourseID;";  
          using (var reader = cmd.ExecuteReader())  
          {  
              while (reader.Read())  
              {  
                  Console.WriteLine($"学生: {reader.GetString(0)}, 所选课程: {reader.GetString(1)}");  
              }  
          }  
      }  
  }
  • 易错点: ON 条件写错;列名不明确时忘记使用表别名前缀。

4.2 结果处理:去重与排序 (DISTINCT & ORDER BY)

  • 核心概念: DISTINCT 移除结果集中完全重复的行;ORDER BY 对结果集排序。

  • 代码示例:

C# 复制代码
 using (var connection = new SqliteConnection("Data Source=University.db"))  
  {  
      connection.Open();  
      Console.WriteLine("\\n--- 查询所有被选过的课程名称(去重并降序排列)---");  
      using (var cmd = connection.CreateCommand())  
      {  
          cmd.CommandText = @"  
              SELECT DISTINCT c.CourseName  
              FROM Courses c  
              JOIN Enrollments e ON c.CourseID = e.CourseID  
              ORDER BY c.CourseName DESC;";  
          using (var reader = cmd.ExecuteReader())  
          {  
              while (reader.Read()) { /\* ... \*/ }  
          }  
      }  
  }
  • 易错点: ORDER BY 必须位于 WHERE/JOIN 之后;多列排序时忘记用逗号分隔。

4.3 事务控制 (Transaction)

  • 核心概念: 将多个操作打包,保证它们"要么全部成功,要么全部失败"。

  • 代码示例:

C# 复制代码
 using (var connection = new SqliteConnection("Data Source=University.db"))  
  {  
      connection.Open();  
      using (var transaction = connection.BeginTransaction())  
      {  
          try  
          {  
              // 操作1  
              using (var cmd1 = connection.CreateCommand())  
              {  
                  cmd1.Transaction = transaction; // 关键:关联事务  
                  cmd1.CommandText = "INSERT INTO Enrollments (StudentID, CourseID) VALUES (3, 2);";  
                  cmd1.ExecuteNonQuery();  
              }

              // 操作2  
              using (var cmd2 = connection.CreateCommand())  
              {  
                  cmd2.Transaction = transaction; // 关键:关联事务  
                  cmd2.CommandText = "DELETE FROM Enrollments WHERE StudentID = 2 AND CourseID = 1;";  
                  cmd2.ExecuteNonQuery();  
              }

              transaction.Commit(); // 全部成功,提交  
              Console.WriteLine("事务成功提交。");  
          }  
          catch (Exception ex)  
          {  
              transaction.Rollback(); // 发生任何错误,回滚  
              Console.WriteLine($"事务回滚: {ex.Message}");  
          }  
      }  
  }
  • 易错点 : 必须为事务中的每个命令设置其 .Transaction 属性,否则该命令无法回滚。

5. 语法对比:SQLite vs. Oracle (含示例)

功能分类 SQLite 示例 Oracle 示例
判空处理 SELECT IFNULL(Major, 'Undeclared') FROM Students; SELECT NVL(Major, 'Undeclared') FROM Students;
获取当前日期 SELECT DATE('now'); SELECT TRUNC(SYSDATE) FROM DUAL;
日期计算 SELECT DATE('now', '+1 month', '-5 day'); SELECT ADD_MONTHS(SYSDATE, 1) - 5 FROM DUAL;
日期格式化 SELECT STRFTIME('%Y年%m月%d日', 'now'); SELECT TO_CHAR(SYSDATE, 'YYYY"年"MM"月"DD"日"') FROM DUAL;
字符串聚合 SELECT GROUP_CONCAT(Name, ', ') FROM Students; SELECT LISTAGG(Name, ', ') WITHIN GROUP (ORDER BY Name) FROM Students;

6. 常见编程异常与避坑指南

异常信息 发生原因 解决方案 / 规避策略
SQLite Error 1: 'no such table: ...' 表名错误,或连接到了错误的数据库文件。 检查拼写;调试时使用绝对路径;确保建表逻辑先于查询逻辑执行。
SQLite Error 5: 'database is locked' (最常见) DataReader 未关闭;其他进程(如DB Browser)锁定了文件。 将 DataReader 和 Connection 放入 using 块中;保持事务简短;检查并关闭其他锁定文件的程序。
SQLite Error 19: 'UNIQUE constraint failed...' 插入了违反 UNIQUE 或 PRIMARY KEY 约束的重复数据。 在 INSERT 前先 SELECT 检查;使用 try-catch 捕获并处理冲突;或使用 INSERT OR IGNORE 等SQLite特定语法。
System.InvalidCastException: 'Unable to cast object of type 'DBNull' to type ...' (新手高频错误) 调用 reader.GetString() 等强类型方法时,数据库中对应的值恰好是 NULL。 最佳实践 : 在读取任何可能为 NULL 的列之前,必须先使用 reader.IsDBNull(index) 进行判断
'Connection must be open.' 在未打开或已关闭的连接上执行命令。 在 using 块内,确保在执行命令前调用 connection.Open()。
'Cannot access a disposed object...' 在 using 块结束后,仍有代码尝试使用已释放的连接。 确保所有数据库操作都在 using 块内完成。如果方法需返回查询结果,请先用 .ToList() 将其物化为列表再返回。

7. 官方及社区资源链接

相关推荐
Code额1 小时前
Python 连接 DeepSeek API,OpenAI 对话方式总结
后端·python·ai·ai编程
星火10241 小时前
【LangChain4j系列10】Guardrails 安全护栏
人工智能·后端
四千岁1 小时前
稀疏向量BM25Retriever不支持中文怎么办?jieba来帮忙
前端·javascript·后端
用户6919026813391 小时前
Docker基本概念
后端·docker·容器
颜进强1 小时前
14 - OpenSpec 老页面改造骨架:定位 + 增量 + 回归三件套
前端·后端·ai编程
唐青枫1 小时前
别只把 switch 当成多路 if:Zig 模式匹配、状态机与 Tagged Union 实战
后端
步行cgn1 小时前
MyBatis 错误 Result Maps collection does not contain value for ... 详解与解决方案
后端
用户250694921611 小时前
Cordis 从入门到实战:插件卸载后,别留下一地鸡毛
后端
SomeB1oody1 小时前
【RustyML入门】5.3. 聚类指标
开发语言·后端·机器学习·rust·教程
IT_陈寒2 小时前
Vite打包给我挖的这个坑,差点搞崩我的项目
前端·人工智能·后端