Nguon: Microsoft Learn · .NET 8.0

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 DykstraRick 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ó:

Một số điều hướng dẫn Razor Pages có mà hướng dẫn này không có:

Ứ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

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

  1. Khởi động Visual Studio và chọn Create a new project.
  2. Trong hộp thoại Create a new project, chọn ASP.NET Core Web Application > Next.
  3. Trong hộp thoại Configure your new project, nhập ContosoUniversity cho Project name.
  4. Chọn Create.
  5. Trong hộp thoại Create a new ASP.NET Core web application, chọn:
  6. .NET CoreASP.NET Core 5.0 trong các dropdown.
  7. ASP.NET Core Web App (Model-View-Controller).
  8. 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:

cshtml
<!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">
            &copy; 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:

powershell
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:

Entity Student (Sinh viên)

Trong thư mục Models, tạo lớp Student với code sau:

csharp
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:

csharp
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:

csharp
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:

csharp
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:

Đă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:

csharp
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:

json
{
  "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:

csharp
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:

csharp
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.

Controller nhận SchoolContext như một tham số constructor:

csharp
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:

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:

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>, awaitToListAsync làm cho code thực thi bất đồng bộ:

csharp
public async Task<IActionResult> Index()
{
    return View(await _context.Students.ToListAsync());
}

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:

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.