# Entity vs Value Object in Domain-Driven Design

> Entities have identity. Value Objects have equality by attributes. Knowing when to use each is fundamental to building a strong domain model. Here is a practical comparison with C# examples.

Published: 2026-09-22. Author: Milan Jovanović.

Canonical: https://milanjovanovic.tech/blog/entity-vs-value-object-ddd

An **entity** is defined by an identity that survives changes to its attributes.
A **value object** is defined by its values and is replaced when those values change.
Use entities for concepts with a business lifecycle, such as orders, and value objects for interchangeable values, such as money or addresses.

## What Is the Difference Between an Entity and a Value Object?

In [**Domain-Driven Design**](https://milanjovanovic.tech/blog/domain-driven-design-dotnet-getting-started), there are two fundamental building blocks for modeling your domain:

- **Entities** - defined by their *identity*
- **Value Objects** - defined by their *attributes*

Two entities with the same properties are still different if they have different IDs.
Two [**Value Objects**](https://milanjovanovic.tech/blog/value-objects-in-dotnet-ddd-fundamentals) with the same properties are equal - they're interchangeable.

This distinction drives almost every design decision in your domain model.

![A side-by-side contrast: an entity has an identity, equality by Id, and is mutable via methods, while a value object has no identity, equality by attributes, and is immutable and replaced to change](https://milanjovanovic.tech/blogs/articles/entity-vs-value-object-ddd/entity-vs-value-object.png)

## Entities

An Entity is an object that has a unique identity that persists over time. Even if every attribute changes, the entity is still the same object.

```csharp
public sealed class Customer : Entity
{
    private Customer(Guid id, string name) : base(id)
    {
        Name = name;
    }

    public string Name { get; private set; }

    public static Customer Create(string name)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(name);
        return new Customer(Guid.NewGuid(), name.Trim());
    }

    public void ChangeName(string newName)
    {
        ArgumentException.ThrowIfNullOrWhiteSpace(newName);
        Name = newName.Trim();
    }
}
```

Key characteristics:
- **Has an ID** - `Customer` with ID `abc-123` is always that customer, even if they change their name
- **Mutable** (controlled) - can change over time through domain methods
- **Equality by identity** - two customers are equal only if they have the same ID
- **Has a lifecycle** - created, modified, possibly deleted

### Entity Base Class

```csharp
public abstract class Entity : IEquatable<Entity>
{
    protected Entity() { } // Used when EF Core materializes derived entities.

    protected Entity(Guid id)
    {
        if (id == Guid.Empty)
            throw new ArgumentException("An entity needs an ID.", nameof(id));

        Id = id;
    }

    public Guid Id { get; private set; }

    public bool Equals(Entity? other) =>
        ReferenceEquals(this, other) ||
        (other is not null && GetType() == other.GetType() &&
         Id != Guid.Empty && Id == other.Id);

    public override bool Equals(object? obj) =>
        obj is Entity entity && Equals(entity);

    public override int GetHashCode() => HashCode.Combine(GetType(), Id);
}
```

The type check prevents an `Order` and a `Customer` with the same GUID from comparing equal.
This example uses sealed entities and application-generated IDs; proxy types and database-generated identities need a different equality policy.

## Value Objects

A Value Object is an immutable object that represents a concept with no identity. It's defined entirely by its attributes.

```csharp
public sealed record Money
{
    private Money() { } // Allows materialization without a public setter.

    private Money(decimal amount, string currency)
    {
        Amount = amount;
        Currency = currency;
    }

    public decimal Amount { get; private init; }
    public string Currency { get; private init; } = null!;

    public static Money Zero(string currency) => Create(0, currency);

    public static Money Create(decimal amount, string currency)
    {
        if (amount < 0)
            throw new ArgumentException("Amount cannot be negative.");

        ArgumentException.ThrowIfNullOrWhiteSpace(currency);
        string normalized = currency.Trim().ToUpperInvariant();
        if (normalized.Length != 3 || normalized.Any(c => c < 'A' || c > 'Z'))
            throw new ArgumentException("Use a three-letter currency code.", nameof(currency));

        return new Money(amount, normalized);
    }

    public Money Add(Money other)
    {
        if (Currency != other.Currency)
            throw new InvalidOperationException("Cannot add different currencies.");

        return new Money(Amount + other.Amount, Currency);
    }

}
```

The record supplies equality for `Amount` and `Currency`.
This model allows nonnegative prices and validates the shape of a currency code; a payment system should also check its supported currencies and rounding rules.

Key characteristics:
- **No identity** - `Money(100, "USD")` is equal to any other `Money(100, "USD")`
- **Immutable** - once created, it never changes. Operations return new instances
- **Equality by attributes** - two Money objects are equal if amount and currency match
- **Validated construction** - the factory enforces the rules this model defines
- **Contains behavior** - `Add` checks currencies before producing a new value

## Side-by-Side Comparison

| Aspect | Entity | Value Object |
| --- | --- | --- |
| Identity | Business identity persists over time | Defined by its values |
| Equality | Same entity type and identity | Same constituent values |
| Changes | Controlled changes preserve identity | Replace with a new value |
| Examples | Customer, Order, Product | Money, Email, DateRange |
| EF Core mapping | Entity type with a key; tables may be shared | Complex type, owned type, or conversion |
| Construction | Constructor or factory with validation | Constructor or factory with validation |

## When to Use Each

### Use an Entity when:

- The object has a unique identity that matters to the business
- You need to distinguish between two objects with identical attributes
- The object changes over time and you need to track those changes

**Examples:**
- `Customer` - two customers named "John" are different people
- `Order` - each order is unique, even with identical items
- `Product` - products have SKUs and need individual tracking

### Use a Value Object when:

- The object is defined by what it is, not who it is
- Two objects with the same properties are interchangeable
- The object should be immutable
- The concept carries validation rules or behavior

**Examples:**
- `Email` - `"john@example.com"` is the same regardless of who holds it
- `Money` - $100 USD is $100 USD, no matter which account it's in
- `Address` - same street, city, zip = same address
- `DateRange` - same start and end = same range

## A Practical Example

Consider an e-commerce domain:

```csharp
public readonly record struct CustomerId(Guid Value);
public readonly record struct ProductId(Guid Value);
public sealed record Address(string Street, string City);
public enum OrderStatus { Draft, Submitted }

public sealed class Order : Entity
{
    private readonly List<OrderLineItem> _lineItems = new();

    private Order() { }

    private Order(Guid id, CustomerId customerId, Address shippingAddress)
        : base(id)
    {
        CustomerId = customerId;
        ShippingAddress = shippingAddress;
        Status = OrderStatus.Draft;
        TotalAmount = Money.Zero("USD");
    }

    public CustomerId CustomerId { get; private set; }
    public Address ShippingAddress { get; private set; } = null!;
    public Money TotalAmount { get; private set; } = null!;
    public OrderStatus Status { get; private set; }
    public IReadOnlyCollection<OrderLineItem> LineItems => _lineItems.AsReadOnly();

    public static Order Create(CustomerId customerId, Address shippingAddress)
    {
        if (customerId.Value == Guid.Empty)
            throw new ArgumentException("A customer ID is required.", nameof(customerId));

        ArgumentNullException.ThrowIfNull(shippingAddress);
        return new Order(Guid.NewGuid(), customerId, shippingAddress);
    }

    public void AddLineItem(ProductId productId, Money unitPrice, int quantity)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Only draft orders can change.");

        ArgumentNullException.ThrowIfNull(unitPrice);
        if (unitPrice.Currency != TotalAmount.Currency)
            throw new ArgumentException("The order uses USD.", nameof(unitPrice));

        var lineItem = new OrderLineItem(productId, unitPrice, quantity);
        var newTotal = TotalAmount.Add(lineItem.TotalPrice);
        _lineItems.Add(lineItem);
        TotalAmount = newTotal;
    }

    public void UpdateShippingAddress(Address newAddress)
    {
        if (Status != OrderStatus.Draft)
            throw new InvalidOperationException("Only draft orders can change.");

        ArgumentNullException.ThrowIfNull(newAddress);
        ShippingAddress = newAddress; // Replace, don't mutate
    }
}

public sealed class OrderLineItem : Entity
{
    private OrderLineItem() { }

    internal OrderLineItem(ProductId productId, Money unitPrice, int quantity)
        : base(Guid.NewGuid())
    {
        if (productId.Value == Guid.Empty)
            throw new ArgumentException("A product ID is required.", nameof(productId));
        if (quantity <= 0)
            throw new ArgumentOutOfRangeException(nameof(quantity));

        ProductId = productId;
        UnitPrice = unitPrice;
        Quantity = quantity;
    }

    public ProductId ProductId { get; private set; }
    public Money UnitPrice { get; private set; } = null!;
    public int Quantity { get; private set; }
    public Money TotalPrice => Money.Create(UnitPrice.Amount * Quantity, UnitPrice.Currency);
}
```

Notice the pattern:
- `Order` and `OrderLineItem` are **Entities** - they have identity and change over time
- `Money`, `Address`, `CustomerId`, `ProductId` are **Value Objects** - they represent concepts, compare by their values, and are immutable

## Common Mistakes

**1. Making everything an Entity.** Use a value object when the business treats equal values as interchangeable.
The same concept can be an entity in another bounded context, such as an address managed by a property registry.

**2. Making Value Objects mutable.** Value Objects must be immutable. If you need to change a value, create a new instance:

```csharp
// Wrong - mutating a value object
address.City = "New York";

// Right - replacing with a new instance
var newAddress = new Address("123 Main St", "New York");
order.UpdateShippingAddress(newAddress);
```

**3. Using primitive types where Value Objects belong.** If a string carries business rules (email format, phone number format), wrap it in a Value Object. See [**Strongly Typed IDs**](https://milanjovanovic.tech/blog/strongly-typed-ids-csharp) for another example.

**4. Overcomplicating equality.** Entities compare by ID. Value Objects compare by attributes. Don't mix these up.

## Persisting Each Type With EF Core

In the infrastructure project, put these mappings inside `DbContext.OnModelCreating`.
They use the types above and [**EF Core**](https://learn.microsoft.com/en-us/ef/core/) 8 or later with a relational provider:

```csharp
modelBuilder.Entity<Order>(entity =>
{
    entity.HasKey(o => o.Id);
    entity.Property(o => o.Id).ValueGeneratedNever();
    entity.Property(o => o.CustomerId)
        .HasConversion(id => id.Value, value => new CustomerId(value));
    entity.HasMany(o => o.LineItems).WithOne().HasForeignKey("OrderId");
    entity.Navigation(o => o.LineItems).UsePropertyAccessMode(PropertyAccessMode.Field);
});

modelBuilder.Entity<OrderLineItem>(entity =>
{
    entity.HasKey(li => li.Id);
    entity.Property(li => li.Id).ValueGeneratedNever();
    entity.Property(li => li.ProductId)
        .HasConversion(id => id.Value, value => new ProductId(value));
    entity.Ignore(li => li.TotalPrice);
    entity.ComplexProperty(li => li.UnitPrice, money =>
    {
        money.Property(m => m.Amount).HasPrecision(18, 2);
        money.Property(m => m.Currency).HasMaxLength(3);
    });
});
```

**Value Objects** use [**Complex Types**](https://milanjovanovic.tech/blog/complex-types-ef-core) (EF Core 8+) or [**Owned Types**](https://milanjovanovic.tech/blog/owned-types-ef-core-ddd):

```csharp
modelBuilder.Entity<Order>(entity =>
{
    entity.ComplexProperty(o => o.ShippingAddress, addressBuilder =>
    {
        addressBuilder.Property(a => a.Street).HasColumnName("shipping_street");
        addressBuilder.Property(a => a.City).HasColumnName("shipping_city");
    });

    entity.ComplexProperty(o => o.TotalAmount, moneyBuilder =>
    {
        moneyBuilder.Property(m => m.Amount).HasColumnName("total_amount").HasPrecision(18, 2);
        moneyBuilder.Property(m => m.Currency).HasColumnName("total_currency").HasMaxLength(3);
    });
});
```

I cover the full set of mapping options in [**How to Model Value Objects With EF Core**](https://milanjovanovic.tech/blog/value-objects-ef-core).

## Summary

The Entity vs Value Object distinction is one of the most important modeling decisions in DDD:

- **Entities preserve identity.** Different instances can represent the same entity when their type and ID match.
- **Value objects compare by values.** Equal instances are interchangeable in the domain model.

Choose a value object when values are interchangeable, and an entity when the business needs to track an identity over time.

Thanks for reading.

And stay awesome!

---

## Frequently asked questions

### What is the difference between an entity and a value object in DDD?

An entity is defined by its identity: two entities with the same attributes but different IDs are different objects. A value object is defined by its attributes: two value objects with the same values are equal and interchangeable.

### Should a value object be immutable?

Yes. Value objects never change after creation. Any operation that would modify one returns a new instance instead, which makes them safe to share and easy to reason about.

### Is Money an entity or a value object?

A value object. One hundred dollars is one hundred dollars regardless of which account holds it. It has no identity, only an amount and a currency.

### How do you persist value objects with EF Core?

Use complex types (EF Core 8 and later), owned types, or value conversions for single-property wrappers. Entities have primary keys, but their table layout can use separate tables, table sharing, or inheritance mapping.

### Should I default to entities or value objects when modeling?

Prefer a value object when equal values are interchangeable in the business model. Use an entity when the business needs to track one identity through changes. The choice depends on the bounded context.
