Domain Model
The heart of a Shesha application is the domain. The domain is the set of entities that represent the core business concepts of your application.
The Base Domain Model
To accelerate the development of your application, Shesha provides a set of core domain entities that you can use as a starting point for your application. We refer to this as the base domain. The base domain includes entities such as Person, Address, Organisation and Site which are recurrent in the vast majority of business applications and means you don't have to recreate them for every application.
| Entity | Description |
|---|---|
Account | Represents a customer account, whether on behalf of a person or organisation. |
Address | Represents an address. |
Organisation | Represents an organizational unit, organisation/company, team or similar. |
Person | Represents a person in the application such as employee, customer, contact, etc. A Person is not necessarily a system user as this will depend on whether there is a corresponding User for Person. See also User management fundamentals. |
Note | Represents a user note or comment. A list of notes may be associated with any parent entity. See also notes. |
ReferenceList and ReferenceListItem | Represents a standard list of values. See also reference lists. |
Site | Represents a geographic location or physical structure such as location, area, buildings, room etc. |
StoredFile | Represents a file stored in the system. See also file storage fundamentals. |
Though it is not a requirement to use the base domain, it is highly recommended to do so as it will accelerate the development of your application and you will be able to leverage other modules that also reference base entities.
Extending the Domain Model
While the base domain entities are beneficial, you will likely need to customize them to fit your application's unique requirements. For instance, you might need to create a Customer entity derived from the Person entity, or a Supplier entity derived from the Organisation entity. You might also find it necessary to append additional properties to the base entities or introduce entirely new entities.
As of v0.45 you can also create and modify entities through the Entity Configurator in the Configuration Studio, without writing code. The code-based approach described here is still used when you need full control in the back-end project. See Entity Configuration.
When an entity is created or extended through the Entity Configurator, Shesha generates the entity as a partial class. This means you can add a matching partial class file of the same name to your back-end project to extend the configurator-generated entity with custom code, such as calculated properties or domain methods, without the generated file overwriting your additions. An entity created through the Configurator can also be marked extensible, which allows other modules further down the module hierarchy to add their own properties to it.
Creating a new entity
To create a new entity, simply create a new class that inherits from the Entity class. The example below illustrates the creation of a Ticket entity.
Create the entity class
public class Ticket : Entity<Guid>
{
public virtual string Title { get; set; }
public virtual string Description { get; set; }
public virtual RefListTicketStatus Status { get; set; }
public virtual Person LoggedBy { get; set; }
public virtual Agent AssignedTo { get; set; }
public virtual DateTime? ClosedOn { get; set; }
}
/// <summary>
/// Ticket status reference list
/// </summary>
[ReferenceList("MyOrg.TicketManagement", "TicketStatus")] // The ReferenceList attribute is used to indicate that this enum is a reference list see [Reference Lists](/docs/back-end-basics/reference-lists) for more details.
public enum RefListTicketStatus : long
{
[Description("Open")]
Open = 1,
[Description("Closed")]
Closed = 2
}
All entities should ultimately inherit from Entity class as per the example above. You may however also inherit from other pre-existing sub-classes of Entity depending of the level of auditing you require for your entity. For example, if you want to track the creation and modification of your entity you can inherit from CreationAuditedEntity. Inheriting from AuditedEntity will track modification timestamp and user, while inheriting from FullAuditedEntity will track deletion timestamp and user as well.
Although it is possible to create entities with any type of Id, it is recommended to use Guid Ids for all entities. This is because Shesha uses Guid Ids for all entities in the base domain and many other modules. Using Guid Ids will therefore ensure consistency across your application and make it easier to integrate with other modules.
Add the database migration
To ensure that your new entity can be persisted to the database you will need to create the corresponding database objects. In order to do this you will need to create a database migration class. Shesha uses Fluent Migrator for the creation of the database migrations with Shesha specific extensions.
Shesha currently uses NHibernate as its ORM rather than the more common EF Core. NHibernate is a mature ORM that has been around for many years and is used in many enterprise applications, however unlike EF Core, NHibernate does not have the ability to generate database migration classes automatically, hence the need to create database migrations manually.
To create a database migration class, simply create a new class that inherits from the Migration class. The example below illustrates the creation of a database migration class for the Order entity as part of MyApp.
using System;
using FluentMigrator;
using Shesha.FluentMigrator;
namespace Shesha.Enterprise
{
[Migration(20250114101300)] // The migration number must be unique across all migrations in the application usually follows the following convention YYYYMMDDHHMMSS
public class M20250114101300 : Migration
{
public override void Up()
{
Create.Table("MyApp_Orders")
.WithIdAsGuid()
.WithFullAuditColumns() // Adds the standard columns required when the new entity inherits from FullAuditedEntity
.WithColumn("OrderNo").AsString().Nullable()
.WithColumn("DeliveryDate").AsDateTime().Nullable()
.WithColumn("Comment").AsString().Nullable()
.WithColumn("StatusLkp").AsInt64().Nullable();
Alter.Table("MyApp_Orders").AddForeignKeyColumn("CustomerId", "core_Accounts").Nullable(); // Adds a foreign key
Create.Table("MyApp_OrderLines")
.WithIdAsGuid()
.WithFullAuditColumns() // Adds the standard columns required when the new entity inherits from FullAuditedEntity
.WithColumn("Description").AsString(400).Nullable()
.WithColumn("Product").AsString().Nullable()
.WithColumn("Price").AsDecimal().Nullable()
.WithColumn("Quantity").AsInt32().Nullable()
.WithColumn("SubTotal").AsDecimal().Nullable();
Alter.Table("MyApp_OrderLines").AddForeignKeyColumn("PartOfId", "MyApp_Orders").NotNullable(); // Adds a foreign key
Alter.Table("MyApp_OrderLines").AddForeignKeyColumn("ProductId", "MyApp_Products").NotNullable(); // Adds a foreign key
}
public override void Down()
{
throw new NotImplementedException();
}
}
}
Update an existing entity
To update an existing entity, you will need to create a new database migration class. The example below illustrates the addition of a new property to the Organisation entity.
using System;
using FluentMigrator;
using Shesha.FluentMigrator;
[assembly: DefaultIntentManaged(Mode.Fully)]
[assembly: IntentTemplate("Boxfusion.Modules.FluentMigrator.Migration", Version = "1.0")]
namespace Shesha.Enterprise
{
[Migration(20250114101302)]
public class M20250114101302 : Migration
{
public override void Up()
{
if (!Schema.Table("Core_Organisations").Column("entpr_SupplierStatusLkp").Exists())
{
Alter.Table("Core_Organisations").AddColumn("entpr_SupplierStatusLkp").AsInt64().Nullable();
}
Alter.Table("entpr_PaymentOuts").AddForeignKeyColumn("BankTransactionId", "entpr_BankTransactions");
}
public override void Down()
{
throw new NotImplementedException();
}
}
}
Custom Queries
You can also use the Execute.Sql method to execute custom SQL queries. This is useful for executing complex queries or for executing queries that are not supported by Fluent Migrator.
using System;
using FluentMigrator;
using Intent.RoslynWeaver.Attributes;
using Shesha.FluentMigrator;
[assembly: DefaultIntentManaged(Mode.Fully)]
[assembly: IntentTemplate("Boxfusion.Modules.FluentMigrator.Migration", Version = "1.0")]
namespace Shesha.Enterprise
{
[Migration(20250114101305), MsSqlOnly]
public class M20250114101305 : Migration
{
public override void Up()
{
if (Schema.Table("entpr_Currencies").Exists())
Execute.Sql(@"
-- YOUR SQL --
");
}
public override void Down()
{
throw new NotImplementedException();
}
}
}
Unlike EF Core, NHibernate does not automatically generate the database schema from the domain model. Instead, the database schema is generated using the database migrations described above, which must be created manually.
Database Object Naming Conventions
Shesha will attempt to map the domain model to the database objects by convention. It is therefore important to understand the naming conventions used by Shesha and reference table and column accordingly in your migrations.
Shesha automatically maps entities to database tables and properties by convention. The naming conventions used are as follows:
Entity to Database Table Mapping
For a new entity, Shesha will automatically creates a database table for the entity where the table name will be derived from the pluralized entity name prefixed it with the module database prefix. For example, if you create an entity called Order, Shesha will create a table called MyApp_Orders in the database, assuming the Database prefix specified is MyApp.
For an entity that inherits from an existing entity, no new table will be created. Instead, the entity will be mapped to the existing table of the base entity. For example, if you create an entity called Employee that inherits from Person, Shesha will map the Employee entity to the Core_Persons table in the database.
Property to Database Column Mapping
Shesha will automatically create a database column for each property of the entity. The column name will be derived from the property name. For example, if you create a property called OrderNo on the Order entity, Shesha will create a column called OrderNo in the database.
If the property is a foreign key, Shesha will create a column with the same name as the property but suffixed with Id. For example, if you create a property called Customer, Shesha will create a column called CustomerId in the database.
If the property is a reference list, Shesha will create a column with the same name as the property but suffixed with Lkp. For example, if you create a property called Status, Shesha will create a column called StatusLkp in the database.
Note that in addition to the naming rules above, if the property is being added to an existing entity, Shesha will prefix the name of the column with the module database prefix. For example, if you create a property called EmployeeNo as part of an Employee entity which inherits from the existing Person entity, Shesha will create a column on the Core_Persons table called MyApp_EmployeeNo in the database. This helps identify any additions to the base entity and also helps avoid any naming conflicts with other modules that may also be extending the same base entity.
Module Database Prefix
The Module Database Prefix helps clearly identify all objects within a database belonging to a specific module. This is especially important when a Shesha application is composed of many modules and it useful to have visibility of source of a database object.
For example, all Tables and Columns belonging to Shesha framework are prefixed with Frwk_ or Core_. Similarly, all Tables and Columns added by your application could be prefixed with MyApp_, clearly identifying them at the database layer.
The prefix is defaulted to App_ in the default starter project but can be changed by editing or adding the following lines to the AssemblyInfo.cs file on the project within which the entity is defined.
...
// Shesha specific attributes
// Specifying the prefix to use for database objects belonging to this project
[assembly: TablePrefix("MyApp_")]
[assembly: BelongsToConfigurableModule("Shesha.Enterprise")]
...
Another example can be found in the starter project on Shesha's GitHub repository. In this project, the Module Database Prefix is defined in the domain module as shown below:
using System.Reflection;
using System.Runtime.InteropServices;
using Intent.RoslynWeaver.Attributes;
using Shesha.Attributes;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("ShaCompanyName.ShaProjectName.Common")]
[assembly: AssemblyTrademark("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("541f6f3c-1e7f-4ded-9807-12ea3ec7a650")]
[assembly: TablePrefix("ShaProjectName_")]
[assembly: IntentTemplate("Boxfusion.Modules.Domain.Properties", Version = "1.0")]
Domain Repositories
Shesha uses ABP's generic repository pattern to query and persist domain entities. For any entity TEntity with a primary key type TPrimaryKey (Guid for most entities in the base domain), ABP automatically provides an IRepository<TEntity, TPrimaryKey> you can inject into an application service or other class without having to write or register it yourself.
Injecting a Repository
Inject IRepository<TEntity, TPrimaryKey> through the constructor, specifying your entity and its primary key type:
public class ShaRoleAppService : SheshaCrudServiceBase<ShaRole, ShaRoleDto, Guid, FilteredPagedAndSortedResultRequestDto, CreateShaRoleDto, ShaRoleDto>, IShaRoleAppService
{
private readonly IRepository<ShaRoleAppointedPerson, Guid> _roleAppointmentRepository;
public ShaRoleAppService(
IRepository<ShaRole, Guid> repository,
IRepository<ShaRoleAppointedPerson, Guid> roleAppointmentRepository
) : base(repository)
{
_roleAppointmentRepository = roleAppointmentRepository;
}
}
If your application service inherits from a Shesha/ABP CRUD base class such as SheshaCrudServiceBase<TEntity, TDto, TPrimaryKey, ...> (as ShaRoleAppService does above), the repository for its main entity is passed to the base constructor and made available as the protected Repository property - you don't need to inject or store it yourself, only additional repositories for other entities.
Querying Data
GetAll() returns an IQueryable<TEntity>, so you can filter it with regular LINQ before executing the query:
var isGranted = await _roleAppointmentRepository.GetAll()
.AnyAsync(a => a.Person != null && a.Person.User != null && a.Person.User.Id == userId && a.Role.Name == input.RoleName);
Abp.Linq.Extensions adds a WhereIf(condition, predicate) extension method, which applies predicate only when condition is true - useful for building up a query with several optional filters without a chain of if statements:
return await _repository.GetAll()
.Where(e => e.MobileNumber1 == mobileNo)
.WhereIf(id.HasValue, e => e.Id != id)
.AnyAsync();
Inserting, Updating, and Deleting
Use GetAsync(id) to load a single entity by its primary key, and InsertAsync/UpdateAsync/DeleteAsync to persist changes:
public override async Task<ShaRoleDto> CreateAsync(CreateShaRoleDto input)
{
var role = ObjectMapper.Map<ShaRole>(input);
await Repository.InsertAsync(role);
await CurrentUnitOfWork.SaveChangesAsync();
return MapToEntityDto(role);
}
public override async Task<ShaRoleDto> UpdateAsync(ShaRoleDto input)
{
var role = await Repository.GetAsync(input.Id);
role.Label = input.Label;
role.Description = input.Description;
await Repository.UpdateAsync(role);
await CurrentUnitOfWork.SaveChangesAsync();
return MapToEntityDto(role);
}
public override async Task DeleteAsync(EntityDto<Guid> input)
{
var role = await Repository.GetAsync(input.Id);
await Repository.DeleteAsync(role);
}
ABP already wraps each application service method in a unit of work that commits automatically when the method returns successfully, so SaveChangesAsync() isn't strictly required after every change. Call it explicitly, as shown above, when you need the change flushed to the database immediately - for example, so a just-inserted entity's generated Id is available before the method continues.