On this page
Entity Framework Core
Entity Framework Core (EF Core) is an ORM that maps C# classes to database tables, letting you work with data as objects.
Setup
dotnet add package Microsoft.EntityFrameworkCore.Sqlite
dotnet add package Microsoft.EntityFrameworkCore.Design
Define Models and DbContext
public class Blog
{
public int Id { get; set; }
public string Title { get; set; } = string.Empty;
public List<Post> Posts { get; set; } = new();
}
public class Post
{
public int Id { get; set; }
public string Content { get; set; } = string.Empty;
public int BlogId { get; set; }
public Blog Blog { get; set; } = null!;
}
public class AppDbContext : DbContext
{
public DbSet<Blog> Blogs => Set<Blog>();
public DbSet<Post> Posts => Set<Post>();
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
options.UseSqlite("Data Source=app.db");
}
}
CRUD Operations
using var db = new AppDbContext();
await db.Database.EnsureCreatedAsync();
// Create
var blog = new Blog { Title = "My Blog" };
blog.Posts.Add(new Post { Content = "First post" });
db.Blogs.Add(blog);
await db.SaveChangesAsync();
// Read
var blogs = await db.Blogs
.Include(b => b.Posts)
.Where(b => b.Title.Contains("Blog"))
.ToListAsync();
// Update
blog.Title = "Updated Title";
await db.SaveChangesAsync();
// Delete
db.Blogs.Remove(blog);
await db.SaveChangesAsync();
Migrations
dotnet ef migrations add InitialCreate
dotnet ef database update
Migrations version-control your schema changes and apply them consistently across environments.
Register in ASP.NET Core
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseSqlServer(builder.Configuration.GetConnectionString("Default")));
Inject AppDbContext into controllers or services via constructor injection.
Fluent API Configuration
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>(entity =>
{
entity.HasKey(b => b.Id);
entity.Property(b => b.Title).HasMaxLength(200).IsRequired();
entity.HasIndex(b => b.Title);
});
modelBuilder.Entity<Post>()
.HasOne(p => p.Blog)
.WithMany(b => b.Posts)
.HasForeignKey(p => p.BlogId)
.OnDelete(DeleteBehavior.Cascade);
}
Raw SQL When Needed
var blogs = await db.Blogs
.FromSqlRaw("SELECT * FROM Blogs WHERE Title LIKE {0}", "%dotnet%")
.ToListAsync();
Prefer LINQ; use raw SQL for complex reports or performance-critical queries.
Change Tracking
var blog = await db.Blogs.FindAsync(1);
blog.Title = "Updated"; // tracked automatically
await db.SaveChangesAsync();
// Read-only query — no tracking overhead
var titles = await db.Blogs
.AsNoTracking()
.Select(b => b.Title)
.ToListAsync();
Seeding Data
public static async Task SeedAsync(AppDbContext db)
{
if (!await db.Blogs.AnyAsync())
{
db.Blogs.Add(new Blog { Title = "Default Blog" });
await db.SaveChangesAsync();
}
}
Call from Program.cs at startup in development.
Common Pitfalls
- Forgetting
Include()— related entities load as null without eager loading. - Calling
SaveChanges()in a loop — batch changes and save once. - Not disposing
DbContext— useawait usingor DI scoped lifetime.