Entity Framework Core — ORM and Database Access Explained
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 AddPostRatingEF 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
| Strategy | How It Works | Best For |
|---|---|---|
| Eager Loading | .Include() loads related data in one query | When you always need the related data |
| Lazy Loading | Related data loaded on access (separate queries) | Simple navigation, small datasets |
| Explicit Loading | .Load() loads when you decide | Conditional 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
- Use
AsNoTracking()for read-only queries — change tracking adds overhead - Batch operations with
ExecuteUpdate()/ExecuteDelete()in EF Core 7+ - Avoid N+1 queries — use
.Include()to eager-load related data - Use compiled queries for frequently executed patterns
- Index your columns — add
[Index]attributes for filtered columns - 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:
BlogContextwith Posts, Authors, and Blogs- Seed data with
modelBuilder.HasData() - An API endpoint:
GET /api/blogsthat returns all blogs with post counts - An API endpoint:
POST /api/poststhat creates a post with validation - Use
AsNoTracking()on the GET endpoint for performance
FAQ
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