Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 8 additions & 3 deletions .github/workflows/build-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ name: Build Samples

on:
push:
branches: [live]
branches: [live, main]
paths:
- "samples/core/**"
- "samples/end2end/**"
- ".github/workflows/build-samples.yml"
pull_request:
branches: [live]
branches: [live, main]
paths:
- "samples/core/**"
- "samples/end2end/**"
Expand All @@ -26,7 +26,12 @@ jobs:
uses: actions/setup-dotnet@v4
with:
dotnet-version: 10.0.x
include-prerelease: true

- name: Setup .NET 11.0 SDK (preview)
uses: actions/setup-dotnet@v4
with:
dotnet-version: 11.0.x
dotnet-quality: preview

- name: Build samples
working-directory: samples/core
Expand Down
287 changes: 287 additions & 0 deletions entity-framework/core/modeling/complex-types.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions entity-framework/core/modeling/owned-entities.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ EF Core allows you to model entity types that can only ever appear on navigation

Owned entities are essentially a part of the owner and cannot exist without it, they are conceptually similar to [aggregates](https://martinfowler.com/bliki/DDD_Aggregate.html). This means that the owned entity is by definition on the dependent side of the relationship with the owner.

> [!TIP]
> If you are modeling a [value object](https://martinfowler.com/bliki/ValueObject.html) - an object without its own identity, such as an `Address` or `Coordinate` - consider using a [complex type](xref:core/modeling/complex-types) instead of an owned entity type. Unlike owned types, complex types have value semantics and no hidden key, which avoids a number of pitfalls; see [Complex types vs. owned entity types](xref:core/modeling/complex-types#complex-types-vs-owned-entity-types) for a comparison.

## Configuring types as owned

In most providers, entity types are never configured as owned by convention - you must explicitly use the `OwnsOne` method in `OnModelCreating` or annotate the type with `OwnedAttribute` to configure the type as owned. The Azure Cosmos DB provider is an exception to this. Because Azure Cosmos DB is a document database, the provider configures all related entity types as owned by default.
Expand Down
3 changes: 3 additions & 0 deletions entity-framework/core/what-is-new/ef-core-10.0/whatsnew.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,9 @@ In EF 10 we improved this experience - EF will now materialize a default value f

Complex types are used to model types which are contained within your entity types and have no identity of their own; while entity types are (usually) mapped to a database table, complex types can be mapped to columns in their container table ("table splitting"), or to a single JSON column. Complex types introduce document modeling techniques, which can bring substantial performance benefits as traditional JOINs are avoided, and can make your database modeling much simpler and more natural.

> [!TIP]
> For comprehensive documentation on complex types, see [Complex types](xref:core/modeling/complex-types).

### Table splitting

For example, the following maps a customer's addresses as complex types:
Expand Down
3 changes: 3 additions & 0 deletions entity-framework/core/what-is-new/ef-core-11.0/whatsnew.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ EF11 requires the .NET 11 SDK to build and requires the .NET 11 runtime to run.

## Complex types

> [!TIP]
> For comprehensive documentation on complex types, see [Complex types](xref:core/modeling/complex-types).

<a name="complex-types-tpt-tpc"></a>

### Complex types and JSON columns on entity types with TPT/TPC inheritance
Expand Down
3 changes: 3 additions & 0 deletions entity-framework/core/what-is-new/ef-core-8.0/whatsnew.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ EF8 requires the [.NET 8 SDK](https://aka.ms/get-dotnet-8) to build and requires

## Value objects using Complex Types

> [!TIP]
> This section introduces complex types. For comprehensive documentation, see [Complex types](xref:core/modeling/complex-types).

Objects saved to the database can be split into three broad categories:

- Objects that are unstructured and hold a single value. For example, `int`, `Guid`, `string`, `IPAddress`. These are (somewhat loosely) called "primitive types".
Expand Down
3 changes: 3 additions & 0 deletions entity-framework/core/what-is-new/ef-core-9.0/whatsnew.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,9 @@ We'd like to call out Andrea Canciani ([@ranma42](https://github.com/ranma42)) f

### Complex types: GroupBy and ExecuteUpdate support

> [!TIP]
> For comprehensive documentation on complex types, see [Complex types](xref:core/modeling/complex-types).

#### GroupBy

> [!TIP]
Expand Down
2 changes: 2 additions & 0 deletions entity-framework/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,8 @@
href: core/modeling/table-splitting.md
- name: Owned entity types
href: core/modeling/owned-entities.md
- name: Complex types
href: core/modeling/complex-types.md
- name: Keyless entity types
href: core/modeling/keyless-entity-types.md
- name: Spatial data
Expand Down
19 changes: 19 additions & 0 deletions samples/core/Modeling/ComplexTypes/ComplexTypes.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net11.0</TargetFramework>
<RootNamespace>EFModeling.ComplexTypes</RootNamespace>
<AssemblyName>EFModeling.ComplexTypes</AssemblyName>
<Nullable>enable</Nullable>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="11.0.0-preview.6.26359.118">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="11.0.0-preview.6.26359.118" />
</ItemGroup>

</Project>
113 changes: 113 additions & 0 deletions samples/core/Modeling/ComplexTypes/ComplexTypesContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using Microsoft.EntityFrameworkCore;

namespace EFModeling.ComplexTypes;

public class ComplexTypesContext : DbContext
{
public DbSet<Customer> Customers { get; set; }
public DbSet<Order> Orders { get; set; }
public DbSet<Distributor> Distributors { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder
.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFComplexTypes;Trusted_Connection=True;ConnectRetryCount=0");

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region ComplexPropertyConfig
modelBuilder.Entity<Customer>(b =>
{
// A required complex property; mapped to columns in the Customers table.
b.ComplexProperty(c => c.Address);

// An optional complex property (EF Core 10+).
b.ComplexProperty(c => c.SecondaryAddress);
});

modelBuilder.Entity<Order>(b =>
{
b.ComplexProperty(o => o.ShippingAddress);
b.ComplexProperty(o => o.BillingAddress);
});
#endregion

#region ComplexPropertyFacets
modelBuilder.Entity<Order>()
.ComplexProperty(
o => o.ShippingAddress,
b =>
{
b.Property(a => a.Line1).HasColumnName("ShipsToStreet").HasMaxLength(100);
b.Property(a => a.City).HasColumnName("ShipsToCity");
});
#endregion

#region ComplexCollectionJson
// Collections of complex types must be mapped to JSON on relational providers.
modelBuilder.Entity<Distributor>()
.ComplexCollection(d => d.ShippingCenters, b => b.ToJson());
#endregion

#region PropertyChaining
// EF Core 11 allows configuring a complex-type property directly by chaining
// member access, without first obtaining the complex-type builder.
modelBuilder.Entity<Customer>()
.Property(c => c.Address.Line1)
.HasMaxLength(200);
#endregion

#region IndexOnComplexProperty
// EF Core 11 allows keys and indexes to target scalar properties nested
// inside non-collection complex types.
modelBuilder.Entity<Customer>()
.HasIndex(c => c.Address.PostCode);
#endregion
}
}

// Indexing into a JSON-mapped complex collection requires a database that supports
// JSON indexes (for example, SQL Server 2025). This context hosts that example
// separately so the main sample can run against LocalDB.
public class ComplexTypesJsonIndexContext : DbContext
{
public DbSet<Distributor> Distributors { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFComplexTypesJsonIndex;Trusted_Connection=True;ConnectRetryCount=0");

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Distributor>()
.ComplexCollection(d => d.ShippingCenters, b => b.ToJson());

#region IndexOnComplexCollection
// Index a scalar inside every element of a JSON-mapped complex collection.
// Requires a provider/database with JSON index support, such as SQL Server 2025.
modelBuilder.Entity<Distributor>()
.HasIndex("ShippingCenters[].City");
#endregion
}
}

public class ComplexTypesDiscriminatorContext : DbContext
{
public DbSet<Place> Places { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
=> optionsBuilder.UseSqlServer(
@"Server=(localdb)\mssqllocaldb;Database=EFComplexTypesDiscriminator;Trusted_Connection=True;ConnectRetryCount=0");

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
#region OptionalWithDiscriminator
// GeoLocation has no required property, so configure a discriminator. EF creates
// it as a required shadow property, which satisfies the requirement that an
// optional complex type have at least one required property.
modelBuilder.Entity<Place>()
.ComplexProperty(p => p.Location, b => b.HasDiscriminator());
#endregion
}
}

35 changes: 35 additions & 0 deletions samples/core/Modeling/ComplexTypes/DataAnnotations.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;

namespace EFModeling.ComplexTypes.DataAnnotations;

#region AddressAttribute
[ComplexType]
public record Address
{
public required string Line1 { get; init; }
public string? Line2 { get; init; }
public required string City { get; init; }
public required string Country { get; init; }
public required string PostCode { get; init; }
}
#endregion

// An entity referencing an attribute-decorated complex type maps it as a complex
// property without any further configuration in OnModelCreating.
public class Customer
{
public int Id { get; set; }
public required string Name { get; set; }
public required Address Address { get; set; }
public List<Order> Orders { get; } = new();
}

public class Order
{
public int Id { get; set; }
public required string Contents { get; set; }
public required Address ShippingAddress { get; set; }
public required Address BillingAddress { get; set; }
public Customer Customer { get; set; } = null!;
}
70 changes: 70 additions & 0 deletions samples/core/Modeling/ComplexTypes/Model.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
using System.Collections.Generic;

namespace EFModeling.ComplexTypes;

#region Address
public record Address
{
public required string Line1 { get; init; }
public string? Line2 { get; init; }
public required string City { get; init; }
public required string Country { get; init; }
public required string PostCode { get; init; }
}
#endregion

#region CustomerOrders
public class Customer
{
public int Id { get; set; }
public required string Name { get; set; }

// A required (non-nullable) complex property.
public required Address Address { get; set; }

// An optional (nullable) complex property.
public Address? SecondaryAddress { get; set; }

public List<Order> Orders { get; } = new();
}

public class Order
{
public int Id { get; set; }
public required string Contents { get; set; }
public required Address ShippingAddress { get; set; }
public required Address BillingAddress { get; set; }
public Customer Customer { get; set; } = null!;
}
#endregion

#region Distributor
public class Distributor
{
public int Id { get; set; }
public required string Name { get; set; }

// A collection of complex types, mapped to a single JSON column.
public List<Address> ShippingCenters { get; set; } = new();
}
#endregion

#region GeoLocation
public class GeoLocation
{
// All properties are optional, so this complex type has no required property
// of its own to anchor an optional (nullable) usage.
public double? Latitude { get; set; }
public double? Longitude { get; set; }
}

public class Place
{
public int Id { get; set; }
public required string Name { get; set; }

// An optional complex property whose type has no required properties.
public GeoLocation? Location { get; set; }
}
#endregion

Loading
Loading