name: ef-core-entity-implementation description: Use this skill when implementing domain entities and their properties in .NET.
Entity Class Construction Rules
Base Structure
namespace GloboTicket.Domain.Entities;
/// <summary>
/// [Clear, concise description of the entity's business purpose]
/// </summary>
public class EntityName : [Entity | MultiTenantEntity]
{
// Properties defined here
}
Property Patterns
Required Properties
- Use
requiredkeyword:public required string Name { get; set; } - Include XML doc comment explaining the property's purpose
- Provide default value in initializer if sensible:
= string.Empty;
Optional Properties
- Make nullable:
public string? Description { get; set; } - Include XML doc comment
- Default to
null(implicit)
Navigation Properties (Collections)
/// <summary>
/// The collection of related entities.
/// </summary>
public ICollection<RelatedEntity> RelatedEntities { get; set; } = new List<RelatedEntity>();
IMPORTANT: When creating a new relationship, ALWAYS add the collection navigation property to the parent (one) side:
- If you're creating a child entity (many side), you MUST also add the collection property to the parent entity
- Example: When creating
Showwith a relationship toVenue, addpublic ICollection<Show> Shows { get; set; } = new List<Show>();to theVenueentity - Use the
WithMany(parent => parent.Collection)pattern in the configuration on the many side
Navigation Properties (Single - Optional)
/// <summary>
/// The parent entity.
/// </summary>
public ParentEntity? Parent { get; set; }
/// <summary>
/// Foreign key for the parent entity.
/// </summary>
public int? ParentId { get; set; }
Navigation Properties (Single - Required)
/// <summary>
/// The parent entity.
/// </summary>
public ParentEntity Parent { get; private set; }
/// <summary>
/// Foreign key for the parent entity.
/// </summary>
public int ParentId { get; private set; }
Important Notes on Required Navigation Properties:
- Required navigation properties should be non-nullable
- Both foreign key and navigation properties should have
private set - Use null assertion assignment (
= null!;) in constructors to avoid compiler warnings - Required navigation properties are initialized via constructor parameters
- A private parameterless constructor must be provided for Entity Framework Core
Complex Types (Value Objects)
/// <summary>
/// The billing address for this entity.
/// </summary>
public required Address BillingAddress { get; set; }
/// <summary>
/// Optional shipping address.
/// </summary>
public Address? ShippingAddress { get; set; }
Geospatial Properties
using NetTopologySuite.Geometries;
/// <summary>
/// Geographic location (latitude/longitude).
/// </summary>
public Point? Location { get; set; }
Multi-Tenant Entity Additional Requirements
When inheriting from MultiTenantEntity, DO NOT manually add:
TenantIdproperty (inherited from base)Tenantnavigation property (inherited from base)Id,CreatedAt,UpdatedAt(inherited through base chain)
These are automatically provided by the inheritance hierarchy.
Common Property Types
- Strings: Default to
string.Emptyfor required,nullfor optional - Decimals: Use
decimalfor monetary values, prices - DateTimes: Use
DateTime(UTC assumed), nullable for optional dates - Booleans: Default to
falsewith clear semantic meaning - Enums: Create strongly-typed enums in
Domain.Enumsnamespace - GUIDs: Use
Guidtype, typically optional except for unique identifiers
Constructor Patterns
For Entities with Required Navigation Properties
Entities that have required navigation properties (non-nullable relationships) must have:
- Public Constructor: Accepts navigation entities as parameters and initializes both the navigation property and its foreign key
- Private Parameterless Constructor: For Entity Framework Core to use during materialization
public class Show : Entity
{
/// <summary>
/// Initializes a new instance of the <see cref="Show"/> class.
/// </summary>
/// <param name="act">The Act that is performing in this show.</param>
/// <param name="venue">The Venue where this show is held.</param>
/// <exception cref="ArgumentNullException">Thrown when act or venue is null.</exception>
public Show(Act act, Venue venue)
{
ArgumentNullException.ThrowIfNull(act);
ArgumentNullException.ThrowIfNull(venue);
Act = act;
ActId = act.Id;
Venue = venue;
VenueId = venue.Id;
}
/// <summary>
/// Private parameterless constructor for Entity Framework Core.
/// </summary>
private Show()
{
Act = null!;
Venue = null!;
}
/// <summary>
/// Gets or sets the start time of the show in UTC.
/// </summary>
public required DateTime StartTime { get; set; }
/// <summary>
/// Gets the foreign key for the Act performing in this show.
/// </summary>
public int ActId { get; private set; }
/// <summary>
/// Gets the Act that is performing in this show.
/// </summary>
public Act Act { get; private set; }
/// <summary>
/// Gets the foreign key for the Venue where this show is held.
/// </summary>
public int VenueId { get; private set; }
/// <summary>
/// Gets the Venue where this show is held.
/// </summary>
public Venue Venue { get; private set; }
}
Key Constructor Rules
- Constructor parameters should ONLY be used for navigation properties, not value properties
- Value properties should use the
requiredkeyword instead - Public constructor validates navigation properties are not null using
ArgumentNullException.ThrowIfNull() - Public constructor sets both the navigation property and its foreign key from the passed entity's Id
- Private parameterless constructor initializes navigation properties with
null!to suppress compiler warnings - Foreign keys and navigation properties have
private setto enforce initialization through constructor