Backend Systemsbeginner
EF Core Setup — DbContext, Entities, First Migration
Get started with Entity Framework Core: define entities, configure DbContext, run your first migration, and query and save data to a real database.
Asma HafeezApril 17, 20263 min read
dotnetef-coreormdatabasemigrations
EF Core Setup
Entity Framework Core (EF Core) is .NET's official ORM. It maps C# classes to database tables and generates SQL for you.
Install Packages
Bash
dotnet add package Microsoft.EntityFrameworkCore.SqlServer # or .Npgsql for PostgreSQL
dotnet add package Microsoft.EntityFrameworkCore.Tools # migrations CLIDefine Entities
C#
public class Blog
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string? Url { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation property — one blog has many posts
public List<Post> Posts { get; set; } = [];
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public string Body { get; set; } = string.Empty;
public bool Published { get; set; }
// Foreign key + navigation to parent
public int BlogId { get; set; }
public Blog Blog { get; set; } = null!;
}DbContext
C#
public class AppDbContext : DbContext
{
public AppDbContext(DbContextOptions<AppDbContext> options) : base(options) { }
public DbSet<Blog> Blogs => Set<Blog>();
public DbSet<Post> Posts => Set<Post>();
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Optional configuration — EF Core has sensible conventions
modelBuilder.Entity<Post>()
.Property(p => p.Title)
.HasMaxLength(200)
.IsRequired();
}
}Register in Program.cs
C#
// For PostgreSQL:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("Default")));
// For SQL Server:
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));JSON
// appsettings.json
{
"ConnectionStrings": {
"Default": "Host=localhost;Database=myapp;Username=postgres;Password=password"
}
}Migrations
Bash
# Create migration (generates SQL from your entities)
dotnet ef migrations add InitialCreate
# Apply to database (creates tables)
dotnet ef database update
# After changing entities — create a new migration
dotnet ef migrations add AddPublishedDate
dotnet ef database update
# Remove last migration (if not yet applied)
dotnet ef migrations remove
# View generated SQL without applying
dotnet ef migrations scriptBasic CRUD
C#
public class BlogService(AppDbContext db)
{
// Create
public async Task<Blog> CreateAsync(string title, string url)
{
var blog = new Blog { Title = title, Url = url };
db.Blogs.Add(blog);
await db.SaveChangesAsync();
return blog;
}
// Read
public async Task<Blog?> GetByIdAsync(int id)
=> await db.Blogs
.Include(b => b.Posts)
.FirstOrDefaultAsync(b => b.Id == id);
public async Task<List<Blog>> GetAllAsync()
=> await db.Blogs.OrderBy(b => b.Title).ToListAsync();
// Update
public async Task UpdateAsync(int id, string newTitle)
{
var blog = await db.Blogs.FindAsync(id);
if (blog is null) return;
blog.Title = newTitle;
await db.SaveChangesAsync();
}
// Delete
public async Task DeleteAsync(int id)
{
var blog = await db.Blogs.FindAsync(id);
if (blog is null) return;
db.Blogs.Remove(blog);
await db.SaveChangesAsync();
}
}Querying
C#
// Filter
var published = await db.Posts
.Where(p => p.Published)
.ToListAsync();
// Include related data
var blogsWithPosts = await db.Blogs
.Include(b => b.Posts)
.ToListAsync();
// Pagination
var page = await db.Posts
.OrderByDescending(p => p.Id)
.Skip((pageNumber - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
// Projection — only load what you need
var titles = await db.Posts
.Where(p => p.Published)
.Select(p => new { p.Id, p.Title })
.ToListAsync();Key Takeaways
- Entities are plain C# classes — EF Core maps them to tables via conventions + configuration
- DbContext is the unit of work — register it as
Scopedin DI - Migrations generate SQL from your entity changes — always commit them to source control
- Use
Include()for eager loading of related entities — avoid N+1 queries - Use projection (
Select) to load only the columns you need — don't fetch full entities for read-only views
Enjoyed this article?
Explore the Backend Systems learning path for more.
Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.