数据过滤

Volo.Abp.Data 包定义了在查询数据库时自动过滤数据的服务.

预定义的过滤

ABP定义了一些开箱即用的过滤.

ISoftDelete

实体标记为已删除,并不是物理删除. 实现 ISoftDelete 接口将你的实体”软删除”.

示例:

  1. using System;
  2. using Volo.Abp;
  3. using Volo.Abp.Domain.Entities;
  4. namespace Acme.BookStore
  5. {
  6. public class Book : AggregateRoot<Guid>, ISoftDelete
  7. {
  8. public string Name { get; set; }
  9. public bool IsDeleted { get; set; } //Defined by ISoftDelete
  10. }
  11. }

ISoftDelete 定义了 IsDeleted 属性. 当你使用仓储删除一条记录时, ABP会自动将 IsDeleted 设置为true,并将删除操作替换为修改操作(如果需要,也可以手动将 IsDeleted 设置为true). 在查询数据库时会自动过滤软删除的实体.

ISoftDelete 过滤默认启用, 想要真正的从数据库删除实体需要显示的禁用过滤. 参见下面提到的 IDataFilter 服务.

IMultiTenant

多租户 是创建 SaaS 应用程序的有效方法. 多租户应用程序通常需要在租户间隔离数据. 实现 IMultiTenant 接口使你的实体支持 “多租户”.

示例:

  1. using System;
  2. using Volo.Abp;
  3. using Volo.Abp.Domain.Entities;
  4. using Volo.Abp.MultiTenancy;
  5. namespace Acme.BookStore
  6. {
  7. public class Book : AggregateRoot<Guid>, ISoftDelete, IMultiTenant
  8. {
  9. public string Name { get; set; }
  10. public bool IsDeleted { get; set; } //Defined by ISoftDelete
  11. public Guid? TenantId { get; set; } //Defined by IMultiTenant
  12. }
  13. }

IMultiTenant 接口定义了 TenantId 属性用于自动过滤当前租户实体. 更多信息参见多租户文档.

IDataFilter 服务: 启用/禁用 数据过滤

你可以使用 IDataFilter 服务控制数据过滤.

示例:

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Threading.Tasks;
  4. using Volo.Abp;
  5. using Volo.Abp.Data;
  6. using Volo.Abp.DependencyInjection;
  7. using Volo.Abp.Domain.Repositories;
  8. namespace Acme.BookStore
  9. {
  10. public class MyBookService : ITransientDependency
  11. {
  12. private readonly IDataFilter _dataFilter;
  13. private readonly IRepository<Book, Guid> _bookRepository;
  14. public MyBookService(
  15. IDataFilter dataFilter,
  16. IRepository<Book, Guid> bookRepository)
  17. {
  18. _dataFilter = dataFilter;
  19. _bookRepository = bookRepository;
  20. }
  21. public async Task<List<Book>> GetAllBooksIncludingDeletedAsync()
  22. {
  23. //Temporary disable the ISoftDelete filter
  24. using (_dataFilter.Disable<ISoftDelete>())
  25. {
  26. return await _bookRepository.GetListAsync();
  27. }
  28. }
  29. }
  30. }
  • 注入 IDataFilter 服务到你的类中.
  • using 语句中使用 Disable 方法创建一个代码块,其中禁用了 ISoftDelete 过滤器(始终与 using 搭配使用,确保代码块执行后将过滤重置为之前的状态).

IDataFilter.Enable 方法可以启用过滤. 可以嵌套使用 EnableDisable 方法定义内部作用域.

AbpDataFilterOptions

AbpDataFilterOptions 用于设置数据过滤系统选项.

下面的示例代码在默认情况下禁用了 ISoftDelete 过滤,除非显示启用,在查询数据库时会包含标记为已删除的实体:

  1. Configure<AbpDataFilterOptions>(options =>
  2. {
  3. options.DefaultStates[typeof(ISoftDelete)] = new DataFilterState(isEnabled: false);
  4. });

更改全局过滤的默认值需要小心,特别是在你使用预构建的模块时该模块可能是在默认启用软删除过滤的情况下开发的. 但你可以安全的为自己定义的数据过滤执行此操作.

自定义数据过滤

定义和实现新的过滤很大程序上取决与数据库提供者. ABP为所有的数据库提供者实现了预构建的过滤.

首先为过滤定义一个接口 (如 ISoftDeleteIMultiTenant) 然后用实体实现它.

示例:

  1. public interface IIsActive
  2. {
  3. bool IsActive { get; }
  4. }

IIsActive 接口可以过滤活跃/消极数据,任何实体都可以实现它:

  1. public class Book : AggregateRoot<Guid>, IIsActive
  2. {
  3. public string Name { get; set; }
  4. public bool IsActive { get; set; } //Defined by IIsActive
  5. }

EntityFramework Core

ABP使用EF Core的全局过滤系统用于EF Core 集成. 所以它很好的集成到EF Core中,即使你直接使用 DbContext 它也可以正常工作.

实现自定义过滤的最佳方法是为重写你的 DbContextShouldFilterEntityCreateFilterExpression 方法. 示例:

  1. protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
  2. protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
  3. {
  4. if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
  5. {
  6. return true;
  7. }
  8. return base.ShouldFilterEntity<TEntity>(entityType);
  9. }
  10. protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>()
  11. {
  12. var expression = base.CreateFilterExpression<TEntity>();
  13. if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
  14. {
  15. Expression<Func<TEntity, bool>> isActiveFilter =
  16. e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
  17. expression = expression == null
  18. ? isActiveFilter
  19. : CombineExpressions(expression, isActiveFilter);
  20. }
  21. return expression;
  22. }
  • 添加 IsActiveFilterEnabled 属性用于检查是否启用了 IIsActive . 内部使用了之前介绍到的 IDataFilter 服务.
  • 重写 ShouldFilterEntityCreateFilterExpression 方法检查给定实体是否实现 IIsActive 接口,在必要时组合表达式.

MongoDB

ABP抽象了 IMongoDbRepositoryFilterer 接口为MongoDB 集成实现数据过滤, 只有正确的使用仓储,它才会工作. 否则你需要手动过滤数据.

目前为MongoDB集成实现数据过滤的最佳方法是重写派生自 MongoDbRepositoryFilterer 基类的 AddGlobalFilters 方法:

  1. [ExposeServices(typeof(IMongoDbRepositoryFilterer<Book, Guid>))]
  2. public class BookMongoDbRepositoryFilterer : MongoDbRepositoryFilterer<Book, Guid> , ITransientDependency
  3. {
  4. public BookMongoDbRepositoryFilterer(
  5. IDataFilter dataFilter,
  6. ICurrentTenant currentTenant) :
  7. base(dataFilter, currentTenant)
  8. {
  9. }
  10. public override void AddGlobalFilters(List<FilterDefinition<Book>> filters)
  11. {
  12. base.AddGlobalFilters(filters);
  13. if (DataFilter.IsEnabled<IIsActive>())
  14. {
  15. filters.Add(Builders<Book>.Filter.Eq(e => ((IIsActive)e).IsActive, true));
  16. }
  17. }
  18. }

示例中仅为 Book 实体实现了过滤. 如果你想要为所有的实体实现过滤 (实现了 IIsActive 接口的实体),可以创建自己的 MongoDbRepositoryFilterer 基类并重 写 AddGlobalFilters 方法. 如下所示:

  1. public abstract class MyMongoRepository<TMongoDbContext, TEntity, TKey> : MongoDbRepository<TMongoDbContext, TEntity, TKey>
  2. where TMongoDbContext : IAbpMongoDbContext
  3. where TEntity : class, IEntity<TKey>
  4. {
  5. protected MyMongoRepository(IMongoDbContextProvider<TMongoDbContext> dbContextProvider)
  6. : base(dbContextProvider)
  7. {
  8. }
  9. protected override void AddGlobalFilters(List<FilterDefinition<TEntity>> filters)
  10. {
  11. base.AddGlobalFilters(filters);
  12. if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))
  13. && DataFilter.IsEnabled<IIsActive>())
  14. {
  15. filters.Add(Builders<TEntity>.Filter.Eq(e => ((IIsActive)e).IsActive, true));
  16. }
  17. }
  18. }
  19. public class MyMongoDbModule : AbpModule
  20. {
  21. public override void ConfigureServices(ServiceConfigurationContext context)
  22. {
  23. //.......
  24. context.Services
  25. .Replace(ServiceDescriptor.Transient(typeof(IMongoDbRepositoryFilterer<,>),typeof(MyMongoDbRepositoryFilterer<,>)));
  26. }
  27. }

参阅MongoDb集成文档的 “设置默认仓储” 部分了解如何使用自定义类替换默认仓储.