DEV Community

Cristian Sifuentes
Cristian Sifuentes

Posted on

1 1

Fluent API Mastery with EF Core — Building Clean Architectures without Data Annotations

Fluent API Mastery with EF Core — Building Clean Architectures without Data Annotations

Fluent API Mastery with EF Core — Building Clean Architectures without Data Annotations

In modern .NET backend development, the debate between Data Annotations and Fluent API continues. As projects grow, Fluent API becomes the professional’s choice for clarity, separation of concerns, and long-term maintainability.

In this article, we’ll build a real Web API using Entity Framework Core with Fluent API only (no mixed data annotations). You'll learn:

  • Why Fluent API is preferred in scalable projects
  • How to configure your DbContext
  • Best practices, pros & cons, and anti-patterns
  • Complete SQL Server + Minimal API example with navigation and enums

Real Project Example: Fluent API for Jobs + Categories

Let’s build an API to manage job categories using DbContext, relationships, and Fluent API.

Models: Category & Job (NO Data Annotations)

public class Category
{
    public Guid CategoryId { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public virtual ICollection<Job> Jobs { get; set; }
}

public class Job
{
    public Guid JobId { get; set; }
    public Guid CategoryId { get; set; }
    public string Title { get; set; }
    public string Description { get; set; }
    public Priority Priority { get; set; }
    public DateTime CreationDate { get; set; }
    public virtual Category Category { get; set; }
    public string Resume { get; set; }
}

public enum Priority
{
    High,
    Medium,
    Low
}
Enter fullscreen mode Exit fullscreen mode

Why Avoid Mixing Data Annotations + Fluent API?

Concern Reason
Conflict Fluent rules override annotations, causing confusion
DRY Violation Logic duplicated in both annotations and model builder
Testability Fluent API config is centralized and testable
Readability Fluent config gives a single source of truth

Fluent API in DbContext

public class JobsDbContext : DbContext
{
    public DbSet<Category> categories { get; set; }
    public DbSet<Job> jobs { get; set; }

    public JobsDbContext(DbContextOptions options) : base(options) {}

    protected override void OnModelCreating(ModelBuilder builder)
    {
        builder.Entity<Category>(category =>
        {
            category.ToTable("Category");
            category.HasKey(c => c.CategoryId);
            category.Property(c => c.Name).IsRequired().HasMaxLength(150);
            category.Property(c => c.Description).IsRequired().HasMaxLength(150);
        });

        builder.Entity<Job>(job =>
        {
            job.ToTable("Job");
            job.HasKey(c => c.JobId);
            job.HasOne(c => c.Category)
                .WithMany(p => p.Jobs)
                .HasForeignKey(p => p.CategoryId);

            job.Property(c => c.Title).IsRequired().HasMaxLength(200);
            job.Property(c => c.Description);
            job.Property(c => c.Priority);
            job.Property(c => c.CreationDate);
            job.Ignore(c => c.Resume); // NotMapped equivalent
        });
    }
}
Enter fullscreen mode Exit fullscreen mode

Minimal API Setup with SQL Server

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSqlServer<JobsDbContext>(
    builder.Configuration.GetConnectionString("sql"));

var app = builder.Build();

app.MapGet("/", () => "Hello World!");

app.MapGet("/dbconextion", async ([FromServices] JobsDbContext db) =>
{
    db.Database.EnsureCreated();
    return Results.Ok("SQL db created: " + db.Database.IsSqlServer());
});

app.Run();
Enter fullscreen mode Exit fullscreen mode

✅ Fluent API Advantages

Benefit Why It Matters
Centralized config All rules in one place (OnModelCreating)
Scales for large domains Keeps models clean
More expressive Fluent handles indexes, sequences, keys, constraints
Runtime testable Can be validated or tested independently
Decouples model Your POCOs stay truly POCO

❌ Fluent API Disadvantages

Limitation Workaround
Slightly more verbose Templates or partials
Learning curve Use code generators and IntelliSense
No UI hints (like [Required]) Use validation libraries (e.g. FluentValidation)

Best Practices

  • Avoid combining annotations + fluent (choose one)
  • Split Fluent config per entity using IEntityTypeConfiguration<T>
  • Always use HasKey, HasMaxLength, HasOne, WithMany
  • Document ignored properties like .Ignore(...)
  • Apply migrations and seed via Fluent config

Final Thoughts

Fluent API offers the ultimate control and scalability for modern .NET backend projects. It's not just about preferences — it's about writing architecture that grows with your product.

By adopting Fluent API only, you’ll ensure consistency, testability, and separation of concerns in every domain.


✍️ Written by: Cristian Sifuentes – Full-stack dev crafting scalable apps with [NET - Azure], [Angular - React], Git, SQL & extensions. Clean code, dark themes, atomic commits

💬 Do you mix data annotations and Fluent API? Or go clean-only? Let's discuss how you handle this in production!

Warp.dev image

The best coding agent. Backed by benchmarks.

Warp outperforms every other coding agent on the market, and gives you full control over which model you use. Get started now for free, or upgrade and unlock 2.5x AI credits on Warp's paid plans.

Download Warp

Top comments (0)

Feature flag article image

Create a feature flag in your IDE in 5 minutes with LaunchDarkly’s MCP server ⏰

How to create, evaluate, and modify flags from within your IDE or AI client using natural language with LaunchDarkly's new MCP server. Follow along with this tutorial for step by step instructions.

Read full post

👋 Kindness is contagious

Delve into a trove of insights in this thoughtful post, celebrated by the welcoming DEV Community. Programmers of every stripe are invited to share their viewpoints and enrich our collective expertise.

A simple “thank you” can brighten someone’s day—drop yours in the comments below!

On DEV, exchanging knowledge lightens our path and forges deeper connections. Found this valuable? A quick note of gratitude to the author can make all the difference.

Get Started