EFCore.ComplexIndexes
3.1.0
See the version list below for details.
dotnet add package EFCore.ComplexIndexes --version 3.1.0
NuGet\Install-Package EFCore.ComplexIndexes -Version 3.1.0
<PackageReference Include="EFCore.ComplexIndexes" Version="3.1.0" />
<PackageVersion Include="EFCore.ComplexIndexes" Version="3.1.0" />
<PackageReference Include="EFCore.ComplexIndexes" />
paket add EFCore.ComplexIndexes --version 3.1.0
#r "nuget: EFCore.ComplexIndexes, 3.1.0"
#:package EFCore.ComplexIndexes@3.1.0
#addin nuget:?package=EFCore.ComplexIndexes&version=3.1.0
#tool nuget:?package=EFCore.ComplexIndexes&version=3.1.0
<p align="center"> <img width="300" height="300" align="center" alt="efcore-complexindexes-logo" src="https://github.com/user-attachments/assets/9b51234a-90e4-44af-91a3-443d159f6d1d" /> </p>
Index support for complex type properties in EF Core migrations — the missing piece for value object-driven architectures.
EF Core 8.0 introduced complex properties, but migration tooling doesn't automatically generate indexes for these nested value objects. This NuGet package bridges that gap with a clean, fluent API for defining single-column, composite, unique, and filtered indexes directly on complex type properties — and, on PostgreSQL, expression (functional) indexes.
Why it matters:
- Value Object Indexing: Seamlessly add database indexes to properties buried inside complex types (e.g.,
Person.EmailAddress.Value) - DDD-Friendly: Supports the Domain-Driven Design pattern of encapsulating logic in value objects without sacrificing database performance
- Migration-Aware: Automatically generates proper
CREATE INDEXandDROP INDEXoperations during EF Core migrations - Flexible Filtering: Supports SQL
WHEREclauses for filtered indexes (e.g., soft deletes) - Composite Indexes: Define multi-column indexes spanning both scalar and nested properties with a single, intuitive expression — with per-column
ASC/DESCordering viaDbOrder.Asc/DbOrder.Desc - Expression Indexes (PostgreSQL): Index arbitrary SQL expressions such as
lower(email)orto_tsvector('english', body)— including on plain, non-complex entities
| Package | NuGet | Description |
|---|---|---|
| EFCore.ComplexIndexes | Core library — single-column, composite, unique, and filtered indexes on complex type properties. Works with any EF Core relational provider. | |
| EFCore.ComplexIndexes.PostgreSQL | PostgreSQL extensions via Npgsql — adds GIN, GiST, BRIN, SP-GiST, and Hash index methods, operator classes, covering indexes (INCLUDE), concurrent creation, nulls-distinct control, and expression (functional) indexes. |
Which package do I need? Install only the core package if you use SQL Server, SQLite, or any provider where the default B-tree index type is sufficient. Add the PostgreSQL package when you need PostgreSQL-specific index types or expression indexes — it includes the core automatically.
Getting started
Complex-property indexes (core)
The complex-property, composite, and provider-method index features are wired up automatically through EF Core's design-time tooling. Just install the package, configure your indexes in OnModelCreating, and run dotnet ef migrations add — zero additional ceremony.
Expression indexes (PostgreSQL) — one-time setup
Expression indexes are the one exception: rendering CREATE INDEX … ((expr)) requires a custom migrations SQL generator that runs when migrations are applied. EF Core does not auto-wire runtime services, so you must opt in once when configuring your DbContext:
services.AddDbContext<AppDbContext>(options =>
options
.UseNpgsql(connectionString)
.UseNpgsqlComplexIndexes()); // ← required for HasExpressionIndex(...)
⚠️
UseNpgsqlComplexIndexes()is a prerequisite forHasExpressionIndex. Without it, applying a migration that contains an expression index will fail (the stock generator can't render the expression). All other features — complex-property indexes, composite indexes, and the GIN/GiST/etc. methods — do not require this call; they flow through Npgsql's own SQL generator.
Usage
Single-column index on a complex property
builder.ComplexProperty(x => x.EmailAddress, c =>
c.Property(x => x.Value)
.HasComplexIndex(isUnique: true, filter: "deleted_at IS NULL")
);
Composite index across scalar and nested properties
builder.HasComplexCompositeIndex(
x => new { x.Name, x.EmailAddress.Value },
isUnique: true);
Per-column sort direction
Wrap any member in DbOrder.Desc(...) (or DbOrder.Asc(...), the default) to control its sort order. Because a wrapped member is a method call, C# requires you to name it in the anonymous type:
builder.HasComplexCompositeIndex(
c => new { c.HybridDateTime.DateTime, Counter = DbOrder.Desc(c.HybridDateTime.Counter), c.Id },
indexName: "IX_Commits_DateTime_Counter_Id");
// CREATE INDEX "IX_Commits_DateTime_Counter_Id" ON ... ("DateTime", "Counter" DESC, "Id");
Direction maps to EF Core's native CreateIndexOperation.IsDescending, so it is rendered by every relational provider (SQL Server, SQLite, PostgreSQL) — no extra wiring required. Re-declaring an index over the same columns updates its direction.
PostgreSQL index methods on a complex property
Use the builder-callback overload to reach the PostgreSQL-specific options (GIN, GiST, BRIN, SP-GiST, Hash, operator classes, INCLUDE, concurrent creation, nulls-distinct):
builder.ComplexProperty(x => x.Payload, c =>
c.Property(x => x.Json)
.HasComplexIndex(idx => idx
.UseGin()
.HasOperators("jsonb_path_ops"))
);
Expression (functional) indexes — PostgreSQL
Requires
UseNpgsqlComplexIndexes()(see Getting started). Available as an extension onEntityTypeBuilder<TEntity>, so it works on any entity — complex or not.
Each string is emitted verbatim — there is no property-to-column resolution and no automatic quoting. Write the final SQL exactly as it should appear inside the index, referencing real column names.
Single expression:
// CREATE INDEX "IX_person_lowerlastname" ON person ((lower(last_name)));
builder.HasExpressionIndex("lower(last_name)");
With unique / filter / explicit name:
builder.HasExpressionIndex(
"lower(email)",
isUnique: true,
filter: "deleted_at IS NULL",
indexName: "ix_person_email_ci");
Multiple ordered parts + provider options (builder callback):
builder.HasExpressionIndex(idx => idx
.Expression("country") // a plain column, written as raw SQL
.Expression("lower(email)") // a SQL expression
.IsUnique()
.HasFilter("deleted_at IS NULL")
.HasName("ix_person_country_email_ci"));
// CREATE UNIQUE INDEX "ix_person_country_email_ci"
// ON person ((country), (lower(email)))
// WHERE deleted_at IS NULL;
Full-text / JSONB with a GIN index:
builder.HasExpressionIndex(idx => idx
.Expression("to_tsvector('english', body)")
.UseGin());
// CREATE INDEX ... ON articles USING gin ((to_tsvector('english', body)));
Covering expression index (INCLUDE):
builder.HasExpressionIndex(idx => idx
.Expression("lower(email)")
.IsUnique()
.IncludeProperties("display_name"));
Quoting tip
Strings are passed through untouched, so identifiers that need PostgreSQL quoting (e.g. PascalCase columns) must include the quotes yourself. C# raw string literals keep this readable:
// CREATE INDEX ... ON "People" ((lower("Email")));
builder.HasExpressionIndex(""" lower("Email") """.Trim());
Roadmap: the expression API is built on an
IIndexExpressionseam. A future LINQ add-on will let you writeHasExpressionIndex(x => x.Email.ToLower())and have it translated to SQL — flowing through the exact same pipeline.
The package integrates seamlessly with EF Core's design-time tooling. Apart from the one-time UseNpgsqlComplexIndexes() call for expression indexes, there is no additional ceremony — just configure and migrate.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net10.0
- Microsoft.EntityFrameworkCore.Abstractions (>= 10.0.0)
NuGet packages (2)
Showing the top 2 NuGet packages that depend on EFCore.ComplexIndexes:
| Package | Downloads |
|---|---|
|
EFCore.ComplexIndexes.PostgreSQL
PostgreSQL provider extensions for EFCore.ComplexIndexes — GIN, GiST, BRIN, SP-GiST, and Hash index support for complex type properties. |
|
|
SIL.Harmony
CRDT application library for building offline-first apps with Entity Framework Core. |
GitHub repositories
This package is not used by any popular GitHub repositories.