FreeSql + Sqlite实战:WPF应用中的CRUD操作全解析(附完整代码)
在.NET生态中,WPF作为成熟的桌面应用框架,与轻量级数据库Sqlite的结合,为开发者提供了快速构建本地数据存储应用的理想方案。而FreeSql作为.NET平台下的高效ORM工具,进一步简化了数据操作流程。本文将深入探讨如何在.NET 8环境下,通过Prism框架实现MVVM模式,完成从数据库配置到完整CRUD功能的实战开发。
1. 环境搭建与项目初始化
1.1 开发环境配置
首先确保已安装以下工具和组件:
- Visual Studio 2022(17.6+版本)
- .NET 8 SDK
- NuGet包管理器
创建WPF项目时,需添加以下关键NuGet包:
dotnet add package FreeSql.Provider.Sqlite dotnet add package Prism.DryIoc dotnet add package HandyControl1.2 项目结构设计
推荐采用分层架构组织代码:
FreeSqlDemo/ ├── DAL/ # 数据访问层 │ └── DB.cs # FreeSql实例管理 ├── Models/ # 数据模型 │ └── Blog.cs # 实体类定义 ├── ViewModels/ # 视图模型 │ └── MainWindowViewModel.cs ├── Views/ # 视图层 │ └── MainWindow.xaml └── App.xaml # 应用入口2. FreeSql与Sqlite深度集成
2.1 数据库连接配置
在DB.cs中配置FreeSql实例时,有几个关键参数需要注意:
public static IFreeSql Sqlite => new FreeSqlBuilder() .UseConnectionString(DataType.Sqlite, @"Data Source=DB\demo.db;Pooling=true;Min Pool Size=1") .UseAutoSyncStructure(true) // 自动同步实体结构到数据库 .UseMonitorCommand(cmd => Debug.WriteLine(cmd.CommandText)) // SQL日志 .Build();提示:生产环境中建议将连接字符串存储在配置文件中,而非硬编码
2.2 实体模型定义最佳实践
定义数据模型时,FreeSql提供了丰富的特性注解:
[Table(Name = "blogs")] // 指定表名 public class Blog { [Column(IsIdentity = true, IsPrimary = true)] public int Id { get; set; } [Column(StringLength = 500)] public string Url { get; set; } [Column(DbType = "INT DEFAULT 0")] public int Rating { get; set; } [Column(ServerTime = DateTimeKind.Utc)] public DateTime CreateTime { get; set; } }3. MVVM模式下的CRUD实现
3.1 增删改查完整示例
在视图模型中实现完整的CRUD操作:
public class MainWindowViewModel : BindableBase { private string _status; public string Status { get => _status; set => SetProperty(ref _status, value); } // 插入数据 public DelegateCommand InsertCommand => new(() => { var blog = new Blog { Url = "https://example.com" }; var id = DB.Sqlite.Insert(blog).ExecuteIdentity(); Status = $"插入成功,ID: {id}"; }); // 条件查询 public DelegateCommand QueryCommand => new(() => { var blogs = DB.Sqlite.Select<Blog>() .Where(b => b.Rating > 3) .OrderByDescending(b => b.CreateTime) .ToList(); Status = $"查询到 {blogs.Count} 条记录"; }); // 批量更新 public DelegateCommand UpdateCommand => new(() => { var affected = DB.Sqlite.Update<Blog>() .Set(b => b.Rating, 5) .Where(b => b.Url.Contains("example")) .ExecuteAffrows(); Status = $"更新了 {affected} 条记录"; }); // 软删除模式 public DelegateCommand DeleteCommand => new(() => { var affected = DB.Sqlite.Delete<Blog>() .Where(b => b.Id == 1) .ExecuteAffrows(); Status = $"删除了 {affected} 条记录"; }); }3.2 高级查询技巧
FreeSql提供了丰富的查询API:
// 分页查询 var pagedList = DB.Sqlite.Select<Blog>() .Where(b => b.Rating > 0) .Count(out var total) .Page(1, 20) .ToList(); // 联表查询 var joinedData = DB.Sqlite.Select<Blog>() .LeftJoin<Comment>((b, c) => b.Id == c.BlogId) .ToList((b, c) => new { b.Url, c.Content }); // 原生SQL查询 var rawQuery = DB.Sqlite.Ado.Query<Blog>( "SELECT * FROM blogs WHERE rating > @rating", new { rating = 3 });4. 性能优化与实战技巧
4.1 批量操作优化
对于大量数据操作,使用批量处理显著提升性能:
// 批量插入(事务自动提交) var inserted = DB.Sqlite.Insert(Enumerable.Range(1, 1000) .Select(i => new Blog { Url = $"blog_{i}.com" })) .ExecuteAffrows(); // 批量更新 var updated = DB.Sqlite.Update<Blog>() .Set(b => b.Rating, b => b.Rating + 1) .Where(b => b.CreateTime > DateTime.Today) .ExecuteAffrows();4.2 事务处理模式
FreeSql支持多种事务管理方式:
// 显式事务 using (var uow = DB.Sqlite.CreateUnitOfWork()) { try { var repo = uow.GetRepository<Blog>(); repo.Insert(new Blog { Url = "trans1.com" }); repo.Update(new Blog { Id = 1, Rating = 5 }); uow.Commit(); } catch { uow.Rollback(); throw; } } // 工作单元模式 var affrows = DB.Sqlite.Transaction(() => { var blog = DB.Sqlite.Insert(new Blog { Url = "trans2.com" }) .ExecuteInserted(); DB.Sqlite.Update<Comment>() .Set(c => c.IsApproved, true) .Where(c => c.BlogId == blog[0].Id) .ExecuteAffrows(); });4.3 缓存策略实现
FreeSql内置二级缓存支持:
// 启用全局缓存 var sqlite = new FreeSqlBuilder() .UseConnectionString(DataType.Sqlite, "...") .UseLazyLoading(true) .UseNoneCommandParameter(true) .UseCache(typeof(MyCacheProvider)) // 自定义缓存实现 .Build(); // 查询时指定缓存 var cachedData = DB.Sqlite.Select<Blog>() .Where(b => b.Rating > 3) .WithCache(TimeSpan.FromMinutes(5)) .ToList();5. 异常处理与调试技巧
5.1 常见问题排查
开发过程中可能遇到的典型问题及解决方案:
| 问题现象 | 可能原因 | 解决方案 |
|---|---|---|
| 连接失败 | 数据库文件路径错误 | 使用绝对路径或检查文件权限 |
| 表不存在 | AutoSyncStructure未启用 | 设置.UseAutoSyncStructure(true) |
| 性能低下 | 未启用连接池 | 连接字符串添加Pooling=true |
5.2 日志记录配置
通过事件监听实现全面的SQL日志:
DB.Sqlite.Aop.CurdAfter += (s, e) => { Debug.WriteLine($"SQL: {e.Sql}\nParams: {string.Join(",", e.Parses)}"); if (e.Exception != null) Logger.Error(e.Exception, "执行SQL出错"); }; DB.Sqlite.Aop.SyncStructureAfter += (s, e) => { if (e.Sql.NotNull()) Debug.WriteLine($"同步结构: {e.Sql}"); };5.3 单元测试策略
为数据访问层编写可靠的单元测试:
[TestClass] public class BlogRepositoryTests { private IFreeSql _testSqlite; [TestInitialize] public void Setup() { _testSqlite = new FreeSqlBuilder() .UseConnectionString(DataType.Sqlite, "Data Source=:memory:") .UseAutoSyncStructure(true) .Build(); } [TestMethod] public void InsertBlog_ShouldReturnId() { var repo = _testSqlite.GetRepository<Blog>(); var blog = new Blog { Url = "test.com" }; var id = repo.Insert(blog); Assert.IsTrue(id > 0); var saved = repo.Select.Where(b => b.Id == id).First(); Assert.AreEqual("test.com", saved.Url); } }在实际项目开发中,我们发现FreeSql的Lambda表达式解析非常稳定,但在处理复杂嵌套查询时,适当拆分查询步骤可以提高代码可读性。对于高频访问的热点数据,建议结合内存缓存减少数据库压力。