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
| State | Description |
|---|---|
Detached | Không được track |
Added | Mới, chưa có trong database |
Unchanged | Khô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 });