一、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 典型应用案例
某次我重构的医疗系统升级时,法规要求所有患者信息修改都要保留历史记录。通过软删除+版本控制组合拳:
- 修改操作前先软删除旧记录
- 插入带版本号的新记录
- 查询时默认过滤历史版本
最终在不影响现有业务逻辑的前提下,满足合规要求的同时,查询效率还提升了30%。
5.2 适用与慎用场景
适合场景:
- 金融交易记录(哪怕错误交易也要留痕)
- CMS内容管理系统(需要回收站功能)
- 多租户SaaS应用(数据隔离是底线)
慎用情况:
- 日志类高频写入数据(存储压力大)
- GDPR严格删除要求的场景(可能需要定时物理清理)
六、技术选择的双刃剑
6.1 优势亮点
- 数据恢复零成本:就像系统自带的时光机
- 查询安全:自动过滤敏感数据,DBA安心睡觉
- 架构统一:框架层统一实现,新人也能快速上手
6.2 需要留神的坑
最近遇到的生产事故:某同事在百万级数据表直接使用GetAll()
导致全表扫描。解决秘诀:
- 给IsDeleted字段建索引
- 分页查询必须加Take/Skip
- 大表配合Archive策略定时迁移
七、老司机避坑指南
- 索引优化:千万记得给IsDeleted、TenantId加联合索引
CREATE INDEX IX_Products_DeleteStatus
ON Products (IsDeleted)
INCLUDE (TenantId, DeletionTime);
性能陷阱:当软删除数据超过10%时要考虑归档策略
安全兜底:关键业务操作必须开启审计日志
[Audited]
public class ProductAppService : ApplicationService
{
// 关键方法自动记录操作日志
}
- 租户隔离:多租户系统必须做双重验证
public async Task UpdateProduct(ProductDto input)
{
// 手动验证租户ID匹配
if (input.TenantId != AbpSession.TenantId)
{
throw new UserFriendlyException("跨租户操作禁止!");
}
// 业务逻辑...
}
八、总结与展望
经历了三个ABP项目的实战洗礼,数据过滤和软删除这组搭档就像系统安全的左右护法。但记住:框架工具虽好,合理使用更重要。未来ABP VNext版本可能会引入更智能的自动归档功能,建议保持对官方文档的持续关注。