- ABP Domain Layer – Entities
- ABP Domain Layer — Repository
- ABP Domain Layer — Unit Of Work
- ABP Domain Layer — Data Filters
- ABP Domain Layer — Domain Events
- Event bus
- Define events
- Triggering event
- The event processing
- Register handler
- Cancellation event
ABP Domain Layer – Entities
Entities are one of the core concepts of DDD (Domain-Driven Design). Eric Evans describes it this way: “Many objects are defined not by their attributes, but by a series of sequential events and identifiers.”
In the framework of the actual combat series (two) – domain layer introduction to do a simple code introduction (recommended to pay attention to the public number, full of dry goods)
Here we take a deeper look at the concept of entities
In ABP, the Entity class inherits from the Entity class, as shown below, where Product is an Entity
public class Product : Entity
{
public virtual string Name { get; set; }
public virtual string Code { get; set; }
public virtual DateTime CreationTime { get; set; }
public Product(a)
{ CreationTime=DateTime.Now; }}Copy the code
It has a primary key ID. All classes that inherit from Entity use this primary key. You can set the value of the primary key to any format, as shown in the code below. Also, the entity class overrides the equality (==) operator to determine whether two entity objects are equal (the ids of the two entities are equal). An IsTransient() method is also defined to detect whether an entity has an Id attribute.
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(a)
{
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(! (thisis IMustHaveTenant) || ! (entity is IMustHaveTenant) ||this.As<IMustHaveTenant>().TenantId == entity.As<IMustHaveTenant>().TenantId) && this.Id.Equals((object) entity.Id);
}
public override string ToString(a) = >string.Format("[{0} {1}]." ", (object) this.GetType().Name, (object) this.Id);
}
Copy the code
The interface contract
In many applications, many entities have attributes like CreationTime (which database tables also have) that indicate when the entity was created. APB provides some useful interfaces to implement these similar functions. In other words, it provides a common encoding method for entities that implement these interfaces. For example, the ABPUser class inherits many default implementation interfaces.
- IAudited audit
- ISoftDelete soft delete
- IPassivable active/idle state
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>
Copy the code
ABP Domain Layer — Repository
ABP framework combat series (a) – durable layer introduction involves the concept of storage, (recommend attention to the public number, dry goods full)
Here, we delve into the repository section, where the repository class implements the IRepository interface in ABP. The best approach is to define different interfaces for different repository objects. A number of generic methods are defined for the repository class IRepository. For example, Select, Insert, Update, Delete methods (CRUD operations). Most of the time, these methods are adequate for the needs of the general entity. If these are sufficient for the entity, we do not need to create the warehouse interface/class that the entity needs. That is, custom warehousing.
IRepository defines common methods for retrieving entities from a database. The IRepository interface is derived from AbpRepositoryBase in ABP framework
public abstract class AbpRepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey>, IRepository, ITransientDependency, IUnitOfWorkManagerAccessor
where TEntity : class.IEntity<TPrimaryKey>
Copy the code
Interface methods mainly include: Query, GetAll, Single, Insert, Update, Delete, Count and other large methods overload.
GetAll():IQueryable<TEntity>
GetAllIncluding(params Expression<Func<TEntity,object>>[] propertySelectors):IQueryable<TEntity>
GetAllList(a):List<TEntity>
GetAllListAsync(a):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(a):int
CountAsync(a):Task<int>
Count(Expression<Func<TEntity,bool>> predicate):int
CountAsync(Expression<Func<TEntity,bool>> predicate):Task<int>
LongCount(a):long
LongCountAsync(a):Task<long>
LongCount(Expression<Func<TEntity,bool>> predicate):long
LongCountAsync(Expression<Func<TEntity,bool>> predicate):Task<long>
Copy the code
In the ABP framework, if you install ABP directly in NuGet or download ABP templates from the official website, you will rarely see IRepository code, which is included in the EntityFrameworkCore class library. By default, the system defines the basic format of the repository.
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)!!!
}
Copy the code
ABP is not designed to specify a specific ORM framework or other database access technologies. Any framework can be used as long as the IRepository interface is implemented.
Warehousing is easy to implement using NHibernate or EF. When you use NHibernate or EntityFramework, you don’t need to create warehousing objects for your entities if the methods provided are sufficient. We can inject IRepository directly (or IRepository<TEntity, TPrimaryKey>).
Managing database connections The opening and closing of database connections, in the warehouse method,ABP automates connection management.
When the repository method is called, the database connection is automatically opened and the transaction is started. When the warehouse method completes and returns, all entity changes are stored, transactions are committed, and database connections are closed, all controlled by ABP automation. If the warehouse method throws any kind of exception, the transaction is automatically rolled back and the data connection is closed. All of the above operations can be called in any exposed methods of a repository class that implements the IRepository interface.
If a repository method invokes other repository methods (even methods of different repositories), they share the same connection and transaction. The connection is managed by the repository method at the top of the repository method call chain. For more information on database management, see the UnitOfWork file.
The life cycle of the warehouse all objects in the warehouse are temporary. That is, they are created when they are needed. ABP makes extensive use of dependency injection. When a repository class needs to be injected, new class entities are automatically created by the injection container.
The best practice for warehousing is to use IRepository for a T-type entity. But don’t create custom warehouses under any circumstances, unless we really need them. Predefined warehousing methods are sufficient for a variety of cases. If you are creating a custom repository (which can implement IRepository), the repository class should be stateless. This means that you should not define warehouse-level state objects and the invocation of a warehouse-method should not affect other calls. While warehousing may use data injection, there may be little or no data injection for other services.
ABP Domain Layer — Unit Of Work
- Common Connection and transaction Management Methods Connection and transaction management are one of the most important concepts in applications that use databases. In an application, there are two common parties to create/release a database connection:
- A long connection
- Short connection
- ABP connection and transaction management
- ABP provides a long-join and short-join model. Repository is a database operation class. When executing a method in Repository, ABP opens a database connection, and when entering a Repository method, ABP enables a transaction. If an exception occurs during method execution, the transaction is rolled back and the connection is released. In this pattern, the warehousing method is Unit of work. Check the source section:
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)
{}Copy the code
-
The unit of work
The key word for a Unit of work is Unit of work, which provides transactional services for the warehouse, and can be used in two ways
-
Add [UnitOfWorkAttribute] to the method to ensure transaction consistency
-
Use iunitofWorkManager.begin (…) in method weight. , IUnitOfWorkManager. The Begin (…). XxunitOfWork.Com plete() ensures transaction consistency.
The ABP framework recommends using UnitOfWorkattribute to add attributes to the method body.
-
Disabling Unit of Work
[UnitOfWork(IsDisabled = true)]
-
Non-transactional unit of work: [UnitOfWork(false)]
-
A unit of work method calls another
- A UnitOfWork method (a method labeled with the UnitOfWork attribute) calls another UnitOfWork method that shares the same connection and transaction
- If a different thread/task is created, it uses its own unit of work
-
Automatically saving Changes (Automatically saving changes)
When we use a unit of work on a method,ABP automatically stores all changes at the end of the method.
-
-
Options: Basic properties such as IsolationLevel and Timeout can be modified through configuration or initial assignment
-
Method: UnIT-of-work systems operate seamlessly and unobserved. However, in some special cases, you need to call its methods. ABP stores all changes at the end of the unit of work, you don’t have to do anything. However, there are times when you might want to store all changes during the course of a unit of work. You can inject IUnitOfWorkManager and invoke IUnitOfWorkManager. Current. SaveChanges () method, which can save work.
-
Events: the unit of work is Completed/Failed/Disposed event
ABP Domain Layer — Data Filters
In database development, we usually use soft-delete mode, that is, we do not delete data directly from the database, but mark it as deleted. Therefore, if the entity is soft deleted, it should not be retrieved in the application. To achieve this effect, we need to add the SQL Where condition IsDeleted = false on each query that retrieves the entity. It’s a tedious job, but it’s a forgettable one. Therefore, we should have an automatic mechanism to deal with these problems.
ABP provides Data filters that use automated, rules-based filtering queries. ABP already has some predefined filters, but you can also create your own.
- filter
- Predefined filter
- SoftDelete Interface (ISoftDelete)
- Multi-tenancy Interface (IMustHaveTenant)
- Multi-tenant (IMayHaveTenant)
- Disabling filters
You can disable a filter by calling the DisableFilter method in the Unit of work
var people1 = _personRepository.GetAllList();
using(_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete)) {
var people2 = _personRepository.GetAllList();
}
var people3 = _personRepository.GetAllList();
Copy the code
-
Enabling filters
Use the EnableFilter method to enable the filter in the Unit of Work as the DisableFilter method (both are positive and negative)
-
Setting filter parameters
The filter can be parametric. The IMustHaveTenant filter is an example of this type of filter because the Id of the current tenant is determined at execution time. For these filters, we can change the value of the filter if we really need to
CurrentUnitOfWork.SetFilterParameter("PersonFilter"."personId".42);
Copy the code
-
Custom filter
-
Other object-relational mapping tools
ABP data filters are implemented only on Entity Framework. Other ORM tools are not yet available.
ABP Domain Layer — Domain Events
In C#, one class can define its own event and other classes can register the event and listen for it, getting notification when it is raised. This is useful for desktop applications or standalone Windows Services. However, for Web applications, this can be problematic because objects are created on request and have a short life cycle. It is difficult to register other categories of events. Similarly, registering events of other classes directly creates coupling between classes.
In application systems, domain events are used to decouple and re-use business logic.
Event bus
The event bus is a singleton object shared by all other classes through which events can be triggered and handled
Get the default instance
EventBus.Default.Trigger(…) ; // Trigger the event
Inject IEventBus Event interface (Injecting IEventBus)
public class TaskAppService : ApplicaService {
public IEventBus EventBus { get; set; }
public TaskAppService(a) { EventBus = NullEventBus.Instance; }}Copy the code
The event parameters inherit from the EventData class with the following source code
[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(a)
{ EventTime = Clock.Now; }}Copy the code
Define events
ABP defines the AbpHandledExceptionData event and automatically fires it when an exception occurs. This is especially useful when you want to get more information about exceptions (even though ABP has automatically logged all exceptions). You can register this event and set it to fire when an exception occurs.
ABP also provides a number of generic event data classes for entity changes: EntityCreatedEventData, EntityUpdatedEventData, and EntityDeletedEventData. They are defined in the abp.events.bus.entitis namespace. These events are automatically triggered by ABP when an entity is added/updated/deleted. If you have a Person entity, you can register it with EntityCreatedEventData, and the event will be triggered when the new Person entity is created and inserted into the database. These events also support inheritance. If the Student class inherits from the Person class and you register with EntityCreatedEventData, then you will get triggered when either Person or Student is added.
Triggering event
public class TaskAppService : ApplicationService {
public IEventBus EventBus { get; set; }
public TaskAppService(a) {
EventBus = NullEventBus.Instance;
}
public void CompleteTask(CompleteTaskInput input) {
//TODO:Tasks on the database are complete
EventBus.Trigger(new TaskCompletedEventData { TaskId = 42}); }}Copy the code
The event processing
To handle events, you should implement the IEventHandler interface as shown below
public class ActivityWriter : IEventHandler<EventData>, ITransientDependency {
public void HandleEvent(EventData eventData) {
WriteActivity("A task is completed by id = "+ eventData.TaskId); }}Copy the code
Handling multiple events
public class ActivityWriter :
IEventHandler<TaskCompletedEventData>,
IEventHandler<TaskCreatedEventData>,
ITransientDependency
{
public void HandleEvent(TaskCompletedEventData eventData) {
//TODO:Handle events
}
public void HandleEvent(TaskCreatedEventData eventData) {
//TODO:Handle events}}Copy the code
Register handler
- Automatic Automatically ABP framework scans all interface implementation classes that inherit IEventHandler and registers them in the event bus. When an event occurs, objects can be instantiated through DI and event methods can be called.
- Manual type (Manually)
EventBus.Register<TaskCompletedEventData>(eventData =>
{
WriteActivity("A task is completed by id = " + eventData.TaskId);
});
Copy the code
Cancellation event
// Register an event
EventBus.Unregister<TaskCompletedEventData>(handler);
Copy the code
GitHub is at github.com/yuyue5945