Skip to main content
Version: 0.43

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.

EntityDescription
AccountRepresents a customer account, whether on behalf of a person or organisation.
AddressRepresents an address.
OrganisationRepresents an organizational unit, organisation/company, team or similar.
PersonRepresents 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.
NoteRepresents a user note or comment. A list of notes may be associated with any parent entity. See also notes.
ReferenceList and ReferenceListItemRepresents a standard list of values. See also reference lists.
SiteRepresents a geographic location or physical structure such as location, area, buildings, room etc.
StoredFileRepresents 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.

Creating a New Entity

To create a new entity, 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")]
public enum RefListTicketStatus : long
{
[Description("Open")]
Open = 1,

[Description("Closed")]
Closed = 2
}

The [ReferenceList] attribute marks this enum as a reference list - see Reference Lists for more details.

Entity base classes

All entities should ultimately inherit from Entity as per the example above. You may also inherit from other pre-existing subclasses of Entity depending on the level of auditing you require. For example, CreationAuditedEntity tracks the creation timestamp and user, AuditedEntity also tracks the modification timestamp and user, and FullAuditedEntity additionally tracks the deletion timestamp and user. Most base domain entities (Account, Address, Person, Organisation, Site) inherit from FullAuditedEntity<Guid>.

Entity IDs

Although it is possible to create entities with any type of ID, it is recommended to use Guid IDs for all entities. Shesha uses Guid IDs for all entities in the base domain and many other modules, so using Guid IDs keeps your application consistent and makes it easier to integrate with other modules.

Add the database migration:

To ensure that your new entity can be persisted to the database, you need to create the corresponding database objects via a database migration class. Shesha uses Fluent Migrator for database migrations, with Shesha-specific extensions such as WithIdAsGuid() and WithFullAuditColumns().

Shesha uses NHibernate as its ORM

Shesha uses NHibernate as its ORM rather than the more common EF Core. NHibernate does not generate database migration classes automatically, hence the need to create database migrations manually.

To create a database migration class, 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
{
// The migration number must be unique across all migrations in the application, and usually follows the convention YYYYMMDDHHMMSS
[Migration(20250114101300)]
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;

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 complex queries, or queries not supported by Fluent Migrator.

using System;
using FluentMigrator;
using Shesha.FluentMigrator;

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();
}
}
}
note

Shesha does not automatically generate the database schema from the domain model. Instead, the database schema is generated from database migrations, which must be created manually as shown above.


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 tables and columns accordingly in your migrations.

Entity to Database Table Mapping

For a new entity, Shesha automatically creates a database table for the entity, where the table name is derived from the pluralized entity name prefixed 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 is created. Instead, the entity is 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 automatically creates a database column for each property of the entity. The column name is 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 creates 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 creates 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

In addition to the naming rules above, if the property is being added to an existing entity, Shesha prefixes the column name 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. This helps identify additions to the base entity and avoids 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, since it gives you visibility of the source of a database object.

For example, all tables and columns belonging to the 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 defaults 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.

/Properties/AssemblyInfo.cs
...
// Shesha specific attributes
// Specifying the prefix to use for database objects belonging to this project
[assembly: TablePrefix("MyApp_")]
[assembly: BelongsToConfigurableModule("Shesha.Enterprise")]
...
Another example

The starter project on Shesha's GitHub repository defines the Module Database Prefix in the domain module, as shown below.

shesha-starter/backend/src/Module/ShaCompanyName.ShaProjectName.Domain/Properties/AssemblyInfo.cs
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 builds on ABP's repository pattern for reading and writing entities from application services and other back-end code. For every entity type, an implementation of IRepository<TEntity, TPrimaryKey> is provided automatically, without you having to write any repository code yourself.

Injecting a Repository

Add a constructor parameter typed IRepository<TEntity, TPrimaryKey> (TPrimaryKey is almost always Guid for Shesha entities), and store it in a field:

public class DeviceRegistrationAppService : SheshaAppServiceBase, IDeviceRegistrationAppService
{
private readonly IRepository<DeviceRegistration, Guid> _repository;

public DeviceRegistrationAppService(IRepository<DeviceRegistration, Guid> repository)
{
_repository = repository;
}
}
Application services already have one

If your application service inherits from AsyncCrudAppService<TEntity, ...> (the base class most CRUD app services use), a repository for that service's own entity is already available as the inherited Repository property. You only need to inject IRepository<TEntity, TPrimaryKey> yourself for other entities your service also needs to work with.

Querying Entities

GetAsync fetches a single entity by its primary key:

var person = await _personRepository.GetAsync(personId);

GetAll() returns an IQueryable<TEntity>, so you can compose it with standard LINQ before executing it:

var items = await Repository.GetAll()
.Where(r => r.OSType == osType && !r.IsDeleted)
.ToListAsync();

FirstOrDefaultAsync is a shortcut for GetAll().Where(...).FirstOrDefaultAsync():

var deviceRegistration = await _repository.FirstOrDefaultAsync(r => r.Person == currentPerson);
Loading related entities

NHibernate lazy-loads navigation properties by default. If you need a related entity or collection loaded as part of the same query, either to avoid an extra round trip or to filter on it, use GetAllIncluding:

var user = await Repository.GetAllIncluding(x => x.Roles)
.FirstOrDefaultAsync(x => x.Id == id);

Persisting Entities

InsertAsync and UpdateAsync create and update an entity respectively:

var newNotification = new Notification { /* ... */ };
await _repository.InsertAsync(newNotification);

existingNotification.IsRead = true;
await _repository.UpdateAsync(existingNotification);

DeleteAsync accepts the entity itself, its primary key, or a filter predicate:

await Repository.DeleteAsync(child);                                    // by entity
await Repository.DeleteAsync(input.Id); // by primary key
await _propertyRepository.DeleteAsync(x => x.EntityConfig.Id == id); // by predicate
Entity tracking still applies

Changes made directly to a tracked entity (one returned from GetAsync, GetAll(), or FirstOrDefaultAsync within the same unit of work) are persisted automatically when the unit of work completes, even without an explicit UpdateAsync call. Call UpdateAsync explicitly when you want the intent to be clear, or when working with an entity that was not loaded in the current unit of work.

See Also