Galosys.Foundation.EntityFrameworkCore
26.5.19.1
dotnet add package Galosys.Foundation.EntityFrameworkCore --version 26.5.19.1
NuGet\Install-Package Galosys.Foundation.EntityFrameworkCore -Version 26.5.19.1
<PackageReference Include="Galosys.Foundation.EntityFrameworkCore" Version="26.5.19.1" />
<PackageVersion Include="Galosys.Foundation.EntityFrameworkCore" Version="26.5.19.1" />
<PackageReference Include="Galosys.Foundation.EntityFrameworkCore" />
paket add Galosys.Foundation.EntityFrameworkCore --version 26.5.19.1
#r "nuget: Galosys.Foundation.EntityFrameworkCore, 26.5.19.1"
#:package Galosys.Foundation.EntityFrameworkCore@26.5.19.1
#addin nuget:?package=Galosys.Foundation.EntityFrameworkCore&version=26.5.19.1
#tool nuget:?package=Galosys.Foundation.EntityFrameworkCore&version=26.5.19.1
Galosys.Foundation.EntityFrameworkCore
成熟度: 🟢 稳定 — 生产可用,测试充分,活跃维护
简介
Galosys.Foundation.EntityFrameworkCore 基于 Entity Framework Core 提供数据访问层集成,包含自动审计、多租户、软删除、仓储模式等企业级特性。
特性
- 自动审计 - 自动填充创建者、修改者、时间戳
- 多租户支持 - 租户数据隔离和全局过滤器
- 软删除 - 标记删除而非物理删除
- 全局查询过滤器 - 自动应用删除、租户、应用过滤条件
- 雪花 ID 自动生成 - 基于 Snowflake 算法的 ID 生成
- snake_case 列名映射 - 自动将 PascalCase 属性映射到 snake_case 列名
- 分表支持 - 按年/月/日动态分表
- 仓储模式 - 通用仓储接口和实现
- 工作单元模式 - 事务管理
- 动态查询 - 基于表达式的动态查询构建
- 领域事件 - 保存时自动发布领域事件
- 乐观并发控制 - 可选的 RowVersion 并发令牌,支持冲突自动重试
- 连接弹性 - 四种数据库自动重试瞬态故障
- 读写分离 - DbCommandInterceptor 自动路由查询到副本
- 支持多种数据库 - SQL Server、PostgreSQL、MySQL、SQLite、Oracle
安装
<PackageReference Include="Galosys.Foundation.EntityFrameworkCore" Version="x.x.x" />
配置
1. 连接字符串配置
在 appsettings.json 中配置连接字符串,需包含 provider 信息:
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=MyDb;User Id=sa;Password=xxx;Provider=Microsoft.Data.SqlClient",
"Postgres": "Host=localhost;Database=mydb;Username=postgres;Password=xxx;Provider=Npgsql",
"MySql": "Server=localhost;Database=mydb;User=root;Password=xxx;Provider=MySqlConnector",
"Sqlite": "Data Source=mydb.db;Provider=Microsoft.Data.Sqlite"
}
}
2. 注册 DbContext
使用 [DbContext] 特性标记 DbContext 类,模块会自动发现并注册:
[DbContext("Default")]
public class AppDbContext : DbContext<AppDbContext>
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<User> Users => Set<User>();
public DbSet<Order> Orders => Set<Order>();
}
3. 模块自动注册
模块会自动扫描带有 [DbContext] 特性的类并注册到 DI 容器:
// 在 Program.cs 或 Startup.cs 中
services.AddDbContext(configuration);
使用示例
实体定义
// 完整实体(含审计、软删除)
[Table("users")]
public class User : FullEntity<long>
{
[SnowflakeId]
public override long Id { get; protected set; }
public string Name { get; set; }
[CreatorId]
public override long CreatorId { get; protected set; }
[CreatedAt]
public override DateTime CreatedAt { get; protected set; }
}
// 多租户实体
[Table("orders")]
public class Order : MtMaEntity
{
[SnowflakeId]
public override long Id { get; protected set; }
public string OrderNo { get; set; }
public decimal Amount { get; set; }
}
DbContext 使用
public class UserService
{
private readonly AppDbContext _context;
public UserService(AppDbContext context)
{
_context = context;
}
public IQueryable<User> GetActiveUsers()
{
return _context.Query<User>(); // 自动过滤已删除记录
}
public async Task AddUserAsync(User user)
{
await _context.Entity<User>().AddAsync(user);
await _context.SaveChangesAsync();
}
public async Task SaveWithEventsAsync()
{
await _context.SaveEntitiesAsync(); // 保存并发布领域事件
}
}
仓储模式
定义业务接口,继承 IRepository<T, TID>,再用 [Repository] 标记实现类:
// 1. 定义业务仓储接口
public interface IUserRepository : IRepository<User, long>
{
Task<User?> GetByNameAsync(string name);
}
// 2. 实现类继承 EfCoreRepository,用 [Repository] 标记自动注册
[Repository]
public class UserRepository : EfCoreRepository<AppDbContext, User, long>, IUserRepository
{
public UserRepository(AppDbContext ctx) : base(ctx) { }
public async Task<User?> GetByNameAsync(string name)
{
return await Query(u => u.Name == name).FirstOrDefaultAsync();
}
}
// 3. 使用时注入业务接口
public class UserService
{
private readonly IUserRepository _repository;
public UserService(IUserRepository repository)
{
_repository = repository;
}
public async Task<User?> GetByIdAsync(long id) => await _repository.FindOneAsync(id);
public async Task AddAsync(params User[] users)
{
await _repository.AddAsync(users);
await _repository.SaveAsync();
}
}
工作单元
行为变更:
CommitAsync现在会自动调用SaveChangesAsync,无需手动保存。内部通过_changesSaved标志位防止重复保存,兼容已有的手动SaveChangesAsync+CommitAsync双调用模式。
简化写法(推荐):
public class OrderService
{
private readonly IUnitOfWork _unitOfWork;
private readonly AppDbContext _context;
public OrderService(IUnitOfWork unitOfWork, AppDbContext context)
{
_unitOfWork = unitOfWork;
_context = context;
}
public async Task CreateOrderAsync(Order order)
{
await _unitOfWork.BeginTransactionAsync();
try
{
_context.Entity<Order>().Add(order);
// CommitAsync 自动调用 SaveChangesAsync,无需手动保存
await _unitOfWork.CommitAsync();
}
catch
{
await _unitOfWork.RollbackAsync();
throw;
}
}
}
兼容写法(手动 SaveChanges + CommitAsync):
public async Task CreateOrderAsync(Order order)
{
await _unitOfWork.BeginTransactionAsync();
try
{
_context.Entity<Order>().Add(order);
await _context.SaveChangesAsync(); // 手动保存
await _unitOfWork.CommitAsync(); // 不会重复保存(_changesSaved 标志位)
}
catch
{
await _unitOfWork.RollbackAsync();
throw;
}
}
ITimeProvider 注入
DbContext<T> 通过构造函数注入 ITimeProvider(Core 模块已注册 Singleton),审计字段自动使用 ITimeProvider 获取时间,确保时区一致且可测试。无需额外配置。
// 单元测试中替换时间源
services.AddSingleton<ITimeProvider>(new TestTimeProvider(fixedTime));
乐观并发控制
继承 ConcurrencyEntity<TID> 即可启用 RowVersion 并发令牌:
[Table("products")]
public class Product : ConcurrencyEntity<long>
{
public string Name { get; set; }
public decimal Price { get; set; }
}
冲突时自动重试更新:
public async Task UpdatePriceAsync(Product product)
{
await _repository.UpdateWithRetryAsync(product, maxRetries: 3);
}
执行策略配置
通过 [DbContext] 特性配置重试参数(默认 3 次 / 5 秒):
[DbContext("Default", MaxRetryCount = 5, MaxRetryDelaySeconds = 10)]
public class AppDbContext : DbContext<AppDbContext> { }
| 参数 | 默认值 | 说明 |
|---|---|---|
MaxRetryCount |
3 | 瞬态故障最大重试次数 |
MaxRetryDelaySeconds |
5 | 最大重试延迟(秒) |
读写分离
1. 配置主库和副本
在 appsettings.json 中添加副本连接字符串({名称}.Replica{N} 格式):
{
"ConnectionStrings": {
"Default": "Server=master-host;Database=MyDb;User Id=sa;Password=xxx;Provider=Microsoft.Data.SqlClient",
"Default.Replica1": "Server=replica1-host;Database=MyDb;User Id=sa;Password=xxx;Provider=Microsoft.Data.SqlClient",
"Default.Replica2": "Server=replica2-host;Database=MyDb;User Id=sa;Password=xxx;Provider=Microsoft.Data.SqlClient"
}
}
最小化配置仅需主库连接字符串,无副本时不启用读写分离。
2. 注册数据源
services.AddDataSources(configuration); // 自动识别副本,创建 HealthCheckedDataSourcePool
3. 使用 DataSourceContext 切换
// 定义业务仓储接口
public interface IOrderQueryRepository : IRepository<Order, long>
{
Task<List<Order>> GetRecentOrdersAsync(int count);
}
[Repository]
public class OrderQueryRepository : EfCoreRepository<AppDbContext, Order, long>, IOrderQueryRepository
{
public OrderQueryRepository(AppDbContext ctx) : base(ctx) { }
public async Task<List<Order>> GetRecentOrdersAsync(int count)
{
using (DataSourceContext.SwitchTo(DataSourceType.Replica)) // SELECT 走副本
{
return await Query()
.OrderByDescending(o => o.CreatedAt)
.Take(count)
.ToListAsync();
}
}
}
// 使用时注入业务接口
public class OrderAppService
{
private readonly IOrderQueryRepository _queryRepo;
private readonly IOrderRepository _orderRepo;
public OrderAppService(IOrderQueryRepository queryRepo, IOrderRepository orderRepo)
{
_queryRepo = queryRepo;
_orderRepo = orderRepo;
}
public async Task<Order> CreateOrderAsync(Order order)
{
await _orderRepo.AddAsync(order); // DML 自动走主库,无需手动切换
await _orderRepo.SaveAsync();
return order;
}
public Task<List<Order>> GetRecentOrdersAsync(int count)
{
return _queryRepo.GetRecentOrdersAsync(count); // 内部切换到副本
}
}
| 场景 | 行为 |
|---|---|
| 默认(Master) | 所有查询走主库 |
SwitchTo(Replica) |
SELECT 走副本 |
| DML 操作 | 强制走主库,忽略上下文 |
| 事务内 | 不切换,保持主库连接 |
using 结束 |
自动恢复之前的上下文 |
4. 嵌套切换
using (DataSourceContext.SwitchTo(DataSourceType.Master))
{
await SaveAsync(); // 写操作走主库
using (DataSourceContext.SwitchTo(DataSourceType.Replica))
{
var stats = await QueryStatsAsync(); // 读操作走副本
}
// 自动恢复为 Master
}
分表
通过 [Table] + [Sharding] 特性标记实体,框架自动拼接表名后缀:
[Table("orders")]
[Sharding(ShardingType.Month)] // 按月分表 → orders_202604
public class Order : FullEntity<long>
{
public string OrderNo { get; set; }
public decimal Amount { get; set; }
}
[Table("logs")]
[Sharding(ShardingType.Day)] // 按日分表 → logs_20260415
public class Log : FullEntity<long>
{
public string Message { get; set; }
}
[Table("reports")]
[Sharding(ShardingType.Year)] // 按年分表 → reports_2026(默认)
public class AnnualReport : FullEntity<long>
{
public string Title { get; set; }
}
| ShardingType | 示例表名 | 适用场景 |
|---|---|---|
Year(默认) |
orders_2026 |
年度汇总、报表 |
Month |
orders_202604 |
订单、交易流水 |
Day |
logs_20260415 |
日志、高频数据 |
表名后缀基于
DateTimeOffset.Now自动生成,DbContextOnModelCreating时自动路由到对应表。需配合[DbContext(Dynamic = true)]启用动态模型缓存。
读写分离 + 分表组合
[Table("order_items")]
[Sharding(ShardingType.Month)]
public class OrderItem : FullEntity<long>
{
public long OrderId { get; set; }
public string ProductName { get; set; }
public int Quantity { get; set; }
}
[Repository]
public class OrderItemRepository : EfCoreRepository<AppDbContext, OrderItem, long>, IOrderItemRepository
{
public OrderItemRepository(AppDbContext ctx) : base(ctx) { }
public async Task<List<OrderItem>> GetItemsByOrderAsync(long orderId)
{
using (DataSourceContext.SwitchTo(DataSourceType.Replica)) // 副本读取 + 按月分表
{
return await Query(i => i.OrderId == orderId).ToListAsync();
}
}
}
EntityTypeConfiguration 接口驱动配置
通过继承 EntityTypeConfigurationBase,根据实体实现的接口自动获得对应的索引和字段约束配置:
| 实体实现的接口 | 自动配置 |
|---|---|
ICreator |
CreatedAt 索引 + CreatorId 索引 + CreatorName HasMaxLength(64) |
ILastModifier |
LastModifierId 索引 + LastModifierName HasMaxLength(64) |
IMultiTenancy(仅) |
TenantId 单列索引 |
IMultiApplication(仅) |
AppId 单列索引 |
IMultiTenancy + IMultiApplication |
(TenantId, AppId) 复合索引 |
IConcurrency |
RowVersion 乐观并发令牌 |
// 用户配置 — 实体实现 IMultiTenancy,自动获得 TenantId 索引
public class SysUserConfiguration : EntityTypeConfigurationBase<SysUser, long>
{
// 基类自动配置:CreatedAt/CreatorId 索引 + LastModifierId 索引 + TenantId 索引
// 可覆写 ConfigureCore 添加自定义配置
}
// 菜单配置 — 实体实现 IMultiApplication,自动获得 AppId 索引
public class SysMenuConfiguration : EntityTypeConfigurationBase<SysMenu, long> { }
// 租户应用配置 — 实体同时实现两个接口,自动获得 (TenantId, AppId) 复合索引
public class SysTenantAppConfiguration : EntityTypeConfigurationBase<SysTenantAppConfig, long> { }
// 全局数据 — 仅 FullEntity 审计字段索引
public class SysRegionConfiguration : EntityTypeConfigurationBase<SysRegion, long> { }
说明:全局查询过滤器(软删除、租户、应用)由
DefaultGlobalFilterProvider基于IDeletable/IMultiTenancy/IMultiApplication接口统一应用;蛇形命名映射由EntityTypeConfigurationBase处理;IConcurrency自动检测并配置并发令牌。索引配置完全由接口检测驱动,无需为不同实体类型选择不同基类。
核心类
| 类/接口 | 说明 |
|---|---|
IRepository<T, TID> |
通用仓储接口,定义增删改查 |
IUnitOfWork |
工作单元接口,管理事务 |
DbContext<T> |
DbContext 基类,提供自动审计、过滤器等 |
[DbContext] |
标记 DbContext 的特性,指定连接字符串名称 |
[Repository] |
标记仓储实现类,自动注册到 DI |
EfCoreRepository<TContext, T, TID> |
EF Core 仓储实现,继承并添加 UpdateWithRetryAsync |
EfCoreUnitOfWork<TContext> |
EF Core 工作单元实现 |
ConcurrencyEntity<TID> |
乐观并发实体基类(RowVersion) |
ReadWriteSplittingInterceptor |
读写分离拦截器 |
ShardingAttribute |
分表特性 |
ShardingExtensions |
分表扩展方法(EnsureShardingTablesAsync) |
手动建表
分表场景下,应用启动时需手动创建当前周期的物理表:
// 在 Program.cs 或 Startup.cs 中调用一次
using (var scope = app.Services.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.EnsureShardingTablesAsync(); // CREATE TABLE IF NOT EXISTS 语义
}
EnsureShardingTablesAsync扫描所有[Sharding]实体,为当前周期创建物理表。表已存在时不报错。
模型缓存策略
DynamicModelCacheKeyFactory 根据 [Sharding] 粒度优化模型缓存刷新频率:
| 粒度 | 缓存刷新周期 | 说明 |
|---|---|---|
Year |
每年 1 月 1 日 | 表名年度不变,缓存年度有效 |
Month |
每月 1 日 | 表名月度不变,缓存月度有效 |
Day |
每天 | 表名每日变化,缓存每日有效 |
混合粒度取最细(Day > Month > Year)。未标记
[Sharding]的 DbContext 不启用动态缓存。
审计属性
行为变更 (v3): 审计字段填充已从
SavingChanges事件移至SaveChanges/SaveChangesAsync重写中,确保 ShardingCore 路由评估完成后再填充。对调用方无感知。审计字段同时支持
DateTime和DateTimeOffset类型,通过ITimeProvider获取时间。
| 属性 | 说明 |
|---|---|
[SnowflakeId] |
雪花 ID 自动生成 |
[CreatedAt] |
创建时间自动填充 |
[CreatorId] |
创建者 ID 自动填充 |
[CreatorName] |
创建者姓名自动填充 |
[LastModifiedAt] |
最后修改时间自动填充 |
[LastModifierId] |
最后修改者 ID 自动填充 |
[LastModifierName] |
最后修改者姓名自动填充 |
全局过滤器
模块通过 IGlobalFilterProvider 接口自动为以下接口实现全局查询过滤器:
IDeletable- 过滤已删除记录 (e.Deleted == false)IMultiTenancy- 租户数据隔离(ITenantContext.TenantId == 0 || e.TenantId == ITenantContext.TenantId)IMultiApplication- 应用数据隔离(IAppContext.AppId == 0 || e.AppId == IAppContext.AppId)
过滤器动态求值机制
过滤器表达式捕获 ITenantContext/IAppContext 对象引用(而非标量值),EF Core 每次 query 时动态求值:
- 无 header(TenantId == 0):表达式短路为
true,查全部数据 - 有 header(TenantId > 0):按 TenantId 过滤,仅返回对应租户数据
- AppId 同理
无需按租户缓存不同模型(
IModelCacheKeyFactory),单个模型 + 动态表达式即可服务所有请求。
Outbox 存储
services.AddEfCoreOutboxStore<MyDbContext>();
自动发现 OutboxMessageEntityTypeConfiguration(表 base.base_outbox_msg),一行注册即可,无需修改 DbContext 的 OnModelCreating。
OutboxSaveChangesInterceptor
AddEfCoreOutboxStore 自动注册 OutboxSaveChangesInterceptor 到 DI(Singleton)。该拦截器在 SaveChanges 时从 PendingMsgCol(AsyncLocal)取出待发送消息,同事务写入 base_outbox_msg 表。
业务代码无需额外配置:
await _db.Orders.AddAsync(order);
await _bus.PublishAsync("order.created", order); // → PendingMsgCol,不入库
await _db.SaveChangesAsync(); // 拦截器同事务写入 orders + outbox_msg
EfCoreOutboxStore
PollAsync — LINQ AsNoTracking,跨数据库兼容。标记操作 — 原生 SQL UPDATE,绕过 ChangeTracker。
依赖
- Microsoft.EntityFrameworkCore
- Microsoft.EntityFrameworkCore.Relational
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Sqlite
- Npgsql.EntityFrameworkCore.PostgreSQL
- Pomelo.EntityFrameworkCore.MySql
- Oracle.EntityFrameworkCore
- Galosys.Foundation.Core
- Galosys.Foundation.Data
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Galosys.Foundation.Core (>= 26.5.19.1)
- Galosys.Foundation.Data (>= 26.5.19.1)
- microsoft.entityframeworkcore (>= 8.0.4)
- microsoft.entityframeworkcore.relational (>= 8.0.4)
- microsoft.entityframeworkcore.sqlite (>= 8.0.4)
- microsoft.entityframeworkcore.sqlserver (>= 8.0.4)
- npgsql.entityframeworkcore.postgresql (>= 8.0.2)
- oracle.entityframeworkcore (>= 8.21.140)
- pomelo.entityframeworkcore.mysql (>= 8.0.2)
NuGet packages (4)
Showing the top 4 NuGet packages that depend on Galosys.Foundation.EntityFrameworkCore:
| Package | Downloads |
|---|---|
|
Galosys.Foundation.ShardingCore
Galosys.Foundation快速开发库 |
|
|
Galosys.Foundation.Yarp.Database
Galosys.Foundation快速开发库 |
|
|
Galosys.Foundation.DataPermission
Galosys.Foundation快速开发库 |
|
|
Galosys.Foundation.Actuator.EntityFrameworkCore
Galosys.Foundation快速开发库 |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 26.5.19.1 | 127 | 5/19/2026 |
| 26.5.18.1 | 157 | 5/18/2026 |
| 26.5.15.1 | 164 | 5/15/2026 |
| 26.5.12.3 | 161 | 5/12/2026 |
| 26.5.12.2 | 148 | 5/12/2026 |
| 26.4.27.1-rc1 | 153 | 4/26/2026 |
| 26.4.25.1-rc1 | 149 | 4/25/2026 |
| 26.4.22.2-rc7 | 150 | 4/22/2026 |
| 26.4.22.2-rc6 | 150 | 4/22/2026 |
| 26.4.22.2-rc4 | 149 | 4/22/2026 |
| 26.4.22.2-rc3 | 155 | 4/22/2026 |
| 26.4.12.8-rc1 | 120 | 4/12/2026 |
| 26.4.12.7-rc1 | 120 | 4/12/2026 |
| 26.1.30.1-rc1 | 143 | 1/30/2026 |
| 26.1.29.1 | 156 | 1/29/2026 |
| 26.1.28.5 | 149 | 1/28/2026 |
| 26.1.28.4 | 135 | 1/28/2026 |
| 26.1.28.2 | 141 | 1/28/2026 |
| 26.1.23.6 | 145 | 1/23/2026 |
| 26.1.21.1 | 139 | 1/21/2026 |