Hướng dẫn: Bắt đầu với EF Core trong ứng dụng web ASP.NET MVC
> Nguồn: Tutorial: Get started with EF Core in an ASP.NET MVC web app
Bởi Tom Dykstra và Rick Anderson
Hướng dẫn này dạy ASP.NET Core MVC và Entity Framework (EF) Core với controllers (bộ điều khiển) và views (giao diện). Razor Pages là một mô hình lập trình thay thế. Đối với phát triển mới, chúng tôi khuyến nghị Razor Pages thay vì MVC với controllers và views. Xem phiên bản Razor Pages của hướng dẫn này.
Một số điều hướng dẫn MVC này có mà hướng dẫn Razor Pages không có:
- Triển khai kế thừa (inheritance) trong data model
- Thực hiện các truy vấn SQL thô (raw SQL queries)
- Sử dụng dynamic LINQ để đơn giản hóa code
Một số điều hướng dẫn Razor Pages có mà hướng dẫn này không có:
- Sử dụng phương thức Select để tải dữ liệu liên quan
- Các thực hành tốt nhất cho EF
Ứng dụng web mẫu Contoso University minh họa cách tạo ứng dụng web ASP.NET Core MVC bằng Entity Framework Core và Visual Studio.
Điều kiện tiên quyết
- Nếu bạn mới với ASP.NET Core MVC, hãy xem qua loạt hướng dẫn Get started with ASP.NET Core MVC trước.
- Visual Studio 2022 với workload ASP.NET and web development.
- .NET 6 SDK
Lưu ý: Hướng dẫn này chưa được cập nhật cho ASP.NET Core trong .NET 6 trở lên. Các hướng dẫn sẽ không hoạt động đúng nếu bạn tạo project nhắm đến ASP.NET Core trong .NET 6 trở lên. Chúng tôi khuyến nghị sử dụng .NET 5 SDK cho hướng dẫn này.
Công cụ database (Database engines)
Hướng dẫn Visual Studio sử dụng SQL Server LocalDB, một phiên bản SQL Server Express chỉ chạy trên Windows.
Giải quyết vấn đề
Nếu bạn gặp vấn đề không thể giải quyết, bạn thường có thể tìm giải pháp bằng cách so sánh code của mình với completed project (project hoàn chỉnh).
Mẹo: Đây là một loạt 10 hướng dẫn, mỗi hướng dẫn xây dựng dựa trên những gì đã thực hiện trong các hướng dẫn trước. Hãy lưu một bản sao của project sau mỗi lần hoàn thành hướng dẫn thành công.
Ứng dụng web Contoso University
Ứng dụng được xây dựng trong các hướng dẫn này là một website đại học cơ bản.
Người dùng có thể xem và cập nhật thông tin sinh viên, khóa học và giảng viên.
Tạo ứng dụng web
- Khởi động Visual Studio và chọn Create a new project.
- Trong hộp thoại Create a new project, chọn ASP.NET Core Web Application > Next.
- Trong hộp thoại Configure your new project, nhập
ContosoUniversitycho Project name. - Chọn Create.
- Trong hộp thoại Create a new ASP.NET Core web application, chọn:
- .NET Core và ASP.NET Core 5.0 trong các dropdown.
- ASP.NET Core Web App (Model-View-Controller).
- Create
Thiết lập phong cách trang web
Mở Views/Shared/_Layout.cshtml và thực hiện các thay đổi sau:
- Thay đổi mỗi lần xuất hiện của
ContosoUniversitythànhContoso University. Có ba lần xuất hiện. - Thêm các mục menu cho About, Students, Courses, Instructors và Departments, và xóa mục menu Privacy.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>@ViewData["Title"] - Contoso University</title>
<link rel="stylesheet" href="~/lib/bootstrap/dist/css/bootstrap.css" />
<link rel="stylesheet" href="~/css/site.css" />
</head>
<body>
<header>
<nav class="navbar navbar-expand-sm navbar-toggleable-sm navbar-light bg-white border-bottom box-shadow mb-3">
<div class="container">
<a class="navbar-brand" asp-area="" asp-controller="Home" asp-action="Index">Contoso University</a>
<div class="navbar-collapse collapse d-sm-inline-flex justify-content-between">
<ul class="navbar-nav flex-grow-1">
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="Index">Home</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Home" asp-action="About">About</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Students" asp-action="Index">Students</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Courses" asp-action="Index">Courses</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Instructors" asp-action="Index">Instructors</a>
</li>
<li class="nav-item">
<a class="nav-link text-dark" asp-area="" asp-controller="Departments" asp-action="Index">Departments</a>
</li>
</ul>
</div>
</div>
</nav>
</header>
<div class="container">
<main role="main" class="pb-3">
@RenderBody()
</main>
</div>
<footer class="border-top footer text-muted">
<div class="container">
© 2020 - Contoso University - <a asp-area="" asp-controller="Home" asp-action="Privacy">Privacy</a>
</div>
</footer>
<script src="~/lib/jquery/dist/jquery.js"></script>
<script src="~/lib/bootstrap/dist/js/bootstrap.bundle.js"></script>
<script src="~/js/site.js" asp-append-version="true"></script>
@await RenderSectionAsync("Scripts", required: false)
</body>
</html>Các gói NuGet EF Core
Hướng dẫn này sử dụng SQL Server, và gói provider (nhà cung cấp) là Microsoft.EntityFrameworkCore.SqlServer.
Thêm gói NuGet Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore. Trong Package Manager Console (PMC), nhập các lệnh sau:
Install-Package Microsoft.AspNetCore.Diagnostics.EntityFrameworkCore Install-Package Microsoft.EntityFrameworkCore.SqlServer
Tạo data model
Các lớp entity (thực thể) sau được tạo cho ứng dụng này:
- Mối quan hệ một-nhiều giữa các entity
StudentvàEnrollment - Mối quan hệ một-nhiều giữa các entity
CoursevàEnrollment
Entity Student (Sinh viên)
Trong thư mục Models, tạo lớp Student với code sau:
using System;
using System.Collections.Generic;
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; }
}
}Thuộc tính ID là cột khóa chính (primary key - PK) của bảng database. Mặc định, EF diễn giải một thuộc tính có tên ID hoặc classnameID là khóa chính.
Thuộc tính Enrollments là một navigation property (thuộc tính điều hướng). Navigation properties chứa các entity khác có liên quan đến entity này.
Entity Enrollment (Đăng ký)
Trong thư mục Models, tạo lớp Enrollment với code sau:
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; }
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}Thuộc tính EnrollmentID là PK. Entity này sử dụng mẫu classnameID thay vì chỉ ID.
Thuộc tính Grade là một enum. Dấu ? sau khai báo kiểu Grade cho biết thuộc tính Grade có thể null (nullable). Grade null khác với grade bằng 0 - null có nghĩa là grade chưa được biết hoặc chưa được gán.
Thuộc tính StudentID là khóa ngoại (foreign key - FK), và navigation property tương ứng là Student.
Entity Course (Khóa học)
Trong thư mục Models, tạo lớp Course với code sau:
using System.Collections.Generic;
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; }
}
}Thuộc tính Enrollments là một navigation property. Một entity Course có thể liên quan đến nhiều entity Enrollment.
Thuộc tính [DatabaseGenerated] cho phép nhập PK cho khóa học thay vì để database tạo ra.
Tạo database context (ngữ cảnh database)
Lớp chính phối hợp chức năng EF cho một data model nhất định là lớp database context DbContext. Lớp này được tạo bằng cách kế thừa từ lớp Microsoft.EntityFrameworkCore.DbContext. Trong project này, lớp được đặt tên là SchoolContext.
Trong thư mục project, tạo một thư mục có tên Data.
Trong thư mục Data, tạo lớp SchoolContext với code sau:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
}
}
}Code này tạo một thuộc tính DbSet cho mỗi entity set (tập thực thể). Trong thuật ngữ EF:
- Một entity set thường tương ứng với một bảng database.
- Một entity tương ứng với một hàng trong bảng.
Đăng ký SchoolContext
ASP.NET Core bao gồm dependency injection (DI - tiêm phụ thuộc). Các service như EF database context được đăng ký với DI trong quá trình khởi động ứng dụng.
Để đăng ký SchoolContext là service, mở Startup.cs và thêm các dòng được tô sáng vào phương thức ConfigureServices:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<SchoolContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddControllersWithViews();
}Mở file appsettings.json và thêm connection string (chuỗi kết nối) như trong ví dụ sau:
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity1;Trusted_Connection=True;MultipleActiveResultSets=true"
},
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*"
}SQL Server Express LocalDB
Connection string chỉ định database SQL Server LocalDB. LocalDB là phiên bản nhẹ của SQL Server Express Database Engine và dành cho phát triển ứng dụng, không phải sử dụng production. Mặc định, LocalDB tạo các file database .mdf trong thư mục C:/Users/<user>.
Khởi tạo DB với dữ liệu test
EF tạo một database trống. Trong phần này, một phương thức được thêm vào được gọi sau khi database được tạo để điền dữ liệu test vào đó.
Trong thư mục Data, tạo lớp mới có tên DbInitializer với code sau:
using ContosoUniversity.Models;
using System;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student{FirstMidName="Carson",LastName="Alexander",EnrollmentDate=DateTime.Parse("2005-09-01")},
new Student{FirstMidName="Meredith",LastName="Alonso",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Arturo",LastName="Anand",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Gytis",LastName="Barzdukas",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Yan",LastName="Li",EnrollmentDate=DateTime.Parse("2002-09-01")},
new Student{FirstMidName="Peggy",LastName="Justice",EnrollmentDate=DateTime.Parse("2001-09-01")},
new Student{FirstMidName="Laura",LastName="Norman",EnrollmentDate=DateTime.Parse("2003-09-01")},
new Student{FirstMidName="Nino",LastName="Olivetto",EnrollmentDate=DateTime.Parse("2005-09-01")}
};
foreach (Student s in students)
{
context.Students.Add(s);
}
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}
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
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},
};
foreach (Enrollment e in enrollments)
{
context.Enrollments.Add(e);
}
context.SaveChanges();
}
}
}Cập nhật Program.cs với code sau:
using ContosoUniversity.Data;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System;
namespace ContosoUniversity
{
public class Program
{
public static void Main(string[] args)
{
var host = CreateHostBuilder(args).Build();
CreateDbIfNotExists(host);
host.Run();
}
private static void CreateDbIfNotExists(IHost host)
{
using (var scope = host.Services.CreateScope())
{
var services = scope.ServiceProvider;
try
{
var context = services.GetRequiredService<SchoolContext>();
DbInitializer.Initialize(context);
}
catch (Exception ex)
{
var logger = services.GetRequiredService<ILogger<Program>>();
logger.LogError(ex, "An error occurred creating the DB.");
}
}
}
public static IHostBuilder CreateHostBuilder(string[] args) =>
Host.CreateDefaultBuilder(args)
.ConfigureWebHostDefaults(webBuilder =>
{
webBuilder.UseStartup<Startup>();
});
}
}Tạo controller và views
Sử dụng scaffolding engine (công cụ tạo scaffolding) trong Visual Studio để thêm MVC controller và views sử dụng EF để truy vấn và lưu dữ liệu.
- Trong Solution Explorer, nhấp chuột phải vào thư mục
Controllersvà chọn Add > New Scaffolded Item. - Trong hộp thoại Add Scaffold:
- Chọn MVC controller with views, using Entity Framework.
- Click Add.
- Trong Model class, chọn Student.
- Trong Data context class, chọn SchoolContext.
- Chấp nhận tên mặc định StudentsController.
- Click Add.
Controller nhận SchoolContext như một tham số constructor:
namespace ContosoUniversity.Controllers
{
public class StudentsController : Controller
{
private readonly SchoolContext _context;
public StudentsController(SchoolContext context)
{
_context = context;
}ASP.NET Core DI đảm nhiệm việc truyền một instance của SchoolContext vào controller.
Xem database
Khi ứng dụng được khởi động, phương thức DbInitializer.Initialize gọi EnsureCreated. EF thấy không có database và tạo một database, sau đó code phương thức Initialize điền database với dữ liệu.
Sử dụng SQL Server Object Explorer (SSOX) để xem database trong Visual Studio:
- Chọn SQL Server Object Explorer từ menu View trong Visual Studio.
- Trong SSOX, chọn (localdb)\MSSQLLocalDB > Databases.
- Chọn
ContosoUniversity1, mục nhập cho tên database trong connection string trong fileappsettings.json. - Mở rộng node Tables để xem các bảng trong database.
Conventions (Quy ước)
Lượng code cần viết để EF có thể tạo ra một database hoàn chỉnh là tối thiểu nhờ việc sử dụng các conventions mà EF dùng:
- Tên của các thuộc tính
DbSetđược sử dụng làm tên bảng. - Tên thuộc tính entity được sử dụng cho tên cột.
- Các thuộc tính entity có tên
IDhoặcclassnameIDđược nhận dạng là thuộc tính PK. - Thuộc tính được diễn giải là thuộc tính FK nếu tên là
<navigation property name><PK property name>.
Hành vi conventional có thể bị ghi đè. Ví dụ, tên bảng có thể được chỉ định rõ ràng như đã thấy trước đó trong hướng dẫn này.
Lập trình bất đồng bộ (Asynchronous code)
Lập trình bất đồng bộ là chế độ mặc định cho ASP.NET Core và EF Core.
Máy chủ web có số lượng threads (luồng) có hạn. Với code đồng bộ (synchronous), nhiều threads có thể bị chiếm dụng trong khi chờ I/O hoàn thành. Với code bất đồng bộ, khi một tiến trình đang chờ I/O hoàn thành, thread của nó được giải phóng để server sử dụng xử lý các yêu cầu khác.
Trong code sau, async, Task<T>, await và ToListAsync làm cho code thực thi bất đồng bộ:
public async Task<IActionResult> Index()
{
return View(await _context.Students.ToListAsync());
}- Từ khóa
asyncyêu cầu trình biên dịch tạo callbacks cho các phần của thân phương thức. - Kiểu trả về
Task<IActionResult>đại diện cho công việc đang diễn ra với kết quả kiểuIActionResult. - Từ khóa
awaitkhiến trình biên dịch chia phương thức thành hai phần. ToListAsynclà phiên bản bất đồng bộ của phương thức mở rộngToList.
Ghi log SQL của Entity Framework Core
Cấu hình logging (ghi nhật ký) thường được cung cấp bởi phần Logging của các file appsettings.{Environment}.json. Để ghi log các câu lệnh SQL, thêm "Microsoft.EntityFrameworkCore.Database.Command": "Information" vào file appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information",
"Microsoft.EntityFrameworkCore.Database.Command": "Information"
}
}
}Chuyển sang hướng dẫn tiếp theo để tìm hiểu cách thực hiện các thao tác CRUD (create, read, update, delete) cơ bản.