-
C#操作SQLite本地数据库——桌面应用数据存储
第一部分:C#基础入门
10.SQLite本地数据库——桌面应用数据存储
实例介绍
你有没有做过这样的桌面小工具?比如本地记事本、个人通讯录、任务管理器,或者小型图片浏览器。这些工具不需要联网,数据只存在本地,但要求关闭应用后数据不丢失,而且查询要快。
举个真实例子:我之前做过一个本地任务管理工具,用户可以添加、修改、删除任务,标记完成状态。一开始用JSON文件存储数据,结果任务多了(比如1000条以上),查询和排序变得很慢;而且修改数据时要整个文件读写,容易丢失数据。后来换成SQLite,不仅查询速度提升了10倍以上,还支持事务操作,数据安全多了。
这一节,我们就用这个任务管理工具的场景,带你掌握SQLite在桌面应用中的核vb.net教程C#教程python教程SQL教程access 2010教程心用法:数据持久化、CRUD操作、事务支持、跨平台适配,让你快速上手桌面应用的本地数据存储。
需求分析
桌面应用选择SQLite的核心原因:轻量级(只有一个文件)、无需安装服务、跨平台、查询快。我们的任务管理工具需要解决以下问题:
1.数据持久化:关闭应用后任务数据不丢失;
2.任务CRUD:添加、修改、删除、查询任务(按状态、截止日期筛选);
3.事务安全:批量操作(比如删除多个任务)时,要么全部成功,要么全部失败;
4.跨平台运行:在Windows、macOS、Linux上都能使用;
5.性能优化:本地查询快,即使有1000条任务也不卡顿;
6.路径安全:数据库文件存放在用户可读写的目录(避免程序目录无权限问题)。
目标:用最少的代码实现一个稳定、高效的本地任务管理工具,数据存储部分完全基于SQLite。
代码实现
前置条件:.NET 6+、EF Core 7+;需安装NuGet包:Microsoft.EntityFrameworkCore.Sqlite;
桌面应用类型:WPF、WinForms、MAUI(跨平台桌面)均可,本节以通用控制台示例为主,适配所有桌面框架。
准备工作:安装依赖与配置
步骤1:安装SQLite Provider
bash
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
步骤2:定义任务实体与DbContext
csharp
// 任务实体
public class TaskItem
{
public int Id { get; set; } // 主键,自增
public string Title { get; set; } = null!; // 任务标题
public string? Description { get; set; } // 任务描述(可选)
public DateTime DueDate { get; set; } // 截止日期
public bool IsCompleted { get; set; } // 是否完成
public DateTime CreatedAt { get; set; } = DateTime.UtcNow; // 创建时间(UTC)
}
// SQLite DbContext配置
public class TaskDbContext : DbContext
{
public DbSet<TaskItem> Tasks { get; set; } = null!; // 任务表
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
// 关键:数据库文件路径选择用户可读写的目录
var appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
var dbDir = Path.Combine(appDataPath, "TaskManager");
Directory.CreateDirectory(dbDir); // 确保目录存在
var dbPath = Path.Combine(dbDir, "TaskManager.db");
// 配置SQLite连接字符串:Data Source指定文件路径
options.UseSqlite($"Data Source={dbPath}")
.LogTo(Console.WriteLine) // 日志输出生成的SQL(调试用)
.EnableSensitiveDataLogging(); // 显示敏感数据(如参数值,生产环境关闭)
}
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// 对TaskItem的DueDate字段创建索引,加快按截止日期查询的速度
modelBuilder.Entity<TaskItem>()
.HasIndex(t => t.DueDate)
.HasDatabaseName("IX_Tasks_DueDate");
}
}
场景1:初始化数据库(创建表)
错误写法:手动写SQL创建表,容易出错且难以维护
csharp
// ❌错误:手动执行SQL创建表,字段变更时需要手动修改
public async Task CreateTableWrong()
{
using var db = new TaskDbContext();
await db.Database.ExecuteSqlRawAsync(@"
CREATE TABLE IF NOT EXISTS Tasks (
Id INTEGER PRIMARY KEY AUTOINCREMENT,
Title TEXT NOT NULL,
Description TEXT,
DueDate DATETIME NOT NULL,
IsCompleted BOOLEAN NOT NULL,
CreatedAt DATETIME NOT NULL
)
");
}
正确写法:用EF Core迁移自动创建表
bash
# 步骤1:生成迁移(记录表结构变更)
dotnet ef migrations add InitialCreate --context TaskDbContext
# 步骤2:应用迁移(创建表)
dotnet ef database update --context TaskDbContext
执行结果:
生成Migrations文件夹,包含迁移文件;
在用户AppData目录下创建TaskManager.db文件;
自动创建Tasks表和__EFMigrationsHistory表(记录迁移历史)。
场景2:任务CRUD操作(核心功能)
2.1 添加任务
csharp
// ✅正确:添加任务,EF Core自动处理插入逻辑
public async Task AddTaskAsync(string title, string? description, DateTime dueDate)
{
var task = new TaskItem
{
Title = title,
Description = description,
DueDate = dueDate,
IsCompleted = false // 默认未完成
};
using var db = new TaskDbContext();
await db.Tasks.AddAsync(task); // 标记实体为新增
await db.SaveChangesAsync(); // 执行插入SQL
}
2.2 查询任务(按状态筛选)
csharp
// ✅正确:查询未完成的任务,按截止日期排序
public async Task<List<TaskItem>> GetPendingTasksAsync()
{
using var db = new TaskDbContext();
return await db.Tasks
.Where(t => !t.IsCompleted) // 筛选未完成
.OrderBy(t => t.DueDate) // 按截止日期升序
.ToListAsync(); // 执行查询
}
2.3 修改任务(标记为完成)
csharp
// ✅正确:修改任务状态,EF Core自动生成更新SQL
public async Task MarkTaskAsCompletedAsync(int taskId)
{
using var db = new TaskDbContext();
var task = await db.Tasks.FindAsync(taskId); // 根据主键快速查询
if (task != null)
{
task.IsCompleted = true; // 修改实体状态
await db.SaveChangesAsync(); // 执行更新SQL
}
}
2.4 删除任务
csharp
// ✅正确:删除任务,EF Core自动生成删除SQL
public async Task DeleteTaskAsync(int taskId)
{
using var db = new TaskDbContext();
var task = await db.Tasks.FindAsync(taskId);
if (task != null)
{
db.Tasks.Remove(task); // 标记实体为删除
await db.SaveChangesAsync(); // 执行删除SQL
}
}
场景3:事务操作(批量删除任务)
错误写法:无事务,批量操作时容易数据不一致
csharp
// ❌错误:批量删除时无事务,部分删除成功部分失败,导致数据混乱
public async Task BatchDeleteWrong(List<int> taskIds)
{
using var db = new TaskDbContext();
foreach (var id in taskIds)
{
var task = await db.Tasks.FindAsync(id);
if (task != null)
{
db.Tasks.Remove(task);
await db.SaveChangesAsync(); // 每次删除都提交,无事务
}
}
}
正确写法:用事务保证批量操作原子性
csharp
// ✅正确:事务操作,要么全部删除成功,要么全部回滚
public async Task BatchDeleteTasksAsync(List<int> taskIds)
{
using var db = new TaskDbContext();
using var transaction = await db.Database.BeginTransactionAsync(); // 开启事务
try
{
// 一次性查询所有要删除的任务
var tasks = await db.Tasks.Where(t => taskIds.Contains(t.Id)).ToListAsync();
db.Tasks.RemoveRange(tasks); // 批量标记为删除
await db.SaveChangesAsync(); // 执行删除SQL
await transaction.CommitAsync(); // 提交事务
}
catch (Exception ex)
{
await transaction.RollbackAsync(); // 回滚事务
Console.WriteLine($"批量删除失败:{ex.Message}");
throw; // 重新抛出异常,让上层处理
}
}
场景4:跨平台适配(Windows/macOS/Linux)
正确写法:利用SQLite跨平台特性,代码无需修改
csharp
// ✅正确:跨平台运行,数据库文件路径自动适配不同系统
public string GetDbPathCrossPlatform()
{
string appDataPath;
if (OperatingSystem.IsWindows())
{
appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
}
else if (OperatingSystem.IsMacOS())
{
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Library", "Application Support");
}
else // Linux
{
appDataPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), ".config");
}
var dbDir = Path.Combine(appDataPath, "TaskManager");
Directory.CreateDirectory(dbDir);
return Path.Combine(dbDir, "TaskManager.db");
}
说明:.NET 6+提供OperatingSystem.IsWindows()等方法,可轻松适配不同系统的目录结构,SQLite数据库文件在各系统下都能正常读写。
逐行讲解
准备工作:DbContext配置
1.数据库路径选择:Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData)获取用户AppData目录(Windows),避免程序目录无写入权限的问题;
2.Directory.CreateDirectory(dbDir):确保目录存在,防止文件创建失败;
3.UseSqlite连接字符串:Data Source=TaskManager.db指定数据库文件路径,SQLite会自动创建该文件(如果不存在);
4.OnModelCreating:对DueDate字段创建索引,加快按截止日期查询的速度(索引是性能优化的关键)。
场景1:迁移初始化
dotnet ef migrations add InitialCreate:生成迁移文件,记录当前实体的表结构;
dotnet ef database update:应用迁移,创建数据库和表,自动处理字段类型、主键、索引等;
迁移的优势:字段变更时只需修改实体,重新生成迁移即可,无需手动写SQL,维护成本低。
场景2:CRUD操作
AddAsync:标记实体为新增状态,EF Core会生成INSERT SQL;
FindAsync:根据主键查询,比Where更快(主键有默认索引);
SaveChangesAsync:执行所有待处理的变更(新增、修改、删除),异步方法不阻塞UI线程(桌面应用关键)。
场景3:事务操作
BeginTransactionAsync:开启事务,所有后续操作都在事务内执行;
CommitAsync:提交事务,所有操作生效;
RollbackAsync:发生异常时回滚事务,所有操作取消,保证数据一致性;
RemoveRange:批量标记实体为删除,比循环删除更高效(减少数据库交互次数)。
场景4:跨平台适配
OperatingSystem.IsWindows():.NET 6+新增的跨平台判断方法,无需引用第三方库;
各系统目录:Windows用AppData,macOS用Library/Application Support,Linux用~/.config,都是用户可读写的目录。
基础知识拓展
- SQLite核心特性
| 特性 | 说明 | 对桌面应用的价值 |
|---|---|---|
| 文件型数据库 | 整个数据库就是一个文件,方便备份(复制文件即可)、移动 | 桌面应用用户可以轻松备份数据,无需复杂操作 |
| 无服务器 | 不需要安装数据库服务(如SQL Server需要安装服务) | 桌面应用无需额外安装依赖,下载即可使用 |
| 跨平台 | 支持Windows、macOS、Linux、iOS、Android | 一次编写,多平台运行,降低开发成本 |
| 动态数据类型 | 弱类型,列可以存储任意类型的数据(EF Core会做类型检查) | 灵活,适合快速开发小型工具 |
| ACID支持 | 事务的原子性、一致性、隔离性、持久性 | 保证数据安全,批量操作不会出现数据混乱 |
| 轻量级 | 库文件小(Windows下约1MB),内存占用低 | 桌面应用启动快,不占用过多资源 |
-
桌面应用SQLite最佳实践
1.目录选择:优先用用户可读写的目录(如AppData),避免程序目录(无写入权限);
2.备份策略:定期复制数据库文件到备份目录,或提供手动备份功能(比如“导出数据”按钮);
3.加密敏感数据:如果存储密码、隐私信息,用SQLCipher加密数据库(EF Core支持:安装Microsoft.EntityFrameworkCore.Sqlite.Core和SQLitePCLRaw.bundle_e_sqlcipher);
4.性能优化:
o对常用查询字段创建索引(如DueDate、IsCompleted);
o用分页查询(Skip/Take)避免一次性加载大量数据;
o关闭不必要的日志(生产环境去掉LogTo);
5.连接池:EF Core默认管理连接池,不需要手动配置,但要注意不要长时间持有DbContext(用using语句及时释放);
6.数据量限制:SQLite适合小到中等数据量(10万条以内),桌面应用一般不会超过这个量级,足够使用。 -
SQLite vs 其他本地存储
存储方式 优点 缺点 适用场景
SQLite 支持SQL、事务、查询快、跨平台 数据量过大时性能下降(百万条以上) 桌面应用、小型工具、移动应用
JSON文件 轻量、易读、无需依赖 查询慢、不支持事务、数据量大时解析慢 小数据量(如配置文件)
XML文件 结构清晰、易扩展 查询慢、不支持事务、解析耗时 配置文件、小数据量存储
CSV文件 简单、易导出 不支持复杂查询、无结构、易出错 数据导出/导入 -
常见坑点与解决
权限问题:数据库文件存放在程序目录导致无写入权限 → 改用用户AppData目录;
数据丢失:未用事务导致批量操作部分失败 → 用事务保证原子性;
性能低下:未创建索引导致查询慢 → 对常用查询字段创建索引;
跨平台路径错误:硬编码目录路径 → 用OperatingSystem类适配不同系统目录;
敏感数据泄露:未加密数据库 → 用SQLCipher加密。
总结
SQLite是桌面应用本地数据存储的首选方案,结合EF Core可以快速开发稳定、高效的数据存储功能。本节的核心要点:
1.路径选择:用用户可读写的目录(如AppData),避免权限问题;
2.迁移初始化:用EF Core迁移自动创建数据库和表,降低维护成本;
3.CRUD操作:用EF Core的异步方法(AddAsync、SaveChangesAsync),不阻塞UI线程;
4.事务安全:批量操作时用事务保证数据一致性;
5.跨平台适配:用.NET 6+的跨平台API,一次编写,多平台运行;
6.性能优化:对常用查询字段创建索引,用分页查询避免加载大量数据。
掌握这些技巧,你就能轻松实现桌面应用的本地数据存储,让你的工具更稳定、更高效。
本站原创,转载请注明出处:https://www.xin3721.com/ArticlecSharp/c49458.html










