如何让 EF core 10 中继承的审计属性生成在表的最后几列?
可以保留 AuditableEntity : IAuditable 的继承结构,无需在每个实体中重复定义审计属性。EF Core 10 没有内置规则,能通过 [Column(Order = ...)] 将某些属性单独标记为“始终排在最后”。
如果每个实体各自映射一张表,可以在 OnModelCreating 中统一排序:先排普通属性,再排审计属性,然后自动生成列顺序编号,无需在实体类中逐个填写。
为什么只给审计属性设置大编号无效?
EF Core 10 生成建表操作时,会先将设置了列顺序的列按编号排序,再追加没有设置顺序的列。因此,即使将审计属性设为 Order = 10000,它仍会排在未设置顺序的业务属性之前。EF Core 10 排序实现
编号大小只影响已设置顺序的列,无法表示“放在所有未设置顺序的列之后”。未设置顺序时,EF Core 也有自己的迁移排序逻辑,不能仅凭反射返回的属性顺序判断最终结果。
保留继承结构,集中生成列顺序
假设审计属性只有 CreatedAt 和 UpdatedAt:
public interface IAuditable
{
DateTime CreatedAt { get; set; }
DateTime? UpdatedAt { get; set; }
}
public abstract class AuditableEntity : IAuditable
{
public DateTime CreatedAt { get; set; }
public DateTime? UpdatedAt { get; set; }
}
public class Order : AuditableEntity
{
public int Id { get; set; }
public string Number { get; set; } = string.Empty;
}
在现有 DbContext 的 OnModelCreating 中,将排序逻辑放在其他实体配置之后:
using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
// 在这里完成现有的实体配置。
modelBuilder.Entity<Order>();
// 审计属性的名称,以及它们在审计列内部的顺序。
var auditOrder = new Dictionary<string, int>(StringComparer.Ordinal)
{
[nameof(AuditableEntity.CreatedAt)] = 0,
[nameof(AuditableEntity.UpdatedAt)] = 1
};
foreach (var entityType in modelBuilder.Model.GetEntityTypes())
{
if (!typeof(AuditableEntity).IsAssignableFrom(entityType.ClrType))
{
continue;
}
var properties = entityType.GetProperties().ToArray();
var businessProperties = properties
.Where(p => !auditOrder.ContainsKey(p.Name))
.OrderBy(p => p.IsPrimaryKey() ? 0 : 1)
.ThenBy(p => p.Name, StringComparer.Ordinal);
var auditProperties = properties
.Where(p => auditOrder.ContainsKey(p.Name))
.OrderBy(p => auditOrder[p.Name]);
var columnOrder = 0;
foreach (var property in businessProperties.Concat(auditProperties))
{
property.SetColumnOrder(columnOrder++);
}
}
}
SetColumnOrder 是关系型元数据 API,用于设置属性所映射的数据库列的顺序。API 文档
排序规则如下:
- 主键属性排在普通属性前面。
- 其他业务属性按名称排序,保证结果稳定。
- 审计属性放在最后,内部按
auditOrder中的编号排序。
示例中 Order 的目标列顺序为:
Id, Number, CreatedAt, UpdatedAt
以后新增业务属性时,排序逻辑会自动将其纳入;新增审计属性时,只需在基类和 auditOrder 中补充定义。
各属性仍会设置列顺序元数据,编号由这段集中配置自动生成。维护时,只需指定哪些属性属于审计列。
生成 migration 后检查什么?
完成配置后,生成包含新表的 migration:
dotnet ef migrations add AddAuditableTables
dotnet ef migrations script
检查 migration 中 CreateTable 的列定义,以及生成的 SQL 中 CREATE TABLE 的列顺序。
如果对应的 migration 已经生成,修改模型配置不会自动改写它。如果最后一个 migration 尚未应用,且允许重新生成,可以先移除,再重新生成。
使用时的边界
上述示例适用于每个业务实体独立映射一张表、AuditableEntity 仅作为 CLR 公共基类的情况。不要为了复用属性,额外将它注册为 EF 实体继承层次的根类型。
如果采用 TPH、TPT、TPC,或存在表拆分、映射到同一张表的 owned entity、复杂类型属性,就需要按实际表映射统一处理。在这些场景下,列与实体属性之间的关系更复杂,示例中对 GetProperties() 的排序无法直接保证整张表的最终列顺序。
这段代码会覆盖目标实体上已有的列顺序配置。如果业务属性有特定的布局要求,应调整 businessProperties 的排序规则。由于审计属性按名称识别,也应避免在派生类中用 new 隐藏同名属性。
列顺序配置主要用于新建表。对于已有表,修改 HasColumnOrder 或 SetColumnOrder 通常无法重新排列数据库中的现有列;后续新增业务列时,也不能保证它会自动插入审计列之前。列顺序文档
备注:内容仅供参考。
