VOOZH about

URL: https://codewithmukesh.com/blog/fluent-api-entity-configuration-efcore/

⇱ Configuring Entities with Fluent API in EF Core 10 - Best Practices - codewithmukesh


Skip to main content
Article complete

Get one like this every Tuesday at 7 PM IST.

Back to blog
dotnet webapi-course 15 min read Lesson 18/128

Configuring Entities with Fluent API in EF Core 10 - Best Practices

Entity configuration is where your domain meets the database. Learn Fluent API in EF Core 10 - why it beats Data Annotations for complex scenarios, how to organize configurations with IEntityTypeConfiguration, configure complex relationships, and avoid common mistakes.

Entity configuration is where your domain meets the database. Learn Fluent API in EF Core 10 - why it beats Data Annotations for complex scenarios, how to organize configurations with IEntityTypeConfiguration, configure complex relationships, and avoid common mistakes.

dotnet webapi-course

efcore fluent-api entity-configuration ef-core-10 dotnet-10 ientitytypeconfiguration data-annotations decimal-precision value-conversions owned-types global-query-filters many-to-many self-referencing composite-keys cascade-delete indexing webapi aspnet-core domain-driven-design dotnet-webapi-zero-to-hero-course

👁 Mukesh Murugan
Mukesh Murugan
Software Engineer
Chapter · 18 of 128 Module 2 of 12 Free
View course

.NET Web API Zero to Hero Course

From dotnet new to docker push — REST, EF Core 10, auth, caching, Clean Architecture, observability. 128 hands-on lessons, source on GitHub.

You’ve got your EF Core CRUD operations working - entities saved, queries run, and migrations apply without errors. But as your domain grows more complex, you start hitting walls: How do I configure a many-to-many relationship that needs extra data? Where should I put all these configuration rules? Why does my decimal column keep truncating values? And why do some developers swear by Fluent API while others stick with Data Annotations?

If you’ve ever found yourself fighting with EF Core’s conventions or staring at a 500-line OnModelCreating method wondering how it got this bad, this article is for you.

We’ll go deep into entity configuration best practices - the patterns that keep your codebase clean, your database schema correct, and your future self grateful. By the end, you’ll know exactly when to use Fluent API vs Data Annotations, how to organize configurations at scale, and how to handle the complex relationship scenarios that tutorials usually skip.

No repository for this one - just code snippets you can adapt to your own projects. Let’s get into it.

TL;DR. In EF Core 10, configure entities with the Fluent API via IEntityTypeConfiguration<T> classes registered with modelBuilder.ApplyConfigurationsFromAssembly(...). Use Fluent API (not Data Annotations) for: decimal precision, indexes, cascade behavior, composite keys, value conversions, owned types, and any non-trivial relationship. Data Annotations are fine for simple [Required] and [MaxLength] on small models. Default to one configuration class per entity in a Persistence/Configurations/ folder. The mistake most teams make is configuring both ends of a relationship - configure each relationship from exactly one side.

Read nextCompanion article

ASP.NET Core Web API CRUD with Entity Framework Core

If you're new to EF Core, start here. This article covers setting up EF Core, creating entities, and building a complete CRUD API.

Why Entity Configuration Matters

Your C# classes represent business concepts - a Product has a name, price, and belongs to a category. The database has different concerns: storage efficiency, indexing for fast queries, constraints to maintain data integrity, and specific data types for each column.

EF Core’s conventions bridge this gap automatically. It figures out that ProductId is likely a primary key, that Category navigation property implies a foreign key, and that string properties map to nvarchar(max). These conventions handle about 80% of cases.

But that remaining 20%? That’s where configuration comes in. Maybe you need:

  • Specific decimal precision for money fields (default precision might truncate $199.999 to $199.99)
  • Indexes on columns you frequently filter by
  • Cascade delete behavior that doesn’t accidentally wipe out your entire category tree
  • Composite keys for join tables with additional data

Three Ways to Configure Entities

EF Core offers three approaches, each with different trade-offs:

ApproachWhere It LivesBest For
ConventionsAutomaticDefault behavior, simple scenarios
Data AnnotationsAttributes on entity propertiesQuick constraints, validation integration
Fluent APICode in OnModelCreating or config classesComplex relationships, full control

Conventions are implicit - EF Core applies them automatically. Data Annotations are attributes you place directly on your entity properties. Fluent API is code you write to explicitly configure the model.

The key insight: Fluent API has the highest precedence. If you configure the same property with both annotations and Fluent API, Fluent API wins. Always.

Data Annotations vs Fluent API - When to Use Each

Let’s be practical about this. Both approaches work, and choosing between them isn’t about which is “better” - it’s about which fits your situation.

Data Annotations - The Quick Win

Data Annotations are attributes you place directly on properties. They’re visible, self-documenting, and work with ASP.NET Core’s model validation.

publicclassProduct
{
publicGuidId { get; set; }
[Required]
[MaxLength(200)]
publicstringName { get; set; } =string.Empty;
[Column(TypeName="decimal(18,2)")]
publicdecimalPrice { get; set; }
[Required]
publicGuidCategoryId { get; set; }
publicCategory? Category { get; set; }
}

The benefit here is visibility. Anyone reading this class immediately understands: Name is required and maxes out at 200 characters, Price uses specific decimal precision, and CategoryId is required.

Data Annotations also integrate with validation. If you use ASP.NET Core’s model validation or FluentValidation, these attributes can serve double duty - configuring EF Core AND validating incoming requests.

Fluent API - The Power Tool

Fluent API moves configuration out of your entities and into dedicated code. Here’s the same configuration:

publicvoidConfigure(EntityTypeBuilder<Product> builder)
{
builder.Property(p=>p.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(p=>p.Price)
.HasPrecision(18, 2);
builder.HasOne(p=>p.Category)
.WithMany(c=>c.Products)
.HasForeignKey(p=>p.CategoryId)
.OnDelete(DeleteBehavior.Restrict);
}

Fluent API is required for certain scenarios that annotations simply can’t express:

  • Composite primary keys
  • Many-to-many relationships with payload data
  • Owned entities and value objects
  • Shadow properties
  • Global query filters

It also keeps your domain models clean of persistence concerns. If you’re following Domain-Driven Design or want entities that could theoretically work with a different ORM, Fluent API keeps EF Core specifics out of your domain layer.

The Trade-off Matrix

Here’s my recommendation for common scenarios:

ScenarioRecommended ApproachWhy
Simple constraints (Required, MaxLength)EitherAnnotations are visible; Fluent API keeps models clean
Complex relationshipsFluent APIAnnotations can’t express all relationship types
Composite keysFluent APINo annotation support
Decimal precisionFluent APIHasPrecision(18, 2) is cleaner than [Column(TypeName = "...")]
DDD domain modelsFluent APIKeep persistence out of the domain
Quick prototypingData AnnotationsLess boilerplate to write

Mixing Both Approaches

Can you use both? Yes. Should you? With caution.

A common pattern is using Data Annotations for simple validation rules (that also serve API validation) and Fluent API for relationships and persistence-specific settings. This works, but remember the precedence rule: Fluent API always overrides Data Annotations.

// Entity with annotation
[MaxLength(100)]
publicstringName { get; set; }
// Fluent API overrides it
builder.Property(p=>p.Name).HasMaxLength(200); // This wins - 200, not 100

The danger is confusion. Six months from now, someone sees [MaxLength(100)] on the property and assumes that’s the limit. But it’s actually 200 because of Fluent API. Pick one approach for each concern and stick to it.

The IEntityTypeConfiguration Pattern

Here’s a pattern I’ve seen in countless real-world projects: a DbContext with an OnModelCreating method that spans 400+ lines, configuring every entity in one massive method. It starts small, but every new feature adds more configuration until it becomes unmaintainable.

// The anti-pattern - don't do this
protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder)
{
// 50 lines for Product
modelBuilder.Entity<Product>(builder=> {
builder.HasKey(p=>p.Id);
builder.Property(p=>p.Name).IsRequired().HasMaxLength(200);
// ... 45 more lines
});
// 50 lines for Category
modelBuilder.Entity<Category>(builder=> {
// ... another 50 lines
});
// 50 lines for Order
// 50 lines for OrderItem
// 50 lines for Customer
// ... imagine 15 more entities
}

This violates the Single Responsibility Principle. It’s hard to find configuration for a specific entity, merge conflicts are constant, and testing is a nightmare.

The Solution: IEntityTypeConfiguration<T>

EF Core provides IEntityTypeConfiguration<T>, an interface for separating configuration into dedicated classes:

publicclassProductConfiguration : IEntityTypeConfiguration<Product>
{
publicvoidConfigure(EntityTypeBuilder<Product> builder)
{
builder.ToTable("Products", "catalog");
builder.HasKey(p=>p.Id);
builder.Property(p=>p.Name)
.IsRequired()
.HasMaxLength(200);
builder.Property(p=>p.Price)
.HasPrecision(18, 2);
builder.Property(p=>p.Sku)
.IsRequired()
.HasMaxLength(50);
builder.HasIndex(p=>p.Sku)
.IsUnique();
builder.HasIndex(p=>p.Name);
builder.HasOne(p=>p.Category)
.WithMany(c=>c.Products)
.HasForeignKey(p=>p.CategoryId)
.OnDelete(DeleteBehavior.Restrict);
}
}

Each entity gets its own configuration file. The configuration is focused, testable, and easy to find.

Automatic Registration with ApplyConfigurationsFromAssembly

You don’t need to manually register each configuration class. EF Core 2.2+ provides a one-liner that scans an assembly for all IEntityTypeConfiguration<T> implementations:

publicclassAppDbContext : DbContext
{
publicDbSet<Product> Products=>Set<Product>();
publicDbSet<Category> Categories=>Set<Category>();
publicDbSet<Order> Orders=>Set<Order>();
protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder)
{
modelBuilder.ApplyConfigurationsFromAssembly(typeof(AppDbContext).Assembly);
base.OnModelCreating(modelBuilder);
}
}

That’s it. Add a new entity and its configuration class - no DbContext changes needed. The assembly scan finds it automatically.

Recommended Folder Structure

For projects with more than a handful of entities, organize configurations in a dedicated folder:

📁 Persistence/
📁 Configurations/
📄 ProductConfiguration.cs
📄 CategoryConfiguration.cs
📄 OrderConfiguration.cs
📄 OrderItemConfiguration.cs
📄 CustomerConfiguration.cs
📄 AppDbContext.cs

Why this structure?

  • Discoverability: Need to check Product’s configuration? It’s in ProductConfiguration.cs
  • Merge conflicts: Each entity has its own file - parallel development rarely conflicts
  • IDE navigation: “Go to definition” and search work naturally

For larger projects with multiple bounded contexts, you might add another level:

📁 Persistence/
📁 Configurations/
📁 Catalog/
📄 ProductConfiguration.cs
📄 CategoryConfiguration.cs
📁 Sales/
📄 OrderConfiguration.cs
📄 OrderItemConfiguration.cs

Configuring Complex Relationships

Basic one-to-many relationships work automatically with EF Core conventions. But real applications have messier requirements. Let’s tackle the scenarios that trip people up.

Many-to-Many with Payload

The classic many-to-many example is StudentCourse. EF Core 5+ handles simple many-to-many automatically. But what if you need additional data on the relationship itself?

Consider orders and products. An order contains products, but you also need:

  • Quantity - How many of this product?
  • UnitPrice - The price at the time of order (not the current product price)
  • Discount - Any discount applied to this line item

This requires a join entity - a class that represents the relationship and holds the payload data:

publicclassOrderItem
{
publicGuidOrderId { get; privateset; }
publicOrderOrder { get; privateset; } =null!;
publicGuidProductId { get; privateset; }
publicProductProduct { get; privateset; } =null!;
publicintQuantity { get; privateset; }
publicdecimalUnitPrice { get; privateset; }
publicdecimalDiscount { get; privateset; }
publicdecimalLineTotal=> (UnitPrice*Quantity) -Discount;
}

Now configure it:

publicclassOrderItemConfiguration : IEntityTypeConfiguration<OrderItem>
{
publicvoidConfigure(EntityTypeBuilder<OrderItem> builder)
{
builder.ToTable("OrderItems");
// Composite primary key
builder.HasKey(oi=>new { oi.OrderId, oi.ProductId });
builder.Property(oi=>oi.Quantity)
.IsRequired();
builder.Property(oi=>oi.UnitPrice)
.HasPrecision(18, 2);
builder.Property(oi=>oi.Discount)
.HasPrecision(18, 2);
// Relationship to Order - cascade delete (delete order = delete items)
builder.HasOne(oi=>oi.Order)
.WithMany(o=>o.Items)
.HasForeignKey(oi=>oi.OrderId)
.OnDelete(DeleteBehavior.Cascade);
// Relationship to Product - restrict (can't delete product with orders)
builder.HasOne(oi=>oi.Product)
.WithMany()
.HasForeignKey(oi=>oi.ProductId)
.OnDelete(DeleteBehavior.Restrict);
}
}

The composite key new { oi.OrderId, oi.ProductId } ensures each product appears only once per order. The delete behaviors are intentionally different: deleting an order removes its items, but deleting a product that’s been ordered should fail.

Self-Referencing Relationships

Categories often have hierarchies - Electronics contains Computers, which contains Laptops. This is a self-referencing relationship:

publicclassCategory
{
publicGuidId { get; privateset; }
publicstringName { get; privateset; } =string.Empty;
// Parent reference (null for root categories)
publicGuid? ParentCategoryId { get; privateset; }
publicCategory? ParentCategory { get; privateset; }
// Children collection
publicICollection<Category> SubCategories { get; privateset; } =newList<Category>();
// Products in this category
publicICollection<Product> Products { get; privateset; } =newList<Product>();
}

The ParentCategoryId is nullable - root categories like “Electronics” have no parent. Here’s the configuration:

publicclassCategoryConfiguration : IEntityTypeConfiguration<Category>
{
publicvoidConfigure(EntityTypeBuilder<Category> builder)
{
builder.ToTable("Categories", "catalog");
builder.HasKey(c=>c.Id);
builder.Property(c=>c.Name)
.IsRequired()
.HasMaxLength(100);
// Self-referencing relationship
builder.HasOne(c=>c.ParentCategory)
.WithMany(c=>c.SubCategories)
.HasForeignKey(c=>c.ParentCategoryId)
.OnDelete(DeleteBehavior.Restrict);
builder.HasIndex(c=>c.ParentCategoryId);
builder.HasIndex(c=>c.Name);
}
}

Warning: Never use DeleteBehavior.Cascade on self-referencing relationships. Deleting “Electronics” would cascade to “Computers,” which cascades to “Laptops,” potentially wiping out your entire category tree. Use Restrict and handle hierarchy deletion explicitly in your business logic.

Composite Keys

We saw composite keys in the OrderItem example. Here’s the key insight: composite keys don’t work with Find() the way single keys do.

// Single key - works great
varproduct=awaitdbContext.Products.FindAsync(productId);
// Composite key - must provide all key parts in order
varorderItem=awaitdbContext.OrderItems.FindAsync(orderId, productId);

The FindAsync parameter order must match the order you defined the key. Alternatively, use a query:

varorderItem=awaitdbContext.OrderItems
.FirstOrDefaultAsync(oi=>oi.OrderId==orderId&&oi.ProductId==productId);
Free resource · Companion download

Interview Questions PDF

100 real interview questions across 9 categories, junior to senior

Advanced Configuration Techniques

Let’s cover a few techniques that aren’t strictly about relationships but come up frequently.

Value Conversions

Got an enum you want stored as a string instead of an integer? Value conversions handle this:

publicenumOrderStatus
{
Pending,
Processing,
Shipped,
Delivered,
Cancelled
}
// In configuration
builder.Property(o=>o.Status)
.HasConversion<string>()
.HasMaxLength(50);

Now the database column contains "Pending", "Shipped", etc., instead of 0, 2. This makes the database more readable and survives enum reordering.

Owned Types for Value Objects

If you’re following DDD, you might have value objects like Address or Money. Owned types let you map these without creating separate tables:

publicclassAddress
{
publicstringStreet { get; privateset; } =string.Empty;
publicstringCity { get; privateset; } =string.Empty;
publicstringPostalCode { get; privateset; } =string.Empty;
publicstringCountry { get; privateset; } =string.Empty;
}
publicclassCustomer
{
publicGuidId { get; privateset; }
publicstringName { get; privateset; } =string.Empty;
publicAddressShippingAddress { get; privateset; } =null!;
publicAddressBillingAddress { get; privateset; } =null!;
}

Configure the owned type:

builder.OwnsOne(c=>c.ShippingAddress, address=>
{
address.Property(a=>a.Street).HasMaxLength(200).HasColumnName("ShippingStreet");
address.Property(a=>a.City).HasMaxLength(100).HasColumnName("ShippingCity");
address.Property(a=>a.PostalCode).HasMaxLength(20).HasColumnName("ShippingPostalCode");
address.Property(a=>a.Country).HasMaxLength(100).HasColumnName("ShippingCountry");
});
builder.OwnsOne(c=>c.BillingAddress, address=>
{
address.Property(a=>a.Street).HasMaxLength(200).HasColumnName("BillingStreet");
address.Property(a=>a.City).HasMaxLength(100).HasColumnName("BillingCity");
address.Property(a=>a.PostalCode).HasMaxLength(20).HasColumnName("BillingPostalCode");
address.Property(a=>a.Country).HasMaxLength(100).HasColumnName("BillingCountry");
});

The Customer table gets columns like ShippingStreet, ShippingCity, BillingStreet, etc. No separate Address table.

Global Query Filters

Implementing soft delete? Global query filters automatically exclude deleted records from all queries:

publicinterfaceISoftDelete
{
boolIsDeleted { get; }
DateTime? DeletedAt { get; }
}
// In configuration
builder.HasQueryFilter(p=>!p.IsDeleted);

Now every query against Products automatically adds WHERE IsDeleted = 0. To include deleted records, use IgnoreQueryFilters():

varallProducts=awaitdbContext.Products
.IgnoreQueryFilters()
.ToListAsync();

Common Mistakes and How to Avoid Them

I’ve reviewed a lot of EF Core code over the years. Here are the mistakes I see most often.

Mistake 1: Configuring Both Ends of a Relationship

This causes confusion and sometimes conflicts:

// In ProductConfiguration
builder.HasOne(p=>p.Category)
.WithMany(c=>c.Products);
// In CategoryConfiguration - DON'T DO THIS
builder.HasMany(c=>c.Products)
.WithOne(p=>p.Category);

Both configurations describe the same relationship. Configure it once, from one side. EF Core figures out the other side automatically.

Mistake 2: Forgetting OnDelete Behavior

EF Core defaults to Cascade for required relationships. This might not be what you want:

// Default cascade - deleting category deletes all its products!
builder.HasOne(p=>p.Category)
.WithMany(c=>c.Products)
.HasForeignKey(p=>p.CategoryId);
// Explicit restrict - can't delete category with products
builder.HasOne(p=>p.Category)
.WithMany(c=>c.Products)
.HasForeignKey(p=>p.CategoryId)
.OnDelete(DeleteBehavior.Restrict);

Always be explicit about delete behavior. Restrict is usually safer for business entities.

Mistake 3: Not Using HasPrecision for Decimals

Default decimal precision varies by database. SQL Server uses decimal(18,2) by default, but relying on this is risky:

// Always specify precision for money/decimal fields
builder.Property(p=>p.Price)
.HasPrecision(18, 2);
builder.Property(p=>p.TaxRate)
.HasPrecision(5, 4); // e.g., 0.0825 for 8.25%

Mistake 4: Inconsistent Configuration Approaches

Some entities use annotations, others use Fluent API, and nobody knows which is authoritative:

// Product uses annotations
[MaxLength(200)]
publicstringName { get; set; }
// Category uses Fluent API
builder.Property(c=>c.Name).HasMaxLength(100);
// OrderItem uses... both? Maybe?

Pick a strategy. My recommendation: IEntityTypeConfiguration for everything. It’s consistent, keeps entities clean, and all configuration lives in predictable locations.

Mistake 5: Missing Indexes on Foreign Keys

Foreign keys are frequently used in queries. Always index them:

builder.HasIndex(p=>p.CategoryId);
builder.HasIndex(o=>o.CustomerId);
builder.HasIndex(oi=>oi.OrderId);

Also add indexes for columns you frequently filter or sort by:

builder.HasIndex(p=>p.Name);
builder.HasIndex(o=>o.OrderDate);
builder.HasIndex(p=>new { p.CategoryId, p.IsActive }); // Composite index

Mistake 6: Not Reviewing Generated Migrations

Always review what EF Core generates before applying migrations:

Terminal window
dotnetefmigrationsaddAddProductIndex
dotnetefmigrationsscript# See the actual SQL

I’ve seen migrations that accidentally drop columns, change data types in breaking ways, or create indexes that tank performance. Trust, but verify.

Configuration Precedence Rules

Quick reference for when configurations conflict:

  1. Fluent API - Highest priority, always wins
  2. Data Annotations - Applied if Fluent API doesn’t configure that aspect
  3. Conventions - Default behavior, lowest priority

Example:

// Convention: string = nvarchar(max)
// Data Annotation: MaxLength(100)
[MaxLength(100)]
publicstringName { get; set; }
// Fluent API: MaxLength(200) - THIS WINS
builder.Property(p=>p.Name).HasMaxLength(200);

The final column is nvarchar(200), not 100, not max.

Best Practices Checklist

Before we wrap up, here’s a checklist you can reference for your projects:

  1. Use IEntityTypeConfiguration<T> for all entity configurations
  2. Register with ApplyConfigurationsFromAssembly - no manual registration needed
  3. Keep configurations in a dedicated folder - Persistence/Configurations/
  4. Use Fluent API for relationships - annotations can’t express everything
  5. Always specify decimal precision - HasPrecision(18, 2) for money fields
  6. Add indexes for foreign keys - and frequently filtered columns
  7. Be explicit about OnDelete behavior - don’t rely on cascade defaults
  8. Configure relationships from one side only - avoid duplicate configuration
  9. Review generated migrations - always check the SQL before applying

Key Takeaways

  • Default to Fluent API via IEntityTypeConfiguration<T> - one configuration class per entity, registered automatically with modelBuilder.ApplyConfigurationsFromAssembly(typeof(YourDbContext).Assembly). This is the recommended pattern in EF Core 10.
  • Use Data Annotations only for simple validation - [Required], [MaxLength], [StringLength]. Anything beyond that goes in Fluent API.
  • Always specify HasPrecision(18, 2) on decimal/money columns - the default precision varies by provider and silently truncates values like $199.999.
  • Configure each relationship from exactly one side. Configuring both ends causes EF Core to throw at startup or - worse - silently use the wrong configuration.
  • Be explicit about OnDelete behavior for every relationship. The defaults are provider-specific and have wiped out production data more than once. Set DeleteBehavior.Restrict or DeleteBehavior.Cascade deliberately.

Frequently Asked Questions

Frequently asked08 questions

Related EF Core Articles

Read nextCompanion article

EF Core Relationships - One-to-One, One-to-Many, Many-to-Many

Most non-trivial Fluent API configuration centers on relationships. This guide covers all three relationship types with real-world e-commerce examples, cascade behavior, and the mistakes that wipe out production data.

Read nextCompanion article

Global Query Filters in EF Core 10

One of the most powerful Fluent API features. Configure soft delete and multi-tenancy once and have EF Core apply them to every query automatically. Includes the new EF Core 10 named-filter syntax.

Read nextCompanion article

Optimistic Concurrency in EF Core 10

The right way to configure RowVersion as a concurrency token using Fluent API - including the IsRowVersion vs IsConcurrencyToken decision and the retry-loop gotcha that infinite-loops in production.

Read nextCompanion article

Soft Deletes in EF Core 10

A complete soft-delete implementation using SaveChangesInterceptor and global query filters - the cleanest pattern when you need recoverable deletes with cascade behavior.

Read nextCompanion article

Multiple DbContext in EF Core 10

When and how to split into multiple DbContexts - bounded contexts, read replicas, and multi-database scenarios. Each context needs its own Fluent API configuration.

Read nextCompanion article

Bulk Operations in EF Core 10

ExecuteUpdate and ExecuteDelete bypass the change tracker, interceptors, and global query filters entirely. Plan your Fluent API configurations with this in mind.

Read nextCompanion article

Tracking vs No-Tracking Queries in EF Core

Real benchmarks showing AsNoTracking is 2.9-5.2x faster on read queries with ~50% less allocation. Use AsNoTracking on every read endpoint that doesn't modify entities.

Summary

Entity configuration is the bridge between your domain models and the database. Get it right, and your schema evolves cleanly as your application grows. Get it wrong, and you’ll spend hours debugging relationship issues, cascade delete disasters, or truncated decimal values.

Take the time to set up proper configuration classes early - your future self will thank you.

What patterns do you use for entity configuration in your projects? Let me know in the comments below.

Happy Coding :)

More from the archive.

View all articles

What's your take?

Push back, share a war story, or ask the obvious question someone else is wondering. I read every comment.

View on GitHub

Weekly .NET tips · free

Subscribed · Tue 7 PM IST

You're in.
Welcome to the crew.

Tuesday's issue lands in your inbox at 7 PM IST. One last step: confirm your email so it actually arrives.

01 · Check your inbox

02 · Every Tuesday

Benchmarks, architecture insights, and production tips that never make it to the blog.

Privacy notice · 30s read

Cookies, but only the useful ones.

I use cookies to understand which articles get read and which CTAs actually work. No third-party advertising trackers, ever. Read the privacy policy →