Razor Pages với Entity Framework Core trong ASP.NET Core - Hướng dẫn 1 trong 8
Đây là series hướng dẫn toàn diện để xây dựng ứng dụng web sử dụng ASP.NET Core Razor Pages và Entity Framework (EF) Core (khung thực thể). Hướng dẫn xây dựng trang web đại học hư cấu Contoso University với chức năng nhập học sinh, tạo khóa học và phân công giảng viên.
Tổng quan
- Phiên bản mục tiêu: ASP.NET Core 8.0
- Cách tiếp cận: Code-first development (phát triển theo hướng code trước)
- Tên dự án: ContosoUniversity
- Ứng dụng mẫu: Có trên GitHub
Điều kiện tiên quyết
Cho Visual Studio
- Visual Studio 2022 với workload ASP.NET and web development
- SQL Server LocalDB (bao gồm trong Visual Studio)
Cho Visual Studio Code
- Visual Studio Code
- C# for Visual Studio Code (phiên bản mới nhất)
- .NET 8 SDK
- Cơ sở dữ liệu SQLite (đa nền tảng)
Kiến trúc Data Model (Mô hình dữ liệu)
Ứng dụng sử dụng ba entity (thực thể) chính với các mối quan hệ:
Student ←→ Enrollment ←→ Course (1 đến Nhiều) (Nhiều đến Nhiều)
Student Entity
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}Điểm chú ý:
IDlà primary key (khóa chính) - EF Core nhận dạng tự độngIDhoặcclassnameIDEnrollmentslà navigation property (thuộc tính điều hướng) cho mối quan hệ one-to-many (một đến nhiều)
Enrollment Entity
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}Điểm chú ý:
EnrollmentIDsử dụng mẫuclassnameIDStudentIDvàCourseIDlà foreign keys (khóa ngoại)Gradecó thể null (null nghĩa là điểm chưa được giao)- Navigation properties liên kết đến các entity liên quan
Course Entity
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
public int CourseID { get; set; }
public string Title { get; set; }
public int Credits { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}Điểm chú ý:
- Primary key không được tự động tạo
- Có thể có nhiều enrollment (đăng ký)
Scaffolding (Tạo tự động) Student Pages
Quá trình scaffolding tạo ra:
- Class DbContext (
SchoolContext) - Razor pages cho các thao tác CRUD (Create - Tạo, Read - Đọc, Update - Cập nhật, Delete - Xóa)
- Cấu hình cơ sở dữ liệu trong
Program.cs
Cho Visual Studio
- Tạo thư mục Pages/Students
- Click chuột phải vào thư mục → Add → New Scaffolded Item
- Chọn "Razor Pages using Entity Framework (CRUD)"
- Cấu hình:
- Model class:
Student - Data context:
ContosoUniversity.Data.SchoolContext
Cho Visual Studio Code
Cài đặt các package cần thiết:
dotnet add package Microsoft.EntityFrameworkCore.SQLite dotnet add package Microsoft.EntityFrameworkCore.SqlServer dotnet add package Microsoft.EntityFrameworkCore.Design dotnet add package Microsoft.EntityFrameworkCore.Tools dotnet add package Microsoft.VisualStudio.Web.CodeGeneration.Design dotnet add package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore
Cài đặt scaffolding tool:
dotnet tool install --global dotnet-aspnet-codegenerator
Chạy scaffolding (Windows):
dotnet aspnet-codegenerator razorpage -m Student -dc ContosoUniversity.Data.SchoolContext -udl -outDir Pages\Students --referenceScriptLibraries -dbProvider sqlite
Cấu hình Cơ sở dữ liệu
Thiết lập SchoolContext
using Microsoft.EntityFrameworkCore;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext (DbContextOptions<SchoolContext> options)
: base(options)
{
}
public DbSet<Student> Students { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Course> Courses { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}Connection Strings (Chuỗi kết nối)
Cho Visual Studio (SQL Server LocalDB):
{
"ConnectionStrings": {
"SchoolContext": "Server=(localdb)\\mssqllocaldb;Database=SchoolContext-0e9;Trusted_Connection=True;MultipleActiveResultSets=true"
}
}Cho Visual Studio Code (SQLite):
{
"ConnectionStrings": {
"SchoolContextSQLite": "Data Source=CU.db"
}
}Cấu hình Program.cs
using ContosoUniversity.Data;
using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddDbContext<SchoolContext>(options =>
options.UseSqlite(builder.Configuration.GetConnectionString("SchoolContextSQLite")));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
var app = builder.Build();
if (!app.Environment.IsDevelopment())
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
else
{
app.UseDeveloperExceptionPage();
app.UseMigrationsEndPoint();
}
using (var scope = app.Services.CreateScope())
{
var services = scope.ServiceProvider;
var context = services.GetRequiredService<SchoolContext>();
context.Database.EnsureCreated();
DbInitializer.Initialize(context);
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();Seed (Khởi tạo) Cơ sở dữ liệu
Tạo file Data/DbInitializer.cs:
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
if (context.Students.Any())
{
return; // DB đã được seed
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2019-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2017-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2016-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2018-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2019-09-01")}
};
context.Students.AddRange(students);
context.SaveChanges();
var courses = new Course[]
{
new Course{CourseID=1050,Title="Chemistry",Credits=3},
new Course{CourseID=4022,Title="Microeconomics",Credits=3},
new Course{CourseID=4041,Title="Macroeconomics",Credits=3},
new Course{CourseID=1045,Title="Calculus",Credits=4},
new Course{CourseID=3141,Title="Trigonometry",Credits=4},
new Course{CourseID=2021,Title="Composition",Credits=3},
new Course{CourseID=2042,Title="Literature",Credits=4}
};
context.Courses.AddRange(courses);
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment{StudentID=1,CourseID=1050,Grade=Grade.A},
new Enrollment{StudentID=1,CourseID=4022,Grade=Grade.C},
new Enrollment{StudentID=1,CourseID=4041,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=1045,Grade=Grade.B},
new Enrollment{StudentID=2,CourseID=3141,Grade=Grade.F},
new Enrollment{StudentID=2,CourseID=2021,Grade=Grade.F},
new Enrollment{StudentID=3,CourseID=1050},
new Enrollment{StudentID=4,CourseID=1050},
new Enrollment{StudentID=4,CourseID=4022,Grade=Grade.F},
new Enrollment{StudentID=5,CourseID=4041,Grade=Grade.C},
new Enrollment{StudentID=6,CourseID=1045},
new Enrollment{StudentID=7,CourseID=3141,Grade=Grade.A},
};
context.Enrollments.AddRange(enrollments);
context.SaveChanges();
}
}
}Lập trình bất đồng bộ (Asynchronous Programming)
EF Core và ASP.NET Core sử dụng async theo mặc định để sử dụng tài nguyên hiệu quả hơn:
public async Task OnGetAsync()
{
Students = await _context.Students.ToListAsync();
}Điểm chú ý:
- Từ khóa
asynccho phép thực thi bất đồng bộ awaittạm dừng thực thi cho đến khi thao tác cơ sở dữ liệu hoàn thànhToListAsync()là phiên bản async củaToList()- Chỉ các câu lệnh gửi truy vấn đến cơ sở dữ liệu mới thực thi bất đồng bộ
- EF Core context không an toàn cho luồng (thread-safe)
Cân nhắc về hiệu suất
- Pagination (Phân trang): Giới hạn các hàng trả về bằng
Take():
public async Task OnGetAsync()
{
Students = await _context.Students.Take(10).ToListAsync();
}- Tránh Enumeration lớn: Các bảng lớn nên được phân trang để tránh các HTTP response bị cắt ngang khi xảy ra lỗi
- MaxModelBindingCollectionSize: Mặc định là 1024, có thể cấu hình qua settings
- Paging: Sẽ được đề cập trong các hướng dẫn sau
Xem cơ sở dữ liệu
Visual Studio: Sử dụng SQL Server Object Explorer (SSOX)
- View → SQL Server Object Explorer
- Điều hướng đến (localdb)\MSSQLLocalDB → Databases → SchoolContext
Visual Studio Code: Sử dụng DB Browser for SQLite
- Mở file
CU.dbvới công cụ SQLite của bạn
Layout của trang
File Pages/Shared/_Layout.cshtml bao gồm:
- Header với nhãn hiệu "Contoso University"
- Menu điều hướng cho: About, Students, Courses, Instructors, Departments
- Kiểu dáng Bootstrap
- Footer với thông tin bản quyền
Quá trình scaffolding tạo ra:
Pages/Students/Index.cshtml- Danh sách sinh viênPages/Students/Create.cshtml- Form tạo sinh viên mớiPages/Students/Edit.cshtml- Form chỉnh sửa sinh viênPages/Students/Delete.cshtml- Xác nhận xóaPages/Students/Details.cshtml- Xem chi tiết sinh viên
Các bước tiếp theo
Hướng dẫn này bao gồm nền tảng. Các hướng dẫn tiếp theo trong series đề cập:
- Các thao tác CRUD chi tiết
- Sắp xếp và lọc
- Dữ liệu liên quan
- Migrations (di chuyển cơ sở dữ liệu)
- Các tính năng nâng cao