Analyze the entity and storage classes in the Domain Layer of the ABP framework

Source: Internet
Author: User

Analyze the entity and storage classes in the Domain Layer of the ABP framework

Domain Layer
Entity is one of the core concepts of DDD (domain-driven design. Eric Evans described "many objects are defined not by their attributes, but by a series of continuity events and identifiers" (referencing the domain-driven design book ).

Note: objects are defined through linear continuity and identifiers instead of their attributes .. Therefore, an object has a unique ID and is stored in a database. Objects are usually mapped to a table in the database.

Entity classes)
The Entity inherits from the Entity class in the ABP. See the following example:

public class Person : Entity{  public virtual string Name { get; set; }  public virtual DateTime CreationTime { get; set; }  public Task()  {    CreationTime = DateTime.Now;  }}

The Person class is defined as an entity. It has two attributes, and its parent class has the Id attribute. Id is the primary key of the object. Therefore, Id is the primary key of all entities inherited from the Entity class (the primary key of all objects is the Id field ).

The Id (primary key) data type can be changed. The default value is int (int32. If you want to define other types for the Id, you should declare the Id type as in the following example.

public class Person : Entity<long>{  public virtual string Name { get; set; }  public virtual DateTime CreationTime { get; set; }  public Task()  {    CreationTime = DateTime.Now;  }}

You can set it to string, Guid, or other data types.

The entity class overwrites the equality (=) operator to determine whether two object objects are equal (whether the IDs of two objects are equal ). An IsTransient () method is also defined to detect whether an object has an Id attribute.

Interface conventions
In many applications, many entities have CreationTime attributes (database tables also have this field) to indicate when the object was created. APB provides some useful interfaces to implement these similar functions. That is to say, a general encoding method is provided for these entities that implement these interfaces (generally speaking, a specified function can be implemented as long as the specified interface is implemented ).

(1) Audit)

The IHasCreationTime interface of the object class can have the CreationTime attribute. When the object is inserted into the database, the value of this attribute is automatically set to the current time.

public interface IHasCreationTime{  DateTime CreationTime { get; set; }}

The Person class can be rewritten to implement the IHasCreationTime interface as follows:

public class Person : Entity<long>, IHasCreationTime{  public virtual string Name { get; set; }  public virtual DateTime CreationTime { get; set; }  public Task()  {    CreationTime = DateTime.Now;  }}

ICreationAudited is extended from IHasCreationTime and the interface has the attribute CreatorUserId:

public interface ICreationAudited : IHasCreationTime{  long? CreatorUserId { get; set; }}

When a new object is saved, the value of CreatorUserId is set to the Id of the current user.

You can easily implement the ICreationAudited interface by deriving from the object class CreationAuditedEntity (because this class has implemented the ICreationAudited interface, we can directly inherit the CreationAuditedEntity class to implement the above functions ). It has a generic version that implements different ID data types (int by default). You can assign different data types to IDS (IDs in the Entity class.
The following is an interface for implementing similar modification functions

public interface IModificationAudited{  DateTime? LastModificationTime { get; set; }  long? LastModifierUserId { get; set; }}

When an object is updated, the value of these attributes is automatically set by the ABP. You only need to implement these attributes in your object class.

If you want to implement all audit attributes, you can directly extend the IAudited interface. For example:

public interface IAudited : ICreationAudited, IModificationAudited{    }

As a quick development method, you can directly derive from the AuditedEntity class without implementing the IAudited interface (the AuditedEntity class has implemented this function, and you can directly inherit this class to implement the above functions ), the AuditedEntity class has a generic version (int by default) that implements different ID data types. You can assign different data types to IDS (IDs in the Entity class.

(2) Soft delete)

Soft Delete is a common mode used to mark a deleted object, rather than deleting records from the database. For example, you may not want to hard delete a user record from the database because it is associated with many other tables. To achieve soft deletion, we can implement this interface ISoftDelete:

public interface ISoftDelete{  bool IsDeleted { get; set; }}

The out-of-the-box soft deletion mode is implemented by the ABP. When an object that implements soft deletion is being deleted, the ABP detects this action and prevents it from being deleted. It sets the IsDeleted attribute value to true and updates the object in the database. That is to say, records that are soft-deleted cannot be retrieved from the database, and the abcwill automatically filter records that are soft-deleted. (For example, Select query, Which is queried through the ABC instead of the query analyzer in the database .)

If you use soft Delete, you may also want to implement this function, that is, to record who deleted the object. To implement this function, you can implement the IDeletionAudited interface. See the following example:

public interface IDeletionAudited : ISoftDelete{  long? DeleterUserId { get; set; }  DateTime? DeletionTime { get; set; }}

As you can see, IDeletionAudited extends from the ISoftDelete interface. When an object is deleted, the ABP automatically sets values for these attributes.
If you want to extend all the audit interfaces (such as creation, modification, and deletion) for the object class, you can directly implement the IFullAudited interface, because this interface has inherited these interfaces, see the following example:

public interface IFullAudited : IAudited, IDeletionAudited{    }

As a shortcut, you can directly derive your object class from the FullAuditedEntity class, because this class has implemented the IFullAudited interface.

Note: All audit interfaces and classes have a generic template to define attributes to your User entity for navigation (for example, ICreationAudited <TUser> and FullAuditedEntity <TPrimaryKey, TUser> ), here TUser refers to the type of the object class of the user who creates, modifies, and deletes. For details, see the source code (Abp. domain. entities. the FullAuditedEntity <TPrimaryKey, TUser> class in the Auditing space). TprimaryKey only belongs to the Entity base class Id type. The default value is int.

(3) Active/idle (Passive)

Some entities need to be marked as active or idle. Then you can take the active/passive action for the entity. For the entity created for this reason, you can extend the IPassivable interface to implement this function. This interface defines the IsActive attribute.

If the entity you created for the first time is marked as active, you can set the IsActive attribute value to true in the constructor.

This is different from IsDeleted ). If an object is soft deleted, it cannot be retrieved from the database (a soft deletion record has been filtered out by the ABP ). However, for entities in the active/idle status, you are totally dependent on how you obtain these marked entities.

IEntity Interface
In fact, Entity implements the IEntity interface (and Entity <TPrimaryKey> implements the IEntity <TPrimaryKey> interface ). If you do not want to derive from the Entity class, you can directly implement these interfaces. Other entity classes can also implement corresponding interfaces. However, this method is not recommended. Unless you have a good reason not to derive from the Entity class.


Warehousing (Repositories)
Warehousing definition: "The intermediary at the domain layer and the data ing layer uses interfaces similar to a set to access domain objects" (Martin Fowler ).

In fact, warehousing is used for database operations on domain objects (Entity and Value object Value types ). In general, we create the corresponding warehouse for different entities (or Aggregate Root.

IRepository Interface
In the ABP, the storage class must implement the IRepository interface. The best way is to define different interfaces for different storage objects.

The following is an example of the warehousing interface declaration for the Person object:

public interface IPersonRepository : IRepository<Person> {}

IPersonRepository inherits from IRepository <TEntity> to define an object of the Id type of int (Int32. If your object Id data type is not int, You can inherit the IRepository <TEntity, TPrimaryKey> interface, as shown below:

public interface IPersonRepository : IRepository<Person, long> { }

For the storage class, IRepository defines many generic methods. For example, Select, Insert, Update, and Delete (CRUD operation ). In most cases, these methods are sufficient to meet the needs of General entities. If these parties are sufficient for the object, we do not need to create the warehousing interface/class required for the object. More details are provided in Implementation.

(1) Query)

IRepository defines common methods for retrieving objects from a database.

A. Getting single entity ):

TEntity Get(TPrimaryKey id);Task<TEntity> GetAsync(TPrimaryKey id);TEntity Single(Expression<Func<TEntity, bool>> predicate);TEntity FirstOrDefault(TPrimaryKey id);Task<TEntity> FirstOrDefaultAsync(TPrimaryKey id);TEntity FirstOrDefault(Expression<Func<TEntity, bool>> predicate);Task<TEntity> FirstOrDefaultAsync(Expression<Func<TEntity, bool>> predicate);TEntity Load(TPrimaryKey id);

The Get method is used to obtain the corresponding entity based on the primary key value (Id. When the database cannot find the corresponding entity based on the primary key value, it will throw an exception. The Single method is similar to the Get method, but its input parameter is an expression rather than the primary key value (Id ). Therefore, we can write Lambda expressions to obtain entities. Example:

var person = _personRepository.Get(42);var person = _personRepository.Single(p => o.Name == "Halil ibrahim Kalkan");

Note: The Single method will throw an exception when the given condition cannot find the entity or more than one entity that meets the condition.

The same is true for FirstOrDefault, but when there is no entity that complies with Lambda expressions or IDs, null is returned (instead of throwing an exception ). When more than one entity meets the condition, it returns only the first entity.

Load does not retrieve entities from the database, but it creates the proxy object required for delayed execution. If you only use the Id attribute, the object is not actually retrieved. It only queries the object from the database when you access an attribute of the object you want to query. This method can be used to replace the Get method when there is a performance requirement. The Load method is also implemented in the integration of nhib.pdf and ABP. If the ORM Provider does not implement this method, the Load method runs the same way as the Get method.

Some methods of ABP have asynchronous (Async) versions and can be applied to the asynchronous development model (see The Async method section ).

B. Getting list of entities ):

List<TEntity> GetAllList();Task<List<TEntity>> GetAllListAsync();List<TEntity> GetAllList(Expression<Func<TEntity, bool>> predicate);Task<List<TEntity>> GetAllListAsync(Expression<Func<TEntity, bool>> predicate);IQueryable<TEntity> GetAll();

GetAllList is used to retrieve all objects from the database. Overload and provide the object filtering function, as follows:

var allPeople = _personRespository.GetAllList();var somePeople = _personRepository.GetAllList(person => person.IsActive && person.Age > 42);
GetAll: IQueryable <T> type object. Therefore, we can perform the Linq operation after calling this method. Example:
// Example 1 var query = from person in _ personRepository. getAll () where person. isActiveorderby person. nameselect person; var people = query. toList (); // Example 2 List <Person> personList2 = _ personRepository. getAll (). where (p => p. name. contains ("H ")). orderBy (p => p. name ). skip (40 ). take (20 ). toList ();

If you call the GetAll method, almost all queries can be completed using Linq. You can even use it to write Join expressions.

Note: IQueryable <T>
When you call the GetAll method outside the Repository object, the database connection will be enabled. This is because IQueryable <T> allows delayed execution. It will not execute the database query until you call the ToList method or use IQueryable on the forEach loop (or some methods for accessing the queried object. Therefore, when you call the ToList method, the database connection must be enabled. We can use the UnitOfWork feature provided by the ABP to implement the call method. Note that the Application Service method defaults to UnitOfWork. Therefore, if you use the GetAll method, you do not need to add the UnitOfWork feature on the Application Service method.

Some methods have an asynchronous version and can be applied to the asynchronous development model (see The async method section ).

Custom return value)

There is also an additional method for implementing the delayed loading effect of IQueryable <T>, instead of adding the UnitOfWork attribute to the called method.

T Query<T>(Func<IQueryable<Tentity>,T> queryMethod);
The query method accepts Lambda (or a method) to receive IQueryable <T> and returns any object type. Example:

Var people = _ personRepository. query (q => q. where (p => p. name. contains ("H ")). orderBy (p => p. name ). toList ());
Because Lambda (or method) is used for execution in the warehousing object method, It will be executed only after the database connection is enabled. You can return an object set, an entity, a partial field (Note: not Select *), or other query result sets after the query is executed.

(2) Add (insert)

The IRepository interface defines a simple method to add an object to the database:

TEntity Insert(TEntity entity);Task<TEntity> InsertAsync(TEntity entity);TPrimaryKey InsertAndGetId(TEntity entity);Task<TPrimaryKey> InsertAndGetIdAsync(TEntity entity);TEntity InsertOrUpdate(TEntity entity);Task<TEntity> InsertOrUpdateAsync(TEntity entity);TPrimaryKey InsertOrUpdateAndGetId(TEntity entity);Task<TPrimaryKey> InsertOrUpdateAndGetIdAsync(TEntity entity);
The new method adds an object to the database and returns the same newly added object. The InsertAndGetId method returns the Id of the newly added object ). It is useful when we use the automatically incrementing Identifier value and need to obtain the newly generated Identifier value of the entity. InsertOfUpdate will add or update objects. The selection is determined based on whether the Id has a value. Finally, InsertOrUpdatedAndGetId returns the Id value after the object is added or updated.

All methods have an asynchronous version that can be applied to the asynchronous development model (see the Asynchronous Method section)

(3) UPDATE)

IRepository defines a method to update an object that already exists in the database. It updates the object and returns the same object.

TEntity Update(TEntity entity);Task<TEntity> UpdateAsync(TEntity entity);
(4) Delete)

IRepository sets some methods to delete existing database entities.

void Delete(TEntity entity);Task DeleteAsync(TEntity entity);void Delete(TPrimaryKey id);Task DeleteAsync(TPrimaryKey id);void Delete(Expression<Func<TEntity, bool>> predicate);Task DeleteAsync(Expression<Func<TEntity, bool>> predicate);
The first method accepts an existing object, and the second method accepts the Id of the existing object.

The last method accepts a condition to delete the entity that meets the condition. Note that all objects that match the predicate expression are first retrieved and then deleted. Therefore, be careful when using it. This may cause many problems. If there are too many entities that meet the conditions, false.

All methods have the async version for application in the asynchronous development model (see the Asynchronous Method section ).

(5) Other methods (others)

IRepository also provides methods to obtain the number of entities in a data table.

int Count();Task<int> CountAsync();int Count(Expression<Func<TEntity, bool>> predicate);Task<int> CountAsync(Expression<Func<TEntity, bool>> predicate);Long LongCount();Task<long> LongCountAsync();Long LongCount(Expression<Func<TEntity, bool>> predicate);Task<long> LongCountAsync(Expression<TEntity, bool>> predicate);

All methods have the async version applied to the asynchronous development model (see the Asynchronous Method section ).

(6) About asynchronous methods (About Async methods)

Supports asynchronous model development. Therefore, the storage method has the Async version. Here is an example of the application service method using the asynchronous model:

public class PersonAppService : AbpWpfDemoAppServiceBase, IPersonAppService{  private readonly IRepository<Person> _personRepository;  public PersonAppService(IRepository<Person> personRepository)  {    _personRepository = personRepository;  }  public async Task<GetPeopleOutput> GetAllPeople()  {    var people = await _personRepository.GetAllListAsync();          return new GetPeopleOutput    {      People = Mapper.Map<List<PersonDto>>(people)    };  }}

The GetAllPeople method is asynchronous and uses GetAllListAsync and await to reserve keywords.

Async is not provided in every ORM framework.

In the preceding example, the asynchronous capability provided by EF is used. If the ORM framework does not provide the Async warehousing method, it will operate in synchronous mode. Similarly, for example, the InsertAsync operation is the same as the addition of EF, because EF will wait until the unit of work (unit of work) the new object will be written to the database (DbContext. saveChanges ).

Warehousing implementation
In terms of design, we adopt a method that does not specify a specific ORM framework or other database access technologies. Any framework can be used to implement the IRepository interface.

It is easy to use nhib.pdf or EF for warehousing.

EntityFramework
When you use nhib.pdf or EntityFramework, if the provided method is sufficient, you do not need to create a warehouse object for your object. We can directly inject IRepository <TEntity> (or IRepository <TEntity, TPrimaryKey> ). The following example shows how application service uses a warehouse object to add an object to the database:

public class PersonAppService : IPersonAppService{  private readonly IRepository<Person> _personRepository;  public PersonAppService(IRepository<Person> personRepository)  {    _personRepository = personRepository;  }  public void CreatePerson(CreatePersonInput input)  {        person = new Person { Name = input.Name, EmailAddress = input.EmailAddress };        _personRepository.Insert(person);  }}

The construction sub-of PersonAppService is injected with IRepository <Person> and Its Insert method is used. When you need to create a customized warehousing method for an object, you should create a storage class for the specified object.

Manage database connections
When the database connection is enabled or disabled, the TTL automatically manages the connection in the warehousing method.

When the warehousing method is called, the database connection is automatically started and the transaction starts. After the warehousing method is executed and returned, all entity changes are stored, transactions are committed, and database connections are closed. Everything is automatically controlled by the abc. If the warehousing method throws any type of exception, the transaction will be automatically rolled back and the data connection will be closed. All the above operations can be called in all the public methods of the warehousing class that implement the IRepository interface.

If the warehousing method calls other warehousing methods (even different warehousing methods), they share the same connection and transaction. The connection is managed by the warehousing method at the top of the call chain of the warehousing method.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.