一、核心目标
在原有 WPF 折线图项目基础上,通过Sugar ORM连接 SQLite 数据库,实现:
-
从 SQLite 读取历史销量数据渲染折线图
-
追加数据时同时写入数据库,实现数据持久化
二、前置准备
1. 安装依赖包
通过 NuGet 安装以下包:
| 包名 | 作用 |
|---|---|
| SqlSugarCore | Sugar ORM 核心(支持 SQLite) |
| System.Data.SQLite | SQLite 数据库驱动 |
| Microsoft.Data.Sqlite | 可选(替代 System.Data.SQLite) |
2. SQLite 数据库准备
(1)创建数据库文件
在项目根目录创建SalesData.db文件(也可让 Sugar 自动创建)。
(2)创建销量表
定义销量实体类,Sugar 会自动生成数据表:
using SqlSugar;
using System;
namespace LineChart2.Model
{
/// <summary>
/// 销量数据表实体
/// </summary>
[SugarTable("SalesRecord")]
public class SalesRecord
{
/// <summary>
/// 主键(自增)
/// </summary>
[SugarColumn(IsPrimaryKey = true, IsIdentity = true)]
public int Id { get; set; }
/// <summary>
/// 年份(2023/2024)
/// </summary>
[SugarColumn(ColumnName = "Year")]
public int SalesYear { get; set; }
/// <summary>
/// 月份(1-12)
/// </summary>
public int Month { get; set; }
/// <summary>
/// 销量数值
/// </summary>
public double SalesValue { get; set; }
/// <summary>
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
}
}
三、Sugar ORM 初始化配置
创建数据库帮助类,封装 Sugar 连接和操作:
using SqlSugar;
using LineChart2.Model;
using System;
using System.IO;
namespace LineChart2.Helper
{
public class DbHelper
{
// Sugar数据库连接实例(单例)
public static SqlSugarClient Db { get; private set; }
static DbHelper()
{
// 初始化连接
InitDb();
}
/// <summary>
/// 初始化SQLite连接
/// </summary>
private static void InitDb()
{
// 获取数据库文件路径(项目根目录)
string dbPath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "SalesData.db");
Db = new SqlSugarClient(new ConnectionConfig
{
ConnectionString = $"Data Source={dbPath};", // SQLite连接字符串
DbType = DbType.Sqlite, // 数据库类型
IsAutoCloseConnection = true, // 自动关闭连接
InitKeyType = InitKeyType.Attribute // 从特性读取主键/自增配置
});
// 初始化数据表(不存在则创建)
Db.CodeFirst.InitTables(typeof(SalesRecord));
// 开启日志(调试用,生产可关闭)
Db.Aop.OnLogExecuting = (sql, pars) =>
{
Console.WriteLine($"SQL:{sql} \r\n参数:{string.Join(",", pars.Select(p => p.ParameterName + "=" + p.Value))}");
};
}
#region 销量数据操作封装
/// <summary>
/// 批量插入销量数据
/// </summary>
public static void BatchInsertSalesData(SalesRecord[] records)
{
Db.Insertable(records).ExecuteCommand();
}
/// <summary>
/// 插入单条销量数据
/// </summary>
public static int InsertSalesData(SalesRecord record)
{
return Db.Insertable(record).ExecuteReturnIdentity();
}
/// <summary>
/// 根据年份查询销量数据
/// </summary>
public static List<SalesRecord> GetSalesDataByYear(int year)
{
return Db.Queryable<SalesRecord>()
.Where(r => r.SalesYear == year)
.OrderBy(r => r.Month)
.ToList();
}
/// <summary>
/// 查询所有年份的销量数据
/// </summary>
public static List<SalesRecord> GetAllSalesData()
{
return Db.Queryable<SalesRecord>()
.OrderBy(r => r.SalesYear)
.ThenBy(r => r.Month)
.ToList();
}
#endregion
}
}
四、修改折线图控件,整合数据库操作
1. 重构 UCLineChart.xaml.cs
替换原有硬编码数据,改为从数据库读取 / 写入:
using LiveCharts;
using LiveCharts.Defaults;
using LiveCharts.Wpf;
using LineChart2.Helper;
using LineChart2.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
namespace LineChart2
{
public partial class UCLineChart : UserControl
{
public SeriesCollection SeriesCollection { get; set; }
public string[] Labels { get; set; }
public Func<double, string> YFormatter { get; set; }
// 模拟销量数据
private readonly Random _random = new Random();
// 当前最大月份(用于追加数据)
private int _currentMaxMonth = 5;
public UCLineChart()
{
InitializeComponent();
// 初始化图表数据(优先从数据库读取)
InitChartDataFromDb();
// Y轴格式化(显示整数)
YFormatter = value => $"{value:N0}";
DataContext = this;
}
/// <summary>
/// 从数据库初始化图表数据
/// </summary>
private void InitChartDataFromDb()
{
SeriesCollection = new SeriesCollection();
// 1. 查询所有销量数据
var allSalesData = DbHelper.GetAllSalesData();
// 2. 如果数据库无数据,初始化默认数据并写入数据库
if (!allSalesData.Any())
{
InitDefaultDataAndWriteToDb();
allSalesData = DbHelper.GetAllSalesData();
}
// 3. 按年份分组构建折线系列
var groupedData = allSalesData.GroupBy(r => r.SalesYear);
foreach (var group in groupedData)
{
int year = group.Key;
var values = group.OrderBy(r => r.Month).Select(r => r.SalesValue).ToList();
// 创建折线系列
LineSeries series = new LineSeries
{
Title = $"{year}年",
Values = new ChartValues<double>(values),
LineSmoothness = 0.3,
StrokeThickness = 2,
PointGeometrySize = 8
};
// 不同年份设置不同颜色
series.Stroke = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
series.PointForeground = year == 2024 ? Brushes.DodgerBlue : Brushes.OrangeRed;
series.PointGeometry = year == 2024 ? DefaultGeometries.Circle : DefaultGeometries.Square;
SeriesCollection.Add(series);
}
// 4. 构建X轴标签(1-当前最大月份)
_currentMaxMonth = allSalesData.Max(r => r.Month);
Labels = Enumerable.Range(1, _currentMaxMonth).Select(m => $"{m}月").ToArray();
}
/// <summary>
/// 初始化默认数据并写入数据库
/// </summary>
private void InitDefaultDataAndWriteToDb()
{
List<SalesRecord> defaultData = new List<SalesRecord>();
// 2024年1-5月数据
double[] sales2024 = { 120, 150, 110, 180, 160 };
for (int i = 0; i < sales2024.Length; i++)
{
defaultData.Add(new SalesRecord
{
SalesYear = 2024,
Month = i + 1,
SalesValue = sales2024[i]
});
}
// 2023年1-5月数据
double[] sales2023 = { 100, 130, 140, 150, 130 };
for (int i = 0; i < sales2023.Length; i++)
{
defaultData.Add(new SalesRecord
{
SalesYear = 2023,
Month = i + 1,
SalesValue = sales2023[i]
});
}
// 批量写入数据库
DbHelper.BatchInsertSalesData(defaultData.ToArray());
}
// 追加数据按钮点击事件
private void BtnAddData_Click(object sender, RoutedEventArgs e)
{
// 1. 月份+1
_currentMaxMonth++;
int newMonth = _currentMaxMonth;
// 2. 生成随机销量(100-200)
double sales2024 = _random.Next(100, 200);
double sales2023 = _random.Next(100, 200);
// 3. 写入数据库
DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2024, Month = newMonth, SalesValue = sales2024 });
DbHelper.InsertSalesData(new SalesRecord { SalesYear = 2023, Month = newMonth, SalesValue = sales2023 });
// 4. 追加数据点到图表
SeriesCollection[0].Values.Add(sales2024);
SeriesCollection[1].Values.Add(sales2023);
// 5. 更新X轴标签
Array.Resize(ref Labels, Labels.Length + 1);
Labels[Labels.Length - 1] = $"{newMonth}月";
// 6. 刷新绑定(触发UI更新)
chart.AxisX[0].Labels = Labels;
// 更新按钮文本(追加7月、8月...)
(sender as Button).Content = $"追加{newMonth + 1}月数据";
}
}
}
2. 修复原代码 Bug
原代码中Labels数组扩容时使用了错误的方法,已替换为Array.Resize:
// 错误写法
// Labels[Labels.Length - 1] = "6月";
// 正确写法
Array.Resize(ref Labels, Labels.Length + 1);
Labels[Labels.Length - 1] = $"{newMonth}月";
五、关键功能说明
1. 数据库初始化逻辑
-
首次运行时,若数据库无数据,自动插入 2023/2024 年 1-5 月默认数据
-
后续运行时,从数据库读取数据渲染图表,保证数据持久化
2. 数据追加逻辑
-
点击按钮时,生成随机销量数据并写入数据库
-
同时更新图表数据,实现 "数据库 + UI" 双向同步
-
按钮文本动态更新(追加 6 月→追加 7 月→...)
3. Sugar ORM 核心操作
| 操作 | 核心代码示例 |
|---|---|
| 初始化连接 | new SqlSugarClient(new ConnectionConfig { ... }) |
| 自动建表 | Db.CodeFirst.InitTables(typeof(SalesRecord)) |
| 批量插入 | Db.Insertable(records).ExecuteCommand() |
| 条件查询 | Db.Queryable<SalesRecord>().Where(r => r.SalesYear == year).ToList() |
| 排序查询 | OrderBy(r => r.Month) |
六、运行验证
-
启动项目,首次运行会自动创建
SalesData.db文件,并插入默认数据 -
图表会显示 2023/2024 年 1-5 月销量折线
-
点击 "追加 6 月数据" 按钮:
-
数据库会新增 2023/2024 年 6 月销量记录
-
图表自动追加 6 月数据点,X 轴标签更新为 "6 月"
-
按钮文本变为 "追加 7 月数据",重复点击可持续追加
-
七、扩展优化建议
1. 异常处理
添加 try-catch 捕获数据库操作异常:
try
{
DbHelper.InsertSalesData(new SalesRecord { ... });
}
catch (Exception ex)
{
MessageBox.Show($"数据写入失败:{ex.Message}", "错误", MessageBoxButton.OK, MessageBoxImage.Error);
}
2. 分页查询(大数据量)
若销量数据量大,可分页读取:
// 分页查询2024年数据(第1页,每页10条)
var pageData = Db.Queryable<SalesRecord>()
.Where(r => r.SalesYear == 2024)
.OrderBy(r => r.Month)
.ToPageList(1, 10);
3. 数据更新 / 删除
扩展 DbHelper 添加更新 / 删除方法:
/// <summary>
/// 更新销量数据
/// </summary>
public static bool UpdateSalesData(SalesRecord record)
{
return Db.Updateable(record).ExecuteCommand() > 0;
}
/// <summary>
/// 删除指定月份数据
/// </summary>
public static bool DeleteSalesData(int year, int month)
{
return Db.Deleteable<SalesRecord>()
.Where(r => r.SalesYear == year && r.Month == month)
.ExecuteCommand() > 0;
}
4. 多表关联(进阶)
若需要关联其他表(如产品表),可使用 Sugar 的联表查询:
// 关联产品表查询销量
var joinData = Db.Queryable<SalesRecord, Product>((s, p) => new JoinQueryInfos(
JoinType.Inner, s.ProductId == p.Id))
.Select((s, p) => new { s.Month, s.SalesValue, p.ProductName })
.ToList();
八、项目结构梳理
LineChart2/
├─ Helper/
│ └─ DbHelper.cs // Sugar ORM数据库操作封装
├─ Model/
│ └─ SalesRecord.cs // 销量数据实体类
├─ UCLineChart.xaml // 折线图用户控件XAML
├─ UCLineChart.xaml.cs // 折线图逻辑(整合数据库)
├─ MainWindow.xaml // 主窗口
└─ SalesData.db // SQLite数据库文件(运行后生成)
通过以上改造,原有的 WPF 折线图项目实现了与 SQLite 数据库的无缝整合,数据不再局限于内存,而是通过 Sugar ORM 实现了持久化存储和动态读写。