zoukankan      html  css  js  c++  java
  • ABP文档笔记

    预定义的过滤

    ISoftDelete

    软删除过滤用来在查询数据库时,自动过滤(从结果中抽取)已删除的实体。如果一个实体可以被软删除,它必须实现ISoftDelete接口,该接口只定义了一个IsDeleted属性,例如:

    public class Person : Entity, ISoftDelete
    {
    	public virtual string Name { get; set; }
    
    	public virtual bool IsDeleted { get; set; }
    }
    

    不会真实删除数据

    不会从数据库里真实删除一个Person实体,当需要删除它时,只是把它的IsDeleted属性设置为true(DbContext.SaveChanges时自动执行)。

    namespace Mt.EntityFramework
    {
    	/// <summary>
    	/// Base class for all DbContext classes in the application.
    	/// </summary>
    	public abstract class AbpDbContext : DbContext, ITransientDependency, IShouldInitialize
    	{
    		protected virtual void CancelDeletionForSoftDelete(DbEntityEntry entry)
    		{
    			if (!(entry.Entity is ISoftDelete))
    			{
    				return;
    			}
    
    			var softDeleteEntry = entry.Cast<ISoftDelete>();
    			softDeleteEntry.Reload();
    			softDeleteEntry.State = EntityState.Modified;
    			softDeleteEntry.Entity.IsDeleted = true;
    		}
    	}
    }
    

    查找数据时软删除数据不会被获取

    实现ISoftDelete之后,当你从数据库获取Person列表,软删除的人员不会被获取,此处有一个示例类,使用一个person仓储获取所有人员:

    public class MyService
    {
    	private readonly IRepository<Person> _personRepository;
    
    	public MyService(IRepository<Person> personRepository)
    	{
    		_personRepository = personRepository;
    	}
    
    	public List<Person> GetPeople()
    	{
    		return _personRepository.GetAllList();
    	}
    }
    

    GetPeople方法仅获取全部IsDeleted=false(不是delete)的Person。所有的仓储方法和导航属性都工作正常。我们可以添加一些其实where条件、连接等,它会自动添加IsDeleted=false条件到生成的Sql查询。

    namespace Mt.EntityFramework
    {
    	/// <summary>
    	/// Base class for all DbContext classes in the application.
    	/// </summary>
    	public abstract class AbpDbContext : DbContext, ITransientDependency, IShouldInitialize
    	{
    		protected override void OnModelCreating(DbModelBuilder modelBuilder)
    		{
    			base.OnModelCreating(modelBuilder);
    			modelBuilder.Filter(AbpDataFilters.SoftDelete, (ISoftDelete d) => d.IsDeleted, false);
    			//While "(int?)t.TenantId == null" seems wrong, it's needed. See https://github.com/jcachat/EntityFramework.DynamicFilters/issues/62#issuecomment-208198058
    			modelBuilder.Filter(AbpDataFilters.MustHaveTenant, (IMustHaveTenant t, int tenantId) => t.TenantId == tenantId || (int?)t.TenantId == null, 0); 
    			modelBuilder.Filter(AbpDataFilters.MayHaveTenant, (IMayHaveTenant t, int? tenantId) => t.TenantId == tenantId, 0);
    		}
    	}
    }
    

    ISoftDelete过滤一直可用,除非你显式禁用它。

    Autdit

    public interface IHasDeletionTime : ISoftDelete
    {
        /// <summary>
        /// Deletion time of this entity.
        /// </summary>
        DateTime? DeletionTime { get; set; }
    }
    public interface IDeletionAudited : IHasDeletionTime
    {
        /// <summary>
        /// Which user deleted this entity?
        /// </summary>
        long? DeleterUserId { get; set; }
    }
    

    所以FullAuditedEntity是软删除, 参阅 ABP框架 - 实体

    IMustHaveTenant

    如果你正在创建多租户应用并存储所有租户数据在一个数据库里,你明确地不想一个租户的数据意外地被另一个租户看到,这种情况下你可用IMustHaveTenant。例如:

    public class Product : Entity, IMustHaveTenant
    {
    	public int TenantId { get; set; }
    
    	public string Name { get; set; }
    }
    

    IMustHaveTenant定义了TenantId,区别不同的租户实体。ABP默认情况下使用IAbpSeesion获取当前TenantId,并自动为当前租户过滤查询。

    IMustHaveTenant默认可用。

    如果当前用户尚未登录系统或当前是个宿主用户(宿主用户是一个更高级别的用户,它管理租户和租户数据),ABP自动禁用IMustHaveTenant过滤,因此,可以获取所有租户的所有数据。注意:这与安全性无关,你应当一直授权敏感数据。

    IMayHaveTenant

    如果一个实体类被租户和宿主共享(也就是说一个实体对象可被租户或宿主拥有),你可以使用IMayHaveTenant过滤。IMayHaveTenant接口定义了TenantId,但它是可空的。

    public class Role : Entity, IMayHaveTenant
    {
    	public int? TenantId { get; set; }
    
    	public string RoleName { get; set; }
    }
    

    一个null值表示这是个宿主实体,一个非null值表示这个实体被Id为TenantId的租户拥有。默认情况下,ABP使用IAbpSeesion获取当前TenantId。IMayHaveTenant过滤不像IMustHaveTenant那么通用,但在实体类型通用宿主和租户时,需要它。

    IMayHaveTenant一直可用,除非你显式禁用它。

    禁用过滤

    调用DisableFilter方法可以禁用每个工作单元的一个过滤,如下:

    var people1 = _personRepository.GetAllList();
    
    using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete))
    {
        var people2 = _personRepository.GetAllList();                
    }
    
    var people3 = _personRepository.GetAllList();
    

    DisableFilter接受一个或多个过滤名称组成的字符串,AbpDataFilters.SoftDelete是个字符串常量,它代表ABP的软删除过滤。

    people2将包含被软删除的people,people1和people3只包含未被软删除的people。使用using声明,你可以在using域内禁用一个过滤。如果不使用using声明,过滤会被禁用,直到当前工作单元结束或是你显示启用这个过滤。

    你可以注入IUnitOfWorkManager,然后像上例那样使用,如果你的类继承自特殊的基类(如应用服务,AbpController,AbpApiController...),你也可以直接使用CurrentUnitOfWork属性。

    关于using声明

    如果一个过滤是启用的,当你使用using声明,调用DisableFilter方法,这个过滤会被禁用,然后在using声明之后,自动地被启用。但是如果这个过滤是在使用using声明前,就是禁用的,那么DisableFilter什么也不做,在using声明之后,它仍然是禁用的。

    关于多租户

    你可以禁用多租户过滤来查询所有租户的数据,但这只对一个数据库有效。如果你为每个租户使用分离的数据库,禁用过滤就无法帮助你获取所有租户的数据,因为数据在不同的数据库甚至是不同的服务器,更多信息查看多租户文档。

    启用过滤

    在一个工作单元里,你可以使用EnableFilter方法启用一个过滤。相似于(也相反于)DisableFilter。EnableFilter也在使用using声明时,返回可释放对象,用来在有需要的情况下重新禁用过滤。

    设置过滤参数

    一个过滤可以参数化,IMusthaveTenant过滤就是一个例子,因为当前租户的Id在运行时才能检测到。对于此类过滤,如果有需要,我们可以修改过滤值,如:

    CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MusthaveTenant, AbpDataFilters.Parameters.TenantId, 42);
    

    另一个例子:为IMayHaveTenant过滤,设置租户Id:

    CurrentUnitOfWork.SetFilterParameter(AbpDataFilters.MayHaveTenant, AbpDataFilters.Parameters.TenantId, 42);
    

    SetFilterParameter方法也返回一个IDisposeble,所以使用一个using声明,让它自动在声明之后恢复原值。

    public class EfDynamicFiltersUnitOfWorkFilterExecuter : IEfUnitOfWorkFilterExecuter
    {
        public void ApplyDisableFilter(IUnitOfWork unitOfWork, string filterName)
        {
            foreach (var activeDbContext in unitOfWork.As<EfUnitOfWork>().GetAllActiveDbContexts())
            {
                activeDbContext.DisableFilter(filterName);
            }
        }
    
        public void ApplyEnableFilter(IUnitOfWork unitOfWork, string filterName)
        {
            foreach (var activeDbContext in unitOfWork.As<EfUnitOfWork>().GetAllActiveDbContexts())
            {
                activeDbContext.EnableFilter(filterName);
            }
        }
    
        public void ApplyFilterParameterValue(IUnitOfWork unitOfWork, string filterName, string parameterName, object value)
        {
            foreach (var activeDbContext in unitOfWork.As<EfUnitOfWork>().GetAllActiveDbContexts())
            {
                if (TypeHelper.IsFunc<object>(value))
                {
                    activeDbContext.SetFilterScopedParameterValue(filterName, parameterName, (Func<object>)value);
                }
                else
                {
                    activeDbContext.SetFilterScopedParameterValue(filterName, parameterName, value);
                }
            }
        }
    
        public void ApplyCurrentFilters(IUnitOfWork unitOfWork, DbContext dbContext)
        {
            foreach (var filter in unitOfWork.Filters)
            {
                if (filter.IsEnabled)
                {
                    dbContext.EnableFilter(filter.FilterName);
                }
                else
                {
                    dbContext.DisableFilter(filter.FilterName);
                }
    
                foreach (var filterParameter in filter.FilterParameters)
                {
                    if (TypeHelper.IsFunc<object>(filterParameter.Value))
                    {
                        dbContext.SetFilterScopedParameterValue(filter.FilterName, filterParameter.Key, (Func<object>)filterParameter.Value);
                    }
                    else
                    {
                        dbContext.SetFilterScopedParameterValue(filter.FilterName, filterParameter.Key, filterParameter.Value);
                    }
                }
            }
        }
    }
    

    SetTenantId 方法

    虽然你可以使用SetFilterParameter方法,为MayHaveTenant和MusthaveTenant修改过滤值,但修改租户过滤有一个更好的方式:SetTenantId()。SetTenantId为这两个过滤修改参数值,并且单数据库或每个租户一个数据库都有效。所以,总是推荐用SetTenantId修改租户过滤的参数值。查看多租户文档获取更多信息。

    自定义过滤

    定义一个接口

    为自定义过滤并整合到ABP,首先,定义一个接口,它将被使用这个过滤的实体实现。假设我们要通过PersonId自动过滤实体,接口示例:

    public interface IHasPerson
    {
        int PersonId { get; set; }
    }
    

    实现这个接口

    public class Phone : Entity, IHasPerson
    {
        [ForeignKey("PersonId")]
        public virtual Person Person { get; set; }
        public virtual int PersonId { get; set; }
    
        public virtual string Number { get; set; }
    }
    

    定义过滤

    因为ABP使用EntityFramework.DynamicFilters,我们使用它的规则来定义这个过滤,在我们的DbContext类里,我们重写OnModelCreating,如下所示:

    protected override void OnModelCreating(DbModelBuilder modelBuilder)
    {
        base.OnModelCreating(modelBuilder);
    
        modelBuilder.Filter("PersonFilter", (IHasPerson entity, int personId) => entity.PersonId == personId, 0);
    }
    

    “PersonFilter”是这个过滤的唯一的名称,第二个参数表明过滤的接口和过滤参数PersonId(如果过滤不可参数化,可不用),最后一个参数是personId的默认值。

    注册这个过滤

    最后在我们模块的PreInitialize方法里,注册这个过滤到ABP工作单元系统:

    Configuration.UnitOfWork.RegisterFilter("PersonFilter", false);
    

    第一个参数就是我们之前定义的名称,第二个参数指明默认情况下是否启用。

    使用

    using (CurrentUnitOfWork.EnableFilter("PersonFilter"))
    {
        using(CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42))
        {
            var phones = _phoneRepository.GetAllList();
            //...
        }
    }
    

    我们应当从其它地方获取personId代替硬编码。上面是个可参数化过滤的例子,一个过滤可能有0或多个参数,如果没有参数,就没有必要设置过滤的参数值,同样,如果默认过滤是启用的,就不必再手动启用它(当然,我们可以禁用它)。

    EntityFramework.DynamicFilters 文档

    获取更多有关动态数据过滤信息,请参阅github页上的文档: https://github.com/jcachat/EntityFramework.DynamicFilters

    我们可以为security, active/passive等实体自定义过滤。

    其它 ORM

    ABP数据过滤是为EntityFramework和NHibernate实现的,其它ORM上不可以用(包含EntityFramework Core)。但实质上,你可以在大多数情况上模仿它,只要你也是用仓储来获取数据的,你可以自定义一个仓储,然后重写GetAll和其它所需的数据获取方法。

  • 相关阅读:
    路由重分发 最重要 最难 ccnp
    (01)Zookeeper简介
    (01)kafka以及消息系统的基本介绍
    (05)使用kafka脚本发送消息和接收消息
    (04)kafka多机多Broker(集群)的基本配置
    (03)kafka单机多Broker(伪分布式)的基本配置
    (02)安装配置kafka单Broker及其基本操作
    (02)安装zookeeper集群
    (09)使用xshell工具查看redis客户端,汉字显示乱码解决方法
    (08)redis之使用java客户端、spring连接redis、redis集群示例
  • 原文地址:https://www.cnblogs.com/wj033/p/6494879.html
Copyright © 2011-2022 走看看