- ABP 畛域层-实体(Entities)
- ABP畛域层——仓储(Repositories)
- ABP畛域层——工作单元(Unit Of work)
- ABP畛域层——数据过滤器(Data filters)
ABP畛域层——畛域事件(Domain events)
- 事件总线
- 定义事件
- 触发事件
- 事件处理
- 注册处理器
- 勾销注册事件
ABP 畛域层-实体(Entities)
实体是DDD(畛域驱动设计)的外围概念之一。Eric Evans是这样形容的“很多对象不是通过它们的属性定义的,而是通过一连串的连续性事件和标识定义的”。
在框架实战系列(二)-畛域层介绍篇中有做代码简略介绍(举荐关注该公众号,干货满满)
在此咱们深刻了解实体概念
在ABP 中,实体类继承Entity类,如下所示,Product为一个实体
public class Product : Entity { public virtual string Name { get; set; } public virtual string Code { get; set; } public virtual DateTime CreationTime { get; set; } public Product() { CreationTime=DateTime.Now; } }
在ABP 框架中Entity 实体类局部内容如下所示,它有一个主键ID,所有继承自Entity的类,都应用该主键,你能够设置主键的值为任何格局,如下代码所示,它为泛型,能够为string,Guid或者其它数据类型。同时实体类重写了 equality (==) 操作符用来判断两个实体对象是否相等(两个实体的Id是否相等)。还定义了一个IsTransient()办法来检测实体是否有Id属性。
public abstract class Entity<TPrimaryKey> : IEntity<TPrimaryKey> { /// <summary>Unique identifier for this entity.</summary> public virtual TPrimaryKey Id { get; set; } /// <summary> /// Checks if this entity is transient (it has not an Id). /// </summary> /// <returns>True, if this entity is transient</returns> public virtual bool IsTransient() { if (EqualityComparer<TPrimaryKey>.Default.Equals(this.Id, default (TPrimaryKey))) return true; if (typeof (TPrimaryKey) == typeof (int)) return Convert.ToInt32((object) this.Id) <= 0; return typeof (TPrimaryKey) == typeof (long) && Convert.ToInt64((object) this.Id) <= 0L; } public virtual bool EntityEquals(object obj) { if (obj == null || !(obj is Entity<TPrimaryKey>)) return false; if (this == obj) return true; Entity<TPrimaryKey> entity = (Entity<TPrimaryKey>) obj; if (this.IsTransient() && entity.IsTransient()) return false; Type type1 = this.GetType(); Type type2 = entity.GetType(); if (!type1.GetTypeInfo().IsAssignableFrom(type2) && !type2.GetTypeInfo().IsAssignableFrom(type1)) return false; if (this is IMayHaveTenant && entity is IMayHaveTenant) { int? tenantId1 = this.As<IMayHaveTenant>().TenantId; int? tenantId2 = entity.As<IMayHaveTenant>().TenantId; if (!(tenantId1.GetValueOrDefault() == tenantId2.GetValueOrDefault() & tenantId1.HasValue == tenantId2.HasValue)) return false; } return (!(this is IMustHaveTenant) || !(entity is IMustHaveTenant) || this.As<IMustHaveTenant>().TenantId == entity.As<IMustHaveTenant>().TenantId) && this.Id.Equals((object) entity.Id); } public override string ToString() => string.Format("[{0} {1}]", (object) this.GetType().Name, (object) this.Id); }
接口约定
在很多应用程序中,很多实体具备像CreationTime的属性(数据库表也有该字段)用来批示该实体是什么时候被创立的。APB提供了一些有用的接口来实现这些相似的性能。也就是说,为这些实现了这些接口的实体,提供了一个通用的编码方式(艰深的说只有实现指定的接口就能实现指定的性能),以ABPUser 为例,如下所示AbpUser<TUser>类,继承了很多默认实现接口。
- IAudited 审计
- ISoftDelete 软删除
- IPassivable 激活/闲置状态
public abstract class AbpUser<TUser> : AbpUserBase, IFullAudited<TUser>, IAudited<TUser>, IAudited, ICreationAudited, IHasCreationTime, IModificationAudited, IHasModificationTime, ICreationAudited<TUser>, IModificationAudited<TUser>, IFullAudited, IDeletionAudited, IHasDeletionTime, ISoftDelete, IDeletionAudited<TUser> where TUser : AbpUser<TUser>
ABP畛域层——仓储(Repositories)
ABP框架实战系列(一)-长久层介绍篇 中有波及仓储概念的解说,(举荐关注该公众号,干货满满)
在这里,咱们深刻对仓储局部加以探讨,在ABP中,仓储类要实现IRepository接口.最好的形式是针对不同仓储对象定义各自不同的接口。 对于仓储类IRepository定义了许多泛型的办法。比方:Select、Insert、Update、Delete办法(CRUD操作)。在大多数的时候,,这些办法已足已应酬个别实体的须要。如果这些方对于实体来说已足够,咱们便不须要再去创立这个实体所需的仓储接口/类。即为自定义仓储 。
IRepository定义了从数据库中检索实体的罕用办法。在ABP框架中AbpRepositoryBase派生了IRepository接口
public abstract class AbpRepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey>, IRepository, ITransientDependency, IUnitOfWorkManagerAccessor where TEntity : class, IEntity<TPrimaryKey>
接口办法次要包含:Query、GetAll、Single、Insert、Update、Delete、Count等几大办法的重载。
GetAll():IQueryable<TEntity> GetAllIncluding(params Expression<Func<TEntity,object>>[] propertySelectors):IQueryable<TEntity> GetAllList():List<TEntity> GetAllListAsync():Task<List<TEntity>> GetAllList(Expression<Func<TEntity,bool>> predicate):List<TEntity> GetAllListAsync(Expression<Func<TEntity,bool>> predicate):Task<List<TEntity>> Query<T>(Func<IQueryable<TEntity>,T> queryMethod):T Get(TPrimaryKey id):TEntity GetAsync(TPrimaryKey id):Task<TEntity> Single(Expression<Func<TEntity,bool>> predicate):TEntity SingleAsync(Expression<Func<TEntity,bool>> predicate):Task<TEntity> FirstOrDefault(TPrimaryKey id):TEntity FirstOrDefaultAsync(TPrimaryKey id):Task<TEntity> FirstOrDefault(Expression<Func<TEntity,bool>> predicate):TEntity FirstOrDefaultAsync(Expression<Func<TEntity,bool>> predicate):Task<TEntity> Load(TPrimaryKey id):TEntity Insert(TEntity entity):TEntity InsertAsync(TEntity entity):Task<TEntity> InsertAndGetId(TEntity entity):TPrimaryKey InsertAndGetIdAsync(TEntity entity):Task<TPrimaryKey> InsertOrUpdate(TEntity entity):TEntity InsertOrUpdateAsync(TEntity entity):Task<TEntity> InsertOrUpdateAndGetId(TEntity entity):TPrimaryKey InsertOrUpdateAndGetIdAsync(TEntity entity):Task<TPrimaryKey> Update(TEntity entity):TEntity UpdateAsync(TEntity entity):Task<TEntity> Update(TPrimaryKey id, Action<TEntity> updateAction):TEntity UpdateAsync(TPrimaryKey id, Func<TEntity,Task> updateAction):Task<TEntity> Delete(TEntity entity):void DeleteAsync(TEntity entity):Task Delete(TPrimaryKey id):void DeleteAsync(TPrimaryKey id):Task Delete(Expression<Func<TEntity,bool>> predicate):void DeleteAsync(Expression<Func<TEntity,bool>> predicate):Task Count():int CountAsync():Task<int> Count(Expression<Func<TEntity,bool>> predicate):int CountAsync(Expression<Func<TEntity,bool>> predicate):Task<int> LongCount():long LongCountAsync():Task<long> LongCount(Expression<Func<TEntity,bool>> predicate):long LongCountAsync(Expression<Func<TEntity,bool>> predicate):Task<long>
在ABP 框架中,若间接在NuGet中装置的ABP 或者在官网下载的ABP 模板,很少会看到IRepository相干代码,看到利用的,根本都在包含在 EntityFrameworkCore类库中,零碎默认给出自定义仓库的根本格局。
public abstract class TestProjectRepositoryBase<TEntity, TPrimaryKey> : EfCoreRepositoryBase<TestProjectDbContext, TEntity, TPrimaryKey> where TEntity : class, IEntity<TPrimaryKey> { protected TestProjectRepositoryBase(IDbContextProvider<TestProjectDbContext> dbContextProvider) : base(dbContextProvider) { } // Add your common methods for all repositories } /// <summary> /// Base class for custom repositories of the application. /// This is a shortcut of <see cref="TestProjectRepositoryBase{TEntity,TPrimaryKey}"/> for <see cref="int"/> primary key. /// </summary> /// <typeparam name="TEntity">Entity type</typeparam> public abstract class TestProjectRepositoryBase<TEntity> : TestProjectRepositoryBase<TEntity, int>, IRepository<TEntity> where TEntity : class, IEntity<int> { protected TestProjectRepositoryBase(IDbContextProvider<TestProjectDbContext> dbContextProvider) : base(dbContextProvider) { } // Do not add any method here, add to the class above (since this inherits it)!!! }
仓储的实现
ABP在设计上是采取不指定特定ORM框架或其它存取数据库技术的形式。只有实现IRepository接口,任何框架都能够应用。
仓储要应用NHibernate或EF来实现都很简略,当你应用NHibernate或EntityFramework,如果提供的办法已足够应用,你就不须要为你的实体创立仓储对象了。咱们能够间接注入IRepository<TEntity>(或IRepository<TEntity, TPrimaryKey>)。
治理数据库连贯
数据库连贯的开启和敞开,在仓储办法中,ABP会自动化的进行连贯治理。
当仓储办法被调用后,数据库连贯会主动开启且启动事务。当仓储办法执行完结并且返回当前,所有的实体变动都会被贮存, 事务被提交并且数据库连贯被敞开,所有都由ABP自动化的管制。如果仓储办法抛出任何类型的异样,事务会主动地回滚并且数据连贯会被敞开。上述所有操作在实现了IRepository接口的仓储类所有公开的办法中都能够被调用。
如果仓储办法调用其它仓储办法(即使是不同仓储的办法),它们共享同一个连贯和事务。连贯会由仓储办法调用链最上层的那个仓储办法所治理。更多对于数据库治理,详见UnitOfWork文件。
仓储的生命周期
所有的仓储对象都是暂时性的。这就是说,它们是在有须要的时候才会被创立。ABP大量的应用依赖注入,当仓储类须要被注入的时候,新的类实体会由注入容器会主动地创立。
仓储的最佳实际
对于一个T类型的实体,是能够应用IRepository<T>。但别任何状况下都创立定制化的仓储,除非咱们真的很须要。预约义仓储办法曾经足够应酬各种案例。
如果你正创立定制的仓储(能够实现IRepository<TEntity>)
仓储类应该是无状态的。这意味着, 你不该定义仓储等级的状态对象并且仓储办法的调用也不应该影响到其它调用。
当仓储能够应用相依据注入,尽可较少或是不相依据于其它服务。
ABP畛域层——工作单元(Unit Of work)
通用连贯和事务管理办法连贯和事务管理是应用数据库的应用程序最重要的概念之一,应用程序中,有两个通用的方来创立/开释一个数据库连贯:
- 长连贯
- 短连贯
ABP的连贯和事务管理
- ABP提供长连贯、短连贯模型,Repository是数据库操作的类,在执行Repository库中的办法的时候,ABP开启了一个数据库连贯,并且在进入仓库办法的时候,会启用一个事务,因而,你能够平安的应用连贯于仓储中的办法,若办法执行过程中产生异样,事务会被回滚并且开释掉连贯。在这个模式中,仓储办法是单元性的(Unit of work)。 查看源码局部:
public class EfCoreRepositoryBase<TDbContext, TEntity> : EfCoreRepositoryBase<TDbContext, TEntity, int>, IRepository<TEntity>, IRepository<TEntity, int>, IRepository, ITransientDependency where TDbContext : DbContext where TEntity : class, IEntity<int> { public EfCoreRepositoryBase(IDbContextProvider<TDbContext> dbContextProvider) : base(dbContextProvider) { }
工作单元
工作单元的关键字为(Unit of work),为仓储提供事务服务,一共能够以两种形式应用
- 在办法上增加[UnitOfWorkAttribute]确保事务一致性
- 在办法体重应用IUnitOfWorkManager.Begin(...)、IUnitOfWorkManager.Begin(...)、xxunitOfWork.Complete()确保事务一致性。
ABP 框架举荐应用UnitOfWorkattribute的在办法体上加属性的形式。
- 禁用工作单元(Disabling unit of work)
办法体上设置如为[UnitOfWork(IsDisabled = true)]
- 无事务的工作单元(Non-transactional unit of work):[UnitOfWork(false)]
工作单元调用其它工作单元(A unit of work method calls another)
- 工作单元办法(一个贴上UnitOfWork属性标签的办法)调用另一个工作单元办法,他们共享同一个连贯和事务
- 如果创立了一个不同的线程/工作,它应用本人所属的工作单元
- 自动化的saving changes (Automatically saving changes)
当咱们应用工作单元到办法上,ABP主动的贮存所有变动于办法的末端。
- 选项:IsolationLevel、Timeout等根本属性,能够通过配置或者初始化赋值批改
- 办法:工作单元零碎运作是无缝且不可视的。然而,在有些特例下,你须要调用它的办法。ABP贮存所有的变动于工作单元的尾端,你不须要做任何事件。然而,有些时候,你或者会想要在工作单元的过程中就贮存所有变动。 你能够注入IUnitOfWorkManager并且调用IUnitOfWorkManager.Current.SaveChanges()办法,即可实现保留工作。
- 事件:工作单元具备Completed/Failed/Disposed事件
ABP畛域层——数据过滤器(Data filters)
在数据库开发中,咱们个别会使用软删除(soft-delete)模式,即不间接从数据库删除数据,而是标记这笔数据为已删除。因而,如果实体被软删除了,那么它就应该不会在应用程序中被检索到。要达到这种成果,咱们须要在每次检索实体的查问语句上增加SQL的Where条件IsDeleted = false。这是个乏味的工作,但它是个容易被忘掉的事件。因而,咱们应该要有个主动的机制来解决这些问题。
ABP提供数据过滤器(Data filters),它应用自动化的,基于规定的过滤查问。ABP曾经有一些预约义的过滤器,当然也能够自行创立你专属的过滤器。
- <font color=blue>过滤</font>
预约义过滤器
- 软删除接口(ISoftDelete)
- 多租接口(IMustHaveTenant)
- 多租接口(IMayHaveTenant)
- 禁用过滤器
能够在工作单元(unit of work)中调用DisableFilter办法来禁用某个过滤器
var people1 = _personRepository.GetAllList();using(_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete)) { var people2 = _personRepository.GetAllList(); }var people3 = _personRepository.GetAllList();
- 启用过滤器
在工作单元(unit of work)中应用EnableFilter办法启用过滤器,如同DisableFilter办法个别(两者互为正反两面)
- 设定过滤器参数
过滤器能够被参数化(parametric)。IMustHaveTenant过滤器是这类过滤器的一个范本,因为以后租户(tenant)的Id是在执行期间决定的。对于这些过滤器,如果真有须要,咱们能够扭转过滤器的值
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
- 自定义过滤器
- 其它对象关系映射工具
ABP数据过滤器仅实现在Entity Framework上。对于其它ORM工具则尚不可用。
ABP畛域层——畛域事件(Domain events)
在C#中,一个类能够定义其专属的事件并且其它类能够注册该事件并监听,当事件被触发时能够取得事件告诉。这对于对于桌面应用程序或独立的Windows Service来说十分有用。然而, 对于Web应用程序来说会有点问题,因为对象是依据申请(request)被创立并且它们的生命周期都很短暂。咱们很难注册其它类别的事件。同样地,间接注册其它类别的事件也造成了类之间的耦合性。
在利用零碎中,畛域事件被用于解耦并且重用(re-use)商业逻辑。
### 事件总线
事件总线为一个单体(singleton)的对象,它由所有其它类所共享,可通过它触发和处理事件
获取默认实例
EventBus.Default.Trigger(...); //触发事件
注入IEventBus事件接口(Injecting IEventBus)
public class TaskAppService : ApplicaService { public IEventBus EventBus { get; set; } public TaskAppService() { EventBus = NullEventBus.Instance; } }
事件参数继承于EventData类,源码如下
[Serializable] public abstract class EventData : IEventData { /// <summary> /// The time when the event occurred. /// </summary> public DateTime EventTime { get; set; } /// <summary> /// The object which triggers the event (optional). /// </summary> public object EventSource { get; set; } /// <summary> /// Constructor. /// </summary> protected EventData() { EventTime = Clock.Now; } }
定义事件
ABP定义AbpHandledExceptionData事件并且在异样产生的时候主动地触发这个事件。这在你想要获得更多对于异样的信息时特地有用(即使ABP已主动地纪录所有的异样)。你能够注册这个事件并且设定它的触发机会是在异样产生的时候。
ABP也提供在实体变更方面许多的通用事件数据类: EntityCreatedEventData, EntityUpdatedEventData和EntityDeletedEventData。它们被定义在Abp.Events.Bus.Entitis命名空间中。当某个实体新增/更新/删除后,这些事件会由ABP主动地触发。如果你有一个Person实体,能够注册到EntityCreatedEventData,事件会在新的Person实体创立且插入到数据库后被触发。这些事件也反对继承。如果Student类继承自Person类,并且你注册到EntityCreatedEventData中,接着你将会在Person或Student新增后收到触发。
### 触发事件
public class TaskAppService : ApplicationService { public IEventBus EventBus { get; set; } public TaskAppService() { EventBus = NullEventBus.Instance; } public void CompleteTask(CompleteTaskInput input) { //TODO: 已实现数据库上的工作 EventBus.Trigger(new TaskCompletedEventData { TaskId = 42 } ); } }
### 事件处理
要进行事件的解决,你应该要实现IEventHandler接口如下所示
public class ActivityWriter : IEventHandler<EventData>, ITransientDependency { public void HandleEvent(EventData eventData) { WriteActivity("A task is completed by id = " + eventData.TaskId); } }
解决多个事件(Handling multiple events)
public class ActivityWriter : IEventHandler<TaskCompletedEventData>, IEventHandler<TaskCreatedEventData>, ITransientDependency { public void HandleEvent(TaskCompletedEventData eventData) { //TODO: 处理事件 } public void HandleEvent(TaskCreatedEventData eventData) { //TODO: 处理事件 } }
### 注册处理器
- 主动型Automatically
ABP框架扫描所有继承了IEventHandler的接口实现类,并注册到事件总线中,当事件产生时,能够通过DI来实例化对象,并调用事件办法。 - 手动型(Manually)
EventBus.Register<TaskCompletedEventData>(eventData =>{ WriteActivity("A task is completed by id = " + eventData.TaskId);});
### 勾销注册事件
//注册一个事件
EventBus.Unregister<TaskCompletedEventData>(handler);
博主GitHub地址
https://github.com/yuyue5945
关注公众号不迷路