项目

数据过滤

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>()方法可用于启用过滤器。EnableDisable方法可以嵌套使用以定义内部作用域。

  • IDataFilter.IsEnabled<T>()可用于检查过滤器当前是否启用。

始终在using块内使用DisableEnable方法,以确保过滤器恢复到其先前的状态。

泛型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为所有数据库提供程序实现了所有预定义的过滤器。

当您需要时,首先为您的过滤器定义一个接口(如ISoftDeleteIMultiTenant),并为您的实体实现它。

示例:

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重写ShouldFilterEntityCreateFilterExpression方法。示例:

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服务。
  • 重写了ShouldFilterEntityCreateFilterExpression方法,检查给定实体是否实现了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<,>)));
    }
}
在本文档中