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

The well-structured Entity in EF Core

The well-structured Entity in EF Core
November 16, 2023 7 min read BEN ABT
EF Core .NET Csharp

This article was written in 2023. Versions, tools and recommendations may have changed since then. Newer posts are collected under 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 database. The purpose of database entities is to provide a structured and efficient implementation for organizing and managing information. By identifying entities, defining attributes and establishing relationships, a coherent and meaningful structure is created that allows data to be stored, retrieved and updated effectively. This facilitates data management and analysis in applications and systems.

Primary Key

Each entity should be uniquely identifiable. This is achieved by a primary key, which ensures that each row in the table has a unique identification. The simplest representation of an entity key is a column that contains a value.

Guid IDs scale better (even with more data usage) in most environments, as they are unique even in scaling environments. With database-generated integer IDs, collisions can occur more quickly if several instances generate database IDs at the same time.

C#
1public class MyEntity
2{
3    [Key] 
4    public Guid Id { get; set; }
5}

The representation as a Guid base type is simple, but relatively error-prone as soon as an entity has several Guid properties. It is therefore always advisable to use Strong-Id implementations for Ids. Better Code with custom Strong-Id Types

C#
1public class UserEntity
2{
3    [Key] 
4    public UserId Id { get; set; }
5}

Contents and Properties

An entity is only meaningful if it also has data. Database languages often speak of attributes, software engineers of properties or contents. In C#, properties is the usual term here because the corresponding language feature class properties are used for the implementation.

Which properties become part of a class is largely defined in the normalization process. In some cases, intentional redundancies can also be part of an entity, e.g. if they are technically or legally necessary (e.g. often the case with invoices).

The amount of properties can therefore also result from the normalization process, whereby it should be noted that there are different recommendations for the maximum number of columns (=properties) depending on the database type. If an entity is to be used with different databases, it is advisable to stay below the lowest maximum column recommendation. Most database engines have a general recommendation for the maximum number of columns, but also a technical limit.

C#
 1public class UserEntity
 2{
 3    [Key] 
 4    public UserId Id { get; set; }
 5
 6    public string UserName {get;set;}
 7
 8    public string EMail {get;set;}
 9
10    public DateOnly DateOfBirth { get;set; }
11
12    public string TwitterHandle { get;set; }
13
14    public string LinkedInUrl { get;set; }
15
16    public string YouTubeUrl { get;set; }
17}

Navigations

Navigations are the relations to other entities. These also result from the normalization process and help to ensure that data is not stored redundantly if this is not explicitly intended. Relations are represented in the code by collections and, in the case of explicit notation, which is recommended, by the keys - at database level they are simply columns with corresponding values and an index to the other table. They result in their own tables and therefore also their own entities.

C#
 1public class UserEntity
 2{
 3    [Key] 
 4    public UserId Id { get; set; }
 5
 6    public string UserName {get;set;}
 7
 8    public string EMail {get;set;}
 9
10        // Navigation
11    public UserProfileEntity Profile {get;set;}
12}
13
14public class UserProfileEntity
15
16    // Navigation
17    public UserEntity User { get;set; }
18    [Key] 
19    public UserId Id { get; set; } // in this case its FK and PK at the same time
20
21    public DateOnly? DateOfBirth { get;set; }
22
23    public string? TwitterHandle { get;set; }
24
25    public string? LinkedInUrl { get;set; }
26
27    public string? YouTubeUrl { get;set; }
28}

Proxy-Entites

Proxy entities are entities that are implemented purely for technical reasons and have no data themselves. The idea is to distribute the write load across several tables so that read operations are not blocked.
This very often relieves the write and thus also the lock situations of a database and results in massively higher read performance.

C#
 1public class UserEntity // Our Proxy Entity
 2{
 3    [Key] 
 4    public UserId Id { get; set; }
 5
 6    // Navigation
 7    public UserProfileEntity Profile {get;set;}
 8    public UserCredentialsEntity Credentials {get;set;}
 9}
10
11public class UserCredentialsEntity
12{
13    [Key] 
14    public UserId Id { get; set; } // in this case its FK and PK at the same time
15
16    // Navigation
17    public UserEntity User { get;set; }
18
19    // Data
20
21    public string UserName {get;set;}
22
23    public string EMail {get;set;}
24}
25
26public class UserProfileEntity
27
28    [Key] 
29    public UserId Id { get; set; } // in this case its FK and PK at the same time
30
31    // Navigation
32    public UserEntity User { get;set; }
33
34    // Data
35
36    public DateOnly? DateOfBirth { get;set; }
37
38    public string? TwitterHandle { get;set; }
39
40    public string? LinkedInUrl { get;set; }
41
42    public string? YouTubeUrl { get;set; }
43}

Annotations and Configurations

Annotations are characteristics of properties, e.g. whether they are mandatory fields or have a certain length. In the case of entity frameworks, this can be implemented in two ways

Annotations

Annotations are attributes that are set directly on the properties. This is the simplest and fastest option, but it also involves the greatest dependency on the entity framework.

C#
 1public class UserProfileEntity
 2
 3    [Key] 
 4    public UserId Id { get; set; } // in this case its FK and PK at the same time
 5
 6    // Navigation
 7    public UserEntity User { get;set; }
 8
 9    // Data
10    
11    [Required(false)]
12    public DateOnly? DateOfBirth { get;set; }
13    
14    [Unicode(true)]
15    [Required(false)]
16    [MaxLength(50)]
17    public string? TwitterHandle { get;set; }
18    
19    [Unicode(true)]
20    [Required(false)]
21    [MaxLength(250)]
22    public string? LinkedInUrl { get;set; }
23
24    [Unicode(false)]
25    [Required(false)]
26    [MaxLength(100)]
27    public string? YouTubeUrl { get;set; }
28}

As the annotations are implemented directly in the entity, they are also valid for all databases in which the entity is used.

Configurations

Configurations ultimately do the same thing, but since the restrictions are declared outside the entity, they can be implemented differently for each supported database. This makes it possible to maximize the use of different database engine support.

C#
 1public class UserProfileEntitySqlServerConfig
 2    : IEntityTypeConfiguration<UserProfileEntity> // SQL Server
 3{
 4    public void Configure(EntityTypeBuilder<UserProfileEntity> b)
 5    {
 6        b.ToTable("UserProfiles");
 7
 8        b.HasKey(e => e.Id);
 9
10        b.Property(e => e.TwitterHandle)
11            .IsUnicode(true).IsRequired(false).HasMaxLength(50);
12
13        b.Property(e => e.LinkedInUrl)
14            .IsUnicode(true).IsRequired(false).HasMaxLength(250);
15
16        b.Property(e => e.YouTubeUrl)
17            .IsUnicode(false).IsRequired(false).HasMaxLength(100);
18    }
19}
20
21public class UserProfileEntityPostgreServerConfig
22    : IEntityTypeConfiguration<UserProfileEntity> // Postgre Server
23{
24    public void Configure(EntityTypeBuilder<UserProfileEntity> b)
25    {
26        b.ToTable("UserProfiles");
27
28        b.HasKey(e => e.Id);
29
30        b.Property(e => e.TwitterHandle)
31            .IsUnicode(true).IsRequired(false).HasMaxLength(50);
32
33        b.Property(e => e.LinkedInUrl)
34            .IsUnicode(true).IsRequired(false).HasMaxLength(250);
35
36        b.Property(e => e.YouTubeUrl)
37            .IsUnicode(false).IsRequired(false).HasMaxLength(100);
38    }
39}

Another advantage of this variant is that it is much easier to implement your own extension methods and therefore reuse them in different places than with the attribute variant.

C#
 1public class UserProfileEntitySqlServerConfig
 2: IEntityTypeConfiguration<UserProfileEntity> // SQL Server
 3{
 4    public void Configure(EntityTypeBuilder<UserProfileEntity> b)
 5    {
 6        b.ToTable("UserProfiles");
 7
 8        b.HasKey(e => e.Id);
 9
10        b.Property(e => e.TwitterHandle).IsSocialMediaColumn(isUnicode: true, 50);
11        b.Property(e => e.LinkedInUrl).IsSocialMediaColumn(isUnicode: true, 250);
12        b.Property(e => e.YouTubeUrl).IsSocialMediaColumn(isUnicode: false, 100);
13    }
14}

Personally, I very rarely have the case that I have different values for constraints, which is why I generally prefer a mixture of both variants in all projects in order to use the maximum of both possibilities. This means that structural definitions (table, keys, index) are implemented as IEntityTypeConfiguration and all content definitions (lengths, Unicode, requirements…) are attached to the entity as attributes. Therefore, this implementation is the one that I use myself, as well as the one that I recommend in my projects and to my customers.

Conclusion

A well-structured entity therefore has the following characteristics:

  • A Strong-Id primary key
  • Unique data in the form of properties
  • Attributes that describe the properties and use them efficiently (e.g. deactivate Unicode if not desired)
  • Navigations to other entities incl. Strong-Id of the foreign keys
  • Use of proxy entities in appropriate places to save performance
  • Specific and optimized configurations for the database, including indexes for more efficient queries

Related articles

Advanced Usage of MSSQL Indexed Views in EF Core

Jan 13, 2025 - 5 min read

Advanced Usage of MSSQL Indexed Views in EF Core

In the world of Microsoft SQL Server , views are a powerful tool for simplifying complex data queries. Simple views offer no real …

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 …


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