一、ABP框架与数据完整性那些事儿

ABP(ASP.NET Boilerplate)框架就像咱们开发者的瑞士军刀,尤其在处理数据完整性这个关键课题上。我去年用ABP重构的电商系统中,商品SKU表每天要处理上万次删除操作,要是直接物理删除,估计DBA早拿着键盘找我真人PK了。这时候软删除和数据过滤这对黄金搭档就派上大用场了。

框架的「数据过滤器」功能好比给数据库操作加了智能滤镜,EntityFrameworkCore模块提供的「软删除」接口就像数据的安全气囊。这组合拳打下去,不仅解决了数据丢失风险,还能优雅地支持数据版本追溯等企业级需求。

二、给数据戴上过滤口罩:ABP筛选器实战

2.1 基础筛选器配置

(技术栈:ASP.NET Core + EF Core)

// 在DbContext中配置租户过滤器
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    // 自动过滤多租户数据
    modelBuilder.Filter("TenantFilter", (IMustHaveTenant entity, int tenantId) 
        => entity.TenantId == tenantId, 0);
    
    // 启用软删除过滤
    modelBuilder.EnableSoftDeleteFilter();
}

// 实体类实现软删除接口
public class Product : Entity<Guid>, ISoftDelete
{
    public string Name { get; set; }
    public bool IsDeleted { get; set; }  // 软删除标记
    public DateTime? DeletionTime { get; set; }
}

2.2 动态过滤的十八般武艺

// 临时禁用租户过滤(危险操作需谨慎)
using (_unitOfWorkManager.Current.DisableFilter("TenantFilter"))
{
    var allProducts = _productRepository.GetAllList();
}

// 创建自定义库存过滤器
public class StockFilter : IDynamicQueryFilter
{
    public Expression<Func<TEntity, bool>> CreateFilter<TEntity>()
    {
        if (typeof(IHasStock).IsAssignableFrom(typeof(TEntity)))
        {
            return e => ((IHasStock)e).StockCount > 0;
        }
        return e => true;
    }
}

// 注册到模块配置中
Configuration.UnitOfWork.RegisterFilter("StockFilter", false);

三、软删除的七十二变玩法

3.1 标准软删除三板斧

// 通过仓储实现软删除
public async Task DeleteProduct(Guid id)
{
    var product = await _productRepository.GetAsync(id);
    await _productRepository.DeleteAsync(product);  // 自动设置IsDeleted = true
    
    // 手动硬删除(慎用)
    // await _productRepository.HardDeleteAsync(product);
}

// 查询包含已删除数据
var deletedProducts = await _productRepository
    .GetAllIncludingDeleted()
    .Where(p => p.IsDeleted)
    .ToListAsync();

3.2 高级场景双保险

// 带审计信息的删除处理
public class ProductManager : DomainService
{
    public async Task SafeDelete(Guid id)
    {
        using var uow = _unitOfWorkManager.Begin(requiresNew: true);
        var product = await _productRepository.GetAsync(id);
        
        product.IsDeleted = true;
        product.DeletionTime = Clock.Now;
        product.DeleterUserId = CurrentUser.Id;
        
        await _eventBus.TriggerAsync(new ProductDeletedEvent(product));
        await uow.CompleteAsync();
    }
}

// 自定义软删除仓储扩展
public static class RepositoryExtensions
{
    public static async Task BatchSoftDelete<T>(this IRepository<T> repository, 
        Expression<Func<T, bool>> predicate) where T : class, ISoftDelete
    {
        var entities = await repository.GetAll().Where(predicate).ToListAsync();
        foreach (var entity in entities)
        {
            entity.IsDeleted = true;
        }
        await repository.UpdateRangeAsync(entities);
    }
}

四、幕后的技术搭档们

4.1 工作单元(UnitOfWork)的里世界

// 嵌套事务处理技巧
public async Task ComplexOperation()
{
    using var outerUow = _unitOfWorkManager.Begin();
    
    try 
    {
        using var innerUow = _unitOfWorkManager.Begin(TransactionScopeOption.RequiresNew);
        // 执行敏感操作
        await innerUow.CompleteAsync();
    }
    catch 
    {
        outerUow.Dispose();
        throw;
    }
    
    // 主事务提交
    await outerUow.CompleteAsync();
}

4.2 仓储模式的正确打开方式

// 自定义仓储的典型场景
public interface ICustomProductRepository : IRepository<Product>
{
    Task<List<Product>> GetHotProductsAsync(int days);
}

public class CustomProductRepository : EfCoreRepositoryBase<MyDbContext, Product>, 
    ICustomProductRepository
{
    public async Task<List<Product>> GetHotProductsAsync(int days)
    {
        return await DbContext.Products
            .Where(p => p.ViewCount > 1000)
            .OrderByDescending(p => p.CreationTime)
            .Take(10)
            .ToListAsync();
    }
}

五、这些场景你必须知道

5.1 典型应用案例

某次我重构的医疗系统升级时,法规要求所有患者信息修改都要保留历史记录。通过软删除+版本控制组合拳:

  1. 修改操作前先软删除旧记录
  2. 插入带版本号的新记录
  3. 查询时默认过滤历史版本

最终在不影响现有业务逻辑的前提下,满足合规要求的同时,查询效率还提升了30%。

5.2 适用与慎用场景

适合场景:

  • 金融交易记录(哪怕错误交易也要留痕)
  • CMS内容管理系统(需要回收站功能)
  • 多租户SaaS应用(数据隔离是底线)

慎用情况:

  • 日志类高频写入数据(存储压力大)
  • GDPR严格删除要求的场景(可能需要定时物理清理)

六、技术选择的双刃剑

6.1 优势亮点

  1. 数据恢复零成本:就像系统自带的时光机
  2. 查询安全:自动过滤敏感数据,DBA安心睡觉
  3. 架构统一:框架层统一实现,新人也能快速上手

6.2 需要留神的坑

最近遇到的生产事故:某同事在百万级数据表直接使用GetAll()导致全表扫描。解决秘诀:

  1. 给IsDeleted字段建索引
  2. 分页查询必须加Take/Skip
  3. 大表配合Archive策略定时迁移

七、老司机避坑指南

  1. 索引优化:千万记得给IsDeleted、TenantId加联合索引
CREATE INDEX IX_Products_DeleteStatus 
ON Products (IsDeleted) 
INCLUDE (TenantId, DeletionTime);
  1. 性能陷阱:当软删除数据超过10%时要考虑归档策略

  2. 安全兜底:关键业务操作必须开启审计日志

[Audited]
public class ProductAppService : ApplicationService
{
    // 关键方法自动记录操作日志
}
  1. 租户隔离:多租户系统必须做双重验证
public async Task UpdateProduct(ProductDto input)
{
    // 手动验证租户ID匹配
    if (input.TenantId != AbpSession.TenantId)
    {
        throw new UserFriendlyException("跨租户操作禁止!");
    }
    // 业务逻辑...
}

八、总结与展望

经历了三个ABP项目的实战洗礼,数据过滤和软删除这组搭档就像系统安全的左右护法。但记住:框架工具虽好,合理使用更重要。未来ABP VNext版本可能会引入更智能的自动归档功能,建议保持对官方文档的持续关注。