EFCore.ComplexIndexes 5.6.0

dotnet add package EFCore.ComplexIndexes --version 5.6.0
                    
NuGet\Install-Package EFCore.ComplexIndexes -Version 5.6.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="EFCore.ComplexIndexes" Version="5.6.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="EFCore.ComplexIndexes" Version="5.6.0" />
                    
Directory.Packages.props
<PackageReference Include="EFCore.ComplexIndexes" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add EFCore.ComplexIndexes --version 5.6.0
                    
#r "nuget: EFCore.ComplexIndexes, 5.6.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package EFCore.ComplexIndexes@5.6.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=EFCore.ComplexIndexes&version=5.6.0
                    
Install as a Cake Addin
#tool nuget:?package=EFCore.ComplexIndexes&version=5.6.0
                    
Install as a Cake Tool

EFCore.ComplexIndexes

On EF Core 11 or later? This package supports EF Core 10 only. From 5.5.0 it throws when you register it on any other major version. Its successor for EF Core 11 is a new package, CodoMetis.EFCore.SchemaKit, tracked in #37.

Indexes on complex type properties in EF Core migrations, for models built on value objects.

EF Core lets you map value objects as complex properties, but dotnet ef migrations add writes no index on their columns. With this package you declare those indexes in OnModelCreating, and migrations add writes the CREATE INDEX and DROP INDEX for you.

This is the core package, and it works with any EF Core relational provider. For a provider's own index features, add its package. Each one brings this package with it:

Package Adds
EFCore.ComplexIndexes.PostgreSQL GIN/GiST/BRIN/SP-GiST/Hash methods, operator classes, INCLUDE, NULLS FIRST/LAST, expression & JSON indexes, temporal and exclusion constraints
EFCore.ComplexIndexes.SqlServer Clustered, covering (INCLUDE), online builds, fill factor, sort-in-tempdb, data compression

Setup

None. The package registers itself with EF Core's design-time tools. Install it, declare your indexes in OnModelCreating, and run dotnet ef migrations add.

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")
);

The package finds the real column names in your model. It follows both EF Core's naming conventions (Origin_Source) and names you set with HasColumnName.

A filter can name properties instead of columns. migrations add turns filter: "{DeletedAt} IS NULL" into the mapped column, quoted for your provider, and writes the result into the migration. The lambda that picks the column reaches into complex properties. For a value object stored through a converter, it reaches one step further: x => x.Email.Value is the converted column.

Several indexes over one column

builder.HasComplexIndex(x => x.EmailAddress.Value,
    isUnique: true, filter: "deleted_at IS NULL", indexName: "ux_person_email_active");
builder.HasComplexIndex(x => x.EmailAddress.Value,
    indexName: "ix_person_email_all");

A declaration on a property holds one index for that property. To give a column several indexes with different filters, as in the usual soft-delete pattern, declare them on the entity. The lambda can reach into complex properties. Each of these indexes needs its own explicit name, or the declaration throws.

migrations add checks every index name, so the database doesn't reject or quietly shorten one:

  • Unique where the provider needs it: across the database on SQLite, per schema on PostgreSQL, per table on SQL Server.
  • Within the provider's length limit: 63 bytes on PostgreSQL, which would otherwise shorten the name without an error.

On SQLite, some migrations rebuild a table: altering or dropping a column, changing a key or constraint, or renaming the table. A rebuild drops the table's complex indexes, and EF Core's SQL generator runs it after everything else. Call UseComplexIndexes() after UseSqlite(), both where dotnet ef builds the context and where the migration runs, and the indexes are created again after the rebuild. Without the call, dotnet ef migrations add fails and says how to split the change into several migrations (Upgrading).

Composite index across scalar and nested properties

builder.HasComplexCompositeIndex(
    x => new { x.Name, x.EmailAddress.Value },
    isUnique: true);

Per-column sort direction

builder.HasComplexCompositeIndex(
    c => new { c.Created, Counter = DbOrder.Desc(c.Version.Counter) },
    indexName: "ix_commits_created_counter");
// CREATE INDEX ix_commits_created_counter ON ... (created, counter DESC);

Every relational provider writes the direction, because it maps to EF Core's own CreateIndexOperation.IsDescending. A wrapped member is a method call, so C# needs you to name it in the anonymous type.

You can declare DbOrder.NullsFirst/NullsLast here too, but only the PostgreSQL package writes them. On SQL Server, and on providers without their own package such as SQLite, an index that uses them makes dotnet ef migrations add fail.


Reading declarations back

GetComplexIndexes() on an entity type, or on the model, returns every declaration: property-level, entity-level, composite and expression indexes. Each one has its parts, IsUnique, Filter and the Name you gave it, so you can check your own conventions in code. FindComplexIndex(name) finds one by that name. This works on the mutable model inside OnModelCreating too, and migrations add reads declarations through the same code.

var unfiltered = modelBuilder.Model.GetEntityTypes()
    .SelectMany(e => e.GetComplexIndexes())
    .Where(ix => ix.IsUnique && ix.Filter is null);

You can also apply a convention instead of checking it. On the mutable model, AddComplexIndexFilter(predicate, where) adds a predicate with AND to the filter of every declaration you select, and repeating the call changes nothing. AddComplexIndex(definition) adds a declaration, and follows the fluent API's rules for duplicates and names. Both change only what is declared when they run, so call them after the configurations.

Documentation

Full documentation, including every provider's features: https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes

Changelog

This package's changelog, or the root changelog covering all three packages.

MIT licensed.

Product 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. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (4)

Showing the top 4 NuGet packages that depend on EFCore.ComplexIndexes:

Package Downloads
SIL.Harmony

CRDT application library for building offline-first apps with Entity Framework Core.

SIL.Harmony.Linq2db

linq2db integration for the Harmony CRDT library.

EFCore.ComplexIndexes.PostgreSQL

PostgreSQL provider extensions for EFCore.ComplexIndexes — GIN, GiST, BRIN, SP-GiST, and Hash index support for complex type properties.

EFCore.ComplexIndexes.SqlServer

SQL Server provider extensions for EFCore.ComplexIndexes — clustered, covering (INCLUDE), online-built, and fill-factor index options for complex type properties.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
5.6.0 74 10/23/2026
5.5.0 405 10/20/2026
5.4.1 1,988 9/22/2026
5.4.0 1,711 9/22/2026
5.3.0 1,064 9/5/2026
5.2.0 183 9/5/2026
5.1.0 151 9/5/2026
5.0.3 1,394 8/15/2026
5.0.2 149 8/15/2026
5.0.1 179 8/10/2026
5.0.0 143 8/10/2026
4.0.0 3,156 6/14/2026
3.1.5 10,122 6/5/2026
3.1.0 159 6/4/2026
3.0.0 143 6/3/2026
2.0.5 456 5/14/2026
2.0.2 1,395 2/14/2026
1.0.2 150 2/12/2026
Loading failed

Migrations keep the indexes and constraints a column change used to drop or rebuild: Changed answers in 5.6.0 (https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes/blob/v5.6.0/docs/upgrading.md#changed-answers-in-560).

Changed answers

- Migrations: moving a table into the default schema keeps the complex indexes and constraints on it. They were dropped and created again, and on PostgreSQL, where Npgsql leaves such a table in its schema, the migration failed (42P01) (#185 (https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes/issues/185), Upgrading (https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes/blob/v5.6.0/docs/upgrading.md#a-move-into-the-default-schema-keeps-what-is-on-the-table)).
- Migrations: renaming a column keeps the complex indexes, exclusion constraints and temporal constraints that read it, and renames a default name that includes the column. They were dropped and created again around the rename. SQL Server still re-creates an index whose filter reads the column (#182 (https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes/issues/182), Upgrading (https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes/blob/v5.6.0/docs/upgrading.md#a-column-rename-keeps-what-reads-the-column)).

Fixed

- Indexes: on SQL Server, a complex index whose filter or INCLUDE entry names a column in another case, [note] for Note, is dropped before an ALTER COLUMN or rename of that column and created after it, where the migration failed at apply, since SQL Server rejects either while the index exists (Msg 5074 for ALTER COLUMN) (#189 (https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes/issues/189)).

Full changelog: https://github.com/CaffeinatedCoder/EFCore.ComplexIndexes/blob/v5.6.0/src/EFCore.ComplexIndexes/CHANGELOG.md