当前位置:网站首页>ABP framework Practice Series (III) - domain layer in depth
ABP framework Practice Series (III) - domain layer in depth
2022-06-26 03:44:00 【yuyue5945】
- ABP Domain layer - Entity (Entities)
- ABP Domain layer —— Storage (Repositories)
- ABP Domain layer —— The unit of work (Unit Of work)
- ABP Domain layer —— Data filters (Data filters)
- ABP Domain layer —— Field events (Domain events)
ABP Domain layer - Entity (Entities)
Entity is DDD( Domain-driven design ) One of the core concepts of .Eric Evans It's described like this “ Many objects are not defined by their properties , It is defined by a series of continuous events and signs ”.
In the frame combat series ( Two )- Introduction to domain layer There is a brief introduction to the code in ( Recommend the official account , Dry cargo is full. )
Here we have a deep understanding of the concept of entity
stay ABP in , Entity class inheritance Entity class , As shown below ,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()
{
CreationTime=DateTime.Now;
}
}
stay ABP In the frame Entity The content of entity class is as follows , It has a primary key ID, All inherited from Entity Class , All use this primary key , You can set the value of the primary key to any format , As shown in the following code , It is generic , It can be for string,Guid Or other data types . At the same time, the entity class overrides equality (==) Operator is used to determine whether two entity objects are equal ( Of two entities Id Whether it is equal or not ). I've also defined one IsTransient() Method to detect whether the entity has 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()
{
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);
}
Interface agreement
In many applications , Many entities have properties like CreationTime Properties of ( The database table also has this field ) Used to indicate when the entity was created .APB Some useful interfaces are provided to implement these similar functions . in other words , For these entities that implement these interfaces , It provides a general coding method ( Generally speaking, as long as the specified interface is implemented, the specified function can be realized ), With ABPUser For example , As shown below AbpUser class , Inherits many default implementation interfaces .
- IAudited Audit
- ISoftDelete Soft delete
- IPassivable Activate / 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>
ABP Domain layer —— Storage (Repositories)
ABP Frame combat series ( One )- Introduction to persistence layer There is an explanation of the concept of warehousing in ,( Recommend the official account , Dry cargo is full. )
ad locum , Let's go into the warehousing section , stay ABP in , Storage class to achieve IRepository Interface . The best way is to define different interfaces for different storage objects . For storage IRepository Defines many generic methods . such as :Select、Insert、Update、Delete Method (CRUD operation ). Most of the time ,, These methods are sufficient to meet the needs of general entities . If these parties are sufficient for the entity , We no longer need to create the warehousing interface required by this entity / class . That is to say Custom warehousing .
IRepository The common methods of retrieving entities from database are defined . stay ABP In the frame AbpRepositoryBase Derived IRepository Interface
public abstract class AbpRepositoryBase<TEntity, TPrimaryKey> : IRepository<TEntity, TPrimaryKey>, IRepository, ITransientDependency, IUnitOfWorkManagerAccessor
where TEntity : class, IEntity<TPrimaryKey>
Interface methods mainly include :Query、GetAll、Single、Insert、Update、Delete、Count And so on .
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>
stay ABP In the frame , If it's directly in NuGet In the installation of ABP Or downloaded from the official website ABP Templates , Rarely seen IRepository Related codes , See the application , Basically included in EntityFrameworkCore Class library , The basic format of user-defined warehouse is given by default .
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)!!!
}
Realization of warehousing
ABP It is designed without specifying specific ORM Framework or other means of accessing database technology . As long as the implementation IRepository Interface , Any framework can use .
Storage should use NHibernate or EF It's easy to realize , When you use NHibernate or EntityFramework, If the method provided is sufficient to use , You don't need to create storage objects for your entities . We can inject directly IRepository( or IRepository<TEntity, TPrimaryKey>).
Manage database connections
Opening and closing of database connection , In the storage method ,ABP Automatic connection management .
When the warehousing method is called , The database connection will automatically open and start the transaction . When the warehousing method execution ends and returns , All physical changes are stored , The transaction is committed and the database connection is closed , Everything is done by ABP Automatic control . If the warehousing method throws any type of exception , The transaction will be rolled back automatically and the data connection will be closed . All the above operations are implemented in IRepository All exposed methods of the warehouse class of the interface can be called .
If the warehousing method calls another warehousing method ( Even different storage methods ), They share the same connection and transaction . The connection will be managed by the warehouse method at the top of the warehouse method call chain . More about database management , See UnitOfWork file .
The life cycle of warehousing
All storage objects are temporary . That is to say , They are created when needed .ABP Extensive use of dependency injection , When the warehouse class needs to be injected , New class instances are created automatically by the injection container .
Best practices for warehousing
For one T Type of entity , Yes, you can use IRepository. But don't create customized warehouses under any circumstances , Unless we really need . Predefined warehousing methods are sufficient for various cases .
If you are creating a custom warehouse ( Can achieve IRepository)
The storage class should be stateless . It means , You should not define the state object of storage level, and the call of storage method should not affect other calls .
When warehousing can use phase based injection , As little as possible or not based on other services .
ABP Domain layer —— The unit of work (Unit Of work)
- Common connection and transaction management method connection and transaction management is one of the most important concepts for applications using databases , In the application , There are two common ways to create / Release a database connection :
- A long connection
- Short connection
- ABP Connection and transaction management
- ABP Provide long connection 、 Short connection model ,Repository Is the class of database operation , In execution Repository Methods in the library ,ABP Opened a database connection , And when entering the warehouse method , Will enable a transaction , therefore , You can safely use the methods connected to the warehouse , If an exception occurs during method execution , The transaction is rolled back and the connection is released . In this mode , The storage method is unitary (Unit of work). Check the source code 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)
{
}
The unit of work
The key word of the unit of work is (Unit of work), Provide transaction services for warehousing , It can be used in two ways
Add... To the method [UnitOfWorkAttribute] Ensure transaction consistency
Use in method weight IUnitOfWorkManager.Begin(…)、IUnitOfWorkManager.Begin(…)、xxunitOfWork.Complete() Ensure transaction consistency .
ABP The framework recommends UnitOfWorkattribute The method of adding attributes to the method body .
Disable unit of work (Disabling unit of work)
The method body is set as [UnitOfWork(IsDisabled = true)]
Unit of work without transactions (Non-transactional unit of work):[UnitOfWork(false)]
Work units call other work units (A unit of work method calls another)
- Unit of work method ( One is pasted UnitOfWork Method of attribute tag ) Call another unit of work method , They share the same connection and transaction
- If you create a different thread / Mission , It uses its own unit of work
Automated saving changes (Automatically saving changes)
When we use units of work on methods ,ABP Automatically store all changes at the end of the method .
Options :IsolationLevel、Timeout And so on , It can be modified by configuration or initialization assignment
Method : Unit of work systems operate seamlessly and invisibly . however , In some special cases , You need to call its methods .ABP Store all changes at the end of the work unit , You don't have to do anything . however , Sometimes , You might want to store all the changes during the unit of work . You can inject IUnitOfWorkManager And call IUnitOfWorkManager.Current.SaveChanges() Method , You can finish saving .
event : The unit of work has Completed/Failed/Disposed event
ABP Domain layer —— Data filters (Data filters)
In database development , We usually use soft deletion (soft-delete) Pattern , That is, do not directly delete data from the database , Instead, mark the data as deleted . therefore , If the entity is soft deleted , Then it should not be retrieved in the application . To achieve this effect , We need to add... To the query statement each time we retrieve entities SQL Of Where Conditions IsDeleted = false. It's a boring job , But it is an easy thing to forget . therefore , We should have an automatic mechanism to deal with these problems .
ABP Provide data filters (Data filters), It uses automated , Rule based filtering query .ABP There are already some predefined filters , Of course, you can also create your own filter .
- Filter
- Predefined filters
- Soft delete interface (ISoftDelete)
- Multi tenancy interface (IMustHaveTenant)
- Multi tenancy interface (IMayHaveTenant)
- Disable filters
It can be in the unit of work (unit of work) Call in DisableFilter Method to disable a filter
var people1 = _personRepository.GetAllList();
using(_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.SoftDelete)) {
var people2 = _personRepository.GetAllList();
}
var people3 = _personRepository.GetAllList();
Enable filter
In the unit of work (unit of work) Use in EnableFilter Method to enable the filter , Like DisableFilter The method is general ( Both are positive and negative to each other )
Set filter parameters
Filters can be parameterized (parametric).IMustHaveTenant The filter is a model of this kind of filter , Because the current tenant (tenant) Of Id It was decided during the implementation period . For these filters , If there is a need , We can change the value of the filter
CurrentUnitOfWork.SetFilterParameter("PersonFilter", "personId", 42);
Custom filter
Other object relationship mapping tools
ABP The data filter only implements Entity Framework On . For others ORM Tools are not yet available .
ABP Domain layer —— Field events (Domain events)
stay C# in , A class can define its own event and other classes can register the event and listen , You can get event notification when an event is triggered . This is for desktop applications or standalone Windows Service It's very useful . however , about Web There will be some problems for the application , Because the object is requested (request) Are created and their lifecycles are short . It is difficult for us to register events of other categories . similarly , Registering events of other classes directly also creates coupling between classes .
In application system , Domain events are used to decouple and reuse (re-use) Business logic .
Event bus
The event bus is a unit (singleton) The object of , It is shared by all other classes , It can trigger and handle events
Get the default instance
EventBus.Default.Trigger(…); // Triggering event
Inject IEventBus Event interface (Injecting IEventBus)
public class TaskAppService : ApplicaService {
public IEventBus EventBus {
get; set; }
public TaskAppService() {
EventBus = NullEventBus.Instance;
}
}
Event parameters are inherited from EventData class , Source code is as follows
[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;
}
}
Defining events
ABP Definition AbpHandledExceptionData Event and automatically trigger this event when an exception occurs . This is especially useful when you want to get more information about exceptions ( Even if ABP All exceptions have been automatically logged ). You can register this event and set its trigger time when the exception occurs .
ABP It also provides many common event data classes for entity changes : EntityCreatedEventData, EntityUpdatedEventData and EntityDeletedEventData. They are defined in Abp.Events.Bus.Entitis In the namespace . When an entity is added / to update / After deleting , These events will be handled by ABP Automatically trigger . If you have one Person Entity , Can be registered to EntityCreatedEventData, The event will be in the new Person The entity is triggered after it is created and inserted into the database . These events also support inheritance . If Student Class inherits from Person class , And you are registered to EntityCreatedEventData in , Then you will be in Person or Student Trigger received after adding .
Triggering event
public class TaskAppService : ApplicationService {
public IEventBus EventBus {
get; set; }
public TaskAppService() {
EventBus = NullEventBus.Instance;
}
public void CompleteTask(CompleteTaskInput input) {
//TODO: Completed task on database
EventBus.Trigger(new TaskCompletedEventData {
TaskId = 42 } );
}
}
Event handling
To handle events , You should achieve IEventHandler The interface is shown below
public class ActivityWriter : IEventHandler<EventData>, ITransientDependency {
public void HandleEvent(EventData eventData) {
WriteActivity("A task is completed by id = " + eventData.TaskId);
}
}
Handling multiple events (Handling multiple events)
public class ActivityWriter :
IEventHandler<TaskCompletedEventData>,
IEventHandler<TaskCreatedEventData>,
ITransientDependency
{
public void HandleEvent(TaskCompletedEventData eventData) {
//TODO: Handling events
}
public void HandleEvent(TaskCreatedEventData eventData) {
//TODO: Handling events
}
}
Register processor
- Automatic Automatically
ABP The framework scans all inherited IEventHandler Interface implementation class of , And register in the event bus , When the event occurs , Can pass DI To instantiate an object , And call the event method . - Manual type (Manually)
EventBus.Register<TaskCompletedEventData>(eventData =>
{
WriteActivity("A task is completed by id = " + eventData.TaskId);
});
Unregister event
// Register an event
EventBus.Unregister<TaskCompletedEventData>(handler);
Blogger GitHub Address
https://github.com/yuyue5945
Pay attention to the official account
- Automatic Automatically
ABP The framework scans all inherited IEventHandler Interface implementation class of , And register in the event bus , When the event occurs , Can pass DI To instantiate an object , And call the event method . - Manual type (Manually)
EventBus.Register<TaskCompletedEventData>(eventData =>
{
WriteActivity("A task is completed by id = " + eventData.TaskId);
});
Unregister event
// Register an event
EventBus.Unregister<TaskCompletedEventData>(handler);
Blogger GitHub Address
https://github.com/yuyue5945
Pay attention to the official account
边栏推荐
- Partition, column, list
- ASP. Net startup and running mechanism
- 进度条
- An easy-to-use tablayout
- Is it safe to open a fund account? How to apply
- Procédures stockées MySQL
- Request object, send request
- Drag and drop
- 360 second understanding of smartx hyper converged infrastructure
- WebRTC系列-网络传输之7-ICE补充之偏好(preference)与优先级(priority)
猜你喜欢
MySQL开发环境
【好书集锦】从技术到产品
Xiaomi TV's web page and jewelry's web page
阿里云函数计算服务一键搭建Z-Blog个人博客
显卡、GPU、CPU、CUDA、显存、RTX/GTX及查看方式
小米电视的网页和珠宝的网页
Group note data representation and operation check code
XGBoost, lightGBM, CatBoost——尝试站在巨人的肩膀上
MySQL高級篇第一章(linux下安裝MySQL)【下】
MySQL addition, deletion, query and modification (Advanced)
随机推荐
Popupwindow utility class
The "eye" of industrial robot -- machine vision
JS array array JSON de duplication
Uni app custom drop-down selection list
usb peripheral 驱动 - 枚举
Andorid hide the title bar of the system
Drag and drop
等保备案是等保测评吗?两者是什么关系?
进度条
redux-thunk 简单案例,优缺点和思考
Classic model - Nin & googlenet
kitti2bag 安装出现的各种错误
2022.6.25-----leetcode. Sword finger offer 091
After Ali failed to start his job in the interview, he was roast by the interviewer in the circle of friends (plug)
When the tiflash function is pushed down, it must be known that it will become a tiflash contributor in ten minutes
USB peripheral driver - Enumeration
Solve the problem that the input box is blocked by the pop-up keyboard under the WebView transparent status bar
Cultivate children's creativity under the concept of project steam Education
2022.6.23-----leetcode.30
拖放