在ASP.NET Core中使用EF Core 6生成自增格式序列编号的实现咨询
在ASP.NET Core中使用EF Core 6生成自增格式序列编号的实现咨询
当然可以在EF Core 6里实现这个需求啦!我给你整理了几个实用的方案,你可以根据自己的业务场景挑合适的:
方案一:数据库自增主键+格式化(推荐,简单可靠)
这个方案利用数据库的自增特性保证唯一性,再通过格式化生成你要的BAN00X格式编号,是最省心的做法。
方式1:代码层面计算(不存数据库)
给实体加一个自增的int类型主键,再用计算属性生成格式化编号:
public class Client { // 数据库自增主键,由EF Core自动维护 public int Id { get; set; } // 计算属性,运行时生成编号,不会存储到数据库 public string ClientCode => $"BAN{Id:D3}"; // 其他业务属性... public string Name { get; set; } }
这种方式的优点是完全不用额外处理编号生成,查询或展示时直接用ClientCode就行;缺点是编号不会存在数据库里,如果需要基于编号做数据库查询,得用Id来转换。
方式2:数据库计算列(存数据库)
如果需要把ClientCode存储到数据库,可以用EF Core配置数据库计算列,让数据库自动帮你生成:
protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.Entity<Client>() .Property(c => c.ClientCode) // 数据库自动计算:前缀BAN + 格式化后的Id(补前导零到3位) .HasComputedColumnSql("'BAN' + FORMAT(Id, '000')"); }
插入数据时你只需要处理其他业务属性,Id会自增,ClientCode由数据库自动生成,完全不用你操心。
方案二:EF Core拦截器实现保存前自动生成(适合需要提前拿到编号的场景)
如果必须在代码保存前就获取到ClientCode,可以用EF Core的SaveChangesInterceptor拦截保存操作,自动生成编号:
1. 创建拦截器类
public class ClientCodeInterceptor : SaveChangesInterceptor { private readonly AppDbContext _dbContext; public ClientCodeInterceptor(AppDbContext dbContext) { _dbContext = dbContext; } public override async ValueTask<InterceptionResult<int>> SavingChangesAsync( DbContextEventData eventData, InterceptionResult<int> result, CancellationToken cancellationToken = default) { // 筛选出新增的Client实体 var addedClients = eventData.Context?.ChangeTracker .Entries<Client>() .Where(e => e.State == EntityState.Added) .ToList(); if (addedClients?.Any() == true) { // 查询当前最大的编号数字部分(加锁避免并发冲突) var maxNumber = await _dbContext.Clients .FromSqlRaw("SELECT ISNULL(MAX(CAST(SUBSTRING(ClientCode, 4, 3) AS INT)), 0) FROM Clients WITH (UPDLOCK, HOLDLOCK)") .AsEnumerable() .FirstAsync(cancellationToken); // 给每个新增的Client生成编号 foreach (var clientEntry in addedClients) { maxNumber++; clientEntry.Entity.ClientCode = $"BAN{maxNumber:D3}"; } } return await base.SavingChangesAsync(eventData, result, cancellationToken); } }
2. 注册拦截器
在Program.cs里把拦截器注入到EF Core上下文:
builder.Services.AddDbContext<AppDbContext>(options => options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")) .AddInterceptors(new ClientCodeInterceptor()));
注意:这里用了UPDLOCK, HOLDLOCK锁来避免高并发场景下的重复编号问题,确保生成编号的操作是原子性的。
方案三:手动生成(低并发场景可用)
如果你的系统并发量很低,也可以在添加客户端时手动查询最大编号再生成新的:
public async Task<Client> AddNewClientAsync(Client newClient) { // 获取当前最大的编号,默认是BAN000 var maxExistingCode = await _dbContext.Clients .Select(c => c.ClientCode) .DefaultIfEmpty("BAN000") .MaxAsync(); // 提取数字部分并加1 var currentMaxNumber = int.Parse(maxExistingCode.Substring(3)); newClient.ClientCode = $"BAN{currentMaxNumber + 1:D3}"; _dbContext.Clients.Add(newClient); await _dbContext.SaveChangesAsync(); return newClient; }
这种方式代码简单,但高并发下可能出现多个请求拿到相同的最大编号,导致重复,所以只适合用户量少、操作不频繁的场景。
备注:内容来源于stack exchange,提问作者Kushal Gautam
相关产品推荐
相关产品推荐

