C# WPF上位机开发:FreeSql+MVVM实战避坑指南(含MySQL/SQLServer双数据库配置)
从Java转型到C# WPF开发的工程师们,往往会在MVVM架构下遇到数据库集成的各种"坑"。本文将分享如何用FreeSql这一轻量级ORM框架,在工业级上位机项目中实现高效、稳定的数据持久化方案。不同于简单的语法介绍,我们聚焦于实际开发中三个最棘手的场景:架构冲突规避、双数据库适配和ViewModel映射优化。
1. 架构设计:如何让FreeSql与MVVM和谐共处
MVVM模式强调分层解耦,而传统ORM往往倾向于侵入式设计。这种理念冲突在实际项目中表现为两种典型问题:一是数据库操作污染ViewModel层,二是变更通知机制与ORM的自动跟踪产生冲突。
解决方案的核心在于建立清晰的隔离层:
// Database层接口定义 public interface IDatabaseService<T> where T : class { Task<IEnumerable<T>> GetAllAsync(); Task<T> GetByIdAsync(long id); Task<int> InsertAsync(T entity); Task<int> UpdateAsync(T entity); Task<bool> DeleteAsync(long id); }实现时需要注意三个关键点:
- 生命周期管理:推荐使用单例模式管理FreeSql实例
- 线程安全:确保所有数据库操作都在后台线程执行
- 异常处理:统一封装数据库异常为业务友好型异常
提示:避免在ViewModel中直接调用FreeSql的复杂API,应该通过中间层进行抽象
2. 双数据库配置:MySQL与SQLServer的无缝切换
工业场景中常需要同时支持多种数据库。FreeSql的DataType枚举虽然支持多种数据库类型,但实际配置时仍有以下注意事项:
| 配置项 | MySQL注意事项 | SQLServer注意事项 |
|---|---|---|
| 连接字符串 | 需要指定Pooling参数 | 建议开启MultipleActiveResultSets |
| 事务隔离级别 | 默认RepeatableRead | 默认ReadCommitted |
| 分页语法 | Limit/Offset | Offset/Fetch Next |
| 时间类型处理 | 需处理时区问题 | 注意datetime2精度 |
典型的多数据库配置方案:
// 在App.xaml.cs中初始化 var dbType = ConfigurationManager.AppSettings["DatabaseType"]; var connectionString = ConfigurationManager.ConnectionStrings[dbType].ConnectionString; var freeSql = new FreeSqlBuilder() .UseConnectionString( dbType == "MySQL" ? DataType.MySql : DataType.SqlServer, connectionString) .UseAutoSyncStructure(false) // 生产环境建议关闭 .UseMonitorCommand(cmd => Debug.WriteLine(cmd.CommandText)) .Build(); // 注册为单例服务 ServiceLocator.Default.RegisterInstance<IFreeSql>(freeSql);3. 实体类与ViewModel的映射技巧
ORM实体与ViewModel的自动映射常会遇到以下问题:
- 循环引用导致序列化失败
- 导航属性加载时机不当
- 变更通知与ORM跟踪冲突
推荐采用分层映射策略:
- 基础实体层:仅包含数据库字段
public class DeviceEntity { [Column(IsPrimary = true)] public long Id { get; set; } public string Name { get; set; } public DateTime CreateTime { get; set; } }- 业务模型层:添加业务逻辑
public class DeviceModel : INotifyPropertyChanged { private string _name; public string Name { get => _name; set => SetField(ref _name, value); } // 实现INotifyPropertyChanged... }- 映射转换层:使用AutoMapper配置
CreateMap<DeviceEntity, DeviceModel>() .ForMember(dest => dest.Status, opt => opt.MapFrom(src => src.IsOnline ? "运行中" : "离线"));4. 工业级Database层封装方案
针对上位机项目特点,我们设计了一套包含以下功能的完整封装:
核心功能模块:
- 多数据库支持
- 读写分离
- 分表分库
- 审计日志
- 性能监控
关键实现代码:
public class DatabaseContext : IDisposable { private readonly IFreeSql _freeSql; public DatabaseContext(IFreeSql freeSql) { _freeSql = freeSql; } public ISelect<T> Query<T>() where T : class { return _freeSql.Select<T>(); } public async Task<PageResult<T>> PagedQueryAsync<T>( int pageIndex, int pageSize, Expression<Func<T, bool>> where = null) where T : class { var select = _freeSql.Select<T>(); if (where != null) select = select.Where(where); long total = 0; var data = await select.Count(out total) .Page(pageIndex, pageSize) .ToListAsync(); return new PageResult<T>(data, total); } // 实现IDisposable... }性能优化建议:
- 批量操作使用
InsertBatch替代循环单条插入 - 复杂查询优先使用SQL语句而非Lambda
- 高频访问数据考虑二级缓存
- 定期监控慢查询日志
5. 典型问题排查与解决方案
问题1:MVVM绑定不更新现象:数据库更新后界面不刷新原因:未正确触发PropertyChanged事件解决:
// 在Repository中手动触发 public async Task UpdateDevice(DeviceModel model) { await _freeSql.Update<DeviceEntity>() .SetSource(model.ToEntity()) .ExecuteAffrowsAsync(); model.RaisePropertyChanged(); // 显式通知 }问题2:跨线程访问异常现象:在非UI线程修改绑定属性报错解决:
Device.BeginInvoke(() => { Status = "已更新"; });问题3:连接池耗尽现象:高并发时出现超时解决:调整连接池参数
{ "ConnectionStrings": { "MySQL": "Server=127.0.0.1;Pooling=true;MinimumPoolSize=10;MaximumPoolSize=100;" } }上位机开发中,良好的数据访问架构能显著提升系统稳定性。建议在项目初期就建立规范的数据库访问规范,避免后期大规模重构。对于工业场景特别要注意事务处理和异常恢复机制的健壮性设计。