1. 为什么要在WPF MVVM项目中封装FreeSql?
如果你是从Java转过来做C# WPF开发的,或者你正在用WPF做上位机、桌面工具,那你肯定对MVVM模式不陌生。这个模式最大的好处就是能把界面(View)和业务逻辑(ViewModel)彻底分开,让代码好维护、好测试。但一涉及到数据库操作,很多人就头疼了:是把SQL语句直接写在ViewModel里,还是到处new一个数据库连接?
我刚开始做WPF项目的时候,也试过直接把FreeSql的IFreeSql对象在ViewModel里到处用。代码是能跑,但问题很快就来了:第一,单元测试没法做,ViewModel和数据库绑死了;第二,同样的查询逻辑,在A页面写一遍,在B页面又复制粘贴一遍,改个字段名得找半天;第三,一旦想换个数据库,或者给操作加个统一的审计日志,那简直就是灾难。
所以,在MVVM架构里,对数据访问层进行封装,不是“可选项”,而是“必选项”。FreeSql本身已经是个非常强大的ORM了,语法糖用起来很爽。但我们要做的,是给它套上一个更适合MVVM的“外壳”,让它在ViewModel里用起来像调用本地服务一样简单自然,同时把所有的数据库“脏活累活”都隔离在后台。这样,你的ViewModel才能保持清爽,只关心要显示什么数据、响应什么命令,而不需要知道数据是从MySQL来的还是从SQL Server来的。
这种封装的核心思想,其实就是依赖注入和仓库模式的混合体。我们通过接口来定义数据操作,ViewModel只依赖接口,不依赖具体的FreeSql实现。数据访问的具体细节,包括连接管理、事务、SQL监控,都封装在服务层里。这么做之后,带来的好处是实实在在的:代码可读性直线上升,因为业务逻辑和数据访问逻辑分开了;可测试性变得极好,你可以用Mock对象轻松模拟数据库返回;最后,维护成本大大降低,因为数据库相关的改动被限制在很小的范围内。
2. 第一步:项目环境搭建与FreeSql基础配置
万事开头难,我们先从最基础的做起。假设你已经有了一个WPF项目,我用的是.NET 6或.NET 8的WPF模板,社区里.NET Framework的老项目也完全适用,原理相通。
2.1 安装必要的NuGet包
首先,打开你的项目,右键点击“依赖项”,选择“管理NuGet程序包”。别小看这一步,依赖管理是.NET现代开发的基石。我们需要安装的核心包就一个:
- FreeSql:这是主角。在NuGet搜索框里输入“FreeSql”,选择由“2881099”发布的那个官方版本。安装时,我建议选择和你项目目标框架匹配的最新稳定版。FreeSql对.NET Standard 2.0/2.1和.NET Core/5/6/7/8都有很好的支持。
根据你实际要连接的数据库,可能还需要安装对应的Provider。比如:
- FreeSql.Provider.MySql:连接MySQL或MariaDB。
- FreeSql.Provider.SqlServer:连接Microsoft SQL Server。
- FreeSql.Provider.Sqlite:连接SQLite(本地轻量数据库,桌面应用常用)。
我以最常用的MySQL为例,通常我会把FreeSql和FreeSql.Provider.MySql一起装上。安装完成后,你的.csproj文件里应该会多出相应的包引用。
2.2 配置数据库连接字符串
把连接字符串硬编码在代码里是初级做法,我们把它放到配置文件中。在WPF项目中,appsettings.json是标准选择。如果项目里没有,就在项目根目录右键“添加” -> “新建项” -> “JSON 配置文件”,命名为appsettings.json。
打开这个文件,我们来配置连接字符串。我习惯用一个ConnectionStrings节点来管理所有数据库连接,这样清晰明了:
{
"ConnectionStrings": {
"MySqlConnection": "Server=127.0.0.1;Port=3306;Database=YourDatabaseName;Uid=root;Pwd=YourPassword;Charset=utf8mb4;SslMode=none;",
"SqlServerConnection": "Server=localhost;Database=YourDatabaseName;User Id=sa;Password=YourPassword;Trusted_Connection=False;"
}
}
这里有几个我踩过坑的细节要提醒你:
- MySQL的端口:默认是3306,如果改了别忘加
Port参数。 - 字符集:强烈建议用
utf8mb4,这样才能支持所有Emoji表情和生僻字,避免存进去变成问号。 - 连接池:FreeSql内部已经做了很好的连接池管理,所以配置里一般不需要再写
Pooling=true。把连接管理交给FreeSql更省心。 - 安全性:生产环境的密码千万别直接写在这里!可以通过环境变量、用户密钥管理器或者部署时替换的方式来处理。开发阶段为了方便可以这么写,但要有这个意识。
接下来,我们需要在程序启动时读取这个配置。在WPF里,通常是在App.xaml.cs的OnStartup方法里,或者用一个专门的配置管理类。我更喜欢后一种,更清晰。我们可以创建一个ConfigurationHelper静态类,用IConfiguration来读取配置。记得通过NuGet安装Microsoft.Extensions.Configuration.Json和Microsoft.Extensions.Configuration包。
3. 核心封装:构建健壮的数据访问层
基础打好了,现在进入重头戏:封装。我们的目标是把FreeSql的能力,通过一层简洁的接口暴露给上层业务(也就是ViewModel),同时隐藏所有实现细节。
3.1 定义FreeSql实例的单例管理接口
首先,我们需要一个地方来创建和管理IFreeSql这个核心对象。直接new一个然后到处传当然可以,但不优雅,也不利于控制生命周期。我采用的方式是定义一个数据库上下文接口。
在你的项目里创建一个Database文件夹(或者叫Infrastructure、DataAccess都行,看团队习惯)。在里面添加一个接口IFreeSqlDbContext.cs:
namespace YourProjectName.Database
{
public interface IFreeSqlDbContext
{
// 暴露只读的FreeSql实例
IFreeSql FreeSql { get; }
}
}
这个接口非常简单,就一个属性。它的目的就是告诉外界:“我这儿有一个配置好的、随时可用的FreeSql实例。”ViewModel和其他服务层只需要依赖这个接口,而不需要关心这个实例是怎么来的、连的什么数据库。
接着,实现这个接口。创建FreeSqlDbContext.cs:
using FreeSql;
using Microsoft.Extensions.Configuration;
using System;
namespace YourProjectName.Database
{
public class FreeSqlDbContext : IFreeSqlDbContext
{
private readonly IFreeSql _freeSql;
public FreeSqlDbContext(IConfiguration configuration)
{
// 从配置中读取连接字符串
var connectionString = configuration.GetConnectionString("MySqlConnection");
// 构建FreeSql实例,这里可以集中进行所有全局配置
_freeSql = new FreeSqlBuilder()
.UseConnectionString(DataType.MySql, connectionString)
// 监控SQL执行,开发阶段非常有用,可以看到生成的SQL语句
.UseMonitorCommand(cmd => Console.WriteLine($"SQL: {cmd.CommandText}"))
// 自动同步实体结构到数据库(慎用于生产环境!)
.UseAutoSyncStructure(true)
// 配置实体类库(如果有的话)
// .UseNameConvert(NameConvertType.PascalCaseToUnderscoreWithLower)
.Build();
// 全局过滤器的配置也可以放在这里,比如软删除
// _freeSql.GlobalFilter.Apply<ISoftDelete>("SoftDelete", a => a.IsDeleted == false);
}
public IFreeSql FreeSql => _freeSql;
}
}
这里我做了几件关键的事:
- 依赖注入配置:构造函数接收
IConfiguration,这样连接字符串的来源就很灵活。 - 集中配置:所有FreeSql的全局设置,比如SQL监控、自动迁移、命名转换规则,都在这里一次性搞定。特别是
UseAutoSyncStructure(true),它在开发时能自动帮你建表、加字段,非常方便。但生产环境一定要关掉,或者通过迁移工具来控制结构变更,不然可能误删数据。 - 生命周期:这个
FreeSqlDbContext我通常注册为单例(Singleton)。因为IFreeSql实例本身是线程安全的,设计上就是用来单例使用的。在整个应用生命周期内用一个实例,能最大化连接池的效率。
3.2 设计通用的基础数据服务接口
有了FreeSql实例,接下来我们封装最常用的CRUD操作。目标是让每一个实体(Entity)对应的数据服务,都能继承一套标准操作方法,避免重复代码。
在Database文件夹下,我通常会再建两个子文件夹:Interfaces和Services。首先在Interfaces里创建IBaseRepository.cs(也有人喜欢叫IBaseService,看个人理解):
using System.Collections.Generic;
using System.Linq.Expressions;
namespace YourProjectName.Database.Interfaces
{
public interface IBaseRepository<TEntity> where TEntity : class
{
// 查询相关
TEntity GetById(object id);
Task<TEntity> GetByIdAs


3112

被折叠的 条评论
为什么被折叠?



