FreeSql 数据访问(Aegis.Core.FreeSql)
Aegis.Core.FreeSql 是 Aegis 中负责数据库接入、FreeSql 实例注册和仓储自动装配的基础组件。它不通过 Component.deps.json 自动启用,而是由项目在代码中显式注册。
组件概览
| 字段 | 说明 |
|---|---|
| 组件名称 | FreeSql 数据访问 |
| 真实类库 | Aegis.Core.FreeSql |
| 组件定位 | FreeSql 数据源、仓储自动注册与 ORM 基础能力 |
| 引入方式 | 安装 NuGet,并在代码中调用 AddDbSource<TDbSource>()、AddDbRepositories<TDbSource>() |
是否需要 Component.deps.json | 否 |
| 配置方式 | 通过 DbOptions 在代码中配置 |
| 核心能力 | 数据源初始化、仓储自动扫描、开发期自动建表、SQL 监视、实体属性扩展 |
| 注册入口 | AddDbSource<TDbSource>()、AddDbRepositories<TDbSource>() |
什么时候要用它
适合场景:
- 项目需要接入关系型数据库
- Repository 层要基于
FreeSql落地 - 业务需要使用
UnitOfWork做事务控制 - 需要统一启用仓储自动注册
先记住它和 Repository、Services 的边界
这组能力建议按下面的边界使用:
Aegis.Core.FreeSql负责数据库接入和仓储底座- Repository 层负责数据访问
- Services 层负责业务规则,不直接写
FreeSql查询
也就是说,日常业务代码更推荐:
- 在 Repository 里注入
IFreeSql<TDbSource> - 在 Services 里注入具体仓储类
如果你还没看过配套主题页,建议后面再结合这两页一起读:
最小可运行路径
第一步:定义数据库源类型
数据库源类型需要实现 IDbSource。
public class AegisDb : IDbSource
{
public AegisDb(IFreeSql<AegisDb> freeSql)
{
SqlClient = freeSql;
}
public IFreeSql SqlClient { get; }
}
第二步:在服务注册阶段接入数据库
var connectionString = ConfigManager.Get("PostgreConnection");
services.AddDbSource<AegisDb>(options =>
{
options.ConnectionString = connectionString;
options.DataType = "PostgreSQL";
});
services.AddDbRepositories<AegisDb>();
第三步:定义实体
[Table(Name = "UserInfo")]
public class UserInfoEntity
{
[Column(IsPrimary = true)]
public long UserSeq { get; set; }
public string UserCode { get; set; }
public string UserName { get; set; }
[Column(ServerTime = DateTimeKind.Local, CanUpdate = false)]
public DateTime CreateTime { get; set; }
[Column(ServerTime = DateTimeKind.Local)]
public DateTime UpdateTime { get; set; }
}
第四步:定义仓储
public class UserRepository : BaseRepository<UserInfoEntity, long>
{
public UserRepository(IFreeSql<AegisDb> freeSql) : base(freeSql)
{
}
public Task<UserInfoEntity> GetByUserCodeAsync(string userCode)
{
return this.Where(x => x.UserCode == userCode).FirstAsync();
}
}
第五步:在 Services 里通过仓储使用数据库
public class UserService : IUserContract
{
private readonly UserRepository _userRepository;
public UserService(UserRepository userRepository)
{
_userRepository = userRepository;
}
public async Task<UserDto> GetUserAsync(string userCode)
{
var entity = await _userRepository.GetByUserCodeAsync(userCode);
return new UserDto
{
UserCode = entity.UserCode,
UserName = entity.UserName
};
}
}
这组配置分别控制什么
AddDbSource<TDbSource>() 的配置对象是 DbOptions。
| 配置项 | 作用 |
|---|---|
DataType | 数据库类型,例如 PostgreSQL、SqlServer |
ConnectionString | 数据库连接字符串 |
UseAutoSyncStructure | 是否自动同步表结构,建议只在开发环境开启 |
UseMonitorCommand | 是否输出执行 SQL,常用于本地调试 |
常见配置示例
PostgreSQL
services.AddDbSource<AegisDb>(options =>
{
options.ConnectionString = ConfigManager.Get("PostgreConnection");
options.DataType = "PostgreSQL";
});
SQL Server
services.AddDbSource<AegisDb>(options =>
{
options.ConnectionString = ConfigManager.Get("SqlServerConnection");
options.DataType = "SqlServer";
});
开发环境自动同步表结构
services.AddDbSource<AegisDb>(options =>
{
options.ConnectionString = ConfigManager.Get("PostgreConnection");
options.DataType = "PostgreSQL";
options.UseAutoSyncStructure = true;
});
适用场景:
- 本地开发新表、新字段时快速验证
- Demo 或临时测试环境快速初始化结构
不建议这样用的场景:
- 生产环境
- 已有严格数据库变更流程的项目
本地调试时输出 SQL
services.AddDbSource<AegisDb>(options =>
{
options.ConnectionString = ConfigManager.Get("PostgreConnection");
options.DataType = "PostgreSQL";
options.UseMonitorCommand = true;
});
仓储自动注册是怎么生效的
AddDbRepositories<TDbSource>() 会扫描当前数据库源所在程序集,把基于 FreeSql 仓储基类实现的仓储注册到容器里。
services.AddDbRepositories<AegisDb>();
这也是为什么更推荐把下面这些内容放在同一个业务程序集里:
AegisDb- 实体
- 仓储
实体层最常见的约定
主键
[Column(IsPrimary = true)]
public long UserSeq { get; set; }
创建时间只在插入时赋值
[Column(ServerTime = DateTimeKind.Local, CanUpdate = false)]
public DateTime CreateTime { get; set; }
更新时间在更新时自动刷新
[Column(ServerTime = DateTimeKind.Local)]
public DateTime UpdateTime { get; set; }
插入默认值
[Column(InsertValueSql = "'0'")]
public bool IsDeleted { get; set; }
软删除过滤怎么开启
如果你的表希望默认过滤掉已删除数据,最常见的做法是给实体补一个删除标记字段,并让它在插入时默认是未删除状态。
[Table(Name = "UserInfo")]
public class UserInfoEntity
{
[Column(IsPrimary = true)]
public long UserSeq { get; set; }
public string UserCode { get; set; }
public string UserName { get; set; }
[Column(InsertValueSql = "'0'")]
public bool IsDeleted { get; set; }
}
这样做之后,Repository 层日常查询通常只会查到未删除的数据。
public class UserRepository : BaseRepository<UserInfoEntity, long>
{
public UserRepository(IFreeSql<AegisDb> freeSql) : base(freeSql)
{
}
public Task<UserInfoEntity> GetByUserCodeAsync(string userCode)
{
return this.Where(x => x.UserCode == userCode).FirstAsync();
}
}
业务上做“删除”时,也更推荐改成更新删除标记,而不是物理删除。
public async Task<bool> DeleteUserAsync(long userSeq)
{
var user = await _userRepository.Where(x => x.UserSeq == userSeq).FirstAsync();
user.IsDeleted = true;
return await _userRepository.UpdateAsync(user);
}
临时关闭软删除过滤
如果当前查询场景需要把已删除数据也一起查出来,可以在仓储里临时关闭软删除过滤。
public List<UserInfoEntity> GetAllWithDeleted()
{
using (this.DisableDeleted())
{
return this.ToList();
}
}
这里建议一定配合 using 使用,这样过滤范围只在当前查询代码块内生效,不会污染后续查询。
和事务该怎么配合
如果你的数据库写入需要事务边界,建议把事务定义在 Services 层,然后让多个仓储参与同一个 UnitOfWork。
using (var uow = _freeSql.CreateUnitOfWork())
{
_userRepository.UnitOfWork = uow;
_accountRepository.UnitOfWork = uow;
try
{
await _userRepository.UpdateAsync(user);
await _accountRepository.UpdateAsync(account);
uow.Commit();
}
catch
{
uow.Rollback();
throw;
}
}
更详细的事务说明见 事务。
接入后怎么确认已经生效
你可以用下面几种方式确认当前组件已经接好了:
- 应用启动时没有数据库初始化异常
- 仓储类可以被容器正常注入
- Repository 层能正常完成增删改查
- 开启
UseMonitorCommand后,本地控制台能看到执行 SQL
常见问题
仓储注入失败
优先检查:
- 是否已经调用
AddDbRepositories<TDbSource>() - 仓储是否位于当前数据库源所在程序集
- 仓储是否继承了
BaseRepository<TEntity, TKey>
Services 里想直接注入 IFreeSql<TDbSource> 可以吗
原则上不推荐。
更稳的写法是:
- Repository 层处理数据库访问
- Services 层通过仓储组织业务
这样后续事务边界、查询封装和 DDD 分层都会更清晰。
UseAutoSyncStructure 要不要一直开
不建议。
它更适合开发环境,不适合作为生产环境的数据库变更方式。