FreeSQL 是一款专为.NET开发者设计的轻量级ORM框架,它以极简的API设计和卓越的性能表现著称。作为传统重量级ORM的替代方案,FreeSQL在中小型项目中展现出明显的效率优势。我在实际项目中使用FreeSQL已有两年时间,处理过从简单的CRUD操作到复杂的分库分表场景,其稳定性令人印象深刻。
这个框架最吸引我的特点是它的"零配置"理念。与需要大量XML或Attribute配置的传统ORM不同,FreeSQL采用约定优于配置的原则。例如,默认情况下它会自动将类名映射为表名,属性名映射为字段名,这种智能约定让开发效率提升显著。在最近的一个电商系统开发中,我们仅用3天就完成了原本需要1周的数据库层开发工作。
FreeSQL支持几乎所有主流数据库,包括MySQL、PostgreSQL、SQLite、Oracle和SQL Server等。特别值得一提的是它对国产数据库的兼容性,比如对达梦、人大金仓的支持,这在某些特定行业项目中是硬性要求。框架采用统一的API接口操作不同数据库,切换数据库类型时几乎不需要修改代码,这种设计在需要支持多数据库的项目中优势明显。
性能方面,FreeSQL的查询执行效率比某些主流ORM快30%-50%。这得益于其精心优化的SQL生成算法和高效的缓存机制。在压力测试中,FreeSQL处理每秒5000次简单查询时,CPU占用率比同类产品低15%左右。对于高并发场景,这个差距会直接影响系统的扩展成本。
开始使用FreeSQL前,需要准备.NET开发环境。推荐使用Visual Studio 2022或Rider作为IDE,它们对FreeSQL都有良好的智能提示支持。项目目标框架建议选择.NET 6或更高版本,因为这些版本对FreeSQL的新特性支持最完善。
通过NuGet安装FreeSQL非常简单,在包管理器控制台执行:
bash复制Install-Package FreeSql
如果需要使用特定数据库,还需安装对应的数据库驱动包,例如MySQL:
bash复制Install-Package FreeSql.Provider.MySql
注意:生产环境建议锁定FreeSQL的版本号,避免自动升级导致兼容性问题。我们曾遇到过因小版本升级导致的查询语法变化,锁定版本可以确保稳定性。
创建IFreeSql实例是使用框架的第一步,这是整个FreeSQL的核心对象。建议在应用程序启动时创建并全局单例使用。以下是典型的初始化代码:
csharp复制static IFreeSql fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(FreeSql.DataType.MySql, "连接字符串")
.UseAutoSyncStructure(true) // 自动同步实体结构到数据库
.UseMonitorCommand(cmd => Trace.WriteLine(cmd.CommandText)) // 打印SQL日志
.Build();
关键配置项说明:
UseAutoSyncStructure:设置为true时,FreeSQL会自动对比实体类与数据库表的差异,并生成迁移SQL。这在开发阶段非常方便,但生产环境建议关闭。UseMonitorCommand:用于输出执行的SQL语句,调试时极为有用。我们项目组要求所有开发环境必须开启此选项。对于ASP.NET Core项目,更推荐使用依赖注入方式:
csharp复制services.AddSingleton<IFreeSql>(provider =>
new FreeSqlBuilder()
.UseConnectionString(DataType.MySql, Configuration.GetConnectionString("Default"))
.Build());
FreeSQL的实体类就是普通的POCO类,不需要继承特定基类或添加复杂特性。以下是一个典型的用户实体定义:
csharp复制[Table(Name = "sys_user")] // 指定表名
public class User
{
[Column(IsPrimary = true, IsIdentity = true)] // 主键且自增
public int Id { get; set; }
[Column(DbType = "varchar(50)")] // 指定字段类型
public string UserName { get; set; }
public string Password { get; set; } // 默认映射为nvarchar(255)
[Column(ServerTime = DateTimeKind.Utc)] // 使用UTC时间
public DateTime CreateTime { get; set; }
[Navigate(nameof(Department.Id))] // 导航属性
public Department Dept { get; set; }
}
实体设计时的经验建议:
FreeSQL的CRUD API设计极其简洁,以下是最常用的操作示例:
插入数据:
csharp复制var user = new User { UserName = "admin", Password = "123456" };
var id = fsql.Insert(user).ExecuteIdentity(); // 返回自增ID
// 批量插入
var users = new List<User> { /*...*/ };
fsql.Insert(users).ExecuteAffrows();
更新数据:
csharp复制fsql.Update<User>()
.Set(u => u.Password, "newpassword")
.Where(u => u.Id == 1)
.ExecuteAffrows();
查询数据:
csharp复制// 获取单个实体
var user = fsql.Select<User>().Where(u => u.Id == 1).First();
// 获取列表
var users = fsql.Select<User>()
.Where(u => u.UserName.Contains("admin"))
.OrderBy(u => u.Id)
.ToList();
删除数据:
csharp复制fsql.Delete<User>().Where(u => u.Id == 1).ExecuteAffrows();
实际项目中发现:ExecuteAffrows()返回受影响行数在事务中特别有用,可以确保操作确实生效。我们曾遇到过因未检查返回值导致业务逻辑错误的情况。
FreeSQL提供了强大的查询构建能力,支持几乎所有SQL特性:
csharp复制// 分页查询
var list = fsql.Select<User>()
.Where(u => u.CreateTime > DateTime.Now.AddDays(-7))
.OrderByDescending(u => u.Id)
.Count(out var total) // 获取总数
.Page(1, 20) // 第一页,每页20条
.ToList();
// 条件组合
var query = fsql.Select<User>();
if (!string.IsNullOrEmpty(searchKey))
query = query.Where(u => u.UserName.Contains(searchKey));
if (departmentId > 0)
query = query.Where(u => u.DepartmentId == departmentId);
// 联表查询
var result = fsql.Select<User>()
.LeftJoin<Department>((u, d) => u.DepartmentId == d.Id)
.Where((u, d) => d.Name == "研发部")
.ToList((u, d) => new { u, DepartmentName = d.Name });
FreeSQL的导航属性让关联操作变得异常简单:
csharp复制// 定义包含导航属性的实体
public class Department
{
public int Id { get; set; }
public string Name { get; set; }
[Navigate(nameof(User.DepartmentId))]
public List<User> Users { get; set; }
}
// 使用Include加载关联数据
var deptWithUsers = fsql.Select<Department>()
.Include(d => d.Users)
.Where(d => d.Id == 1)
.First();
级联操作也非常方便:
csharp复制// 级联保存
var dept = new Department {
Name = "新部门",
Users = new List<User> { new User { UserName = "新用户" } }
};
fsql.Insert(dept).ExecuteAffrows(); // 会自动保存关联的用户
// 级联删除(需要配置外键约束)
fsql.Delete<Department>().Where(d => d.Id == 1).ExecuteAffrows();
重要经验:大量级联操作时建议手动控制事务,我们曾遇到过因自动事务超时导致的操作失败。最佳实践是显式创建事务:
csharp复制using (var uow = fsql.CreateUnitOfWork())
{
var repo = uow.GetRepository<Department>();
// 操作代码...
uow.Commit();
}
FreeSQL提供了多种性能优化手段:
csharp复制// 延迟加载(需要配置导航属性)
var user = fsql.Select<User>().Where(u => u.Id == 1).First();
var deptName = user.Dept?.Name; // 此时才会查询部门表
// 贪婪加载(一次性加载)
var user = fsql.Select<User>()
.Include(u => u.Dept)
.First();
csharp复制// 批量插入比单条插入快10倍以上
fsql.Insert(users).ExecuteAffrows();
// 批量更新
fsql.Update<User>()
.Set(u => u.IsActive, false)
.Where(u => ids.Contains(u.Id))
.ExecuteAffrows();
csharp复制var fsql = new FreeSqlBuilder()
.UseConnectionString(DataType.MySql, "写库连接字符串")
.UseSlave("从库1连接字符串")
.UseSlave("从库2连接字符串")
.Build();
csharp复制// 定义软删除接口
public interface ISoftDelete {
bool IsDeleted { get; set; }
}
// 配置全局过滤器
fsql.GlobalFilter.Apply<ISoftDelete>("soft_delete", d => d.IsDeleted == false);
// 查询时会自动过滤已删除数据
var users = fsql.Select<User>().ToList();
csharp复制fsql.GlobalFilter.ApplyIf<ITenant>(
() => !string.IsNullOrEmpty(tenantId),
"tenant_filter",
d => d.TenantId == tenantId
);
csharp复制fsql.Aop.CurdAfter += (s, e) => {
if (e.ElapsedMilliseconds > 200)
logger.Warning($"慢SQL: {e.Sql}");
};
假设我们需要实现一个电商系统的用户模块,主要功能包括:
数据库设计如下:
sql复制CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(50) NOT NULL,
password VARCHAR(100) NOT NULL,
phone VARCHAR(20),
email VARCHAR(100),
status TINYINT DEFAULT 1,
create_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE user_address (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
receiver VARCHAR(50) NOT NULL,
address TEXT NOT NULL,
is_default BIT DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users(id)
);
用户服务层实现:
csharp复制public class UserService
{
private readonly IFreeSql _fsql;
public UserService(IFreeSql fsql) => _fsql = fsql;
public async Task<int> Register(UserRegisterDto dto)
{
if (await _fsql.Select<User>().AnyAsync(u => u.UserName == dto.UserName))
throw new Exception("用户名已存在");
var user = new User {
UserName = dto.UserName,
Password = BCrypt.HashPassword(dto.Password),
Email = dto.Email,
Phone = dto.Phone
};
return await _fsql.Insert(user).ExecuteIdentityAsync();
}
public async Task<User> Login(string username, string password)
{
var user = await _fsql.Select<User>()
.Where(u => u.UserName == username)
.FirstAsync();
if (user == null || !BCrypt.Verify(password, user.Password))
throw new Exception("用户名或密码错误");
return user;
}
}
地址服务层实现:
csharp复制public class AddressService
{
private readonly IFreeSql _fsql;
public AddressService(IFreeSql fsql) => _fsql = fsql;
public async Task<List<Address>> GetUserAddresses(int userId)
{
return await _fsql.Select<Address>()
.Where(a => a.UserId == userId)
.ToListAsync();
}
public async Task SetDefaultAddress(int userId, int addressId)
{
using (var uow = _fsql.CreateUnitOfWork())
{
await uow.Orm.Update<Address>()
.Set(a => a.IsDefault, false)
.Where(a => a.UserId == userId)
.ExecuteAffrowsAsync();
await uow.Orm.Update<Address>()
.Set(a => a.IsDefault, true)
.Where(a => a.Id == addressId && a.UserId == userId)
.ExecuteAffrowsAsync();
uow.Commit();
}
}
}
在电商系统的实际开发中,我们针对高并发场景做了以下优化:
csharp复制var user = _fsql.Select<User>()
.Where(u => u.Id == userId)
.WithCache(TimeSpan.FromMinutes(5))
.First();
csharp复制await _fsql.Insert(users).ExecuteAffrowsAsync();
csharp复制services.AddSingleton<IFreeSql>(provider =>
new FreeSqlBuilder()
.UseConnectionString(DataType.MySql, masterConnStr)
.UseSlave(slave1ConnStr)
.UseSlave(slave2ConnStr)
.Build());
问题1:实体修改后数据库表未自动更新
问题2:导航属性加载异常
问题3:批量插入速度慢
问题1:连接池耗尽
问题2:慢SQL导致性能问题
csharp复制.UseMonitorCommand(cmd =>
logger.LogInformation($"SQL: {cmd.CommandText}"))
问题3:主从同步延迟
csharp复制_fsql.Select<User>().WithConnection(_fsql.Ado.MasterPool).ToList();
经过多个项目的实践验证,我们总结了以下FreeSQL使用准则:
生命周期管理:
事务控制:
性能调优:
安全规范:
团队协作:
在最近的一个百万级用户系统中,通过遵循这些规范,我们实现了: