Md Mominul Islam | Software and Data Enginnering | SQL Server, .NET, Power BI, Azure Blog

while(!(succeed=try()));

LinkedIn Portfolio Banner

Latest

Home Top Ad

Responsive Ads Here

Post Top Ad

Responsive Ads Here

Wednesday, September 3, 2025

Mastering Entity Framework Core: Tips & Tricks

 

Introduction: Why Entity Framework Core?

Entity Framework Core (EF Core) is a powerful Object-Relational Mapping (ORM) framework for .NET developers, enabling seamless interaction between C# code and databases. Whether you're building a small application or a complex enterprise system, EF Core simplifies data access while offering flexibility for advanced scenarios. This comprehensive tutorial dives into mastering EF Core, covering performance optimization, efficient query writing, and complex data modeling, with real-world examples, best practices, pros, cons, and alternatives.

By the end of this blog, you’ll have a deep understanding of EF Core, from beginner-friendly basics to advanced techniques, with practical code samples to make your learning interactive and engaging. Let’s get started!


Module 1: Getting Started with EF Core - Basics and Setup

What is EF Core?

EF Core is a lightweight, extensible, open-source ORM for .NET. It allows developers to work with databases using C# objects, abstracting away much of the SQL complexity. It supports multiple database providers like SQL Server, PostgreSQL, SQLite, and MySQL.

Why Use EF Core?

  • Productivity: Write less boilerplate code for database operations.

  • Cross-Platform: Runs on Windows, macOS, and Linux.

  • Flexibility: Supports both code-first and database-first approaches.

  • Performance: Optimized for modern applications with proper configuration.

Setting Up EF Core

Let’s set up a basic EF Core project using a real-world example: a Blogging Platform.

Step 1: Install EF Core

Add the following NuGet packages to your .NET project:

dotnet add package Microsoft.EntityFrameworkCore
dotnet add package Microsoft.EntityFrameworkCore.SqlServer
dotnet add package Microsoft.EntityFrameworkCore.Design

Step 2: Create Models and DbContext

Define the data models and the DbContext for the blogging platform.

using Microsoft.EntityFrameworkCore;

public class Blog
{
    public int BlogId { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PublishedDate { get; set; }
    public List<Post> Posts { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public string Body { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

public class BloggingContext : DbContext
{
    public DbSet<Blog> Blogs { get; set; }
    public DbSet<Post> Posts { get; set; }

    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    {
    }
}

Step 3: Configure DbContext

In Program.cs, configure the database connection.

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddDbContext<BloggingContext>(options =>
    options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection")));

var app = builder.Build();
app.Run();

Add the connection string in appsettings.json:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=localhost;Database=Blogging;Trusted_Connection=True;"
  }
}

Step 4: Create Database with Migrations

Run the following commands to create and apply migrations:

dotnet ef migrations add InitialCreate
dotnet ef database update

Pros of EF Core

  • Simplifies database operations with LINQ.

  • Supports multiple database providers.

  • Built-in change tracking and migrations.

Cons of EF Core

  • Can introduce performance overhead if not optimized.

  • Learning curve for advanced features like query tuning.

  • Limited control over raw SQL in some cases.

Alternatives to EF Core

  • Dapper: Lightweight, fast micro-ORM for simple queries.

  • ADO.NET: Low-level data access for maximum control.

  • NHibernate: Mature ORM with advanced features but steeper learning curve.

Best Practices

  • Use dependency injection to manage DbContext.

  • Keep DbContext lifetime short (per request in web apps).

  • Use migrations for schema management.


Module 2: Writing Efficient Queries in EF Core

Efficient queries are critical for application performance. Poorly written queries can lead to slow response times and excessive database load. Let’s explore how to write optimized LINQ queries for our blogging platform.

Example 1: Basic Querying

Fetch all blogs with their posts.

using (var context = new BloggingContext(options))
{
    var blogs = context.Blogs
        .Include(b => b.Posts)
        .ToList();
}

Problem: This query loads all posts for every blog, which can be inefficient for large datasets.

Optimization: Use selective loading with Where and Select.

var blogs = context.Blogs
    .Where(b => b.PublishedDate > DateTime.Now.AddYears(-1))
    .Select(b => new { b.BlogId, b.Title, PostCount = b.Posts.Count })
    .ToList();

Example 2: Avoiding N+1 Query Problem

The N+1 problem occurs when EF Core executes additional queries for related data. For example:

var blogs = context.Blogs.ToList();
foreach (var blog in blogs)
{
    var posts = context.Posts.Where(p => p.BlogId == blog.BlogId).ToList();
}

Optimization: Use eager loading with Include.

var blogs = context.Blogs
    .Include(b => b.Posts)
    .ToList();

Example 3: Using Projections for Performance

Instead of loading entire entities, use projections to fetch only required fields.

var blogSummaries = context.Blogs
    .Select(b => new
    {
        b.BlogId,
        b.Title,
        PostTitles = b.Posts.Select(p => p.Title)
    })
    .ToList();

Best Practices for Queries

  • Use Projections: Select only the data you need.

  • Eager Loading: Use Include for related data when necessary.

  • Avoid Overfetching: Don’t load entire tables unnecessarily.

  • Use AsNoTracking: For read-only queries to reduce memory usage.

var blogs = context.Blogs
    .AsNoTracking()
    .ToList();

Pros of LINQ Queries

  • Type-safe and readable.

  • Easy to compose complex queries.

  • Abstracts SQL complexity.

Cons of LINQ Queries

  • Can generate suboptimal SQL if not careful.

  • Overuse of Include can lead to performance issues.

  • Debugging complex LINQ queries can be challenging.

Alternatives

  • Raw SQL Queries: Use FromSql for complex queries.

  • Stored Procedures: For performance-critical operations.

  • Dapper: For faster raw SQL execution.


Module 3: Performance Optimization in EF Core

Performance is a critical aspect of any application. EF Core provides several tools and techniques to optimize database interactions.

Tip 1: Use AsNoTracking for Read-Only Queries

When you don’t need to update entities, use AsNoTracking to bypass change tracking.

var blogs = context.Blogs
    .AsNoTracking()
    .Where(b => b.PublishedDate > DateTime.Now.AddMonths(-6))
    .ToList();

Tip 2: Batch Updates and Deletes

Instead of updating entities one by one, use batch operations.

context.Blogs
    .Where(b => b.PublishedDate < DateTime.Now.AddYears(-5))
    .ExecuteUpdate(b => b.SetProperty(x => x.Title, x => x.Title + " (Archived)"));

Tip 3: Use Compiled Queries

For frequently executed queries, use compiled queries to reduce overhead.

private static readonly Func<BloggingContext, int, IEnumerable<Blog>> _getBlogById =
    EF.CompileQuery((BloggingContext context, int id) =>
        context.Blogs
            .Where(b => b.BlogId == id)
            .Include(b => b.Posts));

using (var context = new BloggingContext(options))
{
    var blog = _getBlogById(context, 1).FirstOrDefault();
}

Tip 4: Enable Lazy Loading (with Caution)

Lazy loading loads related data on-demand but can lead to N+1 issues.

public class BloggingContext : DbContext
{
    public BloggingContext(DbContextOptions<BloggingContext> options)
        : base(options)
    {
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>()
            .Navigation(b => b.Posts)
            .AutoInclude();
    }
}

Pros of Performance Optimization

  • Faster application response times.

  • Reduced database load.

  • Scalable for large datasets.

Cons

  • Requires careful query planning.

  • Over-optimization can make code harder to maintain.

  • Lazy loading can introduce hidden performance issues.

Best Practices

  • Profile queries using tools like SQL Server Profiler or MiniProfiler.

  • Use batch operations for bulk updates.

  • Monitor and optimize database indexes.


Module 4: Complex Data Modeling with EF Core

Complex data modeling is essential for real-world applications like our blogging platform, which may include relationships, inheritance, and custom types.

Example 1: One-to-Many Relationships

Model a blog with multiple posts.

public class Blog
{
    public int BlogId { get; set; }
    public string Title { get; set; }
    public List<Post> Posts { get; set; } = new List<Post>();
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

Configure the relationship in OnModelCreating:

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasMany(b => b.Posts)
        .WithOne(p => p.Blog)
        .HasForeignKey(p => p.BlogId);
}

Example 2: Many-to-Many Relationships

Add tags to posts with a many-to-many relationship.

public class Tag
{
    public int TagId { get; set; }
    public string Name { get; set; }
    public List<PostTag> PostTags { get; set; }
}

public class PostTag
{
    public int PostId { get; set; }
    public Post Post { get; set; }
    public int TagId { get; set; }
    public Tag Tag { get; set; }
}

public class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
    public List<PostTag> PostTags { get; set; }
}

Configure in OnModelCreating:

modelBuilder.Entity<PostTag>()
    .HasKey(pt => new { pt.PostId, pt.TagId });

modelBuilder.Entity<PostTag>()
    .HasOne(pt => pt.Post)
    .WithMany(p => p.PostTags)
    .HasForeignKey(pt => pt.PostId);

modelBuilder.Entity<PostTag>()
    .HasOne(pt => pt.Tag)
    .WithMany(t => t.PostTags)
    .HasForeignKey(pt => pt.TagId);

Example 3: Table-Per-Hierarchy (TPH) Inheritance

Model a hierarchy for different types of posts (e.g., Article, VideoPost).

public abstract class Post
{
    public int PostId { get; set; }
    public string Title { get; set; }
    public int BlogId { get; set; }
    public Blog Blog { get; set; }
}

public class Article : Post
{
    public string Content { get; set; }
}

public class VideoPost : Post
{
    public string VideoUrl { get; set; }
}

Configure TPH in OnModelCreating:

modelBuilder.Entity<Post>()
    .HasDiscriminator<string>("PostType")
    .HasValue<Article>("Article")
    .HasValue<VideoPost>("Video");

Pros of Complex Data Modeling

  • Supports advanced domain models.

  • Simplifies relationships with fluent configuration.

  • Flexible for inheritance and custom types.

Cons

  • Complex configurations can be error-prone.

  • Performance overhead for complex queries.

  • Requires deep understanding of EF Core’s conventions.

Best Practices

  • Use fluent API for explicit relationship configuration.

  • Validate models with migrations before deployment.

  • Test complex queries with realistic data.


Module 5: Advanced Tips and Tricks

Tip 1: Use Value Converters for Custom Types

Store complex types like JSON or enums in the database.

public class Blog
{
    public int BlogId { get; set; }
    public string Title { get; set; }
    public List<string> Tags { get; set; } // Store as JSON
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .Property(b => b.Tags)
        .HasConversion(
            v => JsonSerializer.Serialize(v, null),
            v => JsonSerializer.Deserialize<List<string>>(v, null));
}

Tip 2: Implement Soft Deletes

Instead of deleting records, mark them as deleted.

public class Blog
{
    public int BlogId { get; set; }
    public string Title { get; set; }
    public bool IsDeleted { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .HasQueryFilter(b => !b.IsDeleted);
}

Tip 3: Use Owned Types for Value Objects

Model address as an owned type within a blog.

public class Address
{
    public string Street { get; set; }
    public string City { get; set; }
}

public class Blog
{
    public int BlogId { get; set; }
    public string Title { get; set; }
    public Address Address { get; set; }
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
    modelBuilder.Entity<Blog>()
        .OwnsOne(b => b.Address);
}

Pros of Advanced Techniques

  • Enables complex domain-driven design.

  • Improves maintainability with soft deletes and value objects.

  • Flexible for custom data types.

Cons

  • Increases configuration complexity.

  • Potential performance overhead for complex mappings.

  • Requires thorough testing.


Module 6: Real-World Example - Building a Blogging Platform API

Let’s tie everything together by building a REST API for our blogging platform using ASP.NET Core and EF Core.

Step 1: Create the API Controller

using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;

[ApiController]
[Route("api/[controller]")]
public class BlogsController : ControllerBase
{
    private readonly BloggingContext _context;

    public BlogsController(BloggingContext context)
    {
        _context = context;
    }

    [HttpGet]
    public async Task<IActionResult> GetBlogs()
    {
        var blogs = await _context.Blogs
            .AsNoTracking()
            .Select(b => new { b.BlogId, b.Title, PostCount = b.Posts.Count })
            .ToListAsync();
        return Ok(blogs);
    }

    [HttpPost]
    public async Task<IActionResult> CreateBlog([FromBody] Blog blog)
    {
        _context.Blogs.Add(blog);
        await _context.SaveChangesAsync();
        return CreatedAtAction(nameof(GetBlogs), new { id = blog.BlogId }, blog);
    }
}

Step 2: Optimize with Async/Await

Use async methods to improve scalability.

[HttpGet("{id}")]
public async Task<IActionResult> GetBlog(int id)
{
    var blog = await _context.Blogs
        .Include(b => b.Posts)
        .AsNoTracking()
        .FirstOrDefaultAsync(b => b.BlogId == id);

    if (blog == null) return NotFound();
    return Ok(blog);
}

Step 3: Add Error Handling

Handle database exceptions gracefully.

[HttpPost]
public async Task<IActionResult> CreateBlog([FromBody] Blog blog)
{
    try
    {
        _context.Blogs.Add(blog);
        await _context.SaveChangesAsync();
        return CreatedAtAction(nameof(GetBlogs), new { id = blog.BlogId }, blog);
    }
    catch (DbUpdateException ex)
    {
        return BadRequest($"Error creating blog: {ex.InnerException?.Message}");
    }
}

Best Practices for APIs

  • Use async/await for database operations.

  • Return meaningful HTTP status codes.

  • Implement proper error handling.


Conclusion

Mastering Entity Framework Core requires understanding its core concepts, optimizing queries, and leveraging advanced features for complex data modeling. By following the tips, tricks, and best practices outlined in this tutorial, you can build high-performance, scalable applications. Whether you're developing a simple blog or a complex enterprise system, EF Core provides the tools to succeed.

No comments:

Post a Comment

Thanks for your valuable comment...........
Md. Mominul Islam

Post Bottom Ad

Responsive Ads Here