数据过滤
Volo.Abp.Data包定义了服务,用于在从数据库查询时自动过滤数据。
预定义过滤器
ABP开箱即用地定义了一些过滤器。
ISoftDelete
用于将实体标记为已删除,而不是实际删除它。实现ISoftDelete接口可以使您的实体支持“软删除”。
示例:
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
namespace Acme.BookStore
{
public class Book : AggregateRoot<Guid>, ISoftDelete
{
public string Name { get; set; }
public bool IsDeleted { get; set; } //由ISoftDelete定义
}
}
ISoftDelete定义了IsDeleted属性。当您使用仓储删除一本书时,ABP会自动将IsDeleted设置为true,并防止其被实际删除(如果需要,您也可以手动将IsDeleted属性设置为true)。此外,在查询数据库时,它会自动过滤已删除的实体。
ISoftDelete过滤器默认启用,除非您显式禁用它,否则无法从数据库获取已删除的实体。请参阅下面的IDataFilter服务。
当您在仓储上使用
HardDeleteAsync方法时,软删除的实体可以被硬删除。
IMultiTenant
多租户 是创建SaaS应用程序的有效方式。一旦您创建了一个多租户应用程序,通常需要在租户之间隔离数据。实现IMultiTenant接口可以使您的实体“支持多租户”。
示例:
using System;
using Volo.Abp;
using Volo.Abp.Domain.Entities;
using Volo.Abp.MultiTenancy;
namespace Acme.BookStore
{
public class Book : AggregateRoot<Guid>, ISoftDelete, IMultiTenant
{
public string Name { get; set; }
public bool IsDeleted { get; set; } //由ISoftDelete定义
public Guid? TenantId { get; set; } //由IMultiTenant定义
}
}
IMultiTenant接口定义了TenantId属性,该属性用于自动过滤当前租户的实体。更多信息请参阅 多租户 文档。
IDataFilter服务:启用/禁用数据过滤器
您可以使用IDataFilter服务来控制过滤器。
示例:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class MyBookService : ITransientDependency
{
private readonly IDataFilter _dataFilter;
private readonly IRepository<Book, Guid> _bookRepository;
public MyBookService(
IDataFilter dataFilter,
IRepository<Book, Guid> bookRepository)
{
_dataFilter = dataFilter;
_bookRepository = bookRepository;
}
public async Task<List<Book>> GetAllBooksIncludingDeletedAsync()
{
//临时禁用ISoftDelete过滤器
using (_dataFilter.Disable<ISoftDelete>())
{
return await _bookRepository.GetListAsync();
}
}
}
}
- 将
IDataFilter服务注入到您的类中。 - 在
using语句中使用Disable方法创建一个代码块,在该块内禁用ISoftDelete过滤器。
除了Disable<T>()方法外:
IDataFilter.Enable<T>()方法可用于启用过滤器。Enable和Disable方法可以嵌套使用以定义内部作用域。IDataFilter.IsEnabled<T>()可用于检查过滤器当前是否启用。
始终在
using块内使用Disable和Enable方法,以确保过滤器恢复到其先前的状态。
泛型IDataFilter服务
IDataFilter服务有一个泛型版本IDataFilter<TFilter>,它根据过滤器类型注入一个更受限制且显式的数据过滤器。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp;
using Volo.Abp.Data;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Domain.Repositories;
namespace Acme.BookStore
{
public class MyBookService : ITransientDependency
{
private readonly IDataFilter<ISoftDelete> _softDeleteFilter;
private readonly IRepository<Book, Guid> _bookRepository;
public MyBookService(
IDataFilter<ISoftDelete> softDeleteFilter,
IRepository<Book, Guid> bookRepository)
{
_softDeleteFilter = softDeleteFilter;
_bookRepository = bookRepository;
}
public async Task<List<Book>> GetAllBooksIncludingDeletedAsync()
{
//临时禁用ISoftDelete过滤器
using (_softDeleteFilter.Disable())
{
return await _bookRepository.GetListAsync();
}
}
}
}
- 这种用法在注入
IDataFilter<T>服务时确定了过滤器类型。 - 在这种情况下,您可以使用
Disable()和Enable()方法而无需指定过滤器类型。
AbpDataFilterOptions
AbpDataFilterOptions可用于为数据过滤系统设置选项。
以下示例代码默认禁用ISoftDelete过滤器,这将导致在查询数据库时包含已删除的实体,除非您显式启用过滤器:
Configure<AbpDataFilterOptions>(options =>
{
options.DefaultStates[typeof(ISoftDelete)] = new DataFilterState(isEnabled: false);
});
谨慎更改全局过滤器的默认值,特别是如果您使用的是预构建模块,这些模块可能是在假设软删除过滤器默认开启的情况下开发的。但您可以安全地为自己定义的过滤器执行此操作。
定义自定义过滤器
定义和实现新过滤器高度依赖于数据库提供程序。ABP为所有数据库提供程序实现了所有预定义的过滤器。
当您需要时,首先为您的过滤器定义一个接口(如ISoftDelete和IMultiTenant),并为您的实体实现它。
示例:
public interface IIsActive
{
bool IsActive { get; }
}
这样的IIsActive接口可用于过滤活动/非活动数据,并且可以由任何实体轻松实现:
public class Book : AggregateRoot<Guid>, IIsActive
{
public string Name { get; set; }
public bool IsActive { get; set; } //由IIsActive定义
}
EntityFramework Core
ABP使用 EF Core的全局查询过滤器 系统来实现 EF Core集成 。因此,它与EF Core良好集成,即使您直接使用DbContext,也能按预期工作。
实现自定义过滤器的最佳方式是为您的DbContext重写ShouldFilterEntity和CreateFilterExpression方法。示例:
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}
return expression;
}
- 添加了一个
IsActiveFilterEnabled属性来检查IIsActive是否启用。它在内部使用前面介绍的IDataFilter服务。 - 重写了
ShouldFilterEntity和CreateFilterExpression方法,检查给定实体是否实现了IIsActive接口,并在必要时组合表达式。
此外,您还可以使用HasAbpQueryFilter为实体设置过滤器。它将您的过滤器与ABP EF Core内置的全局查询过滤器组合。
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
base.OnModelCreating(modelBuilder);
modelBuilder.Entity<MyEntity>(b =>
{
b.HasAbpQueryFilter(e => e.Name.StartsWith("abp"));
});
}
使用用户定义函数映射进行全局过滤
使用用户定义函数映射进行全局过滤将获得性能提升。
要使用此功能,您需要按如下方式更改DbContext:
protected bool IsActiveFilterEnabled => DataFilter?.IsEnabled<IIsActive>() ?? false;
protected override bool ShouldFilterEntity<TEntity>(IMutableEntityType entityType)
{
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
return true;
}
return base.ShouldFilterEntity<TEntity>(entityType);
}
protected override Expression<Func<TEntity, bool>> CreateFilterExpression<TEntity>(ModelBuilder modelBuilder)
{
var expression = base.CreateFilterExpression<TEntity>(modelBuilder);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity)))
{
Expression<Func<TEntity, bool>> isActiveFilter = e => !IsActiveFilterEnabled || EF.Property<bool>(e, "IsActive");
if (UseDbFunction())
{
isActiveFilter = e => IsActiveFilter(((IIsActive)e).IsActive, true);
var abpEfCoreCurrentDbContext = this.GetService<AbpEfCoreCurrentDbContext>();
modelBuilder.HasDbFunction(typeof(MyProjectNameDbContext).GetMethod(nameof(IsActiveFilter))!)
.HasTranslation(args =>
{
// (bool isActive, bool boolParam)
var isActive = args[0];
var boolParam = args[1];
if (abpEfCoreCurrentDbContext.Context?.DataFilter.IsEnabled<IIsActive>() == true)
{
// isActive == true
return new SqlBinaryExpression(
ExpressionType.Equal,
isActive,
new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping),
boolParam.Type,
boolParam.TypeMapping);
}
// 空where sql
return new SqlConstantExpression(Expression.Constant(true), boolParam.TypeMapping);
});
}
expression = expression == null ? isActiveFilter : QueryFilterExpressionHelper.CombineExpressions(expression, isActiveFilter);
}
return expression;
}
public static bool IsActiveFilter(bool isActive, bool boolParam)
{
throw new NotSupportedException(AbpEfCoreDataFilterDbFunctionMethods.NotSupportedExceptionMessage);
}
public override string GetCompiledQueryCacheKey()
{
return $"{base.GetCompiledQueryCacheKey()}:{IsActiveFilterEnabled}";
}
使用户定义函数映射与DevExtreme.AspNet.Data的IAsyncAdapter兼容
如果您正在使用DevExtreme.AspNet.Data和用户定义函数映射,您需要创建一个自定义适配器以与IAsyncAdapter兼容。
var adapter = new AbpDevExtremeAsyncAdapter();
CustomAsyncAdapters.RegisterAdapter(typeof(AbpEntityQueryProvider), adapter);
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using DevExtreme.AspNet.Data.Async;
namespace Abp.EntityFrameworkCore;
public class AbpDevExtremeAsyncAdapter : IAsyncAdapter
{
private readonly MethodInfo _countAsyncMethod;
private readonly MethodInfo _toListAsyncMethod;
public AbpDevExtremeAsyncAdapter()
{
var extensionsType = Type.GetType("Microsoft.EntityFrameworkCore.EntityFrameworkQueryableExtensions, Microsoft.EntityFrameworkCore");
_countAsyncMethod = FindQueryExtensionMethod(extensionsType, "CountAsync");
_toListAsyncMethod = FindQueryExtensionMethod(extensionsType, "ToListAsync");
}
public Task<int> CountAsync(IQueryProvider provider, Expression expr, CancellationToken cancellationToken)
{
return InvokeCountAsync(_countAsyncMethod, provider, expr, cancellationToken);
}
public Task<IEnumerable<T>> ToEnumerableAsync<T>(I极速提供者 provider, Expression expr, CancellationToken cancellationToken)
{
return InvokeToListAsync<T>(_toListAsyncMethod, provider, expr, cancellationToken);
}
static MethodInfo FindQueryExtensionMethod(Type extensionsType, string name)
{
return extensionsType.GetMethods().First(m =>
{
if (!m.IsGenericMethod || m.Name != name)
{
return false;
}
var parameters = m.GetParameters();
return parameters.Length == 2 && parameters[1].ParameterType == typeof(CancellationToken);
});
}
static Task<int> InvokeCountAsync(MethodInfo method, IQueryProvider provider, Expression expr, CancellationToken cancellationToken)
{
var countArgument = ((MethodCallExpression)expr).Arguments[0];
var query = provider.CreateQuery(countArgument);
return (Task<int>)InvokeQueryExtensionMethod(method, query.ElementType, query, cancellationToken);
}
static async Task<IEnumerable<T>> InvokeToListAsync<T>(MethodInfo method, IQueryProvider provider, Expression expr, CancellationToken cancellationToken)
{
return await (Task<List<T>>)Inv极速QueryExtensionMethod(method, typeof(T), provider.CreateQuery(expr), cancellationToken);
}
static object InvokeQueryExtensionMethod(MethodInfo method, Type elementType, IQueryable query, CancellationToken cancellationToken)
{
return method.MakeGenericMethod(elementType).Invoke(null, new object[] { query, cancellationToken });
}
}
MongoDB
ABP抽象了 IMongoDbRepositoryFilterer 接口,用于为 MongoDB集成 实现数据过滤,仅当您正确使用仓储时才有效。否则,您应手动过滤数据。
目前,为MongoDB集成实现数据过滤的最佳方法是创建 MongoDbRepositoryFilterer 的派生类并重写 FilterQueryable 。示例:
[ExposeServices(typeof(IMongoDbRepositoryFilterer<Book, Guid>))]
public class BookMongoDbRepositoryFilterer : MongoDbRepositoryFilterer<Book, Guid> , ITransientDependency
{
public BookMongoDbRepositoryFilterer(
IDataFilter dataFilter,
ICurrentTenant currentTenant) :
base(dataFilter, currentTenant)
{
}
public override TQueryable FilterQueryable<TQueryable>(TQueryable query)
{
if (DataFilter.IsEnabled<IIsActive>())
{
return (TQueryable)query.Where(x => x.IsActive);
}
return base.FilterQueryable(query);
}
}
此示例仅针对Book实体实现。如果您想为所有实体(那些实现IIsActive接口的实体)实现,创建您自己的自定义MongoDB仓储过滤器基类,并按如下方式重写AddGlobalFilters:
public abstract class MyMongoRepository<TMongoDbContext, TEntity, TKey> : MongoDbRepository<TMongoDbContext, TEntity, TKey>
where TMongoDbContext : IAbpMongoDbContext
where TEntity : class, IEntity<TKey>
{
protected MyMongoRepository(IMongoDbContextProvider<T极速DbContext> dbContextProvider)
: base(dbContextProvider)
{
}
protected override void AddGlobalFilters(List<FilterDefinition<TEntity>> filters)
{
base.AddGlobalFilters(filters);
if (typeof(IIsActive).IsAssignableFrom(typeof(TEntity))
&& DataFilter.IsEnabled<II极速Active>())
{
filters.Add(Builders<TEntity>.Filter.Eq(e => ((IIsActive)e).IsActive, true));
}
}
}
public class MyMongoDbModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
//.......
context.Services
.Replace(ServiceDescriptor.Transient(typeof(IMongoDbRepositoryFilterer<,>),typeof(MyMongoDbRepositoryFilterer<,>)));
}
}
抠丁客


