从‘Shape’到真实项目:在ASP.NET Core Web API中如何优雅地使用C#继承设计模型?
从电商系统实战看C#继承在ASP.NET Core中的高阶应用当我们在Visual Studio中新建一个ASP.NET Core Web API项目时那些自动生成的Controller基类和DbContext基类已经暗示了继承在这个框架中的核心地位。但很多开发者对继承的理解仍停留在动物-猫狗的教科书示例层面这就像拿着瑞士军刀却只用它开瓶盖。1. 电商系统中的继承架构设计在真实的电商系统开发中继承不是语法练习题而是解决以下痛点的利器重复代码每个实体类都需要ID、创建时间等基础字段统一行为所有订单都需要状态验证逻辑标准化响应API返回格式需要全局一致性审计追踪谁在什么时候修改了什么数据假设我们正在构建一个跨境电商平台产品模型的核心继承结构可以这样设计public abstract class AuditableEntity { public int Id { get; set; } public DateTime CreatedAt { get; set; } public string CreatedBy { get; set; } public DateTime? ModifiedAt { get; set; } public string ModifiedBy { get; set; } public virtual void UpdateAuditFields(string userId) { ModifiedAt DateTime.UtcNow; ModifiedBy userId; } } public class Product : AuditableEntity { public string Sku { get; set; } public decimal Price { get; set; } // 其他产品特有属性 } public class InventoryItem : AuditableEntity { public int ProductId { get; set; } public int StockQuantity { get; set; } // 库存特有属性 }这种设计带来了几个实际优势自动审计追踪所有实体自动获得修改记录能力统一标识不用在每个类中重复定义Id字段简化仓储层基础CRUD操作可以抽象到泛型仓储2. EF Core中的继承映射策略实战当这些继承的模型遇到Entity Framework Core时我们有三种主要的映射策略选择策略类型存储方式适用场景性能特点TPH (Table Per Hierarchy)单表存储所有类型子类差异小查询最快但可能产生稀疏列TPT (Table Per Type)每个类型单独表子类差异大需要JOIN操作写入较慢TPC (Table Per Concrete)只存具体类型表明确类型边界无继承关系查询优势对于我们的电商系统TPH可能是最佳选择protected override void OnModelCreating(ModelBuilder modelBuilder) { modelBuilder.EntityAuditableEntity() .HasDiscriminatorstring(EntityType) .HasValueProduct(Product) .HasValueInventoryItem(Inventory); // 其他配置... }实际项目中还需要考虑鉴别器列的索引优化批量操作时的性能影响导航属性的特殊处理3. 服务层中的行为继承模式继承在服务层的应用往往比数据层更有价值。比如支付处理服务public abstract class PaymentServiceBase { protected readonly ILogger _logger; public PaymentServiceBase(ILogger logger) { _logger logger; } public abstract TaskPaymentResult ProcessAsync(PaymentRequest request); protected virtual bool ValidateRequest(PaymentRequest request) { // 基础验证逻辑 if(request.Amount 0) { _logger.LogWarning(无效的支付金额); return false; } return true; } } public class CreditCardPaymentService : PaymentServiceBase { public CreditCardPaymentService(ILoggerCreditCardPaymentService logger) : base(logger) { } public override async TaskPaymentResult ProcessAsync(PaymentRequest request) { if(!ValidateRequest(request)) return PaymentResult.Failed(验证失败); // 信用卡特定处理逻辑 } }这种设计模式带来了日志等横切关注点的统一处理验证逻辑的可复用性模板方法模式的天然支持4. Controller层的继承技巧在ASP.NET Core中Controller继承可以解决很多重复劳动[ApiController] [Route(api/[controller])] public abstract class ApiControllerBase : ControllerBase { protected IMediator Mediator HttpContext.RequestServices.GetServiceIMediator(); protected ActionResultT HandleResultT(ResultT result) { if (result.IsSuccess) return Ok(result.Value); return BadRequest(result.Error); } } [Authorize(Roles Admin)] public class ProductsController : ApiControllerBase { [HttpGet] public async TaskActionResultListProductDto GetProducts() { var result await Mediator.Send(new GetProductsQuery()); return HandleResult(result); } }这种基类Controller提供了统一响应处理所有API返回相同格式依赖解析简化服务获取全局特性如认证授权配置5. 继承的替代方案与权衡虽然继承强大但在某些场景下组合可能更合适// 使用组合代替继承的例子 public class ProductService { private readonly IAuditTrail _auditTrail; private readonly IStockValidator _validator; public ProductService(IAuditTrail auditTrail, IStockValidator validator) { _auditTrail auditTrail; _validator validator; } public void UpdateProduct(Product product) { if(_validator.Validate(product)) { // 业务逻辑 _auditTrail.RecordUpdate(product); } } }何时选择组合而非继承当行为可能动态变化时需要模拟多重继承时不同维度关注点混合时在最近的一个库存系统重构中我们将原本深层次的继承结构改为了组合模式使得系统更灵活应对业务变化。但要注意过度使用组合也会导致服务膨胀问题。