跳到主要内容
版本:3.0

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数据库类型,例如 PostgreSQLSqlServer
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 要不要一直开

不建议。
它更适合开发环境,不适合作为生产环境的数据库变更方式。

配套阅读