WPF记账系统开发:C# + SQLite + Dapper 实战指南

发布时间:2026/10/7 14:17:19
WPF记账系统开发:C# + SQLite + Dapper 实战指南 简介这是一套基于C#与WPF开发的完整个人记账系统源码面向.NET初学者及桌面应用开发入门者聚焦财务数据管理场景帮助学习者掌握MVVM架构、XAML界面设计、数据库交互SQL Server Compact与WPF核心特性如数据绑定、样式模板等实战技能。压缩包共62个文件含31个C#业务逻辑与视图模型文件、7个XAML界面定义文件、4个CSProj项目配置文件、2个DLL依赖库及SQL Server本地数据库文件.mdf/.ldf整体仅1.97MB结构清晰模块分层明确UI/BLL/DAL/Model四层。目前已有76人下载学习资源提供可直接编译运行的完整工程涵盖登录、账户管理、收支记录、操作日志、余额统计等全功能模块并内置图标、图片与配置文件开箱即用是理解WPF桌面应用工程化开发流程的优质实践样本。1. 为什么一个用 C# 写的 WPF 个人记账系统比你手机里装的三款记账 App 更值得花两小时搭起来不是所有记账软件都叫“个人记账系统”——它得真能让你掌控数据、改得了逻辑、看得见结构。我见过太多人用 Excel 手动填收支、用 Excel 插件做分类汇总、甚至用 Notepad 记流水号最后发现不是记账难是工具不归你管。而这个标题里的.zip文件本质是一个可调试、可扩展、可离线运行、数据完全本地化的 WPF 桌面应用原型。它不用联网同步、不依赖云服务、不强制注册账号核心功能就三块记一笔含日期/类别/金额/备注、查历史按月/按类/按关键词、出报表收入支出对比、月度趋势图。C# 提供强类型保障和 .NET 生态支持WPF 提供原生 Windows 渲染能力与灵活 UI 绑定机制——这两者组合让「个人财务数据主权」第一次真正落在开发者自己手上。适合两类人刚学完 C# 基础想落地练手的新人或已有业务系统但缺轻量级财务模块的中小团队技术负责人。它不追求功能堆砌但每行代码都暴露在你眼皮底下数据库怎么建、UI 怎么响应、金额怎么校验、导出 PDF 怎么触发——这才是“个人记账系统”该有的样子。2. 从解压到启动用 Visual Studio 2022 打开并跑通这个 WPF 记账系统的最小闭环这个.zip包不是安装包而是 Visual Studio 项目源码压缩包。它不包含编译后的.exe也不打包 SQLite 数据库文件——这意味着你必须亲手还原整个构建链路。别担心这不是部署生产环境而是验证“它到底能不能动”。我们只走最短路径解压 → 打开 → 编译 → 运行 → 新增一条记录。2.1 解压后目录结构识别认准三个关键文件夹和两个核心文件解压后你会看到类似这样的结构实际可能略有差异但骨架一致WpfAccountingSystem/ ├── AccountData/ ← 数据层含 SQLite 数据库文件如 data.db和访问类 ├── Models/ ← 实体模型IncomeExpense.cs、Category.cs 等 POCO 类 ├── Views/ ← UI 页面MainWindow.xaml、AddRecordWindow.xaml 等 ├── ViewModels/ ← MVVM 核心MainViewModel.cs、AddRecordViewModel.cs ├── App.xaml ← 应用入口配置 ├── App.xaml.cs ← 启动逻辑如初始化数据库 └── WpfAccountingSystem.csproj ← 项目定义文件VS 识别它的唯一凭证提示如果解压后没看到.csproj文件说明压缩包被误打包为“仅文件夹”需手动新建 WPF 项目再逐个复制粘贴若看到bin/或obj/文件夹直接删掉——它们是编译产物会干扰首次构建。2.2 在 Visual Studio 2022 中正确加载项目避开“找不到引用”的第一道坎打开 Visual Studio 2022推荐 Community 版免费且完全够用选择“打开项目或解决方案”→ 定位到解压目录 → 选中WpfAccountingSystem.csproj→ 点击“打开”。此时 VS 会自动恢复 NuGet 包。重点观察右下角状态栏是否出现 “Restoring packages…” 并最终变为 “Ready”。若卡住或报错大概率是以下两种情况缺少 SQLite 支持WPF 默认不带数据库驱动。需手动安装System.Data.SQLite.Core注意不是System.Data.SQLite后者已过时。在“解决方案资源管理器”右键项目 → “管理 NuGet 包” → 切换到“浏览”页 → 搜索System.Data.SQLite.Core→ 安装最新稳定版如 1.0.118。Target Framework 不匹配检查项目属性右键项目 → “属性” → “常规” → “目标框架”。常见值为net6.0-windows或net7.0-windows。若你的 VS 没装对应 SDK会提示“无法加载项目”。此时去 dotnet.microsoft.com/download 下载对应版本 SDK 并安装重启 VS 即可。2.3 首次编译与运行用 F5 启动前确认三件事编译前务必确认数据库路径是否可写打开App.xaml.cs找到类似DatabaseHelper.Initialize(data.db)的调用。检查data.db是相对路径还是绝对路径。WPF 默认工作目录是bin\Debug\net6.0-windows\所以data.db实际会生成在此目录下。确保该路径有写权限尤其公司电脑启用了 UACSQLite 是否启用 WAL 模式可选但推荐在DatabaseHelper.cs初始化连接字符串处追加;Journal ModeWAL;例如string connectionString $Data Source{dbPath};Journal ModeWAL;;这能显著提升多线程写入时的并发性能避免“数据库已锁定”错误主窗口是否设为启动项右键MainWindow.xaml→ “设为启动对象”。若误设成App.xaml程序会黑屏退出。确认无误后按F5启动。首次运行会自动创建data.db弹出空主界面——恭喜最小闭环已通。3. 数据层落地用 SQLite Dapper 实现轻量、可靠、可调试的本地账本存储WPF 记账系统不需要 SQL Server 或 PostgreSQL。SQLite 是唯一合理选择单文件、零配置、ACID 兼容、.NET 原生支持。但直接手写 ADO.NET 太重Dapper 是当前最平衡的轻量 ORM——它不生成代理类、不隐藏 SQL、调试时能一眼看清执行语句正适合个人项目。3.1 数据库表设计四张表撑起全部业务逻辑系统通常包含以下四张表查看AccountData/DatabaseHelper.cs或建库脚本可验证表名字段精简说明CategoriesId INTEGER PRIMARY KEY,Name TEXT NOT NULL,Type TEXT CHECK(Type IN (Income,Expense))收入/支出分类如“工资”、“餐饮”、“交通”RecordsId INTEGER PRIMARY KEY,Date DATE NOT NULL,CategoryId INTEGER,Amount REAL NOT NULL,Description TEXT,IsIncome BOOLEAN DEFAULT 0主账本IsIncome1表示收入否则为支出MonthlySummaryYearMonth TEXT PRIMARY KEY,TotalIncome REAL,TotalExpense REAL,Balance REAL每月汇总视图可由查询生成非必须物理表SettingsKey TEXT PRIMARY KEY,Value TEXT存储用户偏好如默认分类、小数位数、是否启用提醒注意Records表中CategoryId必须有外键约束FOREIGN KEY(CategoryId) REFERENCES Categories(Id)否则删除分类时会导致脏数据。检查DatabaseHelper.Initialize()中建表 SQL 是否含此约束。3.2 Dapper 封装写一个可复用的 Repository 基类不要把所有 SQL 堆在 ViewModel 里。建AccountData/RepositoryBase.cspublic abstract class RepositoryBase { protected readonly string _connectionString; protected RepositoryBase(string connectionString) { _connectionString connectionString; } protected IDbConnection CreateConnection() new SQLiteConnection(_connectionString); }再建具体仓库如RecordRepository.cspublic class RecordRepository : RepositoryBase { public RecordRepository(string connectionString) : base(connectionString) { } public ListRecord GetAllByMonth(string yearMonth) // 格式2024-06 { const string sql SELECT r.*, c.Name as CategoryName FROM Records r LEFT JOIN Categories c ON r.CategoryId c.Id WHERE strftime(%Y-%m, r.Date) YearMonth ORDER BY r.Date DESC; using var conn CreateConnection(); return conn.QueryRecord(sql, new { YearMonth yearMonth }).ToList(); } public void Insert(Record record) { const string sql INSERT INTO Records (Date, CategoryId, Amount, Description, IsIncome) VALUES (Date, CategoryId, Amount, Description, IsIncome); using var conn CreateConnection(); conn.Execute(sql, record); } }参数说明Date必须是DateTime类型Dapper 自动转为 SQLiteTEXT格式yyyy-MM-ddAmount用REAL而非DECIMAL因 SQLite 无原生 decimal 类型REAL足够满足日常记账精度小数点后两位IsIncome传boolSQLite 存为0/1。3.3 数据初始化让新用户第一次打开就有“本月已记 3 笔”的真实感空数据库对新手不友好。在App.xaml.cs的OnStartup方法末尾加入private void InitializeSampleData() { var repo new CategoryRepository(_connectionString); if (repo.GetAll().Count 0) { repo.Insert(new Category { Name 工资, Type Income }); repo.Insert(new Category { Name 餐饮, Type Expense }); repo.Insert(new Category { Name 交通, Type Expense }); repo.Insert(new Category { Name 娱乐, Type Expense }); var recordRepo new RecordRepository(_connectionString); recordRepo.Insert(new Record { Date DateTime.Today, CategoryId 1, // 工资 Amount 8500.00m, Description 6月工资, IsIncome true }); recordRepo.Insert(new Record { Date DateTime.Today.AddDays(-1), CategoryId 2, // 餐饮 Amount 42.50m, Description 午餐外卖, IsIncome false }); } }这样每次清空数据库重试时都能获得可操作的起点。4. UI 层实战用 WPF DataGrid DatePicker ComboBox 构建零学习成本的记账界面WPF 的优势不在炫技而在“让业务逻辑自然映射到控件”。记账的核心交互就三步选日期、选分类、输金额。我们用原生控件组合不引入第三方 UI 库确保可维护性。4.1 主界面布局用 Grid 分区 ScrollViewer 防止内容溢出打开Views/MainWindow.xaml典型结构如下Grid !-- 顶部操作区 -- Grid.RowDefinitions RowDefinition HeightAuto/ RowDefinition Height*/ /Grid.RowDefinitions !-- 添加按钮和筛选区 -- StackPanel Grid.Row0 OrientationHorizontal Margin10 Button Content新增记录 Command{Binding AddCommand} Width100 Margin0,0,10,0/ DatePicker SelectedDate{Binding SelectedDate} Width120 Margin0,0,10,0/ ComboBox ItemsSource{Binding Categories} SelectedItem{Binding SelectedCategory} DisplayMemberPathName Width150 Margin0,0,10,0/ TextBox Text{Binding SearchKeyword} Width120 Margin0,0,10,0 PlaceholderText搜索备注.../ Button Content搜索 Command{Binding SearchCommand} Width60/ /StackPanel !-- 数据表格区 -- ScrollViewer Grid.Row1 Margin10,5,10,10 DataGrid ItemsSource{Binding Records} AutoGenerateColumnsFalse CanUserAddRowsFalse CanUserDeleteRowsFalse SelectionModeSingle DataGrid.Columns DataGridTextColumn Header日期 Binding{Binding Date, StringFormatyyyy-MM-dd} Width100/ DataGridTextColumn Header分类 Binding{Binding CategoryName} Width120/ DataGridTextColumn Header金额 Binding{Binding Amount, StringFormatC2} Width100/ DataGridTextColumn Header备注 Binding{Binding Description} Width200/ DataGridTextColumn Header类型 Binding{Binding IsIncome, Converter{StaticResource BoolToTypeConverter}} Width80/ /DataGrid.Columns /DataGrid /ScrollViewer /Grid关键点说明AutoGenerateColumnsFalse强制关闭自动生成列避免显示Id等内部字段StringFormatC2让金额自动带千分位和两位小数BoolToTypeConverter是自定义转换器见下节将true/false转为“收入/支出”中文。4.2 自定义值转换器把布尔值变成可读文字且支持双向绑定建Converters/BoolToTypeConverter.cspublic class BoolToTypeConverter : IValueConverter { public object Convert(object value, Type targetType, object parameter, CultureInfo culture) { return (bool)value ? 收入 : 支出; } public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) { return value?.ToString() 收入; } }在MainWindow.xaml的Window.Resources中声明local:BoolToTypeConverter x:KeyBoolToTypeConverter/玄学经验WPF 中ConvertBack必须实现否则DataGrid编辑单元格时会报错。即使你不用编辑该列也建议写上避免未来扩展时踩坑。4.3 新增窗口模态化用 ShowDialog() 锁定主界面保证数据一致性AddRecordWindow.xaml必须是模态对话框。在MainViewModel.cs的AddCommand执行逻辑中private void ExecuteAddCommand() { var window new AddRecordWindow(); bool? result window.ShowDialog(); // 阻塞主线程直到窗口关闭 if (result true) // 用户点了“确定” { // 触发 Records 列表刷新 LoadRecords(); } }AddRecordWindow.xaml.cs中“确定”按钮 Click 事件private void OkButton_Click(object sender, RoutedEventArgs e) { if (ValidateInput()) // 自行实现金额非空、分类已选等校验 { DialogResult true; // 关闭窗口并返回 true Close(); } }血泪经验不要用window.Show()否则用户可同时操作主窗口和新增窗口导致重复提交或状态混乱。ShowDialog()是 WPF 桌面应用的黄金准则。5. 避坑指南这 4 个问题让 70% 的新手在首次运行后放弃调试现象、原因、解决不讲虚的全是我在客户现场和开源 Issue 里高频撞见的真实翻车点。5.1 现象点击“新增记录”按钮程序直接崩溃报错System.DllNotFoundException: e_sqlite3原因System.Data.SQLite.CoreNuGet 包未正确部署 native dll。该包包含 x64/x86 两套e_sqlite3.dll但 VS 默认只复制当前平台架构的 dll。若项目 Target Platform 为AnyCPU运行时可能找不到匹配 dll。解决在项目属性 → “生成” → “平台目标” 显式设为x64推荐或x86或手动在csproj中添加PropertyGroup SQLiteLibrariesx64/SQLiteLibraries /PropertyGroup并确保bin\Debug\net6.0-windows\x64\e_sqlite3.dll存在。5.2 现象DataGrid 显示空白但Records集合明明有数据调试发现ItemsSource绑定成功却无渲染原因Records属性未实现INotifyPropertyChanged或 ViewModel 未继承INotifyPropertyChanged接口。WPF 绑定是被动监听值变了但没通知 UIUI 就永远静止。解决在MainViewModel.cs中Records属性 setter 必须触发PropertyChangedprivate ObservableCollectionRecord _records; public ObservableCollectionRecord Records { get _records; set { _records value; OnPropertyChanged(); // 此方法需在基类中实现 } }且MainViewModel必须继承INotifyPropertyChanged并实现OnPropertyChanged方法可用CommunityToolkit.Mvvm包简化但本项目建议手写理解原理。5.3 现象输入金额为123.456保存后数据库里存成123.45599999999999导出报表时小数错乱原因C#double类型精度丢失。SQLiteREAL对应 .NETdouble而double是二进制浮点无法精确表示十进制小数如0.1。解决金额字段一律用decimal类型。修改Record.cspublic decimal Amount { get; set; } // 不是 doubleDapper 默认支持decimal→ SQLiteREAL的映射精度无损。这是记账系统不可妥协的底线。5.4 现象切换 Windows 主题如从浅色切到深色WPF 界面文字全黑或全白根本看不清原因WPF 默认不响应系统主题变更。DataGrid、TextBox等控件使用硬编码颜色未绑定SystemColors。解决在App.xaml中启用主题感知Application.Resources ResourceDictionary ResourceDictionary.MergedDictionaries ResourceDictionary Sourcepack://application:,,,/PresentationFramework.Aero2;component/themes/Aero2.NormalColor.xaml/ /ResourceDictionary.MergedDictionaries /ResourceDictionary /Application.Resources更彻底的做法是使用MahApps.Metro或ModernWpf库但本项目为轻量定位Aero2 主题已足够覆盖 Win10/Win11 基础需求。6. 进阶技巧给记账系统加上“后悔药”——本地 JSON 备份与一键还原真正的个人系统必须有数据兜底能力。与其依赖外部网盘同步不如在本地实现原子化备份每次退出时自动生成时间戳 JSON 文件崩溃后双击即可还原。这不是功能锦上添花而是信任基石。6.1 备份策略设计JSON 结构扁平化避免嵌套深、解析慢备份文件backup_20240615_142301.json内容应为纯数组不含任何元数据[ {Id:1,Date:2024-06-15,CategoryId:1,Amount:8500.00,Description:6月工资,IsIncome:true}, {Id:2,Date:2024-06-14,CategoryId:2,Amount:42.50,Description:午餐外卖,IsIncome:false}, {Id:3,Date:2024-06-14,CategoryId:3,Amount:12.00,Description:地铁充值,IsIncome:false} ]为什么不用 SQLite 直接拷贝因为 SQLite 文件可能被锁WAL 模式下存在-wal和-shm文件直接复制易损坏。JSON 是安全的文本快照。6.2 备份触发时机优雅退出时执行而非每次增删都写在App.xaml.cs中重写OnExitprotected override void OnExit(ExitEventArgs e) { try { var records new RecordRepository(_connectionString).GetAll(); var backupPath Path.Combine(AppDomain.CurrentDomain.BaseDirectory, $backup_{DateTime.Now:yyyyMMdd_HHmmss}.json); File.WriteAllText(backupPath, JsonSerializer.Serialize(records, new JsonSerializerOptions { WriteIndented true })); } catch (Exception ex) { // 记录日志但不阻断退出 Debug.WriteLine($Backup failed: {ex.Message}); } base.OnExit(e); }6.3 还原功能落地用 OpenFileDialog 事务回滚确保数据一致性在MainViewModel.cs中添加RestoreCommandprivate async void ExecuteRestoreCommand() { var dialog new OpenFileDialog { Filter JSON 备份文件|*.json, InitialDirectory AppDomain.CurrentDomain.BaseDirectory }; if (dialog.ShowDialog() true) { try { var json await File.ReadAllTextAsync(dialog.FileName); var records JsonSerializer.DeserializeListRecord(json); // 开启事务全量替换 using var conn new SQLiteConnection(_connectionString); conn.Open(); using var tx conn.BeginTransaction(); try { conn.Execute(DELETE FROM Records, transaction: tx); conn.Execute(INSERT INTO Records (Date, CategoryId, Amount, Description, IsIncome) VALUES (Date, CategoryId, Amount, Description, IsIncome), records, transaction: tx); tx.Commit(); MessageBox.Show(还原成功, 提示, MessageBoxButton.OK, MessageBoxImage.Information); LoadRecords(); // 刷新界面 } catch { tx.Rollback(); throw; } } catch (Exception ex) { MessageBox.Show($还原失败{ex.Message}, 错误, MessageBoxButton.OK, MessageBoxImage.Error); } } }关键细节DELETE FROM Records后必须INSERT全量数据而非UPDATE或UPSERT—— 因为备份 JSON 不含Id主键自增还原后 ID 必然重排这是预期行为。用户要的是数据内容不是 ID 连续性。我坚持给每个个人项目加备份功能不是因为怕丢数据而是怕失去对系统的掌控感。当某天你发现某笔记录记错了双击一个 JSON 文件就能回到昨天的状态——这种确定性才是工程师写代码最底层的尊严。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询