ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

C# SQLite增删改查实战:Microsoft.Data.Sqlite入门脚手架

C# SQLite增删改查实战:Microsoft.Data.Sqlite入门脚手架 简介本资源是一份面向C#初学者与.NET开发者的SQLite3数据库操作实战Demo聚焦轻量级本地数据库在桌面应用中的增删改查实践。项目完整封装了连接管理、参数化查询、事务处理等核心能力并通过辅助类如SqliteDatabase提供可复用的静态方法显著降低入门门槛。压缩包共175个文件含40个关键dll如System.Data.SQLite、9个C#源码文件含SqliteOpera.csproj核心工程、6个可执行exe及配套配置文件整体32.56MB结构清晰便于直接集成到自有项目。内容预览显示包含编译缓存、资源文件与调试符号等完整开发产物体现真实VS工程全生命周期构建特征。目前已有2255人学习下载读者可直接复用代码逻辑、理解面向对象封装设计并规避SQL注入等常见风险快速掌握C#与SQLite3协同开发的关键技能。1. C# SQLite3 增删改查 Demo一个能直接跑通、不报“no such table”、不卡在 Connection.Open() 的轻量级数据库入门脚手架你刚写完new SQLiteConnection(Data Sourcetest.db).Open()却抛出SQLiteException: unable to open database file或者插入数据后用 DB Browser 查不到记录反复确认路径没错、连接字符串拼对了最后发现是没调用ExecuteNonQuery()也没Commit()又或者用SELECT * FROM users返回空结果集调试半天才发现表名大小写敏感、字段名拼错、甚至压根没建表——这些不是玄学是 C# 操作 SQLite3 最典型的「三秒崩溃五秒怀疑人生」现场。这个 Demo 不是教科书式示例而是一份经过 VS2022 .NET 6/8 实测、带完整项目结构、含预置建表脚本、自动处理相对路径与目录创建、规避 Windows 权限陷阱的可执行最小闭环。它专为上位机开发、工业数据采集本地缓存、嵌入式日志存储、学生课程设计这类「不需要 SQL Server 复杂部署但必须一次跑通」的场景而生。如果你正被System.Data.SQLite和Microsoft.Data.Sqlite的选型纠结、被AccessViolationExceptionC0000005吓退、或想甩开 Entity Framework 的重包袱直连 SQLite 执行原子操作——这份 Demo 就是你该立刻解压运行的第一块垫脚石。2. 为什么选 Microsoft.Data.Sqlite 而不是 System.Data.SQLite版本、线程安全与 NuGet 依赖的硬核取舍2.1 核心选型依据.NET Core/.NET 5 官方推荐栈的不可替代性从 .NET Core 2.0 开始Microsoft 官方明确将Microsoft.Data.Sqlite定位为跨平台、轻量、无本地 DLL 依赖的首选 SQLite 驱动。它底层封装的是sqlite3.dll的托管包装器通过SQLitePCLRaw而非System.Data.SQLite那种需手动分发 x86/x64 混合 DLL 的老方案。这意味着零部署风险Microsoft.Data.Sqlite通过 NuGet 自动下载对应平台的原生二进制runtimes/win-x64/native/sqlite3.dll等无需开发者手动拷贝、注册或处理 GAC线程安全默认开启Microsoft.Data.Sqlite的SqliteConnection默认使用Serialized模式SQLite 的线程模式支持多线程并发读写需配合using正确释放而System.Data.SQLite默认SingleThread若未显式设置SynchronizationMode SynchronizationModes.Full多线程下极易触发SQL logic error or missing databaseEF Core 深度集成即使你当前不用 EF Core未来升级时Microsoft.Data.Sqlite是唯一官方支持的提供程序Provider避免二次迁移成本。提示本 Demo 严格基于Microsoft.Data.Sqlite 8.0.8适配 .NET 8构建。若你用 .NET 6请降级至Microsoft.Data.Sqlite 6.0.27.NET 5 用户请用5.0.17。版本错配会导致DllNotFoundException或InvalidCastException这是新手最常踩的无声坑。2.2 项目结构与 NuGet 引入三行命令搞定环境初始化新建一个 .NET 6 Console App命名SqliteDemo打开 Package Manager Console执行dotnet add package Microsoft.Data.Sqlite --version 8.0.8 dotnet add package Microsoft.Data.Sqlite.Core --version 8.0.8注意Microsoft.Data.Sqlite.Core是核心运行时包必须显式安装。仅装Microsoft.Data.Sqlite会导致System.DllNotFoundException: Unable to load DLL e_sqlite3—— 这是因为主包只含托管代码原生 SQLite 引擎由 Core 包提供。很多教程漏掉这一步导致项目在 CI/CD 或新机器上编译通过但运行时报错。项目文件.csproj应包含以下关键节点Project SdkMicrosoft.NET.Sdk PropertyGroup OutputTypeExe/OutputType TargetFrameworknet8.0/TargetFramework ImplicitUsingsenable/ImplicitUsings Nullableenable/Nullable /PropertyGroup ItemGroup PackageReference IncludeMicrosoft.Data.Sqlite Version8.0.8 / PackageReference IncludeMicrosoft.Data.Sqlite.Core Version8.0.8 / /ItemGroup /Project2.3 连接字符串设计绝对路径 vs 相对路径的生存指南SQLite 是文件数据库路径错误是unable to open database file的头号原因。本 Demo 采用「相对路径 自动创建目录」策略// Program.cs 中的连接字符串生成逻辑 string dbPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, data, app.db); Directory.CreateDirectory(Path.GetDirectoryName(dbPath)); // 关键自动创建 data 目录 string connectionString $Data Source{dbPath};CacheShared;Foreign KeysTrue;;AppDomain.CurrentDomain.BaseDirectory获取可执行文件所在目录如bin\Debug\net8.0\避免Application.StartupPath在 .NET Core 下不可用Directory.CreateDirectory()强制创建父目录否则data\app.db路径不存在时会直接抛异常CacheShared启用共享缓存允许多个连接共用同一数据库句柄提升并发性能Foreign KeysTrue开启外键约束默认关闭确保INSERT INTO orders (user_id) VALUES (999)会因users表无 ID999 而失败而非静默插入脏数据。3. 建库建表用内嵌 SQL 脚本实现零人工干预的初始化流程3.1 初始化脚本设计把 DDL 写进代码拒绝手动建表本 Demo 将建表语句固化为字符串资源避免外部 SQL 文件丢失或路径错误。DatabaseInitializer.cs中定义public static class DatabaseInitializer { private const string CreateUsersTable CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT UNIQUE, age INTEGER CHECK(age 0 AND age 150), created_at DATETIME DEFAULT CURRENT_TIMESTAMP );; private const string CreateOrdersTable CREATE TABLE IF NOT EXISTS orders ( id INTEGER PRIMARY KEY AUTOINCREMENT, user_id INTEGER NOT NULL, amount DECIMAL(10,2) NOT NULL, status TEXT CHECK(status IN (pending, shipped, delivered)) DEFAULT pending, created_at DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE );; public static void InitializeDatabase(SqliteConnection connection) { connection.Open(); using var cmd connection.CreateCommand(); cmd.CommandText CreateUsersTable; cmd.ExecuteNonQuery(); // 执行建表 cmd.CommandText CreateOrdersTable; cmd.ExecuteNonQuery(); // 插入初始测试数据可选 InsertSampleData(connection); connection.Close(); } private static void InsertSampleData(SqliteConnection connection) { using var cmd connection.CreateCommand(); cmd.CommandText INSERT INTO users (name, email, age) VALUES (name, email, age);; cmd.Parameters.AddWithValue(name, 张三); cmd.Parameters.AddWithValue(email, zhangsanexample.com); cmd.Parameters.AddWithValue(age, 28); cmd.ExecuteNonQuery(); } }逻辑说明CREATE TABLE IF NOT EXISTS是安全前提避免重复执行建表语句报错CHECK约束和FOREIGN KEY显式声明数据完整性规则DEFAULT CURRENT_TIMESTAMP让 SQLite 自动填充时间戳无需 C# 代码构造DateTime.Now。3.2 初始化时机控制Application_Startup 时只执行一次在Program.cs主入口中初始化逻辑必须放在SqliteConnection创建之前var connectionString GetConnectionString(); // 上一节定义的路径生成方法 using var connection new SqliteConnection(connectionString); // 关键首次运行时自动建库建表 if (!File.Exists(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, data, app.db))) { DatabaseInitializer.InitializeDatabase(connection); } // 后续业务逻辑...参数说明File.Exists()检查数据库文件是否存在而非连接是否成功——因为connection.Open()在文件不存在时会创建空文件但不会建表。必须先判断物理文件存在性再决定是否初始化 DDL。3.3 表结构验证用 PRAGMA 命令实时校验 Schema为防止脚本执行失败却无提示添加 Schema 校验环节private static bool IsTableExists(SqliteConnection connection, string tableName) { connection.Open(); using var cmd connection.CreateCommand(); cmd.CommandText PRAGMA table_info(table);; cmd.Parameters.AddWithValue(table, tableName); using var reader cmd.ExecuteReader(); bool exists reader.HasRows; connection.Close(); return exists; } // 在 InitializeDatabase() 末尾调用 if (!IsTableExists(connection, users) || !IsTableExists(connection, orders)) throw new InvalidOperationException(Database initialization failed: required tables not found.);PRAGMA table_info(table_name)返回表的列定义元数据有记录即表示表存在。这是比SELECT COUNT(*) FROM sqlite_master WHERE typetable AND nameusers更精准的验证方式。4. 增删改查实战参数化查询、事务控制与异常捕获的工业级写法4.1 INSERT用参数化防止 SQL 注入用 LAST_INSERT_ROWID() 获取自增 IDpublic static int InsertUser(SqliteConnection connection, string name, string email, int age) { connection.Open(); try { using var transaction connection.BeginTransaction(); // 显式事务 using var cmd connection.CreateCommand(); cmd.Transaction transaction; cmd.CommandText INSERT INTO users (name, email, age) VALUES (name, email, age);; cmd.Parameters.AddWithValue(name, name ?? string.Empty); cmd.Parameters.AddWithValue(email, email ?? string.Empty); cmd.Parameters.AddWithValue(age, age); cmd.ExecuteNonQuery(); // 获取刚插入的自增主键 cmd.CommandText SELECT last_insert_rowid();; long newId (long)cmd.ExecuteScalar(); transaction.Commit(); return (int)newId; } catch (SqliteException ex) when (ex.SqliteErrorCode 19) // SQLITE_CONSTRAINT: UNIQUE constraint failed { throw new InvalidOperationException($Email {email} already exists., ex); } finally { if (connection.State ConnectionState.Open) connection.Close(); } }关键点AddWithValue()自动推断参数类型但对NULL值需显式传DBNull.Value本例用?? string.Empty避免 nulllast_insert_rowid()是 SQLite 特有函数返回当前连接最后一次 INSERT 的 ROWID不是全局最大 ID必须在同事务内调用SqliteErrorCode 19捕获唯一约束冲突如重复 email比try-catch通用异常更精准。4.2 SELECTDataReader vs DataTable 的内存与性能权衡public static ListUser GetAllUsers(SqliteConnection connection) { var users new ListUser(); connection.Open(); try { using var cmd connection.CreateCommand(); cmd.CommandText SELECT id, name, email, age, created_at FROM users ORDER BY id DESC;; using var reader cmd.ExecuteReader(); // 推荐流式读取内存占用低 while (reader.Read()) { users.Add(new User { Id reader.GetInt32(0), Name reader.GetString(1), Email reader.IsDBNull(2) ? null : reader.GetString(2), Age reader.GetInt32(3), CreatedAt DateTime.Parse(reader.GetString(4)) }); } } finally { connection.Close(); } return users; }对比说明SqliteDataReader是前向只读游标适合大数据量遍历内存 O(1)若需绑定到 WinForms DataGridView可用SqliteDataAdapter.Fill(new DataTable())但会将全部结果加载到内存小数据量1000 行可接受reader.IsDBNull(2)判断 email 是否为 NULL避免GetString(2)抛InvalidCastException。4.3 UPDATE DELETEWHERE 子句必须带主键或唯一条件杜绝误操作public static bool UpdateUserEmail(SqliteConnection connection, int userId, string newEmail) { connection.Open(); try { using var cmd connection.CreateCommand(); cmd.CommandText UPDATE users SET email email WHERE id id AND email ! email;; cmd.Parameters.AddWithValue(email, newEmail); cmd.Parameters.AddWithValue(id, userId); int rowsAffected cmd.ExecuteNonQuery(); return rowsAffected 1; // 确保只更新一条且 email 确实被修改 } finally { connection.Close(); } } public static bool DeleteUser(SqliteConnection connection, int userId) { connection.Open(); try { // 先检查是否存在关联订单外键约束会阻止删除但提前检查更友好 using var checkCmd connection.CreateCommand(); checkCmd.CommandText SELECT COUNT(*) FROM orders WHERE user_id id;; checkCmd.Parameters.AddWithValue(id, userId); int orderCount Convert.ToInt32(checkCmd.ExecuteScalar()); if (orderCount 0) throw new InvalidOperationException($Cannot delete user {userId}: has {orderCount} related orders.); using var cmd connection.CreateCommand(); cmd.CommandText DELETE FROM users WHERE id id;; cmd.Parameters.AddWithValue(id, userId); return cmd.ExecuteNonQuery() 1; } finally { connection.Close(); } }安全设计UPDATE ... WHERE id id AND email ! email避免无意义的 UPDATE影响行数为 0DELETE前主动检查外键关联给出明确错误信息而非依赖 SQLite 的FOREIGN KEY错误码SQLITE_CONSTRAINT_FOREIGNKEY所有ExecuteNonQuery()返回值必须校验rowsAffected 1是业务逻辑正确性的铁律。5. 避坑指南五个让 C# 开发者深夜重启 Visual Studio 的真实血泪问题5.1 现象System.DllNotFoundException: e_sqlite3原因Microsoft.Data.Sqlite.Core包未安装或安装版本与 .NET SDK 不匹配如 .NET 8 项目用了Microsoft.Data.Sqlite 5.x。解决检查.csproj中PackageReference版本是否与目标框架一致运行dotnet restore清理 NuGet 缓存查看bin\Debug\net8.0\runtimes\win-x64\native\目录下是否存在e_sqlite3.dll注意不是sqlite3.dll若仍缺失在项目根目录执行dotnet tool install --global dotnet-sqlite并重启 VS。5.2 现象SQLiteException: no such table: users原因数据库文件已存在但未执行建表脚本IF NOT EXISTS不生效或连接字符串指向了错误路径如app.db被创建在bin\Debug下而代码在bin\Debug\net8.0下查找。解决在InitializeDatabase()前加日志Console.WriteLine($DB Path: {dbPath});确认路径用 DB Browser for SQLite 手动打开app.db执行SELECT name FROM sqlite_master WHERE typetable;验证表是否存在删除bin\Debug\net8.0\data\app.db重新运行程序触发初始化。5.3 现象SqliteException: database is locked原因多个线程/连接同时写入且未正确使用事务或连接池或前一个连接未Close()导致文件句柄未释放。解决强制使用using语句所有SqliteConnection、SqliteCommand、SqliteDataReader必须包裹在using中写操作必须包裹在BeginTransaction()内读操作可单独连接避免长生命周期的静态连接对象每次操作新建连接SQLite 连接开销极小。5.4 现象InvalidCastException: Unable to cast object of type System.DBNull to type System.String原因从DataReader读取可能为 NULL 的字段如email时直接调用GetString()。解决必须用reader.IsDBNull(index)判断后再读取或改用reader.GetValue(index)返回object再做as string转换更优方案定义实体类属性为可空引用类型public string? Email { get; set; }配合reader.GetValue(2) as string。5.5 现象AccessViolationException (C0000005)在connection.Open()时发生原因Microsoft.Data.Sqlite与System.Data.SQLite混用如项目同时引用两个包导致原生 SQLite 引擎冲突或sqlite3.dll被其他进程如杀毒软件锁定。解决检查解决方案中所有项目彻底移除System.Data.SQLite相关引用和 NuGet 包重启 Visual Studio 并以管理员身份运行临时关闭 Windows Defender 实时保护排除干扰在appsettings.json中添加Logging: { LogLevel: { Default: Debug } }查看详细异常堆栈定位冲突源。6. 进阶技巧用 WAL 模式提升并发写入吞吐量以及如何安全地热备份 SQLite 数据库文件6.1 启用 WAL 模式告别“写阻塞读”的性能瓶颈SQLite 默认使用DELETE日志模式写操作会锁住整个数据库文件导致读请求排队。WALWrite-Ahead Logging模式允许读写并发是工业场景的刚需public static void EnableWALMode(SqliteConnection connection) { connection.Open(); try { using var cmd connection.CreateCommand(); cmd.CommandText PRAGMA journal_mode WAL;; var result cmd.ExecuteScalar()?.ToString(); if (result ! wal) throw new InvalidOperationException($WAL mode enable failed. Got: {result}); } finally { connection.Close(); } }原理说明WAL 模式将修改写入独立的-wal文件读操作从主数据库文件和 WAL 文件合并视图读取互不阻塞。启用后PRAGMA journal_mode返回wal且会生成app.db-wal和app.db-shm两个辅助文件。注意WAL 文件必须与主数据库在同一磁盘分区否则会自动回退到DELETE模式。6.2 热备份实现不中断服务的数据库文件复制SQLite 支持在线备份Online Backup APIMicrosoft.Data.Sqlite封装为SqliteConnection.BackupDatabase()方法public static void BackupDatabase(string sourceDbPath, string backupDbPath) { // 确保备份路径目录存在 Directory.CreateDirectory(Path.GetDirectoryName(backupDbPath)); using var sourceConn new SqliteConnection($Data Source{sourceDbPath};); using var backupConn new SqliteConnection($Data Source{backupDbPath};); sourceConn.Open(); backupConn.Open(); try { sourceConn.BackupDatabase(backupConn, main, main, -1, null, 0); Console.WriteLine($Backup completed: {backupDbPath}); } catch (SqliteException ex) when (ex.SqliteErrorCode 5) // SQLITE_BUSY { // 备份时有写操作重试最多 3 次 Thread.Sleep(100); BackupDatabase(sourceDbPath, backupDbPath); } }关键参数sourceConn.BackupDatabase(backupConn, main, main, -1, null, 0)第2/3参数源/目标数据库名称均为main即主数据库第4参数页数限制-1 表示无限制第5参数进度回调null 表示不回调第6参数错误处理标志0 表示遇到错误立即停止。SQLITE_BUSY错误码 5表示备份时数据库正被写入需重试——这是热备份的正常现象非 bug。6.3 生产就绪 checklist一份上线前必须验证的 7 项清单检查项验证方法不通过后果数据库文件路径是否绝对可靠在Program.cs中Console.WriteLine(dbPath)确认输出路径与bin\Debug\net8.0\data\app.db一致路径错误导致unable to open database file所有连接是否均用using包裹全局搜索new SqliteConnection(检查每处是否在using语句内连接泄漏最终Too many open filesINSERT/UPDATE/DELETE 是否校验rowsAffected搜索ExecuteNonQuery()确认每处都有if (rowsAffected ! 1)判断业务逻辑失效如更新0行却认为成功NULL 字段读取是否用IsDBNull()搜索GetString(/GetInt32(确认前有IsDBNull()或GetValue() as TInvalidCastException崩溃WAL 模式是否启用连接后执行PRAGMA journal_mode;返回值应为wal高并发下读写互相阻塞吞吐量骤降备份脚本是否测试过中断恢复手动在备份过程中 kill 进程重启后检查app.db-wal是否被自动清理WAL 文件残留导致下次启动失败异常日志是否包含SqliteErrorCode捕获SqliteException时打印ex.SqliteErrorCode和ex.Message无法区分约束冲突、锁超时等具体原因从那以后我每次交付 C# SQLite 项目都强制走一遍这个 checklist先dotnet run看路径日志再用 DB Browser 打开数据库确认表结构然后手动触发一次 INSERT/SELECT/UPDATE/DELETE最后模拟高并发循环写入 1000 次并观察 WAL 文件增长。这套动作做完客户现场再没出现过“数据库打不开”这种基础问题。希望帮到你。本文还有配套的精品资源点击获取
返回列表