ASP.NET Core: Tạo mô hình dữ liệu phức tạp
Trong bài viết này
- Điều kiện tiên quyết
- Tùy chỉnh mô hình dữ liệu
- Thay đổi đối với thực thể Student
- Tạo thực thể Instructor
- Tạo thực thể OfficeAssignment
- Sửa đổi thực thể Course
- Tạo thực thể Department
- Sửa đổi thực thể Enrollment
- Mối quan hệ nhiều-nhiều
- Thực thể CourseAssignment
- Cập nhật ngữ cảnh cơ sở dữ liệu
- Giới thiệu về một giải pháp thay thế API thông thạo
- Sơ đồ thực thể thể hiện mối quan hệ
- Cơ sở dữ liệu hạt giống (seed) với dữ liệu thử nghiệm
- Thêm migration
- Thay đổi chuỗi kết nối
- Cập nhật cơ sở dữ liệu
- Lấy mã
- Bước tiếp theo
Trong các hướng dẫn trước, bạn đã làm việc với một mô hình dữ liệu đơn giản bao gồm ba thực thể. Trong hướng dẫn này, bạn sẽ thêm nhiều thực thể và mối quan hệ hơn, đồng thời bạn sẽ tùy chỉnh mô hình dữ liệu bằng cách chỉ định các quy tắc định dạng, xác thực và ánh xạ cơ sở dữ liệu.
Khi bạn hoàn tất, các lớp thực thể sẽ tạo thành mô hình dữ liệu hoàn chỉnh như minh họa sau:
Trong hướng dẫn này, bạn:
- Tùy chỉnh mô hình dữ liệu
- Thực hiện các thay đổi đối với thực thể Student
- Tạo thực thể Instructor
- Tạo thực thể OfficeAssignment
- Sửa đổi thực thể Course
- Tạo thực thể Department
- Sửa đổi thực thể Enrollment
- Cập nhật ngữ cảnh cơ sở dữ liệu
- Cơ sở dữ liệu hạt giống (seed) với dữ liệu thử nghiệm
- Thêm migration
- Thay đổi chuỗi kết nối
- Cập nhật cơ sở dữ liệu
Điều kiện tiên quyết
Tùy chỉnh mô hình dữ liệu
Trong phần này, bạn sẽ thấy cách tùy chỉnh mô hình dữ liệu bằng cách sử dụng các attribute chỉ định các quy tắc định dạng, xác thực và ánh xạ cơ sở dữ liệu. Sau đó, trong một số phần sau, bạn sẽ tạo mô hình dữ liệu School hoàn chỉnh bằng cách thêm attribute vào các lớp bạn đã tạo và tạo lớp mới cho các loại thực thể còn lại trong mô hình.
Attribute DataType
Đối với ngày nhập học của sinh viên, tất cả các trang web hiện đang hiển thị thời gian cùng với ngày, mặc dù tất cả những gì bạn quan tâm đối với trường này là ngày. Bằng cách sử dụng các attribute chú thích dữ liệu (data annotation), bạn có thể thực hiện một thay đổi mã sẽ sửa định dạng hiển thị trong mọi view hiển thị dữ liệu. Để xem ví dụ về cách thực hiện điều đó, bạn sẽ thêm attribute vào property EnrollmentDate
trong lớp Student
.
Trong Models/Student.cs
, hãy thêm một câu lệnh using
cho namespace cho System.ComponentModel.DataAnnotations
và thêm các attribute DataType
và DisplayFormat
cho property EnrollmentDate
, như trong ví dụ sau:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Attribute DataType
được sử dụng để chỉ định loại dữ liệu cụ thể hơn loại nội tại của cơ sở dữ liệu. Trong trường hợp này, ta chỉ muốn theo dõi ngày chứ không phải ngày và giờ. Bảng DataType
liệt kê cung cấp nhiều loại dữ liệu, chẳng hạn như Ngày, Giờ, Số điện thoại, Tiền tệ, Địa chỉ Email, v.v. Attribute DataType
cũng có thể cho phép ứng dụng tự động cung cấp các tính năng dành riêng cho từng loại. Ví dụ: mailto:
có thể tạo liên kết cho DataType.EmailAddress
và có thể cung cấp bộ chọn ngày DataType.Date
trong các trình duyệt hỗ trợ HTML5. Attribute DataType
phát ra các attribute HTML 5 data-
(dấu gạch ngang dữ liệu được phát âm) mà trình duyệt HTML 5 có thể hiểu được. Các attribute DataType
không cung cấp bất kỳ xác nhận nào.
DataType.Date
không chỉ định định dạng của ngày được hiển thị. Theo mặc định, trường dữ liệu được hiển thị theo định dạng mặc định dựa trên CultureInfo của máy chủ.
Attribute DisplayFormat
được sử dụng để chỉ định rõ ràng định dạng ngày:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Cài đặt ApplyFormatInEditMode
chỉ định rằng định dạng cũng sẽ được áp dụng khi giá trị được hiển thị trong hộp văn bản để chỉnh sửa. (Bạn có thể không muốn điều đó đối với một số trường -- ví dụ: đối với giá trị tiền tệ, bạn có thể không muốn ký hiệu tiền tệ trong hộp văn bản để chỉnh sửa.)
Bạn có thể sử dụng chính attribute DisplayFormat
đó, nhưng nói chung bạn cũng nên sử dụng attribute DataType
. Attribute DataType
truyền tải ngữ nghĩa của dữ liệu thay vì cách hiển thị dữ liệu trên màn hình và cung cấp các lợi ích sau mà bạn không nhận được DisplayFormat
:
-
Trình duyệt có thể kích hoạt các tính năng HTML5 (ví dụ: để hiển thị điều khiển lịch, ký hiệu tiền tệ phù hợp với địa phương, liên kết email, một số xác thực đầu vào phía máy khách, v.v.).
-
Theo mặc định, trình duyệt sẽ hiển thị dữ liệu bằng định dạng chính xác dựa trên ngôn ngữ của bạn.
Để biết thêm thông tin, hãy xem tài liệu trợ giúp thẻ <input>.
Chạy ứng dụng, đi tới trang Students Index và nhận thấy rằng thời gian không còn được hiển thị cho các ngày đăng ký. Điều này cũng đúng với bất kỳ view nào sử dụng model Student.
Attribute StringLength
Bạn cũng có thể chỉ định các quy tắc xác thực dữ liệu và thông báo lỗi xác thực bằng cách sử dụng các attribute. Attribute StringLength
đặt độ dài tối đa trong cơ sở dữ liệu và cung cấp xác thực phía máy khách và phía máy chủ cho ASP.NET Core MVC. Bạn cũng có thể chỉ định độ dài chuỗi tối thiểu trong attribute này, nhưng giá trị tối thiểu không ảnh hưởng đến lược đồ cơ sở dữ liệu.
Giả sử bạn muốn đảm bảo rằng người dùng không nhập quá 50 ký tự cho một tên. Để thêm giới hạn này, hãy thêm attribute StringLength
vào attribute LastName
và FirstMidName
, như trong ví dụ sau:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Attribute StringLength
sẽ không ngăn người dùng nhập khoảng trắng cho tên. Bạn có thể sử dụng attribute RegularExpression
để áp dụng các hạn chế cho đầu vào. Ví dụ: đoạn mã sau yêu cầu ký tự đầu tiên phải viết hoa và các ký tự còn lại là chữ cái:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Attribute MaxLength
chức năng tương tự như attribute StringLength
nhưng không cung cấp xác thực phía máy khách.
Mô hình cơ sở dữ liệu hiện đã thay đổi theo cách đòi hỏi phải thay đổi lược đồ cơ sở dữ liệu. Bạn sẽ sử dụng migration để cập nhật lược đồ mà không làm mất bất kỳ dữ liệu nào mà bạn có thể đã thêm vào cơ sở dữ liệu bằng cách sử dụng giao diện người dùng ứng dụng.
Lưu các thay đổi của bạn và xây dựng dự án. Sau đó mở cửa sổ lệnh trong thư mục dự án và nhập các lệnh sau:
dotnet ef migrations add MaxLengthOnNames
dotnet ef database update
Lệnh migrations add
cảnh báo rằng có thể xảy ra mất dữ liệu vì thay đổi này làm cho độ dài tối đa của hai cột trở nên ngắn hơn. Migration sẽ tạo một tệp có tên <timeStamp>_MaxLengthOnNames.cs
. Tệp này chứa mã trong phương thức Up
sẽ cập nhật cơ sở dữ liệu để khớp với mô hình dữ liệu hiện tại. Lệnh database update
sẽ chạy mã đó.
Dấu thời gian có tiền tố trước tên tệp migration được Entity Framework sử dụng để ra lệnh migration. Bạn có thể tạo nhiều lần migration trước khi chạy lệnh cập nhật cơ sở dữ liệu và sau đó tất cả các lần migration sẽ được áp dụng theo thứ tự chúng được tạo.
Chạy ứng dụng, chọn tab Students, nhấp vào Create New và thử nhập một trong hai tên dài hơn 50 ký tự. Ứng dụng sẽ ngăn bạn thực hiện việc này.
Attribute Column
Bạn cũng có thể sử dụng các attribute để kiểm soát cách các class và property của bạn được ánh xạ tới cơ sở dữ liệu. Giả sử bạn đã sử dụng tên FirstMidName
cho trường first-name vì trường này cũng có thể chứa tên đệm. Tuy nhiên, bạn muốn cột cơ sở dữ liệu được đặt tên FirstName
vì người dùng sẽ viết các truy vấn đặc biệt đối với cơ sở dữ liệu đã quen với tên đó. Để thực hiện ánh xạ này, bạn có thể sử dụng attribute Column
.
Attribute Column
chỉ định rằng khi cơ sở dữ liệu được tạo, cột của bảng Student
ánh xạ tới property FirstMidName
sẽ được đặt tên FirstName
. Nói cách khác, khi mã của bạn tham chiếu đến Student.FirstMidName
, dữ liệu sẽ đến từ hoặc được cập nhật trong cột FirstName
của bảng Student
. Nếu bạn không chỉ định tên cột, chúng sẽ có cùng tên với tên property.
Trong file Student.cs
, hãy thêm câu lệnh using
System.ComponentModel.DataAnnotations.Schema
và thêm attribute tên cột vào property FirstMidName
, như minh họa trong mã được đánh dấu sau:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Việc bổ sung attribute Column
sẽ thay đổi mô hình sao lưu SchoolContext
, vì vậy nó sẽ không khớp với cơ sở dữ liệu.
Lưu các thay đổi của bạn và build dự án. Sau đó mở cửa sổ lệnh trong thư mục dự án và nhập các lệnh sau để tạo một lần migration khác:
dotnet ef migrations add ColumnFirstName
dotnet ef database update
Trong SQL Server Object Explorer, hãy mở trình thiết kế bảng Student bằng cách bấm đúp vào bảng Students.
Trước khi bạn áp dụng hai lần migration đầu tiên, các cột tên thuộc loại nvarchar(MAX). Bây giờ chúng là nvarchar(50) và tên cột đã thay đổi từ FirstMidName thành FirstName.
Ghi chú
Nếu bạn cố gắng biên dịch trước khi hoàn thành việc tạo tất cả các lớp thực thể trong các phần sau, bạn có thể gặp lỗi trình biên dịch.
Thay đổi đối với thực thể Student
Trong Models/Student.cs
, thay thế mã bạn đã thêm trước đó bằng mã sau. Những thay đổi được đánh dấu.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50)]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Attribute Required
Attribute Required
tạo ra các trường bắt buộc property tên. Required
không cần thiết cho các kiểu non-nullable, chẳng hạn như loại giá trị (DateTime, int, double, float, v.v.). Các kiểu non-nullable sẽ tự động được coi là trường bắt buộc.
Attribute Required
phải được sử dụng với MinimumLength
để MinimumLength
được thực thi.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
Attribute Display
Attribute Display
chỉ định rằng chú thích cho các hộp văn bản phải là "First Name", "Last Name", "Full Name" và "Enrollment Date" thay vì tên property trong mỗi trường hợp (không có khoảng cách phân chia các từ).
Property tính toán FullName
FullName
là property được tính toán trả về giá trị được tạo bằng cách ghép hai property khác. Do đó, nó chỉ có một trình truy cập get và FullName
sẽ không có cột nào được tạo trong cơ sở dữ liệu.
Tạo thực thể Instructor
Tạo Models/Instructor.cs
, thay thế mã mẫu bằng mã sau:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<CourseAssignment> CourseAssignments { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
Lưu ý rằng một số property giống nhau trong thực thể Student và Instructor. Trong hướng dẫn Thực hiện Kế thừa ở phần sau của loạt bài này, bạn sẽ cấu trúc lại mã này để loại bỏ sự dư thừa.
Bạn có thể đặt nhiều attribute trên một dòng, vì vậy bạn cũng có thể viết các attribute HireDate
như sau:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Các property điều hướng CourseAssignments và OfficeAssignment
Các property CourseAssignments
và OfficeAssignment
là các property điều hướng.
Một giảng viên có thể dạy bất kỳ số lượng khóa học nào, do đó CourseAssignments
được định nghĩa là một collection.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Nếu property điều hướng có thể chứa nhiều thực thể thì loại của nó phải là danh sách trong đó các mục có thể được thêm, xóa và cập nhật. Bạn có thể chỉ định ICollection<T>
hoặc một loại như List<T>
hoặc HashSet<T>
. Nếu bạn chỉ định ICollection<T>
, EF sẽ tạo một collection HashSet<T>
theo mặc định.
Lý do tại sao đây là các thực thể CourseAssignment
thì sẽ được giải thích bên dưới trong phần về mối quan hệ nhiều-nhiều.
Các quy tắc nghiệp vụ của Đại học Contoso nêu rõ rằng giảng viên chỉ có thể có nhiều nhất một văn phòng, do đó property OfficeAssignment
chứa một thực thể OfficeAssignment duy nhất (có thể rỗng nếu không có văn phòng nào được chỉ định).
public OfficeAssignment OfficeAssignment { get; set; }
Tạo thực thể OfficeAssignment
Tạo Models/OfficeAssignment.cs
với đoạn mã sau:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor Instructor { get; set; }
}
}
Attribute Key
Có mối quan hệ one-to-zero-or-one giữa các thực thể Instructor
và các thực thể OfficeAssignment
. Một nhiệm vụ văn phòng chỉ tồn tại trong mối quan hệ với giảng viên mà nó được giao và do đó, khóa chính của nó cũng là khóa ngoại đối với thực thể Instructor
. Nhưng Entity Framework không thể tự động nhận dạng khóa chính InstructorID
của thực thể này vì tên của nó không tuân theo quy ước đặt tên ID
hoặc classnameID
. Do đó, attribute Key
được sử dụng để xác định nó là khóa:
[Key]
public int InstructorID { get; set; }
Bạn cũng có thể sử dụng attribute Key
nếu thực thể có khóa chính riêng nhưng bạn muốn đặt tên property khác với classnameID hoặc ID.
Theo mặc định, EF coi khóa là không được tạo bởi cơ sở dữ liệu vì cột dành cho mối quan hệ xác định.
Property điều hướng Instructor
Thực thể Instructor có property điều hướng OfficeAssignment
có thể rỗng (vì giảng viên có thể không có nhiệm vụ văn phòng) và thực thể OfficeAssignment có property điều hướng Instructor
không thể rỗng (vì nhiệm vụ văn phòng không thể tồn tại nếu không có giảng viên -- InstructorID
là không thể rỗng). Khi một thực thể Instructor có một thực thể OfficeAssignment liên quan, mỗi thực thể sẽ có một tham chiếu đến thực thể kia trong property điều hướng của nó.
Bạn có thể đặt một attribute [Required]
trên property điều hướng Instructor để chỉ định rằng phải có giảng viên liên quan, nhưng bạn không cần phải làm điều đó vì khóa ngoại InstructorID
(cũng là khóa của bảng này) là không thể rỗng.
Sửa đổi thực thể Course
Trong Models/Course.cs
, thay thế mã bạn đã thêm trước đó bằng mã sau. Những thay đổi được đánh dấu.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}
Thực thể Course có property khóa ngoại DepartmentID
trỏ đến thực thể Department liên quan và nó có property điều hướng Department
.
Entity Framework không yêu cầu bạn thêm property khóa ngoại vào mô hình dữ liệu của mình khi bạn có property điều hướng cho thực thể liên quan. EF tự động tạo các khóa ngoại trong cơ sở dữ liệu ở bất cứ nơi nào cần thiết và tạo property shadow cho chúng. Nhưng việc có khóa ngoại trong mô hình dữ liệu có thể giúp việc cập nhật trở nên đơn giản và hiệu quả hơn. Ví dụ: khi bạn tìm nạp một thực thể Course
để chỉnh sửa, thì thực thể Department
đó sẽ rỗng nếu bạn không tải nó, vì vậy khi cập nhật thực thể Course
, trước tiên bạn phải tìm nạp thực thể Department
. Khi property khóa ngoại DepartmentID
được đưa vào mô hình dữ liệu, bạn không cần tìm nạp thực thể Department
trước khi cập nhật.
Attribute DatabaseGenerated
Attribute DatabaseGenerated
có tham số None
trên property CourseID
chỉ định rằng các giá trị khóa chính được cung cấp bởi người dùng chứ không phải do cơ sở dữ liệu tạo ra.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Theo mặc định, Entity Framework giả định rằng các giá trị khóa chính được cơ sở dữ liệu tạo ra. Đó là những gì bạn muốn trong hầu hết các kịch bản. Tuy nhiên, đối với các thực thể Course
, bạn sẽ sử dụng số khóa học do người dùng chỉ định, chẳng hạn như chuỗi 1000 cho một bộ phận, chuỗi 2000 cho bộ phận khác, v.v.
Attribute DatabaseGenerated
cũng có thể được sử dụng để tạo ra các giá trị mặc định, như trong trường hợp các cột cơ sở dữ liệu được sử dụng để ghi lại ngày một hàng được tạo hoặc cập nhật. Để biết thêm thông tin, hãy xem Property Generated.
Các property khóa ngoại và điều hướng
Các property khóa ngoại và thuộc tính điều hướng trong thực thể Course
phản ánh các mối quan hệ sau:
Một khóa học được chỉ định cho một bộ phận nên sẽ có khóa ngoại DepartmentID
và property điều hướng Department
vì những lý do đã đề cập ở trên.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Một khóa học có thể có số lượng sinh viên đăng ký tham gia tùy ý, vì vậy property điều hướng Enrollments
là một collection:
public ICollection<Enrollment> Enrollments { get; set; }
Một khóa học có thể được giảng dạy bởi nhiều giảng viên, do đó property điều hướng CourseAssignments
là một collection (kiểu CourseAssignment
sẽ được giải thích sau ):
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Tạo thực thể Department
Tạo Models/Department.cs
với đoạn mã sau:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Attribute Column
Ở trên bạn đã sử dụng attribute Column
để thay đổi ánh xạ tên cột. Trong mã dành cho thực thể Department
, attribute Column
đang được sử dụng để thay đổi ánh xạ kiểu dữ liệu SQL để cột sẽ được xác định bằng cách sử dụng kiểu money
SQL Server trong cơ sở dữ liệu:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Ánh xạ cột thường không bắt buộc vì Entity Framework chọn kiểu dữ liệu SQL Server thích hợp dựa trên loại CLR mà bạn xác định cho property. Loại CLR decimal
ánh xạ tới loại SQL Server decimal
. Nhưng trong trường hợp này, bạn biết rằng cột sẽ chứa số lượng tiền tệ và kiểu dữ liệu tiền phù hợp hơn cho điều đó.
Các property khóa ngoại và điều hướng
Các property khóa ngoại và điều hướng phản ánh các mối quan hệ sau:
Một department có thể có hoặc không có quản trị viên và quản trị viên luôn là người hướng dẫn. Do đó, property InstructorID
được đưa vào làm khóa ngoại cho thực thể Instructor và một dấu chấm hỏi được thêm vào sau chỉ định kiểu int
để đánh dấu property là nullable. Property điều hướng được đặt tên Administrator
nhưng chứa thực thể Instructor:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Một khoa có thể có nhiều khóa học nên có thuộc tính điều hướng Courses:
public ICollection<Course> Courses { get; set; }
Ghi chú
Theo quy ước, Entity Framework cho phép xóa theo tầng đối với các khóa ngoại không thể rỗng và đối với các mối quan hệ nhiều-nhiều. Điều này có thể dẫn đến các quy tắc xóa tầng vòng tròn, điều này sẽ gây ra ngoại lệ khi bạn cố gắng thêm migration. Ví dụ: nếu bạn không định nghĩa property Department.InstructorID
là null, thì EF sẽ định cấu hình quy tắc xóa theo tầng để xóa khoa khi bạn xóa giảng viên, đây không phải là điều bạn muốn xảy ra. Nếu quy tắc nghiệp vụ của bạn yêu cầu property InstructorID
không thể rỗng, thì bạn sẽ phải sử dụng câu lệnh API trôi chảy sau đây để vô hiệu hóa tính năng xóa tầng trên mối quan hệ:
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Sửa đổi thực thể Enrollment
Trong Models/Enrollment.cs
, thay thế mã bạn đã thêm trước đó bằng mã sau:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
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; }
}
}
Property khóa ngoại và điều hướng
Property khóa ngoại và property điều hướng phản ánh các mối quan hệ sau:
Bản ghi đăng ký dành cho một khóa học duy nhất, do đó có property khóa ngoại CourseID
và property điều hướng Course
:
public int CourseID { get; set; }
public Course Course { get; set; }
Bản ghi đăng ký dành cho một sinh viên, do đó có property khóa ngoại StudentID
và property điều hướng Student
:
public int StudentID { get; set; }
public Student Student { get; set; }
Mối quan hệ nhiều-nhiều
Có mối quan hệ nhiều-nhiều giữa các thực thể Student
và thực thể Course
, và thực thể Enrollment
hoạt động như một bảng nối nhiều-nhiều với tải trọng (payload) trong cơ sở dữ liệu. "Có tải trọng" nghĩa là bảng Enrollment
chứa dữ liệu bổ sung ngoài khóa ngoại cho các bảng đã nối (trong trường hợp này là khóa chính và property Grade
).
Hình minh họa sau đây cho thấy những mối quan hệ này trông như thế nào trong sơ đồ thực thể. (Sơ đồ này được tạo bằng cách sử dụng Entity Framework Power Tools cho EF 6.x; việc tạo sơ đồ không phải là một phần của hướng dẫn, nó chỉ được sử dụng ở đây như một minh họa.)
Mỗi dòng mối quan hệ có số 1 ở một đầu và dấu hoa thị (*) ở đầu kia, biểu thị mối quan hệ một-nhiều.
Nếu bảng Enrollment
không bao gồm thông tin điểm thì nó chỉ cần chứa hai khóa ngoại CourseID
và StudentID
. Trong trường hợp đó, đó sẽ là bảng nối nhiều-nhiều không có tải trọng (hoặc bảng nối thuần túy) trong cơ sở dữ liệu. Các thực thể Instructor
và Course
có loại mối quan hệ nhiều-nhiều và bước tiếp theo của bạn là tạo một lớp thực thể để hoạt động như một bảng nối mà không cần tải trọng.
EF Core hỗ trợ các bảng nối ngầm cho các mối quan hệ nhiều-nhiều, nhưng hướng dẫn này chưa được cập nhật để sử dụng bảng nối ngầm. Xem Mối quan hệ Nhiều-nhiều, phiên bản Razor Pages của hướng dẫn này đã được cập nhật.
Thực thể CourseAssignment
Tạo Models/CourseAssignment.cs
với đoạn mã sau:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}
Nối tên các thực thể
Cần có một bảng nối trong cơ sở dữ liệu cho mối quan hệ nhiều-nhiều giữa Instructor với Courses và nó phải được thể hiện bằng một tập thực thể. Người ta thường đặt tên cho một thực thể nối theo dạng EntityName1EntityName2
, trong trường hợp này là CourseInstructor
. Tuy nhiên, bạn nên chọn tên mô tả mối quan hệ. Các mô hình dữ liệu bắt đầu đơn giản và phát triển, với các kết nối không tải trọng thường xuyên nhận được tải trọng sau này. Nếu bạn bắt đầu bằng tên thực thể mang tính mô tả, bạn sẽ không phải thay đổi tên sau này. Lý tưởng nhất là thực thể tham gia sẽ có tên tự nhiên (có thể là một từ) riêng trong miền nghiệp vụ. Ví dụ: Books và Customers có thể được liên kết thông qua Ratings. Đối với mối quan hệ này, CourseAssignment
là một lựa chọn tốt hơn CourseInstructor
.
Khóa tổng hợp
Vì các khóa ngoại không thể rỗng và cùng nhau xác định duy nhất từng hàng của bảng nên không cần có khóa chính riêng. Các property InstructorID
và CourseID
sẽ hoạt động như một khóa chính tổng hợp. Cách duy nhất để xác định các khóa chính tổng hợp cho EF là sử dụng API thông thạo (fluent API) (không thể thực hiện được bằng cách sử dụng attribute). Bạn sẽ xem cách định cấu hình khóa chính tổng hợp trong phần tiếp theo.
Khóa tổng hợp đảm bảo rằng mặc dù bạn có thể có nhiều hàng cho một khóa học và nhiều hàng cho một giảng viên nhưng bạn không thể có nhiều hàng cho cùng một giảng viên và khóa học. Thực thể liên kết Enrollment
xác định khóa chính của riêng nó, vì vậy có thể có các bản sao thuộc loại này. Để ngăn chặn những sự trùng lặp như vậy, bạn có thể thêm chỉ mục duy nhất trên các trường khóa ngoại hoặc định cấu hình Enrollment
bằng khóa tổng hợp chính tương tự như CourseAssignment
. Để biết thêm thông tin, hãy xem Chỉ mục.
Cập nhật ngữ cảnh cơ sở dữ liệu
Thêm mã được đánh dấu sau vào file Data/SchoolContext.cs
:
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; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Mã này thêm các thực thể mới và định cấu hình khóa chính tổng hợp của thực thể CourseAssignment.
Giới thiệu về một giải pháp thay thế API thông thạo
Mã trong phương thức OnModelCreating
của lớp DbContext
sử dụng API thông thạo để định cấu hình hành vi của EF. API được gọi là "thông thạo" vì nó thường được sử dụng bằng cách xâu chuỗi một loạt lệnh gọi phương thức lại với nhau thành một câu lệnh, như trong ví dụ này từ tài liệu EF Core:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
Trong hướng dẫn này, bạn chỉ sử dụng API thông thạo để ánh xạ cơ sở dữ liệu mà bạn không thể thực hiện với các attribute. Tuy nhiên, bạn cũng có thể sử dụng API thông thạo để chỉ định hầu hết các quy tắc định dạng, xác thực và ánh xạ mà bạn có thể thực hiện bằng cách sử dụng attribute. Một số attribute như MinimumLength
không thể áp dụng được với API thông thạo. Như đã đề cập trước đó, MinimumLength
không thay đổi lược đồ, nó chỉ áp dụng quy tắc xác thực phía máy khách và máy chủ.
Một số nhà phát triển thích sử dụng riêng API thông thạo để họ có thể giữ cho các lớp thực thể của mình "sạch - clean". Bạn có thể kết hợp các attribute và API thông thạo nếu muốn và có một số tùy chỉnh chỉ có thể được thực hiện bằng cách sử dụng API thông thạo, nhưng nói chung, cách thực hành được khuyến nghị là chọn một trong hai phương pháp này và sử dụng phương pháp đó một cách nhất quán nhất có thể. Nếu bạn sử dụng cả hai, hãy lưu ý rằng bất cứ khi nào có xung đột, API thông thạo sẽ ghi đè các attribute.
Để biết thêm thông tin về các thuộc tính so với API thông thạo, hãy xem Phương thức cấu hình.
Sơ đồ thực thể thể hiện mối quan hệ
Hình minh họa sau đây thể hiện sơ đồ mà Entity Framework Power Tools tạo cho mô hình School đã hoàn thành.
Bên cạnh các đường quan hệ một-nhiều (1 đến *), bạn có thể thấy ở đây đường quan hệ một-không-hoặc-một (1 đến 0..1) giữa các thực thể Instructor
và OfficeAssignment
và 0 hoặc một -đường quan hệ -to-many (0..1 đến *) giữa các thực thể Instructor và Department.
Cơ sở dữ liệu hạt giống (seed) với dữ liệu thử nghiệm
Thay thế mã trong file Data/DbInitializer.cs
bằng mã sau để cung cấp dữ liệu gốc cho các thực thể mới mà bạn đã tạo.
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;
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("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-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 instructors = new Instructor[]
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
foreach (Instructor i in instructors)
{
context.Instructors.Add(i);
}
context.SaveChanges();
var departments = new Department[]
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};
foreach (Department d in departments)
{
context.Departments.Add(d);
}
context.SaveChanges();
var courses = new Course[]
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};
foreach (OfficeAssignment o in officeAssignments)
{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();
var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};
foreach (CourseAssignment ci in courseInstructors)
{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}
Như bạn đã thấy trong hướng dẫn đầu tiên, hầu hết mã này chỉ đơn giản là tạo các đối tượng thực thể mới và tải dữ liệu mẫu vào các thuộc tính theo yêu cầu để thử nghiệm. Lưu ý cách xử lý các mối quan hệ nhiều-nhiều: mã tạo ra các mối quan hệ bằng cách tạo các thực thể trong tập thực thể nối Enrollments
và CourseAssignment
.
Thêm migration
Lưu các thay đổi của bạn và xây dựng dự án. Sau đó mở cửa sổ lệnh trong thư mục dự án và nhập lệnh migrations add
(chưa thực hiện lệnh update-database):
dotnet ef migrations add ComplexDataModel
Bạn nhận được cảnh báo về khả năng mất dữ liệu.
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'
Nếu bạn cố chạy lệnh database update
vào thời điểm này (chưa thực hiện), bạn sẽ gặp lỗi sau:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in database "ContosoUniversity", table "dbo.Department", column 'Department
Đôi khi, khi thực hiện migration với dữ liệu hiện có, bạn cần chèn dữ liệu sơ khai vào cơ sở dữ liệu để đáp ứng các ràng buộc khóa ngoại. Mã được tạo trong phương thức Up
sẽ thêm khóa ngoại không thể rỗng DepartmentID
vào bảng Course
. Nếu đã có hàng trong bảng Course khi mã chạy thì thao tác AddColumn
sẽ không thành công vì SQL Server không biết nên đặt giá trị nào vào cột không thể rỗng. Đối với hướng dẫn này, bạn sẽ chạy migration trên cơ sở dữ liệu mới, nhưng trong ứng dụng production, bạn phải thực hiện migration xử lý dữ liệu hiện có, vì vậy các hướng dẫn sau đây sẽ đưa ra ví dụ về cách thực hiện điều đó.
Để migration này hoạt động với dữ liệu hiện có, bạn phải thay đổi mã để đặt giá trị mặc định cho cột mới và tạo một department sơ khai có tên "Temp" để hoạt động như department mặc định. Do đó, tất cả các hàng Course hiện tại sẽ liên quan đến department "Temp" sau khi phương thức Up
chạy.
-
Mở file
{timestamp}_ComplexDataModel.cs
. -
Comment dòng mã thêm cột DepartmentID vào bảng Course.
migrationBuilder.AlterColumn<string>( name: "Title", table: "Course", maxLength: 50, nullable: true, oldClrType: typeof(string), oldNullable: true); //migrationBuilder.AddColumn<int>( // name: "DepartmentID", // table: "Course", // nullable: false, // defaultValue: 0);
-
Thêm mã được đánh dấu sau đây sau mã tạo bảng Department:
migrationBuilder.CreateTable( name: "Department", columns: table => new { DepartmentID = table.Column<int>(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), Budget = table.Column<decimal>(type: "money", nullable: false), InstructorID = table.Column<int>(nullable: true), Name = table.Column<string>(maxLength: 50, nullable: true), StartDate = table.Column<DateTime>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Department", x => x.DepartmentID); table.ForeignKey( name: "FK_Department_Instructor_InstructorID", column: x => x.InstructorID, principalTable: "Instructor", principalColumn: "ID", onDelete: ReferentialAction.Restrict); }); migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())"); // Default value for FK points to department created above, with // defaultValue changed to 1 in following AddColumn statement. migrationBuilder.AddColumn<int>( name: "DepartmentID", table: "Course", nullable: false, defaultValue: 1);
Trong ứng dụng production, bạn sẽ viết mã hoặc tập lệnh để thêm các hàng Department và liên kết các hàng Course với các hàng Department mới. Khi đó bạn sẽ không còn cần đến phần "Temp" hoặc giá trị mặc định trên cột Course.DepartmentID
.
Lưu các thay đổi của bạn và build dự án để test.
Thay đổi chuỗi kết nối
Bây giờ bạn có mã mới trong lớp DbInitializer
để thêm dữ liệu gốc cho các thực thể mới vào cơ sở dữ liệu trống. Để EF tạo một cơ sở dữ liệu trống mới, hãy đổi tên cơ sở dữ liệu trong chuỗi kết nối appsettings.json
thành ContosoUniversity3 hoặc một số tên khác mà bạn chưa sử dụng trên máy tính đang sử dụng.
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Lưu thay đổi của bạn vào appsettings.json
.
Ghi chú
Để thay thế cho việc thay đổi tên cơ sở dữ liệu, bạn có thể xóa cơ sở dữ liệu. Sử dụng SQL Server Object Explorer (SSOX) hoặc lệnh CLI database drop
:
dotnet ef database drop
Cập nhật cơ sở dữ liệu
Sau khi bạn đã thay đổi tên cơ sở dữ liệu hoặc xóa cơ sở dữ liệu, hãy chạy lệnh database update
trong cửa sổ lệnh để thực hiện migration.
dotnet ef database update
Chạy ứng dụng để chạy phương thức DbInitializer.Initialize
và điền vào cơ sở dữ liệu mới.
Mở cơ sở dữ liệu trong SSOX như bạn đã làm trước đó và mở rộng nút Tables để thấy rằng tất cả các bảng đã được tạo. (Nếu bạn vẫn mở SSOX từ lần trước, hãy nhấp vào nút Refresh.)
Chạy ứng dụng để kích hoạt mã khởi tạo tạo cơ sở dữ liệu.
Bấm chuột phải vào bảng CourseAssignment và chọn View Data để xác minh rằng bảng có dữ liệu trong đó.
Lấy mã
Tải xuống hoặc xem ứng dụng đã hoàn thành.
Bước tiếp theo
Trong bai hướng dẫn này, bạn đã:
- Tùy chỉnh mô hình dữ liệu
- Thực hiện các thay đổi đối với thực thể Student
- Đã tạo thực thể Instructor
- Đã tạo thực thể OfficeAssignment
- Thực thể Course đã sửa đổi
- Đã tạo thực thể Department
- Thực thể Enrollment đã sửa đổi
- Đã cập nhật ngữ cảnh cơ sở dữ liệu
- Cơ sở dữ liệu hạt giống (seed) với dữ liệu thử nghiệm
- Đã thêm migration
- Đã thay đổi chuỗi kết nối
- Đã cập nhật cơ sở dữ liệu
Chuyển sang hướng dẫn tiếp theo để tìm hiểu thêm về cách truy cập dữ liệu liên quan.
Tiếp theo: Truy cập dữ liệu liên quan