FreeSQL是一款专为.NET开发者设计的轻量级对象关系映射(ORM)框架,它的核心设计理念是"简单不简陋"。我在实际项目中使用FreeSQL近两年时间,处理过从单体应用到微服务架构的各种数据访问场景。相比Entity Framework Core等重型ORM,FreeSQL最吸引我的特点是其仅3MB左右的程序集大小和接近原生ADO.NET的执行效率。
这个框架特别适合以下场景:
FreeSQL提供了近乎零配置的入门体验。比如连接SQL Server数据库,只需要几行代码:
csharp复制var fsql = new FreeSql.FreeSqlBuilder()
.UseConnectionString(DataType.SqlServer, "连接字符串")
.Build();
通过NuGet安装FreeSQL非常简单,但有几个版本选择需要注意:
对于新项目,我建议使用以下命令安装基础包:
bash复制dotnet add package FreeSql
dotnet add package FreeSql.Provider.SqlServer
重要提示:生产环境务必锁定NuGet包版本,避免自动升级导致兼容性问题。我遇到过3.2.6到3.2.8的小版本升级导致的分页查询行为变化。
开发环境下可以直接硬编码连接字符串,但生产环境建议采用更安全的方式:
csharp复制// 从配置系统读取
var connStr = builder.Configuration.GetConnectionString("Default");
// 使用加密连接字符串
var fsql = new FreeSqlBuilder()
.UseConnectionString(DataType.SqlServer,
Decrypt(connStr))
.Build();
对于多租户应用,可以这样动态切换连接:
csharp复制fsql.ChangeDatabase(tenantId, () => {
// 根据租户ID返回对应连接字符串
return GetTenantConnectionString(tenantId);
});
FreeSQL支持多种方式的实体映射,我个人推荐使用特性标注的方式:
csharp复制[Table(Name = "sys_user")]
public class User
{
[Column(IsPrimary = true, IsIdentity = true)]
public int Id { get; set; }
[Column(DbType = "nvarchar(50)")]
public string UserName { get; set; }
[Column(ServerTime = DateTimeKind.Utc)]
public DateTime CreateTime { get; set; }
}
实际项目中我总结了几条黄金规则:
插入数据时推荐使用批量插入,性能比单条插入高10倍以上:
csharp复制var users = new List<User>();
// 构造1000条测试数据
fsql.Insert(users).ExecuteAffrows();
查询操作支持链式调用,非常直观:
csharp复制var list = fsql.Select<User>()
.Where(u => u.Id > 100)
.OrderBy(u => u.CreateTime)
.Page(1, 20)
.ToList();
更新操作支持只更新变化的字段:
csharp复制var user = fsql.Select<User>().Where(u => u.Id == 1).First();
user.UserName = "新名称";
fsql.Update<User>().SetSource(user).UpdateColumns(u => u.UserName).ExecuteAffrows();
FreeSQL的表达式解析非常强大,支持各种复杂条件:
csharp复制var query = fsql.Select<User>()
.Where(u => u.UserName.Contains("admin")
&& (u.Status == 1 || u.Status == 2))
.WhereIf(!string.IsNullOrEmpty(searchKey),
u => u.Email.Contains(searchKey))
.GroupBy(u => u.DepartmentId)
.Having(u => SqlExt.Count(u.Id) > 5);
关联查询支持多种写法,这是我认为最清晰的一种:
csharp复制var list = fsql.Select<User>()
.LeftJoin<Department>((u, d) => u.DepartmentId == d.Id)
.Where((u, d) => d.Name == "研发部")
.ToList((u, d) => new {
u.Id,
u.UserName,
DepartmentName = d.Name
});
对于复杂的多表关联,建议使用SQL视图+实体映射的方式:
csharp复制[Table(Name = "v_user_detail")]
public class UserDetailView
{
public int UserId { get; set; }
public string UserName { get; set; }
public string DepartmentName { get; set; }
}
// 查询时直接使用视图实体
var list = fsql.Select<UserDetailView>().ToList();
FreeSQL提供了多种事务控制方式,最简单的是使用UnitOfWork:
csharp复制using (var uow = fsql.CreateUnitOfWork())
{
try
{
var repo = uow.GetRepository<User>();
repo.Insert(new User { UserName = "test" });
uow.GetRepository<Log>().Insert(new Log { /*...*/ });
uow.Commit();
}
catch
{
uow.Rollback();
throw;
}
}
对于跨服务的分布式事务,可以使用CAP等框架集成:
csharp复制services.AddFreeSql(option => {
option.UseFreeSql(fsql);
option.UseCap(cap => {
cap.UseEntityFramework<CapDbContext>();
cap.UseRabbitMQ("...");
});
});
csharp复制// 普通分页(小数据量)
fsql.Select<User>().Page(1, 20).ToList();
// 优化分页(大数据量)
fsql.Select<User>().Where(u => u.Id > lastId).Take(20).ToList();
csharp复制// 按月分表查询
fsql.Select<User>()
.AsTable((type, oldname) => $"user_{DateTime.Now:yyyyMM}")
.ToList();
批量插入使用ExecuteAffrows替代ExecuteInserted:
csharp复制// 低效方式(返回插入的实体)
fsql.Insert(users).ExecuteInserted();
// 高效方式(只返回影响行数)
fsql.Insert(users).ExecuteAffrows();
批量更新使用SetSource配合UpdateColumns:
csharp复制fsql.Update<User>()
.SetSource(users)
.UpdateColumns(u => new { u.Name, u.Age })
.ExecuteAffrows();
典型的三层架构中FreeSQL的定位:
code复制User.API (Controller)
↓
User.Service (业务逻辑)
↓
User.Repository (FreeSQL操作)
↓
Database
Repository层示例:
csharp复制public class UserRepository : IUserRepository
{
private readonly IFreeSql _fsql;
public UserRepository(IFreeSql fsql)
{
_fsql = fsql;
}
public Task<User> GetByIdAsync(int id)
{
return _fsql.Select<User>().Where(u => u.Id == id).FirstAsync();
}
// 其他数据访问方法...
}
一个完整的用户分页查询实现:
csharp复制public async Task<PageResult<UserDto>> QueryUsersAsync(UserQueryRequest request)
{
var query = _fsql.Select<User>()
.WhereIf(!string.IsNullOrEmpty(request.Keyword),
u => u.UserName.Contains(request.Keyword) || u.Email.Contains(request.Keyword))
.WhereIf(request.Status.HasValue,
u => u.Status == request.Status);
var total = await query.CountAsync();
var list = await query
.OrderBy(u => u.Id)
.Page(request.Page, request.PageSize)
.ToListAsync(u => new UserDto {
Id = u.Id,
Name = u.UserName,
// 其他字段映射...
});
return new PageResult<UserDto>(list, total);
}
问题现象:连接超时或连接池耗尽
解决方案:
csharp复制.UseConnectionString(DataType.SqlServer, connStr)
.UseConnectionPool(10, 100) // 最小10,最大100连接
问题现象:简单查询执行缓慢
排查步骤:
csharp复制var sql = fsql.Select<User>().Where(u => u.Id == 1).ToSql();
问题现象:并发更新导致数据不一致
解决方案:
csharp复制using (var uow = fsql.CreateUnitOfWork(IsolationLevel.ReadCommitted))
{
// 业务操作...
uow.Commit();
}
Startup.cs中的标准配置:
csharp复制services.AddSingleton<IFreeSql>(provider =>
new FreeSqlBuilder()
.UseConnectionString(DataType.SqlServer, Configuration.GetConnectionString("Default"))
.UseMonitorCommand(cmd =>
{
// SQL执行监控
Console.WriteLine(cmd.CommandText);
})
.Build());
FreeSQL支持同时连接多种数据库:
csharp复制var sqlServerFsql = new FreeSqlBuilder()
.UseConnectionString(DataType.SqlServer, sqlServerConnStr)
.Build();
var mySqlFsql = new FreeSqlBuilder()
.UseConnectionString(DataType.MySql, mySqlConnStr)
.Build();
配置主从读写分离:
csharp复制var fsql = new FreeSqlBuilder()
.UseConnectionString(DataType.SqlServer,
"主库连接字符串",
"从库1连接字符串",
"从库2连接字符串")
.Build();
经过多个项目的实战检验,我总结了以下经验:
一个生产环境推荐的配置示例:
csharp复制services.AddHealthChecks()
.AddCheck<FreeSqlHealthCheck>("freesql-check");
services.AddSingleton<IFreeSql>(_ =>
new FreeSqlBuilder()
.UseConnectionString(DataType.SqlServer, connStr)
.UseConnectionPool(10, 100)
.UseMonitorCommand(cmd =>
{
_logger.LogDebug("Executing SQL: {sql}", cmd.CommandText);
if (cmd.ExecuteSeconds > 1) // 慢查询记录
_logger.LogWarning("Slow query detected: {sql}", cmd.CommandText);
})
.Build());
在项目规模扩大后,可以考虑以下进阶方案:
FreeSQL的轻量特性使得它非常适合作为.NET技术栈中的核心数据访问组件。从我个人的使用体验来看,它在保持简单易用的同时,也能满足绝大多数企业级应用的需求。特别是在需要快速迭代的项目中,FreeSQL的开发效率优势非常明显。