Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

EF Core - Cơ bản & Thiết kế

Code First vs Database First

Code First

Tạo database từ C# classes:

// Models
public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
    public decimal Price { get; set; }
    public int CategoryId { get; set; }
    public Category Category { get; set; }
}

public class Category
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Product> Products { get; set; }
}

// DbContext
public class AppDbContext : DbContext
{
    public DbSet<Product> Products { get; set; }
    public DbSet<Category> Categories { get; set; }

    protected override void OnConfiguring(DbContextOptionsBuilder options)
        => options.UseSqlServer("ConnectionString");
    
    protected override void OnModelCreating(ModelBuilder modelBuilder)
    {
        modelBuilder.Entity<Product>(entity =>
        {
            entity.HasKey(p => p.Id);
            entity.Property(p => p.Name).IsRequired().HasMaxLength(100);
            entity.Property(p => p.Price).HasColumnType("decimal(18,2)");
        });
    }
}

Database First

Reverse engineer từ existing database:

# Scaffold từ database
dotnet ef dbcontext scaffold "ConnectionString" Microsoft.EntityFrameworkCore.SqlServer

# Với options
dotnet ef dbcontext scaffold "ConnectionString" Microsoft.EntityFrameworkCore.SqlServer `
    --table Products,Categories `
    --context AppDbContext `
    --output-dir Models

Migrations

Cài đặt

dotnet tool install --global dotnet-ef
dotnet add package Microsoft.EntityFrameworkCore.Design

Commands

# Tạo migration
dotnet ef migrations add InitialCreate

# Apply migrations
dotnet ef database update

# Update với migration cụ thể
dotnet ef database update 20240101000000_InitialCreate

# Rollback
dotnet ef database update PreviousMigrationName

# Remove last migration
dotnet ef migrations remove

# List migrations
dotnet ef migrations list

# Generate SQL script
dotnet ef migrations script

Migration Structure

public partial class InitialCreate : Migration
{
    protected override void Up(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.CreateTable(
            name: "Categories",
            columns: table => new
            {
                Id = table.Column<int>(type: "int", nullable: false)
                    .Annotation("SqlServer:Identity", "1, 1"),
                Name = table.Column<string>(type: "nvarchar(100)", maxLength: 100, nullable: false)
            },
            constraints: table =>
            {
                table.PrimaryKey("PK_Categories", x => x.Id);
            });
    }

    protected override void Down(MigrationBuilder migrationBuilder)
    {
        migrationBuilder.DropTable(name: "Categories");
    }
}

DbContext và Change Tracker

Change Tracker

EF Core theo dõi các thay đổi trên entities:

var context = new AppDbContext();

// Read
var product = context.Products.First(); // Tracked

// Update
product.Price = 99.99m;
// EF tự động mark là Modified

// Delete
context.Products.Remove(product);
// EF mark là Deleted

// SaveChanges - Tạo SQL UPDATE/DELETE
context.SaveChanges();

Entity States

StateDescription
DetachedKhông được track
AddedMới, chưa có trong database
UnchangedKhông thay đổi
ModifiedĐã thay đổi
DeletedĐánh dấu xóa

Tracking Behavior

// Tracked (default)
var product = context.Products.First();

// No tracking - tốt cho read-only
var product = context.Products.AsNoTracking().First();

// Chỉ định rõ ràng tracking
var product = context.Products.AsTracking().First();

// Kiểm tra state
var entry = context.Entry(product);
Console.WriteLine(entry.State); // Modified

DbContext Lifetime

// ✅ Tốt - Scoped cho mỗi request
builder.Services.AddScoped<AppDbContext>();

// ❌ Tránh - Singleton
builder.Services.AddSingleton<AppDbContext>(); // Bad practice!

Relationships Configuration

One-to-Many

public class Author
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<Book> Books { get; set; }
}

public class Book
{
    public int Id { get; set; }
    public string Title { get; set; }
    public int AuthorId { get; set; }
    public Author Author { get; set; }
}

// Configuration
modelBuilder.Entity<Book>()
    .HasOne(b => b.Author)
    .WithMany(a => a.Books)
    .HasForeignKey(b => b.AuthorId);

One-to-One

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public StudentProfile Profile { get; set; }
}

public class StudentProfile
{
    public int Id { get; set; }
    public string Bio { get; set; }
    public int StudentId { get; set; }
    public Student Student { get; set; }
}

// Configuration
modelBuilder.Entity<Student>()
    .HasOne(s => s.Profile)
    .WithOne(p => p.Student)
    .HasForeignKey<StudentProfile>(p => p.StudentId);

Many-to-Many

public class Student
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<StudentCourse> StudentCourses { get; set; }
}

public class Course
{
    public int Id { get; set; }
    public string Name { get; set; }
    public ICollection<StudentCourse> StudentCourses { get; set; }
}

// Junction table
public class StudentCourse
{
    public int StudentId { get; set; }
    public Student Student { get; set; }
    public int CourseId { get; set; }
    public Course Course { get; set; }
}

// Cấu hình composite key
modelBuilder.Entity<StudentCourse>()
    .HasKey(sc => new { sc.StudentId, sc.CourseId });