Skip to main content

C# Entity Framework Basics

Introduction

Entity Framework (EF) is Microsoft's object-relational mapping (ORM) framework for .NET. It eliminates the need to write much of the data-access code that developers usually need to write. With Entity Framework, you can work with databases using .NET objects, without focusing too much on the underlying database tables and columns where your data is stored.

Entity Framework allows you to:

  • Work with data as objects and properties
  • Query your data using LINQ (Language Integrated Query)
  • Create and update data models using code or visual tools
  • Track changes automatically and persist data to the database

In this tutorial, we'll learn the fundamentals of Entity Framework Core, which is the lightweight, extensible, cross-platform version of Entity Framework.

Getting Started with Entity Framework Core

Installation

To start using Entity Framework Core, you need to install the necessary NuGet packages:

bash
dotnet add package Microsoft.EntityFrameworkCore.SqlServer

For migrations support (which we'll discuss later), add:

bash
dotnet add package Microsoft.EntityFrameworkCore.Tools

Key Components of Entity Framework

Entity Framework consists of several key components:

  1. DbContext: The primary class that coordinates Entity Framework functionality for a data model
  2. Entity Classes: Plain C# objects that represent your data
  3. DbSet Properties: Collections within your context that represent tables in the database
  4. Migrations: A way to keep your database schema in sync with your EF Core model

Creating Your First EF Core Application

Let's create a simple book tracking application to understand the basics of Entity Framework.

Step 1: Define Entity Classes

First, let's define our entity classes:

csharp
public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public int PublicationYear { get; set; }
public string ISBN { get; set; }

// Navigation property
public virtual Category Category { get; set; }
public int CategoryId { get; set; }
}

public class Category
{
public int CategoryId { get; set; }
public string Name { get; set; }

// Navigation property
public virtual ICollection<Book> Books { get; set; }
}

Step 2: Create a DbContext

Next, create a DbContext class that will act as our session with the database:

csharp
using Microsoft.EntityFrameworkCore;

public class BookstoreContext : DbContext
{
public DbSet<Book> Books { get; set; }
public DbSet<Category> Categories { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Bookstore;Trusted_Connection=True;");
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Fluent API configurations can go here
modelBuilder.Entity<Book>()
.HasOne(b => b.Category)
.WithMany(c => c.Books)
.HasForeignKey(b => b.CategoryId);
}
}

Step 3: Migrations

Migrations allow you to create and update your database schema based on your model changes. In the Package Manager Console, run:

bash
dotnet ef migrations add InitialCreate
dotnet ef database update

The first command creates a migration file that contains code to create the database schema. The second command applies the migration to create the database.

Step 4: Basic CRUD Operations

Let's see how to perform basic CRUD (Create, Read, Update, Delete) operations:

Create (Insert Data)

csharp
using (var context = new BookstoreContext())
{
// Add a new category
var fictionCategory = new Category { Name = "Fiction" };
context.Categories.Add(fictionCategory);

// Add a new book
var book = new Book
{
Title = "The Great Gatsby",
Author = "F. Scott Fitzgerald",
PublicationYear = 1925,
ISBN = "978-0743273565",
Category = fictionCategory
};
context.Books.Add(book);

// Save changes to the database
context.SaveChanges();

Console.WriteLine($"Added book: {book.Title} with ID: {book.BookId}");
}

Read (Query Data)

csharp
using (var context = new BookstoreContext())
{
// Get all books
var allBooks = context.Books.ToList();
Console.WriteLine("All books:");
foreach (var b in allBooks)
{
Console.WriteLine($"- {b.Title} by {b.Author} ({b.PublicationYear})");
}

// Get books with a specific condition
var recentBooks = context.Books
.Where(b => b.PublicationYear > 2000)
.OrderBy(b => b.Title)
.ToList();

Console.WriteLine("\nBooks published after 2000:");
foreach (var b in recentBooks)
{
Console.WriteLine($"- {b.Title} by {b.Author} ({b.PublicationYear})");
}

// Get books with their category (eager loading)
var booksWithCategories = context.Books
.Include(b => b.Category)
.ToList();

Console.WriteLine("\nBooks with categories:");
foreach (var b in booksWithCategories)
{
Console.WriteLine($"- {b.Title} ({b.Category?.Name ?? "No category"})");
}
}

Update Data

csharp
using (var context = new BookstoreContext())
{
// Find the book to update
var bookToUpdate = context.Books
.FirstOrDefault(b => b.Title == "The Great Gatsby");

if (bookToUpdate != null)
{
// Update properties
bookToUpdate.PublicationYear = 1926; // Update year

// Save changes
context.SaveChanges();

Console.WriteLine($"Updated book: {bookToUpdate.Title}");
}
}

Delete Data

csharp
using (var context = new BookstoreContext())
{
// Find the book to delete
var bookToDelete = context.Books
.FirstOrDefault(b => b.Title == "The Great Gatsby");

if (bookToDelete != null)
{
// Remove the book
context.Books.Remove(bookToDelete);

// Save changes
context.SaveChanges();

Console.WriteLine($"Deleted book: {bookToDelete.Title}");
}
}

Advanced Features

1. Relationships

Entity Framework Core supports different types of relationships:

  • One-to-Many: Most common relationship type (like the Category to Books relationship we saw)
  • One-to-One: When an entity relates to exactly one instance of another entity
  • Many-to-Many: When multiple instances of an entity relate to multiple instances of another entity

2. Data Annotations

You can use data annotations to customize your model:

csharp
public class Book
{
[Key]
public int BookId { get; set; }

[Required]
[MaxLength(200)]
public string Title { get; set; }

[Required]
[MaxLength(100)]
public string Author { get; set; }

[Range(1000, 9999)]
public int PublicationYear { get; set; }

[MaxLength(20)]
public string ISBN { get; set; }

public int CategoryId { get; set; }

[ForeignKey("CategoryId")]
public virtual Category Category { get; set; }
}

3. Fluent API

For more advanced model configuration, use the Fluent API in the OnModelCreating method:

csharp
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Book>()
.Property(b => b.Title)
.IsRequired()
.HasMaxLength(200);

modelBuilder.Entity<Book>()
.HasIndex(b => b.ISBN)
.IsUnique();

modelBuilder.Entity<Book>()
.HasOne(b => b.Category)
.WithMany(c => c.Books)
.HasForeignKey(b => b.CategoryId)
.OnDelete(DeleteBehavior.Restrict);
}

Real-World Example: Building a Library Management System

Let's implement a simple library management system with Entity Framework Core.

Step 1: Define the Entity Classes

csharp
public class User
{
public int UserId { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public virtual ICollection<BookLoan> BookLoans { get; set; }
}

public class Book
{
public int BookId { get; set; }
public string Title { get; set; }
public string Author { get; set; }
public string ISBN { get; set; }
public bool IsAvailable { get; set; } = true;
public virtual ICollection<BookLoan> BookLoans { get; set; }
}

public class BookLoan
{
public int BookLoanId { get; set; }
public int BookId { get; set; }
public int UserId { get; set; }
public DateTime LoanDate { get; set; }
public DateTime? ReturnDate { get; set; }

public virtual Book Book { get; set; }
public virtual User User { get; set; }
}

Step 2: Create the Context

csharp
public class LibraryContext : DbContext
{
public DbSet<User> Users { get; set; }
public DbSet<Book> Books { get; set; }
public DbSet<BookLoan> BookLoans { get; set; }

protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{
optionsBuilder.UseSqlServer(@"Server=(localdb)\mssqllocaldb;Database=Library;Trusted_Connection=True;");
}

protected override void OnModelCreating(ModelBuilder modelBuilder)
{
// Configure relationships
modelBuilder.Entity<BookLoan>()
.HasOne(bl => bl.Book)
.WithMany(b => b.BookLoans)
.HasForeignKey(bl => bl.BookId);

modelBuilder.Entity<BookLoan>()
.HasOne(bl => bl.User)
.WithMany(u => u.BookLoans)
.HasForeignKey(bl => bl.UserId);
}
}

Step 3: Implement Library Operations

csharp
public class LibraryService
{
// Check out a book
public bool CheckoutBook(int userId, int bookId)
{
using (var context = new LibraryContext())
{
var book = context.Books.Find(bookId);
var user = context.Users.Find(userId);

if (book == null || user == null)
return false;

if (!book.IsAvailable)
return false;

// Create the loan
var loan = new BookLoan
{
BookId = bookId,
UserId = userId,
LoanDate = DateTime.Now,
ReturnDate = null
};

// Update book availability
book.IsAvailable = false;

// Save to database
context.BookLoans.Add(loan);
context.SaveChanges();

return true;
}
}

// Return a book
public bool ReturnBook(int bookId)
{
using (var context = new LibraryContext())
{
var book = context.Books.Find(bookId);

if (book == null)
return false;

// Find the active loan for this book
var loan = context.BookLoans
.Where(l => l.BookId == bookId && l.ReturnDate == null)
.FirstOrDefault();

if (loan == null)
return false;

// Update the loan and book
loan.ReturnDate = DateTime.Now;
book.IsAvailable = true;

// Save changes
context.SaveChanges();

return true;
}
}

// Get all books on loan
public List<BookLoan> GetActiveLoans()
{
using (var context = new LibraryContext())
{
return context.BookLoans
.Include(l => l.Book)
.Include(l => l.User)
.Where(l => l.ReturnDate == null)
.ToList();
}
}
}

Step 4: Using the Library Service

csharp
class Program
{
static void Main(string[] args)
{
// Initialize database with some data
SeedDatabase();

var libraryService = new LibraryService();

// Check out a book
libraryService.CheckoutBook(1, 1);
Console.WriteLine("Book checked out");

// Get active loans
var activeLoans = libraryService.GetActiveLoans();
Console.WriteLine("Active loans:");
foreach (var loan in activeLoans)
{
Console.WriteLine($"{loan.Book.Title} borrowed by {loan.User.Name} on {loan.LoanDate.ToShortDateString()}");
}

// Return the book
libraryService.ReturnBook(1);
Console.WriteLine("Book returned");
}

static void SeedDatabase()
{
using (var context = new LibraryContext())
{
// Create database if it doesn't exist
context.Database.EnsureCreated();

// Add users if none exist
if (!context.Users.Any())
{
context.Users.Add(new User { Name = "John Doe", Email = "[email protected]" });
context.Users.Add(new User { Name = "Jane Smith", Email = "[email protected]" });
context.SaveChanges();
}

// Add books if none exist
if (!context.Books.Any())
{
context.Books.Add(new Book {
Title = "The Pragmatic Programmer",
Author = "Andrew Hunt, David Thomas",
ISBN = "978-0201616224"
});

context.Books.Add(new Book {
Title = "Clean Code",
Author = "Robert C. Martin",
ISBN = "978-0132350884"
});

context.SaveChanges();
}
}
}
}

Summary

In this tutorial, we've covered the basics of Entity Framework Core, Microsoft's modern ORM for .NET applications. We've learned:

  1. How to set up Entity Framework Core in a project
  2. How to define entity classes and create a DbContext
  3. How to manage database schema through migrations
  4. How to perform basic CRUD operations
  5. How to configure relationships between entities
  6. How to use advanced features like data annotations and Fluent API
  7. A real-world example of a library management system

Entity Framework Core significantly reduces the amount of data access code you need to write while providing a powerful, type-safe way to interact with your database using C# objects.

Additional Resources

Exercises

  1. Create a simple blog application with Posts, Comments, and Users entities using Entity Framework Core.
  2. Implement a product inventory system with Products, Categories, and Suppliers.
  3. Extend the library example to include features like late fees and book reservations.
  4. Implement a many-to-many relationship between two entities (like Students and Courses).
  5. Use data seeding to populate your database with initial data.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)