You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 19:53:21