Skip to content
  • English Deutsch

benjamin-abt.com

Performance is a feature | Cloud - AI - Azure - .NET - Sustainability

  • Home
  • Blog
  • Portfolio
    • Overview
    • Workshops & Training
    • Publications
  • Contact
  • Home
  • Blog

Advanced Usage of MSSQL Indexed Views in EF Core

Advanced Usage of MSSQL Indexed Views in EF Core
January 13, 2025 5 min read BEN ABT
Csharp .NET EF Core MSSQL

In the world of Microsoft SQL Server , views are a powerful tool for simplifying complex data queries. Simple views offer no real performance gain but are often used to implement simple database queries (I still would prefer App implementations over Database implementations for simple views). But indexed views offer a real performance gain.

Indexed views, also known as materialized views, are special database objects that store the results of a view persistently in the database. This persistence is updated as soon as a write access occurs that affects data records contained in the view. This mechanism makes it possible to save the results of a complex query and retrieve them as required without having to execute the query again. This is a real performance boost, especially for applications that have an enormously high read rate.

Simple Views in EF Core

Unfortunately, EF Core only offers very limited support for views in MSSQL; ultimately only basic features.

For this purpose, a so-called keyless entity is created - here as an example a user and their number of forum posts - which are then mapped to a view in the configuration.

C#
 1public class UserForumPostCount
 2{
 3    public int UserId { get; set; }
 4    public int TotalPostCount { get; set; }
 5}
 6
 7public class UserForumPostCountConfig : IEntityTypeConfiguration<UserForumPostCount>
 8{
 9    public void Configure(EntityTypeBuilder<UserForumPostCount> builder)
10    {
11        builder.HasNoKey();
12        
13        builder.ToView("UserForumPostCountView");
14    }
15}

The view - this is not a materialized view - itself is created within a migration using an SQL command.

SQL
1CREATE VIEW UserForumPostCountView
2AS
3    SELECT 
4        UserId,
5        COUNT(*) AS TotalPostCount
6    FROM 
7        ForumPosts
8    GROUP BY 
9        UserId;

Finally, only the view needs to be registered in the DbContext.

C#
 1public class MyDbContext : DbContext
 2{
 3    // ....
 4
 5    public DbSet<UserForumPostCount> UserForumPostCountViews
 6        => Set<UserForumPostCount>();
 7
 8    protected override void OnModelCreating(ModelBuilder modelBuilder)
 9    {
10        // ...
11        modelBuilder.ApplyConfiguration(new UserForumPostCountConfig());
12    }

Disadvantages

This way of declaration has some disadvantages, the two biggest ones are:

  • Views in EF Core do not support navigation properties; other entities can therefore not be accessed - although this is supported in all common relational databases. EF Core refuses to do this with corresponding error messages like The navigation cannot be added because it targets the keyless entity type 'Document'. Navigations can only target entity types with keys. (see See ‘Entity Framework Core Keyless Navigation Problem’ on stackoverflow ).
  • Depending on the database system, there are additional functionalities that cannot be used by simply declaring the EF Core view (ToView ). For indexed views, there is the so-called NOEXPAND hint, which is there to ensure that the SQL server really reads from the persisted view and does not re-execute the view. This hint cannot be set via EF Core ToView.

Advanced Usage - Indexed Views

Indexed Views are so powerful that every means is right to use them. One workaround is to declare the view as a normal entity and abuse the functionality of EF Core here - at your own risk, of course.

First, the view is created via the migration.

SQL
 1CREATE VIEW UserForumPostCountView WITH SCHEMABINDING AS
 2SELECT 
 3    UserId,
 4    COUNT_BIG (*) AS TotalPostCount
 5FROM 
 6    dbo.ForumPosts
 7GROUP BY 
 8    UserId;
 9
10CREATE UNIQUE CLUSTERED INDEX IX_UserForumPostCountView
11    ON UserForumPostCountView (UserId);

The special features here are:

  • Some SQL functions are not supported in indexed views.
  • COUNT_BIG must be used, which means we no longer use int in the entity but must use long
  • The whole thing is done with the help of schema binding
  • A clustered index is created

In our entity configuration we can now not use ToView, but use our own SQL command.

C#
 1public class UserForumPostCount
 2{
 3    public int UserId { get; set; }
 4    public long TotalPostCount { get; set; }
 5}
 6
 7public class UserForumPostCountConfig : IEntityTypeConfiguration<UserForumPostCount>
 8{
 9    public void Configure(EntityTypeBuilder<UserForumPostCount> builder)
10    {
11        builder.HasKey(x => x.UserId);
12
13        builder.ToSqlQuery(
14            /* language = sql*
15             @"""
16                 SELECT 
17                    UserId,
18                    TotalPostCount
19                 FROM 
20                    UserForumPostCountView WITH (NOEXPAND);
21             """;
22    }
23}

This is absolutely necessary in order to be able to use the SQL hint NoExpand, which ensures that the query is never performed dynamically but always against the already persisted result - and thus brings the performance gain.

Side Effects

A nice side effect is that this type of view declaration now also allows navigation properties in our view entity:

C#
 1public class UserForumPostCount
 2{
 3    public int UserId { get; set; }
 4    public virtual UserEntity User { get; set; } = null!;
 5
 6    public long TotalPostCount { get; set; }
 7}
 8
 9public class UserForumPostCountConfig : IEntityTypeConfiguration<UserForumPostCount>
10{
11    public void Configure(EntityTypeBuilder<UserForumPostCount> builder)
12    {
13        builder.HasKey(x => x.UserId);
14        
15        builder.ToSqlQuery(
16            /* language = sql*
17             @"""
18                 SELECT 
19                    UserId,
20                    TotalPostCount
21                 FROM 
22                    UserForumPostCountView WITH (NOEXPAND);
23             """;
24
25         builder.HasOne(x => x.User)
26            .WithOne()
27            .HasForeignKey(x => x.UserId);
28        }
29    }

Effect on the migration

Due to the workaround of the declaration of the view, EF Core does not know that it is a view. EF Core now thinks that this is a separate entity and generates corresponding CREATE commands in the migration.

These must all be adapted manually:

  • Manual view creation
  • Manual index creation
  • Adapt/remove EF Core migration commands

Conclusion

EF Core can be used wonderfully with indexed views in MSSQL; even navigation properties work without a wall, which I have been using successfully for years even in very large applications. However, everything is done at your own risk with the knowledge that this is really an unsupported workaround - for which we may not get official support even in 200 years.

It is to be used with caution; you need advanced experience, you need to know what you are doing - but you will be rewarded with a huge performance boost.

Related articles

Increase Command Timeout for EF Core Migration Bundles

Jan 3, 2025 - 2 min read

Increase Command Timeout for EF Core Migration Bundles

EF Core Migration Bundles are standalone executable files that contain one or more Entity Framework Core migrations and can be applied …

New Data Annotation attributes with .NET 8

Nov 19, 2023 - 2 min read

New Data Annotation attributes with .NET 8

With .NET 8, the System.ComponentModel.DataAnnotations Namespace has been revised and some new attributes have been added that have been …

The well-structured Entity in EF Core

Nov 16, 2023 - 7 min read

The well-structured Entity in EF Core

A database entity represents an identifiable entry, often also things from the real world, which are to be stored and managed in the …


BEN ABT

BEN ABT

Ben is Chief Engineering Officer and Principal Software Engineer at Medialesson.de, with a strong focus on .NET and Microsoft Azure. In his professional role, he designs and builds highly scalable platforms for hybrid and cloud-based scenarios. He also advises companies and C-levels on migrating to the sovereign cloud.

He runs myCSharp.de, the largest and most active German-speaking C# community forum, actively contributes to both his own and external open-source projects and regularly shares his knowledge through his blog, at community events, conferences and as a writer for technical magazines.

Let's Work Together

Looking for an experienced Platform Architect or Engineer for your next project? Whether it's cloud migration, platform modernization or building new solutions from scratch - I'm here to help you succeed.

New Platforms

Modernization

Training & Consulting

Contact Me
X Facebook LinkedIn WhatsApp
Back Next Blog Post

Open for work!

Contact Me
BEN ABT BEN ABT Software Engineer · Microsoft MVP Passionate about .NET, Azure, performance optimization and sustainable software engineering.

Medialesson GmbH Logo

Microsoft MVP Logo

Recent posts

View all
Stop Using nginx as Your Default Static File Server in Docker

Stop Using nginx as Your Default Static File Server in …

Oct 24, 2026
Containerizing .NET in 2026 Without a Dockerfile

Containerizing .NET in 2026 Without a Dockerfile

Oct 20, 2026
Problem Details in ASP.NET Core and .NET 11

Problem Details in ASP.NET Core and .NET 11

Oct 5, 2026
Resilience in .NET with Microsoft.Extensions.Resilience

Resilience in .NET with Microsoft.Extensions.Resilience

Sep 21, 2026
What Matters in ASP.NET Core 11

What Matters in ASP.NET Core 11

Sep 14, 2026

Tags

.NET (192) Csharp (96) ASP.NET Core (48) Azure (41) DevOps (17) Docker (14) Microsoft (14) EF Core (11) Microsoft MVP (11) AI (10) GitHub (10) GitHub Copilot (10) ASP.NET (9) Security (9) Synology (9) Architecture (8) Raspberry (8) Azure Functions (7) Cloudflare (7) Bicep (6)
benjamin-abt.com

About BEN

Benjamin Abt is a Software Engineer, Platform Architect and Microsoft MVP specializing in .NET, Azure, cloud architecture, performance optimization and sustainable software development.

Connect

  • Follow me on GitHub
  • Follow me on LinkedIn
  • Follow me on Mastodon
  • Microsoft MVP profile
  • Follow SchwabenCode on GitHub
  • Follow me on X

Quick Links

  • Portfolio
  • Blog
  • Workshops & Training
  • Publications
  • Get in Touch
  • Sitemap
Open for Work

© Copyright Benjamin Abt & SchwabenCode | 2011 - 2026

Content licensed under CC BY 4.0 unless otherwise noted.

  • Impressum
  • Privacy
  • Contact