Skip to content

Entities

Info

At ConnectSoft, Entities are modeled with utmost precision:
they are not just data holders, but lifelong identities carrying
meaningful business behaviors and state evolution across the system lifecycle.


Introduction

In Domain-Driven Design (DDD), an Entity is an object that is defined primarily by its identity, not just by its attributes.

While values may change over time —
the identity remains constant, ensuring that the system can track, evolve, and reason about the entity across its lifecycle.

At ConnectSoft, Entities:

  • Represent core business concepts like Customers, Orders, Accounts, and Appointments.
  • Carry business behavior tied to their life journey — not just static data.
  • Form the foundation of Aggregates, governing transactional consistency and invariants.

Concept Definition

An Entity:

Aspect Description
Identity Every entity must have a globally unique and immutable identifier (e.g., Id, AccountNumber).
Mutable State Entities evolve — fields may change, but identity remains the same.
Business Behavior Entities encapsulate domain behaviors, not just data storage.
Life Cycle Entities move through different states: Created ➔ Modified ➔ Deleted ➔ Archived.
Equality Entity equality is based on Identity, not attribute values.

📚 Why Entities Matter at ConnectSoft

Enable Real-World Traceability

  • We can track a Customer, Patient, or Order reliably across years of data, evolution, and integrations.

Model State and Behavior Evolution

  • Entities naturally embody the state transitions and business rule validations critical to SaaS, AI, Healthcare, and Finance systems.

Serve as Aggregate Roots

  • Many Aggregates at ConnectSoft are Entities that protect internal consistency boundaries.

Support Long-Term System Integrity

  • A well-managed identity lifecycle ensures migrations, reporting, and system integrations stay reliable over time.

Entities vs Value Objects

Concept Entity Value Object
Identity Globally unique identifier No identifier needed
Mutability Attributes can change over time Immutable once created
Equality Compared by identity Compared by all attributes
Lifecycle Tracked across operations Replaced entirely if attributes change
Examples Customer, Order, Patient Address, Money, GeoLocation

🧩 Visual: Entity Identity Across a System

flowchart TD
    NewEntity[Create Entity - Assign Unique Id]
    UpdateEntity[Update Entity Attributes]
    ArchiveEntity[Archive or Delete Entity]

    NewEntity --> UpdateEntity
    UpdateEntity --> UpdateEntity
    UpdateEntity --> ArchiveEntity
Hold "Alt" / "Option" to enable pan & zoom

✅ The Entity's identity remains constant,
✅ while state evolves over its lifecycle.


Strategic Design Principles for Entities

At ConnectSoft, we model Entities with rigorous attention to behavior, identity, and evolution
ensuring they remain trustworthy, testable, and business-aligned across the system’s lifetime.


📚 Core Principles for Good Entity Design

Define a Unique, Immutable Identity

  • Every Entity must have a unique identifier assigned at creation time (e.g., Guid, CustomerId).

Identity-Based Equality

  • Entities are considered equal if and only if their identities match — attribute values do not matter for equality.

Encapsulate Behavior with State

  • Entities are responsible for protecting their own invariants, not just holding data.

Support Evolution of Attributes

  • Fields (like Name, Email, Address) may change over time — but Identity never changes.

Maintain Isolation of Concerns

  • Business rules and state transitions belong inside Entities — avoid bloating Application Services.

Stay Consistent in Persistence

  • Identity must persist across sessions, database operations, and system migrations.

Be ORM-Friendly, but Domain-First

  • Design Entities for domain clarity first, persistence concerns second (e.g., support private constructors for ORMs without polluting business logic).

ConnectSoft.Extensions.EntityModel.EntityWithTypedId Base Class

At ConnectSoft, we use the ConnectSoft.Extensions.EntityModel.EntityWithTypedId<TIdentity> base class
to provide a consistent, powerful foundation for all entity implementations.

The base class provides:

  • Id Property — Abstract property that must be implemented by derived classes
  • Equals() — Identity-based equality comparison (entities are equal if their IDs match)
  • GetHashCode() — Hash code based on identity (handles transient entities gracefully)
  • IsTransient() — Protected method to check if entity hasn't been persisted yet
  • Transient Entity Handling — Proper equality and hashing for entities before persistence

Key Characteristics:

  • Typed Identity — Supports any identity type (Guid, int, string, etc.)
  • Identity-Based Equality — Entities are equal if and only if their IDs match
  • Transient Entity Support — Handles entities that haven't been assigned an ID yet
  • ORM-Friendly — Virtual properties enable lazy loading and change tracking
  • Type Safety — Compile-time checking of identity types

📚 How to Implement Identity-Based Equality Correctly

Real Implementation from ConnectSoft.Extensions.EntityModel:

namespace ConnectSoft.Extensions.EntityModel
{
    /// <summary>
    /// Base class for entities with typed id.
    /// </summary>
    /// <typeparam name="TIdentity">Identity type.</typeparam>
    public abstract class EntityWithTypedId<TIdentity> : IGenericEntity<TIdentity>
    {
        private int? requestedHashCode;

        /// <summary>
        /// Gets or sets the unique identifier of this entity.
        /// </summary>
        public abstract TIdentity Id { get; protected set; }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        public override bool Equals(object obj)
        {
            IGenericEntity<TIdentity> that = obj as IGenericEntity<TIdentity>;
            return this.Equals(that);
        }

        /// <summary>
        /// Serves as the default hash function.
        /// For transient entities the base GetHashCode method used.
        /// Otherwise Id (primary key) used as hash value.
        /// </summary>
        public override int GetHashCode()
        {
            if (!this.requestedHashCode.HasValue)
            {
                this.requestedHashCode = this.IsTransient() ? base.GetHashCode() : this.Id.GetHashCode();
            }

            return this.requestedHashCode.Value;
        }

        /// <summary>
        /// Indicates whether the current object is equal to another object of the same type.
        /// </summary>
        public virtual bool Equals(IGenericEntity<TIdentity> other)
        {
            if (other == null || !this.GetType().IsInstanceOfType(other))
            {
                return false;
            }

            if (ReferenceEquals(this, other))
            {
                return true;
            }

            bool otherIsTransient = Equals(other.Id, default(TIdentity));
            bool thisIsTransient = this.IsTransient();

            if (otherIsTransient && thisIsTransient)
            {
                return ReferenceEquals(other, this);
            }

            return other.Id.Equals(this.Id);
        }

        /// <summary>
        /// Gets a value indicating whether the entity is transient.
        /// </summary>
        protected bool IsTransient()
        {
            return Equals(this.Id, default(TIdentity));
        }
    }
}

Equality is based only on Id — Type and identity matching required
Transient entity handling — Entities without IDs are compared by reference
Type safety — Compile-time checking ensures entities of same type are compared
Easy to unit test — Predictable equality and hashing behavior


🎯 Good vs Bad Identity Handling

✅ Good Practice 🚫 Bad Practice
Assign identity at creation time Assign identity later or lazily
Use immutable IDs (e.g., Guid, ULID) Use mutable fields as identity
Compare by Id in equality Compare by full attribute set
Persist Id explicitly Rely on database auto-increment only
Expose behavior through methods Expose raw data and let services mutate

🛑 Common Anti-Patterns to Avoid with Entities

Anti-Pattern Symptom Why It's Dangerous
Anemic Entities Only getters/setters, no behavior. Forces logic into services, procedural bloat.
Identity Confusion Identity assigned inconsistently. Broken references, hard to track entities.
DTO-Like Entities Designed for database shape, not business model. Fragile to evolution, pollutes domain purity.
Multiple Changing IDs Identity mutates over time. System cannot reliably trace entity history.
Overloading Equals with Attributes Entity equality based on all fields. Performance issues, fragile equality logic.

🧩 Visual: Good Entity Equality Logic

flowchart LR
    EntityA["Entity A (Id: 1234)"]
    EntityB["Entity B (Id: 1234)"]
    EntityC["Entity C (Id: 5678)"]

    EntityA -- Equals --> EntityB
    EntityA -- Not Equals --> EntityC
Hold "Alt" / "Option" to enable pan & zoom

EntityA == EntityB because IDs match.
EntityA != EntityC because IDs differ, even if attributes are similar.


C# Examples: Advanced Entity Modeling at ConnectSoft

At ConnectSoft, Entities are behavior-rich, identity-driven, and encapsulate Value Objects to model complex business realities.

All examples below are based on real implementations from the ConnectSoft.Saas.ProductsCatalog microservice.


🛠️ Real-World Example: EditionEntity from ConnectSoft.Saas.ProductsCatalog

Step 1: Define the Entity Interface

Real Implementation from ConnectSoft.Saas.ProductsCatalog.EntityModel:

namespace ConnectSoft.Saas.ProductsCatalog.EntityModel
{
    using System;
    using System.Collections.Generic;
    using ConnectSoft.Extensions.EntityModel;

    /// <summary>
    /// Edition entity contract.
    /// </summary>
    public interface IEdition : IGenericEntity<Guid>
    {
        /// <summary>
        /// Gets or sets a object identifier.
        /// </summary>
        Guid EditionId { get; set; }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        string Name { get; set; }

        /// <summary>
        /// Gets or sets the display name.
        /// </summary>
        string DisplayName { get; set; }

        /// <summary>
        /// Gets or sets the unique identifier for the edition.
        /// </summary>
        string Key { get; set; }

        /// <summary>
        /// Gets or sets the detailed description.
        /// </summary>
        string Description { get; set; }

        /// <summary>
        /// Gets or sets the date when the edition was created.
        /// </summary>
        DateTime CreationDate { get; set; }

        /// <summary>
        /// Gets or sets the status of the edition.
        /// </summary>
        EditionStatusEnumeration Status { get; set; }

        /// <summary>
        /// Gets or sets the product this edition belongs to.
        /// </summary>
        IProduct Product { get; set; }

        /// <summary>
        /// Gets or sets the edition pricing model.
        /// </summary>
        IEditionPricingModel EditionPricing { get; set; }

        /// <summary>
        /// Gets or sets the list of features included in this edition.
        /// </summary>
        IList<IEditionFeature> EditionFeatures { get; set; }

        /// <summary>
        /// Gets or sets the list of service level agreements for this edition.
        /// </summary>
        IList<IServiceLevelAgreement> ServiceLevelAgreements { get; set; }
    }
}

Key Pattern Elements:

Extends IGenericEntity<Guid> — Base interface for all entities
Interface Segregation — Separates contract from implementation
Entity References — References aggregate root (IProduct) and other entities via interfaces
Collections — Uses IList<T> for child entity collections
Uses EnumerationsEditionStatusEnumeration for domain-specific status


Step 2: Implement the Entity Class

Real Implementation from ConnectSoft.Saas.ProductsCatalog.EntityModel.PocoEntities:

namespace ConnectSoft.Saas.ProductsCatalog.EntityModel.PocoEntities
{
    using System;
    using System.Collections.Generic;
    using ConnectSoft.Extensions.EntityModel;

    /// <summary>
    /// Edition poco entity.
    /// </summary>
    public class EditionEntity : EntityWithTypedId<Guid>, IEdition
    {
        /// <inheritdoc/>
        required public virtual Guid EditionId { get; set; }

        /// <inheritdoc/>
        required public virtual string Name { get; set; }

        /// <inheritdoc/>
        required public virtual string DisplayName { get; set; }

        /// <inheritdoc/>
        required public virtual string Key { get; set; }

        /// <inheritdoc/>
        required public virtual string Description { get; set; }

        /// <inheritdoc/>
        required public virtual DateTime CreationDate { get; set; }

        /// <inheritdoc/>
        required public virtual EditionStatusEnumeration Status { get; set; }

        /// <inheritdoc/>
        required public virtual IProduct Product { get; set; }  // ✅ References aggregate root

        /// <inheritdoc/>
        required public virtual IEditionPricingModel EditionPricing { get; set; }

        /// <inheritdoc/>
        required public virtual IList<IEditionFeature> EditionFeatures { get; set; } = new List<IEditionFeature>();

        /// <inheritdoc/>
        required public virtual IList<IServiceLevelAgreement> ServiceLevelAgreements { get; set; } = new List<IServiceLevelAgreement>();

        /// <inheritdoc/>
        public override Guid Id
        {
            get => this.EditionId;
            protected set => this.EditionId = value;
        }
    }
}

Key Implementation Patterns:

Inherits from EntityWithTypedId<Guid> — Base class provides identity-based equality and hashing
Implements IEdition — Satisfies the entity interface contract
Virtual Properties — Enables ORM lazy loading and change tracking
Required Properties — Uses C# 11 required keyword for non-nullable properties
Id Property Mapping — Overrides Id to map to EditionId for domain clarity
Collection Initialization — Collections initialized with empty lists to avoid null reference issues
Entity References — References other entities via interfaces for loose coupling


🛠️ Real-World Example: PricingModelEntity with Rich Domain Model

Real Implementation showing entity with enumerations and collections:

namespace ConnectSoft.Saas.ProductsCatalog.EntityModel.PocoEntities
{
    using System;
    using System.Collections.Generic;
    using ConnectSoft.Extensions.EntityModel;

    /// <summary>
    /// Pricing model poco entity.
    /// </summary>
    public class PricingModelEntity : EntityWithTypedId<Guid>, IPricingModel
    {
        /// <inheritdoc/>
        required public virtual Guid PricingModelId { get; set; }

        /// <inheritdoc/>
        required public virtual string Name { get; set; }

        /// <inheritdoc/>
        required public virtual string Key { get; set; }

        /// <inheritdoc/>
        required public virtual string DisplayName { get; set; }

        /// <inheritdoc/>
        required public virtual string Description { get; set; }

        /// <inheritdoc/>
        required public virtual DateTime CreationDate { get; set; }

        /// <inheritdoc/>
        required public virtual PricingTypeEnumeration PricingType { get; set; }  // ✅ Using enumeration

        /// <inheritdoc/>
        required public virtual string Currency { get; set; }

        /// <inheritdoc/>
        required public virtual decimal BasePrice { get; set; }

        /// <inheritdoc/>
        public virtual decimal? DiscountPercentage { get; set; }  // ✅ Nullable property

        /// <inheritdoc/>
        required public virtual BillingCycleEnumeration BillingCycle { get; set; }  // ✅ Using enumeration

        /// <inheritdoc/>
        required public virtual decimal MinimumPrice { get; set; }

        /// <inheritdoc/>
        required public virtual decimal MaximumPrice { get; set; }

        /// <inheritdoc/>
        required public virtual IList<IEditionPricingModel> EditionPricingModels { get; set; } = new List<IEditionPricingModel>();

        /// <inheritdoc/>
        required public virtual IBusinessModel BusinessModel { get; set; }  // ✅ References other entity

        /// <inheritdoc/>
        public override Guid Id
        {
            get => this.PricingModelId;
            protected set => this.PricingModelId = value;
        }
    }
}

Key Entity Characteristics:

Uses EnumerationsPricingTypeEnumeration and BillingCycleEnumeration for domain-specific types
Nullable PropertiesDiscountPercentage? allows optional values
Collection ManagementEditionPricingModels initialized as empty list
Entity References — References IBusinessModel entity via interface
Rich Domain Model — Contains business-relevant properties (currency, prices, billing cycle)


📚 Key Lessons from ConnectSoft Entity Examples

Entities inherit from EntityWithTypedId<TIdentity> and implement domain-specific interfaces

Virtual properties enable ORM lazy loading and change tracking

Required properties use C# 11 required keyword for compile-time safety

Id property is overridden to map to domain-specific IDs (e.g., EditionId, PricingModelId)

Collections are initialized with empty lists to avoid null reference exceptions

Entity references use interfaces for loose coupling and testability

Enumerations express domain concepts clearly (e.g., EditionStatusEnumeration)

Entities can reference aggregate roots and other entities via interfaces


📋 Complete Implementation Template

Here's a complete template you can use to create new entities following ConnectSoft patterns:

namespace YourNamespace.EntityModel
{
    using System;
    using System.Collections.Generic;
    using ConnectSoft.Extensions.EntityModel;

    /// <summary>
    /// Your entity contract.
    /// </summary>
    public interface IYourEntity : IGenericEntity<Guid>
    {
        /// <summary>
        /// Gets or sets a object identifier.
        /// </summary>
        Guid YourEntityId { get; set; }

        /// <summary>
        /// Gets or sets the name.
        /// </summary>
        string Name { get; set; }

        // ... other properties ...

        /// <summary>
        /// Gets or sets child entities.
        /// </summary>
        IList<IChildEntity> ChildEntities { get; set; }
    }
}
namespace YourNamespace.EntityModel.PocoEntities
{
    using System;
    using System.Collections.Generic;
    using ConnectSoft.Extensions.EntityModel;

    /// <summary>
    /// Your entity poco class.
    /// </summary>
    public class YourEntity : EntityWithTypedId<Guid>, IYourEntity
    {
        /// <inheritdoc/>
        required public virtual Guid YourEntityId { get; set; }

        /// <inheritdoc/>
        required public virtual string Name { get; set; }

        // ... other properties ...

        /// <inheritdoc/>
        public virtual IList<IChildEntity> ChildEntities { get; set; } = new List<IChildEntity>();

        /// <inheritdoc/>
        public override Guid Id
        {
            get => this.YourEntityId;
            protected set => this.YourEntityId = value;
        }
    }
}

Follow this pattern for all new entities to ensure consistency across ConnectSoft projects.


🎯 ConnectSoft Entity Checklist

When implementing entities at ConnectSoft, ensure:

Checklist Item Description Example
Inherit from EntityWithTypedId<TIdentity> Use base class for identity management EntityWithTypedId<Guid>
Implement Domain Interface Create interface for entity contract IEdition : IGenericEntity<Guid>
Use Virtual Properties Enable ORM lazy loading public virtual string Name { get; set; }
Use Required Properties Use C# 11 required keyword required public virtual string Name { get; set; }
Map Id Property Override Id to map to domain-specific ID EditionId maps to Id
Initialize Collections Avoid null reference exceptions = new List<IChildEntity>()
Use Enumerations Express domain states clearly EditionStatusEnumeration
Reference Entities by Interface Maintain loose coupling IProduct Product { get; set; }
Support Nullable Properties Use nullable types for optional values decimal? DiscountPercentage { get; set; }
Document with XML Comments Use /// <inheritdoc/> for interface implementations /// <inheritdoc/>

📚 Lessons from Real-World Entity Modeling

Good Practice Why It Matters ConnectSoft Implementation
Inherit from EntityWithTypedId<TIdentity> Provides consistent identity management and equality semantics All entities inherit from base class
Use Virtual Properties Enables ORM lazy loading and change tracking All properties marked virtual
Use Required Properties Compile-time safety for non-nullable properties C# 11 required keyword
Map Id Property Domain-specific ID names improve readability EditionId maps to base Id property
Initialize Collections Prevents null reference exceptions Collections initialized: = new List<T>()
Reference Entities by Interface Loose coupling and testability IProduct Product { get; set; }
Use Enumerations for Status Type-safe domain concepts EditionStatusEnumeration instead of string or int
Support Nullable Properties Explicit optional values decimal? DiscountPercentage { get; set; }
Interface-Based Design Separates contract from implementation IEdition interface, EditionEntity implementation
Document with XML Comments IntelliSense and documentation generation /// <inheritdoc/> for interface implementations

🧩 Visual: Entity Lifecycle and Behavior in Complex Systems

flowchart LR
    CreateEntity["Create Entity (Assign ID + Initial Valid State)"]
    MutateEntity["Perform Valid State Changes (Business Rules Enforced)"]
    EvolveEntity["Evolve State Across Life Cycle"]
    EndEntity["Archive or Terminate Entity"]

    CreateEntity --> MutateEntity --> EvolveEntity --> MutateEntity --> EndEntity
Hold "Alt" / "Option" to enable pan & zoom

✅ Entities evolve carefully over time,
✅ always protected by domain rules and identity consistency.


Best Practices for Entities

At ConnectSoft, Entity modeling is careful, tactical, and business-driven
ensuring identity consistency, behavioral richness, and system resilience across evolving platforms.


📚 Best Practices Checklist

Always Define a Unique Immutable Identity

  • Assign IDs at creation — never rely on mutable fields for identification.

Equality Based on Identity

  • Entity equality must depend solely on identifiers, not attribute comparisons.

Encapsulate State and Behavior

  • Entities must protect their internal state through controlled methods.

Validate During Construction

  • Ensure entities are created in valid, consistent states immediately.

Use Value Objects for Clusters of Attributes

  • Aggregate related fields (e.g., Address, Money) into Value Objects inside Entities.

Encapsulate Collections Carefully

  • Expose read-only access to internal collections (e.g., Appointments, MedicalRecords).

Keep Entities Focused

  • Entities should only carry attributes and behaviors relevant to their real-world business purpose.

Persistence-Agnostic Design

  • Model Entities for domain clarity — not database schema optimization.

Maintain Auditability

  • Design Entities to support tracking important lifecycle events (e.g., CreatedAt, ModifiedAt).

Conclusion

Entities are the lifeblood of domain models at ConnectSoft.

When modeled properly:

  • Identity remains strong across system evolutions.
  • State transitions are safe, consistent, and validated.
  • Business operations become natural, reflecting real-world processes.
  • Testing becomes easier, because behaviors are encapsulated and predictable.
  • Event-driven workflows and resilient microservices scale reliably over years.

When modeled poorly:

  • Identity confusion grows.
  • State management becomes chaotic.
  • Fragile systems emerge — unable to adapt safely to change.

At ConnectSoft, every Entity is treated with the respect it deserves:
a living, evolving, meaningful business concept — not just a data container.

We build Entities to last, to protect, and to empower future evolutions
from SaaS platforms to AI ecosystems to healthcare and finance solutions.

"Entities are not records;
they are the heartbeat of meaningful systems.
Treat their identity and behavior as sacred.
"


References

  • Books and Literature

    • Eric Evans — Domain-Driven Design: Tackling Complexity in the Heart of Software
    • Vaughn Vernon — Implementing Domain-Driven Design
    • Jimmy Nilsson — Applying Domain-Driven Design and Patterns
  • Online Resources

  • ConnectSoft Packages and Code

    • ConnectSoft.Extensions.EntityModel — Base classes and interfaces:
      • EntityWithTypedId<TIdentity> — Base class for entities with typed identity
      • IGenericEntity<TIdentity> — Base interface for all entities
      • Identity-based Equals() and GetHashCode() implementations
      • Transient entity handling (entities without assigned IDs)
      • Type-safe identity management
    • Real-World Examples — See ConnectSoft.Saas.ProductsCatalog.EntityModel.PocoEntities for production implementations:
      • EditionEntity — Complete entity example with collections and references
      • PricingModelEntity — Entity with enumerations and business properties
      • ProductEntity — Aggregate root entity example
      • FeatureEntity, BusinessModelEntity, etc. — Additional entity examples
  • ConnectSoft Internal Standards

    • ConnectSoft Entity Modeling Guidelines
    • ConnectSoft Domain Layer Modeling Playbook
    • ConnectSoft Resilient Microservice Blueprints