Skip to content
Entity Framework Core — ORM and Database Access Explained

Entity Framework Core — ORM and Database Access Explained

DodaTech Updated Jun 15, 2026 6 min read

Entity Framework Core (EF Core) is .NET’s modern object-relational mapper (ORM). It lets you work with databases using C# objects instead of raw SQL — the ORM translates your LINQ queries into SQL commands.

What You’ll Learn

You’ll master DbContext, migrations, relationships, LINQ queries, loading strategies (eager/lazy/explicit), and performance optimization. You’ll build an EF Core model for a blog system.

Why Entity Framework Matters

EF Core is the standard data access layer for .NET applications. It handles connection management, change tracking, and SQL generation. At DodaTech, we use EF Core in DodaZIP for metadata storage and in Durga Antivirus Pro for signature database management.

Entity Framework Learning Path

    flowchart LR
  A[.NET Overview] --> B[ASP.NET Core]
  B --> C[Entity Framework Core]
  C --> D{You Are Here}
  D --> E[Identity & Auth]
  D --> F[Blazor]
  style D fill:#f90,color:#fff
  

DbContext

DbContext is the bridge between your C# code and the database:

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

    protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
    {
        optionsBuilder.UseSqlServer(
            "Server=localhost;Database=BlogDb;Trusted_Connection=True;");
    }

    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Blog>(entity =>
        {
            entity.HasKey(e => e.Id);
            entity.Property(e => e.Name).HasMaxLength(200).IsRequired();
            entity.HasMany(e => e.Posts)
                  .WithOne(e => e.Blog)
                  .HasForeignKey(e => e.BlogId);
        });
    }
}

Model Classes

public class Blog
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Description { get; set; }
    public DateTime CreatedAt { get; set; }

    // Navigation property
    public ICollection<Post> Posts { get; set; }
}

public class Post
{
    public int Id { get; set; }
    public string Title { get; set; }
    public string Content { get; set; }
    public DateTime PublishedAt { get; set; }

    // Foreign key
    public int BlogId { get; set; }
    public Blog Blog { get; set; }

    public int AuthorId { get; set; }
    public Author Author { get; set; }
}

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
    public string Email { get; set; }
    public ICollection<Post> Posts { get; set; }
}

Migrations

Migrations track database schema changes:

# Create initial migration
dotnet ef migrations add InitialCreate

# Apply to database
dotnet ef database update

# Add another migration
dotnet ef migrations add AddPostRating

EF Core generates SQL from your model changes. Never edit migration files manually — let EF Core manage them.

LINQ Queries

EF Core translates LINQ to efficient SQL:

using (var db = new BlogContext())
{
    // Filtering and projection
    var recentPosts = await db.Posts
        .Where(p => p.PublishedAt > DateTime.UtcNow.AddDays(-7))
        .OrderByDescending(p => p.PublishedAt)
        .Select(p => new { p.Title, p.Author.Name, DaysAgo = (DateTime.UtcNow - p.PublishedAt).Days })
        .ToListAsync();

    // Aggregation
    var stats = await db.Blogs
        .Select(b => new
        {
            b.Name,
            PostCount = b.Posts.Count,
            LatestPost = b.Posts.Max(p => p.PublishedAt)
        })
        .ToListAsync();
}

Loading Strategies

StrategyHow It WorksBest For
Eager Loading.Include() loads related data in one queryWhen you always need the related data
Lazy LoadingRelated data loaded on access (separate queries)Simple navigation, small datasets
Explicit Loading.Load() loads when you decideConditional loading
// Eager loading
var blogs = await db.Blogs
    .Include(b => b.Posts)
        .ThenInclude(p => p.Author)
    .ToListAsync();

// Explicit loading
var blog = await db.Blogs.FindAsync(1);
await db.Entry(blog).Collection(b => b.Posts).LoadAsync();

Performance Tips

  1. Use AsNoTracking() for read-only queries — change tracking adds overhead
  2. Batch operations with ExecuteUpdate() / ExecuteDelete() in EF Core 7+
  3. Avoid N+1 queries — use .Include() to eager-load related data
  4. Use compiled queries for frequently executed patterns
  5. Index your columns — add [Index] attributes for filtered columns
  6. Split queries with AsSplitQuery() for wide star-schema queries

Common Mistakes Beginners Make

1. The N+1 Query Problem

Loading a list of blogs and accessing blog.Posts without .Include() fires one query per blog. Fix: Include() or Load().

2. Forgetting await on Async Methods

EF Core methods like SaveChangesAsync() return Task. Forgetting await means exceptions are lost and operations may not complete.

3. Not Using Migrations in Production

Auto-creating databases with EnsureCreated() works for dev but breaks in production. Always use migrations.

4. Tracking Too Many Entities

Thousands of tracked entities slows change detection. Use AsNoTracking() for bulk reads and AddRange() for bulk inserts.

5. Ignoring Connection Pooling

Opening/closing connections per request is expensive. EF Core pools connections by default — don’t disable it.

6. Using .ToList() Too Early

Calling .ToList() executes the query immediately. Chain all filters first: .Where().OrderBy().Select().ToList().

7. Not Handling Concurrency Conflicts

When two users edit the same record, the second save overwrites the first. Use [ConcurrencyCheck] or IsRowVersion().

Practice Questions

1. What is the purpose of DbContext?

It manages database connections, tracks changes, executes queries, and maps objects to database tables.

2. What is a migration in EF Core?

A migration is a code-generated script that updates the database schema to match your model classes.

3. What problem does the N+1 query problem cause?

Loading a list of items and then accessing related data for each item fires N additional queries, causing slow performance.

4. When should you use AsNoTracking()?

For read-only queries where you don’t need to update the entities. It eliminates change tracking overhead.

5. Challenge: Add a post rating system.

Create a Rating entity with Value (1-5), PostId, and UserId. Add a LINQ query that returns average rating per blog.

Mini Project: Blog Admin Panel

Build an ASP.NET Core app with EF Core:

  1. BlogContext with Posts, Authors, and Blogs
  2. Seed data with modelBuilder.HasData()
  3. An API endpoint: GET /api/blogs that returns all blogs with post counts
  4. An API endpoint: POST /api/posts that creates a post with validation
  5. Use AsNoTracking() on the GET endpoint for performance

FAQ

Is EF Core faster than ADO.NET?
Raw ADO.NET is faster for simple queries. EF Core adds overhead for change tracking and query translation. For complex apps, the productivity gain outweighs the performance cost.
Can EF Core use NoSQL databases?
Yes. EF Core supports Cosmos DB (NoSQL) via the Microsoft.EntityFrameworkCore.Cosmos provider.
Does EF Core support stored procedures?
Yes. Use FromSql() for raw SQL and stored procedure calls when needed.

What’s Next

Congratulations on completing this Entity Framework tutorial! Here’s where to go from here:

  • Practice daily — Model one new entity per day
  • Build a project — Create a CRUD API with EF Core
  • Explore related topics — Raw SQL interop, Cosmos DB provider
  • Join the community — Share your EF Core projects and get feedback

Remember: every expert was once a beginner. Keep querying!

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro