C#中如何通过接口默认实现实现实体审计的自动调用
优化方案
方案1:在基类BaseEntity中统一处理审计逻辑
既然所有实体都继承自BaseEntity,可以直接在基类构造函数中检查当前实例是否实现IAuditable,若实现则自动触发审计逻辑,从根源避免子类手动调用的遗漏问题。同时通过构造注入解决userNameProvider的依赖传递:
// 定义用户信息提供者接口 public interface IUserNameProvider { Guid GetUserId(); } // 修改基类逻辑 public abstract class BaseEntity { public Guid Id { get; } = Guid.NewGuid(); protected BaseEntity(IUserNameProvider userNameProvider) { if (this is IAuditable auditable) { auditable.CreatedBy = userNameProvider.GetUserId(); auditable.Audit(); } } } // 调整IAuditable接口,拆分职责 public interface IAuditable { DateTime CreatedTime { get; set; } Guid CreatedBy { get; set; } void Audit() { CreatedTime = DateTime.UtcNow; } } // 子类只需继承+实现接口,无需手动处理审计 public class Car : BaseEntity, IAuditable { public string Name { get; private set; } public DateTime CreatedTime { get; set; } public Guid CreatedBy { get; set; } public Car(string name, IUserNameProvider userNameProvider) : base(userNameProvider) { Name = name; } }
所有继承BaseEntity且实现IAuditable的实体,实例化时都会自动执行审计逻辑,无需子类编写额外代码,也避免了类型转换操作。
方案2:用泛型工厂封装实体创建逻辑
如果不想修改基类构造,可通过泛型工厂统一管理实体创建流程,确保审计逻辑被强制执行:
public interface IEntityFactory { T Create<T>(Func<T> factoryMethod) where T : BaseEntity; } public class EntityFactory : IEntityFactory { private readonly IUserNameProvider _userNameProvider; public EntityFactory(IUserNameProvider userNameProvider) { _userNameProvider = userNameProvider; } public T Create<T>(Func<T> factoryMethod) where T : BaseEntity { var entity = factoryMethod(); if (entity is IAuditable auditable) { auditable.CreatedBy = _userNameProvider.GetUserId(); auditable.Audit(); } return entity; } } // 使用示例 var factory = new EntityFactory(new DefaultUserNameProvider()); // 替换为实际实现 var car = factory.Create(() => new Car("bmw"));
这种方式无需修改基类,所有实体创建都通过工厂完成,既能保证审计逻辑不遗漏,也可扩展其他公共创建逻辑。
额外优化:强化审计属性的安全性
可将CreatedTime和CreatedBy的外部访问权限设为只读,仅允许内部逻辑修改,避免外部随意篡改:
public interface IAuditable { DateTime CreatedTime { get; } Guid CreatedBy { get; } void Audit(Guid createdBy) { (this as IAuditableInternal).CreatedTime = DateTime.UtcNow; (this as IAuditableInternal).CreatedBy = createdBy; } } // 内部接口用于属性设置 internal interface IAuditableInternal { DateTime CreatedTime { get; set; } Guid CreatedBy { get; set; } } // 子类实现双接口 public class Car : BaseEntity, IAuditable, IAuditableInternal { public string Name { get; private set; } DateTime IAuditable.CreatedTime { get; } Guid IAuditable.CreatedBy { get; } DateTime IAuditableInternal.CreatedTime { get; set; } Guid IAuditableInternal.CreatedBy { get; set; } public Car(string name, IUserNameProvider userNameProvider) : base(userNameProvider) { Name = name; } }
外部代码仅能读取审计属性,无法直接修改,保证审计数据的完整性。
内容的提问来源于stack exchange,提问作者Simon Great
相关产品推荐
相关产品推荐

