如何在EF Core中为电子发票系统实现按供应商维度的自增整数列?
解决方案:实现供应商维度的发票自增编号
这是一个非常合理的需求——分维度的自增编号确实能大幅提升特定场景下的检索效率,同时优化打印等用户体验。下面是针对你的EF Core配置和需求的完整实现方案:
1. 修改Invoice实体结构
首先需要在Invoice类中新增两个关键属性:显式的供应商外键(方便后续逻辑处理)和供应商维度的自增编号:
public class Invoice { public Guid Id { get; set; } // 全局GUID主键(保留) public string Title { get; set; } public string Description { get; set; } public DateTime Date { get; set; } public decimal TotalValue { get; set; } public decimal TaxValue { get; set; } // 显式供应商外键(建议添加,简化关联逻辑) public Guid ProviderId { get; set; } public Provider Provider { get; set; } public Taker Taker { get; set; } public ServiceType ServiceType { get; set; } // 新增:供应商维度的自增发票编号 public int ProviderInvoiceNumber { get; set; } }
2. 更新EF Core实体配置
接下来调整你的EntityTypeBuilder配置,添加组合唯一索引(确保同一供应商的编号不会重复),并优化导航属性关联:
public void Configure(EntityTypeBuilder<Invoice> builder) { builder.HasKey(x => x.Id); // 保留GUID主键 // 原有必填属性配置不变 builder.Property(x => x.Title) .IsRequired() .HasMaxLength(100); builder.Property(x => x.Description) .IsRequired(); builder.Property(x => x.Date) .IsRequired(); builder.Property(x => x.TotalValue) .IsRequired(); builder.Property(x => x.TaxValue) .IsRequired(); // 显式配置供应商关联(假设Provider类有对应的Invoices集合) builder.HasOne(x => x.Provider) .WithMany(p => p.Invoices) .HasForeignKey(x => x.ProviderId) .OnDelete(DeleteBehavior.Restrict); // 根据业务需求调整删除规则 builder.HasOne(x => x.Taker); builder.HasOne(x => x.ServiceType); // 关键:添加ProviderId + ProviderInvoiceNumber的组合唯一索引 // 确保同一供应商下的发票编号唯一,同时提升查询效率 builder.HasIndex(x => new { x.ProviderId, x.ProviderInvoiceNumber }) .IsUnique(); }
3. 实现分维度自增逻辑
EF Core没有内置的“按分组自增”功能,所以需要选择以下两种方案之一:
方案A:数据库触发器(推荐高并发场景)
通过数据库触发器在插入时自动计算当前供应商的最大编号并加1,以SQL Server为例:
CREATE TRIGGER trg_Invoice_GenerateProviderInvoiceNumber ON Invoices INSTEAD OF INSERT AS BEGIN SET NOCOUNT ON; INSERT INTO Invoices (Id, Title, Description, Date, TotalValue, TaxValue, ProviderId, TakerId, ServiceTypeId, ProviderInvoiceNumber) SELECT i.Id, i.Title, i.Description, i.Date, i.TotalValue, i.TaxValue, i.ProviderId, i.TakerId, i.ServiceTypeId, -- 取当前供应商的最大编号,无则从1开始 COALESCE((SELECT MAX(ProviderInvoiceNumber) FROM Invoices WHERE ProviderId = i.ProviderId), 0) + 1 FROM inserted i; END
优点:逻辑在数据库层面,无需应用层额外处理,高并发下冲突概率低;缺点:调试和跨数据库迁移需要适配不同语法。
方案B:应用层事务处理(适合低并发场景)
在插入发票前,通过事务查询当前供应商的最大编号并赋值,避免并发冲突:
public async Task<Invoice> CreateInvoiceAsync(Invoice newInvoice) { if (newInvoice.ProviderId == Guid.Empty) throw new ArgumentException("必须指定供应商ID"); // 使用事务包裹查询和插入操作,避免并发导致的编号重复 using (var transaction = await _dbContext.Database.BeginTransactionAsync()) { try { // 获取当前供应商的最大发票编号,无则返回0 var maxNumber = await _dbContext.Invoices .Where(i => i.ProviderId == newInvoice.ProviderId) .MaxAsync(i => (int?)i.ProviderInvoiceNumber) ?? 0; newInvoice.ProviderInvoiceNumber = maxNumber + 1; _dbContext.Invoices.Add(newInvoice); await _dbContext.SaveChangesAsync(); await transaction.CommitAsync(); return newInvoice; } catch (Exception) { await transaction.RollbackAsync(); throw; } } }
优点:逻辑直观,易于维护;缺点:高并发下可能出现冲突,需要额外添加重试机制。
4. 优化检索逻辑
有了组合索引后,你可以快速通过“供应商ID + 发票编号”查询目标发票,效率远高于GUID检索:
public async Task<Invoice> GetInvoiceByProviderNumberAsync(Guid providerId, int invoiceNumber) { return await _dbContext.Invoices .FirstOrDefaultAsync(i => i.ProviderId == providerId && i.ProviderInvoiceNumber == invoiceNumber); }
这样用户只需输入简洁的供应商发票编号,就能快速定位到目标记录,打印时也可以直接使用这个编号替代冗长的GUID。
内容的提问来源于stack exchange,提问作者Gabriel Francisco
相关产品推荐
相关产品推荐

