.NET通用CRUD类实现:用Active标记替代物理删除的方案
泛型CRUD基类实现逻辑删除(设置Active标记)
问题描述
我有大量需要执行CRUD操作的表,因此创建了一个用于基础CRUD方法的基类。代码如下:
public abstract class OTCCrudBaseClass<TEntity> where TEntity : class { protected readonly SpkContext _context; private readonly DbSet<TEntity> _entities; public OTCCrudBaseClass(SpkContext context) { _context = context; } public async Task<IEnumerable<TEntity>> GetAllAsync() { return await _entities.ToListAsync(); } public async Task<TEntity> GetByIdAsync(Guid id) { return await _entities.FindAsync(id); } public async Task InsertAsync(TEntity entity) { await _entities.AddAsync(entity); await _context.SaveChangesAsync(); } public async Task UpdateAsync(TEntity entity) { _entities.Attach(entity); _context.Entry(entity).State = EntityState.Modified; await _context.SaveChangesAsync(); } public async Task DeleteAsync(Guid id) { TEntity entityToDelete = await _entities.FindAsync(id); //Do not delete, set the enabled flag to 0 //_entities.Remove(entityToDelete); await _context.SaveChangesAsync(); } }
但在DeleteAsync方法中,我不希望物理删除记录。大多数支持删除操作的表都有一个名为“Active”的标记,我希望将该标记设为0,以此表示记录在系统中不再活跃,但仍可用于报表或在某些情况下重新激活旧记录。在此泛型实体的场景下该如何实现?我可以通过反射来操作,但仅为设置一个标记而使用反射似乎性能开销过大。
解决方案
方案1:定义统一接口约束(推荐,类型安全+高性能)
创建一个包含Active属性的接口,让所有需要逻辑删除的实体实现该接口,基类通过泛型约束直接访问属性,完全避免反射。
1. 定义逻辑删除接口
public interface ISoftDeletable { int Active { get; set; } // 若你的字段是bool类型,可改为bool Active { get; set; } }
2. 修改基类泛型约束与DeleteAsync方法
public abstract class OTCCrudBaseClass<TEntity> where TEntity : class, ISoftDeletable { protected readonly SpkContext _context; private readonly DbSet<TEntity> _entities; public OTCCrudBaseClass(SpkContext context) { _context = context; _entities = context.Set<TEntity>(); // 补全_entities的初始化代码 } // 保留原有CRUD方法... public async Task DeleteAsync(Guid id) { TEntity entityToDelete = await _entities.FindAsync(id); if (entityToDelete != null) { entityToDelete.Active = 0; // 对应bool类型则设为false await _context.SaveChangesAsync(); } } }
3. 实体类实现接口
public class Customer : ISoftDeletable { public Guid Id { get; set; } public string Name { get; set; } public int Active { get; set; } // 实现ISoftDeletable接口的属性 // 其他业务属性... }
方案2:使用EF Core Entry.Property直接设置(无需修改实体)
如果不想给实体添加接口约束,可利用EF Core的实体条目(Entry)直接定位Active属性,依赖EF Core的元数据缓存,性能远优于反射:
public async Task DeleteAsync(Guid id) { TEntity entityToDelete = await _entities.FindAsync(id); if (entityToDelete != null) { // 直接通过Entry.Property设置属性值,EF Core会自动处理类型转换 _context.Entry(entityToDelete).Property("Active").CurrentValue = 0; await _context.SaveChangesAsync(); } }
方案3:预编译表达式树(灵活且高性能)
通过预编译表达式树,仅在第一次使用泛型类型时执行一次反射操作,后续调用直接执行编译后的委托,性能接近直接访问属性:
public abstract class OTCCrudBaseClass<TEntity> where TEntity : class { protected readonly SpkContext _context; private readonly DbSet<TEntity> _entities; private static readonly Action<TEntity, object> _setActiveProperty; // 静态构造函数预编译表达式 static OTCCrudBaseClass() { var activeProperty = typeof(TEntity).GetProperty("Active"); if (activeProperty != null) { // 构建表达式树:(entity, value) => entity.Active = value var entityParam = Expression.Parameter(typeof(TEntity), "entity"); var valueParam = Expression.Parameter(typeof(object), "value"); var convertedValue = Expression.Convert(valueParam, activeProperty.PropertyType); var assignExpr = Expression.Assign(Expression.Property(entityParam, activeProperty), convertedValue); _setActiveProperty = Expression.Lambda<Action<TEntity, object>>(assignExpr, entityParam, valueParam).Compile(); } } public OTCCrudBaseClass(SpkContext context) { _context = context; _entities = context.Set<TEntity>(); } // 保留原有CRUD方法... public async Task DeleteAsync(Guid id) { TEntity entityToDelete = await _entities.FindAsync(id); if (entityToDelete != null && _setActiveProperty != null) { _setActiveProperty(entityToDelete, 0); await _context.SaveChangesAsync(); } } }
内容的提问来源于stack exchange,提问作者john
相关产品推荐
相关产品推荐

