Domain Layer
entity is one of the core concepts of DDD (domain driven design). Eric Evans describes this as "a lot of objects are not defined by their attributes, but by a series of continuity events and Identity Definitions" (a reference to the domain-driven design book).
Translators note that objects are not defined by their attributes, but by their linear continuity and identity. Therefore, an entity is an ID that has a unique identity and is stored in a database. Entities are usually mapped to a table in a database.
Entity class (Entity classes)
in ABP, the entity inherits from the entity class, 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 properties and has an ID attribute in its parent class. The ID is the primary key of the entity. Therefore, the ID is the primary key for all entities that inherit from the entity class (the primary key for all entities is the ID field).
The Id (primary key) data type can be changed. The default is the Int (int32) type. If you want to define other types for IDs, you should declare the type of ID as shown 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 overrides the equality (= =) operator to determine whether two entity objects are equal (the IDs of two entities are equal). A istransient () method is also defined to detect whether an entity has an id attribute.
Interface conventions
in many applications, many entities have properties like CreationTime (which are also available in database tables) to indicate when the entity was created. APB provides a number of useful interfaces to implement these similar functions. That is, for those entities that implement these interfaces, a common encoding is provided (in layman's terms, the specified functionality can be implemented as long as the specified interface is implemented).
(1) Audit (auditing)
An entity class implements the Ihascreationtime interface and can have creationtime properties. When the entity is inserted into the database, ABP automatically sets the value of the property to the current time.
Public interface Ihascreationtime
{
DateTime creationtime {get; set;}
}
The person class can be overridden to implement the Ihascreationtime interface as in the following example:
public class Person:entity<long> Ihascreationtime
{public
virtual string Name {get; set;}
Public virtual DateTime CreationTime {get; set;}
Public Task ()
{
creationtime = DateTime.Now
}
}
Icreationaudited extends from Ihascreationtime and the interface has attributes Creatoruserid:
Public interface Icreationaudited:ihascreationtime
{
long? Creatoruserid {get; set;}
}
When you save a new entity, ABP automatically sets the Creatoruserid property value to the current user's ID
You can easily implement the icreationaudited interface by deriving from the entity class Creationauditedentity (since the class has implemented the Icreationaudited interface, We can directly inherit the Creationauditedentity class to achieve these functions. It has a generic version that implements different ID data types (the default is int) and can give different data types to IDs (IDs in the entity Class).
The following is an interface for implementing a similar modification function
Public interface imodificationaudited
{
DateTime? LastModificationTime {get; set;}
Long? Lastmodifieruserid {get; set;}
}
When an entity is updated, ABP automatically sets the values of these properties. You only need to implement these attributes within your entity classes.
If you want to implement all of the audit properties, you can extend the iaudited interface directly;
Public interface iaudited:icreationaudited, imodificationaudited
{
}
As a quick way to develop, you can directly derive from the Auditedentity class, do not need to implement the Iaudited interface (Auditedentity class has implemented this function, directly inherit the class can achieve the above functions), auditedentity class has a generic version that implements different ID data types (the default is int) and can give different data types to IDs (IDs in the entity Class).
(2) Soft deletion (Soft delete)
Soft deletion is a generic pattern that is used to mark an entity that has been deleted, rather than actually deleting records from the database. For example, you might not want to hard delete a user record from a database because it is associated with many other tables. In order to achieve the purpose of soft deletion we can implement the interface Isoftdelete:
public interface isoftdelete{
bool isdeleted {get; set;}
}
The ABP realizes the soft deletion mode which is used in the Open box. When an entity that implements a soft deletion is being deleted, ABP perceives the action and prevents it from being deleted, setting the IsDeleted property value to True and updating the entities in the database. In other words, the deleted records can not be retrieved from the database, ABP will automatically filter the records of soft deletion. (For example: Select query, which refers to queries through ABP, not through Query Analyzer in the database.) )
If you use a soft deletion, you may also want to implement this function, which is to record who deleted the entity. To implement this feature 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, the ideletionaudited extends from the Isoftdelete interface. When an entity is deleted, ABP automatically sets the values for these properties.
If you want to extend all audit interfaces for entity classes (e.g., create (creation), modify (modification) and delete (deletion), you can implement the Ifullaudited interface directly, because the interface has inherited these interfaces, see the following example:
Public interface ifullaudited:iaudited, ideletionaudited
{
}
As a shortcut, you can derive your entity class directly from the Fullauditedentity class, because the class has implemented the Ifullaudited interface.
Note: All audit interfaces and classes have a generic template for navigating the definition attributes to your user entities (such as:icreationaudited<tuser> and Fullauditedentity<tprimarykey, TUser >), where Tuser refers to the type of entity classes that are created, modified, and deleted by the user, please see the source code (Fullauditedentity<tprimarykey in Abp.Domain.Entities.Auditing space, Tuser> Class), Tprimarykey is the entity base class ID type, and the default is int.
(3) Activation status/Idle state (active/passive)
Some entities need to be marked as active or idle. Then you can take the action of Active/passive state for the entity. Entities created for this reason, you can extend the Ipassivable interface to implement this functionality. The interface defines the properties of the IsActive.
If the entity you created for the first time is marked as active, you can set the IsActive property value to True in the constructor.
This is different from the soft deletion (isdeleted). If the entity is soft deleted, it cannot be retrieved from the database (ABP has filtered the soft delete record). But for entities that activate State/idle state, you depend entirely on how you obtain the marked entities.
IEntity interface
in fact entity implements the IEntity interface (and entity<tprimarykey> implements the Ientity<tprimarykey> interface). If you don't want to derive from the entity class, you can implement these interfaces directly. Other entity classes can also implement the appropriate interfaces. But it is not recommended that you use this approach. Unless you have a good reason not to derive from the entity class.
Warehousing (repositories)
Warehousing Definition: "In the domain layer and the data mapping layer of the intermediary, using a similar set of interfaces to access domain objects" (Martin Fowler).
In fact, warehousing is used for the operations of domain objects on the database (Entity entity and value types). In general, we create a corresponding warehouse for different entities (or aggregate root aggregate root).
IRepository interface
in ABP, the warehousing class implements the IRepository interface. The best way to do this is to define different interfaces for different storage objects.
An example of a warehouse interface declaration for a person entity is shown below:
Public interface ipersonrepository:irepository<person>
{
}
IPersonRepository inherits from Irepository<tentity>, an entity that defines the type int (INT32) for the ID. If your Entity ID data type is not int, you can inherit irepository<tentity, tprimarykey> interface, as follows:
Public interface Ipersonrepository:irepository<person, long>
{
}
For warehousing classes, IRepository defines a number of generic methods. For example: The Select,insert,update,delete method (Crud operation). In most cases, these methods have been sufficient to meet the needs of the general entity. If these parties are sufficient for the entity, then we do not need to create the warehousing interface/class required for this entity. There are more details in the implementation section.
(1) Queries (query)
IRepository defines common methods for retrieving entities from a database.
A, obtain A single entity (getting one 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 a conforming entity is not found in the database based on the primary key value, it throws an exception. The single method is similar to a GET method, but its input parameter is an expression rather than a primary key value (ID). So we can write lambda expressions to get the entities. Examples are as follows:
var person = _personrepository.get (n);
var person = _personrepository.single (p => o.name = = "Halil Ibrahim Kalkan");
Note that the single method throws an exception if the given condition does not find an entity or more than one conforming entity.
FirstOrDefault is the same, but when there are no entities that match the lambda expression or ID, NULL is returned (instead of throwing an exception). When more than one entity meets the criteria, it returns only the first entity.
Load does not retrieve entities from the database, but it creates proxy objects that are required for deferred execution. If you only use the id attribute, you will not actually retrieve the entity, it will only query the entity from the database when you access an attribute that you want to query the entity. 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 NHibernate and ABP. If the ORM provider (Provider) does not implement this method, the Load method runs the same as the Get method.
ABP Some methods have asynchronous (Async) versions that can be applied to the asynchronous development model (see Async Method related chapters).
B, obtain list of entities (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 entities from the database. Overloads and provides the ability to filter entities as follows:
var allpeople = _personrespository.getalllist ();
var somepeople = _personrepository.getalllist (person => person. IsActive && person. Age > 42);
GetAll returns an object of type iqueryable<t>. So we can do a LINQ operation after this method is called. Example:
Example one
var query = from person in _personrepository.getall ()
where person. IsActive by Person
. Name
Select person;
var people = query. ToList ();
Example two
list<person> personList2 = _personrepository.getall (). Where (P => p.name.contains ("H")). By (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.
Description: About Iqueryable<t>
When you call GetAll this method outside the Repository object, the database connection must be opened. This is because iqueryable<t> allows deferred execution. It will not actually execute the database query until you invoke the ToList method or use iqueryable<t> on the Foreach loop (or some access to the queried object method). Therefore, when you call the ToList method, the database connection must be enabled. We can use the Unitofwork feature provided by ABP to implement the method invoked. Note that the application service method preset is already unitofwork. Therefore, using the GetAll method does not require the addition of the Unitofwork attribute as a method of application service.
Some methods have asynchronous versions that can be applied to the asynchronous development model (see the section on async methods).
Custom return value (custom returns values)
ABP also has an additional way to implement iqueryable<t> delay loading without having to add unitofwork this property volume label on the invoked method.
T query<t> (func<iqueryable<tentity>,t> querymethod);
The Query method accepts a lambda (or a method) to receive Iqueryable<t> and returns any object type. Examples are as follows:
var people = _personrepository.query (q => q.where (P => p.name.contains ("H")). By (P => p.name). ToList ());
Because the lambda (or method) is executed in the method of the storage object, it is executed after the database connection is opened. You can return a collection of entities, or an entity, or a query result set with a partial field (Note: Non select *) or other query execution.
(2) Add (insert)
The IRepository interface defines a simple way to provide a new entity to a 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 entity to the database and returns the same new entity. The Insertandgetid method returns the identifier (ID) of the new entity. It is very handy when we take the automatic increment identifier value and need to get the new generated identifier value of the entity. Insertofupdate will add or update entities, choosing which is determined by whether the ID has a value. Finally, INSERTORUPDATEDANDGETID returns the ID value after the entity is added or updated.
All methods have an asynchronous version that can be applied to the asynchronous development model (see the section on asynchronous methods)
(3) Updating (update)
IRepository defines a method to implement updating an entity that already exists in the database. It updates the entity and returns the same entity object.
Tentity Update (tentity entity);
Task<tentity> Updateasync (tentity entity);
IRepository fixed a number of methods to delete entities that already exist in the database.
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 entity, and the second method accepts the ID of an existing entity.
The last method accepts a condition to delete the eligible entity. Be aware that all entities that conform to the predicate expression are retrieved and then deleted. Therefore, use to be very careful, which is likely to cause many problems if there are too many entities to meet the conditions.
All methods have a async version that is applied to the asynchronous development model (see the section on asynchronous methods).
(5) Other methods (others)
IRepository also provides methods to obtain the number of entities in a datasheet.
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 a async version that is applied to the asynchronous development model (see the section on asynchronous methods).
(6) About asynchronous methods (about Async methods)
ABP supports the asynchronous development model. Therefore, the warehousing method has a async version. Here is an example of a application service method that uses an 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 preserve the keyword.
Async is not available in every ORM framework.
The above example is the asynchronous capability provided from the EF. If the ORM framework does not provide a async warehousing method, it operates in a synchronized fashion. Similarly, for example, Insertasync is the same as the EF addition, because the EF will not write the new entity to the database (dbcontext.savechanges) until the cell job (unit of work) completes.
The realization of warehousing
ABP is designed to take the form of not specifying a particular ORM framework or other Access database technology. Any framework can be used as long as the IRepository interface is implemented.
It is easy to use nhibernate or EF to achieve storage.
EntityFramework
when you use NHibernate or entityframework, if the method provided is sufficient, you do not need to create a storage object for your entity. We can directly inject irepository<tentity> (or irepository<tentity, tprimarykey>). The following example uses a storage object for the application service to add entities 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 constructor of the personappservice is injected into the irepository<person> and its Insert method is used. When you need to create a customer-made warehousing method for the entity, you should create a storage class for the specified entity.
Managing Database connections
database connection is turned on and off, in the warehousing method, ABP will automate the connection management.
When the warehousing method is invoked, the database connection is automatically opened and the transaction starts. When the warehousing method finishes and returns, all entity changes are stored, the transaction is committed and the database connection is closed, and everything is controlled by ABP Automation. If the warehousing method throws any type of exception, the transaction is automatically rolled back and the data connection is closed. All of the above operations can be invoked in all exposed methods of the warehouse class that implements the IRepository interface.
If the warehousing method invokes other warehousing methods (that is, the method of different warehousing), they share the same connection and transaction. The connection is managed by the warehouse method at the top of the storage method call chain. More about database management, see Unitofwork file.
The life cycle of the storage
all storage objects are temporary. This means that they are created when they are needed. ABP A large number of use dependency injection, when the storage class needs to be injected, the new class entity will be automatically created by the injection container. See according to the injection file for more information.
Best Practices for Warehousing
for an entity of type T, you can use irepository<t>. But don't create custom warehouses in any case unless we really need them. Predefined warehousing methods are sufficient to cover a variety of cases.
If you are creating a custom warehouse (you can implement irepository<tentity>)
The warehousing class should be stateless. This means that you should not define the status object for the warehouse level and the call to the warehousing method should not affect other calls.
When warehousing can be used according to the injection, do less or less according to other services.