Bagian 5, Razor Halaman dengan EF Core inti ASP.NET - Model Data
Oleh Tom Dykstra, Jeremy Likness, dan Jon P Smith
Aplikasi web Contoso University menunjukkan cara membuat Razor aplikasi web Pages menggunakan EF Core dan Visual Studio. Untuk informasi tentang seri tutorial, lihat tutorial pertama.
Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai dan bandingkan kode tersebut dengan apa yang Anda buat dengan mengikuti tutorial.
Tutorial sebelumnya bekerja dengan model data dasar yang terdiri dari tiga entitas. Dalam tutorial ini:
- Lebih banyak entitas dan hubungan ditambahkan.
- Model data dikustomisasi dengan menentukan aturan pemformatan, validasi, dan pemetaan database.
Model data yang telah selesai ditampilkan dalam ilustrasi berikut:
Diagram database berikut dibuat dengan Dataedo:
Untuk membuat diagram database dengan Dataedo:
- Menyebarkan aplikasi ke Azure
- Unduh dan instal Dataedo di komputer Anda.
- Ikuti instruksi Hasilkan dokumentasi untuk Azure SQL Database dalam 5 menit
Dalam diagram Dataedo sebelumnya, CourseInstructor
adalah tabel gabungan yang dibuat oleh Kerangka Kerja Entitas. Untuk informasi selengkapnya, lihat Banyak ke banyak
Entitas Siswa
Ganti kode di Models/Student.cs
dengan kode berikut:
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
[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; }
}
}
Kode sebelumnya menambahkan FullName
properti dan menambahkan atribut berikut ke properti yang ada:
Properti terhitung FullName
FullName
adalah properti terhitung yang mengembalikan nilai yang dibuat dengan menggabungkan dua properti lainnya. FullName
tidak dapat diatur, sehingga hanya memiliki aksesor get. Tidak ada FullName
kolom yang dibuat dalam database.
Atribut DataType
[DataType(DataType.Date)]
Untuk tanggal pendaftaran siswa, semua halaman saat ini menampilkan waktu hari bersama dengan tanggal, meskipun hanya tanggal yang relevan. Dengan menggunakan atribut anotasi data, Anda dapat membuat satu perubahan kode yang akan memperbaiki format tampilan di setiap halaman yang menunjukkan data.
Atribut DataType menentukan jenis data yang lebih spesifik daripada jenis intrinsik database. Dalam hal ini hanya tanggal yang harus ditampilkan, bukan tanggal dan waktu. Enumerasi DataType menyediakan banyak jenis data, seperti Tanggal, Waktu, Nomor Telepon, Mata Uang, EmailAddress, dll. Atribut ini DataType
juga dapat memungkinkan aplikasi untuk secara otomatis menyediakan fitur khusus jenis. Contohnya:
mailto:
Tautan secara otomatis dibuat untukDataType.EmailAddress
.- Pemilih tanggal disediakan untuk
DataType.Date
sebagian besar browser.
Atribut memancarkan DataType
atribut HTML 5 data-
(tanda hubung data yang diucapkan). Atribut DataType
tidak memberikan validasi.
Atribut DisplayFormat
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
tidak menentukan format tanggal yang ditampilkan. Secara default, bidang tanggal ditampilkan sesuai dengan format default berdasarkan CultureInfo server.
Atribut DisplayFormat
digunakan untuk secara eksplisit menentukan format tanggal. Pengaturan ApplyFormatInEditMode
menentukan bahwa pemformatan juga harus diterapkan ke antarmuka pengguna edit. Beberapa bidang tidak boleh menggunakan ApplyFormatInEditMode
. Misalnya, simbol mata uang umumnya tidak boleh ditampilkan dalam kotak teks edit.
Atribut DisplayFormat
dapat digunakan dengan sendirinya. Umumnya merupakan ide yang baik untuk menggunakan DataType
atribut dengan DisplayFormat
atribut . Atribut DataType
menyampaikan semantik data dibandingkan dengan cara merendernya di layar. Atribut DataType
memberikan manfaat berikut yang tidak tersedia di DisplayFormat
:
- Browser dapat mengaktifkan fitur HTML5. Misalnya, tampilkan kontrol kalender, simbol mata uang yang sesuai lokal, tautan email, dan validasi input sisi klien.
- Secara default, browser merender data menggunakan format yang benar berdasarkan lokal.
Untuk informasi selengkapnya, lihat <dokumentasi Input> Tag Helper.
Atribut StringLength
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Aturan validasi data dan pesan kesalahan validasi dapat ditentukan dengan atribut. Atribut StringLength menentukan panjang minimum dan maksimum karakter yang diizinkan dalam bidang data. Kode yang ditampilkan membatasi nama hingga tidak lebih dari 50 karakter. Contoh yang mengatur panjang string minimum ditampilkan nanti.
Atribut ini StringLength
juga menyediakan validasi sisi klien dan sisi server. Nilai minimum tidak berdampak pada skema database.
Atribut StringLength
tidak mencegah pengguna memasukkan spasi kosong untuk nama. Atribut RegularExpression dapat digunakan untuk menerapkan pembatasan pada input. Misalnya, kode berikut mengharuskan karakter pertama menjadi huruf besar dan karakter yang tersisa menjadi alfabet:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Di SQL Server Object Explorer (SSOX), buka perancang tabel Siswa dengan mengklik dua kali tabel Siswa .
Gambar sebelumnya menunjukkan skema untuk Student
tabel. Bidang nama memiliki jenis nvarchar(MAX)
. Ketika migrasi dibuat dan diterapkan nanti dalam tutorial ini, bidang nama menjadi nvarchar(50)
sebagai hasil dari atribut panjang string.
Atribut Kolom
[Column("FirstName")]
public string FirstMidName { get; set; }
Atribut dapat mengontrol bagaimana kelas dan properti dipetakan ke database. Student
Dalam model, Column
atribut digunakan untuk memetakan nama FirstMidName
properti ke "FirstName" dalam database.
Saat database dibuat, nama properti pada model digunakan untuk nama kolom (kecuali saat Column
atribut digunakan). Model menggunakan Student
FirstMidName
untuk bidang nama depan karena bidang mungkin juga berisi nama tengah.
[Column]
Dengan atribut , Student.FirstMidName
dalam model data dipetakan ke FirstName
kolom Student
tabel. Penambahan Column
atribut mengubah model yang SchoolContext
mendukung . Model yang SchoolContext
mendukung tidak lagi cocok dengan database. Perbedaan tersebut akan diselesaikan dengan menambahkan migrasi nanti dalam tutorial ini.
Atribut Wajib
[Required]
Atribut Required
membuat properti nama menjadi bidang yang diperlukan. Atribut Required
tidak diperlukan untuk jenis yang tidak dapat diubah ke null seperti jenis nilai (misalnya, , DateTime
int
, dan double
). Jenis yang tidak boleh null secara otomatis diperlakukan sebagai bidang yang diperlukan.
Atribut Required
harus digunakan untuk MinimumLength
MinimumLength
diberlakukan.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength
dan Required
izinkan spasi kosong untuk memenuhi validasi. RegularExpression
Gunakan atribut untuk kontrol penuh atas string.
Atribut Tampilan
[Display(Name = "Last Name")]
Atribut Display
menentukan bahwa keterangan untuk kotak teks harus "Nama Depan", "Nama Belakang", "Nama Lengkap", dan "Tanggal Pendaftaran." Keterangan default tidak memiliki spasi membalik kata, misalnya "Nama Belakang."
Buat sebuah migrasi
Jalankan aplikasi dan buka halaman Siswa. Pengecualian dilemparkan. Atribut [Column]
menyebabkan EF berharap menemukan kolom bernama FirstName
, tetapi nama kolom dalam database masih FirstMidName
.
Pesan kesalahan mirip dengan contoh berikut:
SqlException: Invalid column name 'FirstName'.
There are pending model changes
Pending model changes are detected in the following:
SchoolContext
Di PMC, masukkan perintah berikut untuk membuat migrasi baru dan perbarui database:
Add-Migration ColumnFirstName Update-Database
Perintah pertama dari perintah ini menghasilkan pesan peringatan berikut:
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Peringatan dihasilkan karena bidang nama sekarang dibatasi hingga 50 karakter. Jika nama dalam database memiliki lebih dari 50 karakter, karakter 51 hingga terakhir akan hilang.
Buka tabel Siswa di SSOX:
Sebelum migrasi diterapkan, kolom nama berjenis nvarchar(MAX). Kolom nama sekarang
nvarchar(50)
. Nama kolom telah berubah dariFirstMidName
menjadiFirstName
.
- Jalankan aplikasi dan buka halaman Siswa.
- Perhatikan bahwa waktu tidak input atau ditampilkan bersama dengan tanggal.
- Pilih Buat Baru, dan coba masukkan nama yang lebih panjang dari 50 karakter.
Catatan
Di bagian berikut, membangun aplikasi pada beberapa tahap menghasilkan kesalahan pengkompilasi. Instruksi menentukan kapan harus membuat aplikasi.
Entitas Instruktur
Buat Models/Instructor.cs
dengan kode berikut:
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<Course> Courses { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
Beberapa atribut dapat berada di satu baris. Atribut HireDate
dapat ditulis sebagai berikut:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Properti navigasi
Properti Courses
dan OfficeAssignment
adalah properti navigasi.
Instruktur dapat mengajarkan sejumlah kursus, sehingga Courses
didefinisikan sebagai koleksi.
public ICollection<Course> Courses { get; set; }
Instruktur dapat memiliki paling banyak satu kantor, sehingga OfficeAssignment
properti memiliki satu OfficeAssignment
entitas. OfficeAssignment
null jika tidak ada kantor yang ditetapkan.
public OfficeAssignment OfficeAssignment { get; set; }
Entitas OfficeAssignment
Buat Models/OfficeAssignment.cs
dengan kode berikut:
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; }
}
}
Atribut Kunci
Atribut [Key]
digunakan untuk mengidentifikasi properti sebagai kunci primer (PK) ketika nama properti adalah sesuatu selain classnameID
atau ID
.
Ada hubungan satu-ke-nol-atau-satu antara Instructor
entitas dan OfficeAssignment
. Penugasan kantor hanya ada dalam kaitannya dengan instruktur yang ditetapkannya. OfficeAssignment
PK juga merupakan kunci asingnya (FK) ke Instructor
entitas. Hubungan satu-ke-nol-atau-satu terjadi ketika PK dalam satu tabel adalah PK dan FK di tabel lain.
EF Coretidak dapat secara otomatis dikenali InstructorID
sebagai PK karena InstructorID
tidak mengikuti konvensi penamaan OfficeAssignment
ID atau classnameID. Oleh karena itu, Key
atribut digunakan untuk mengidentifikasi InstructorID
sebagai PK:
[Key]
public int InstructorID { get; set; }
Secara default, EF Core memperlakukan kunci sebagai non-database yang dihasilkan karena kolom adalah untuk hubungan identifikasi. Untuk informasi selengkapnya, lihat Kunci EF.
Properti navigasi Instruktur
Properti Instructor.OfficeAssignment
navigasi bisa null karena mungkin tidak OfficeAssignment
ada baris untuk instruktur tertentu. Instruktur mungkin tidak memiliki tugas kantor.
Properti OfficeAssignment.Instructor
navigasi akan selalu memiliki entitas instruktur karena jenis kunci InstructorID
asing adalah int
, jenis nilai yang tidak dapat diubah ke null. Penugasan kantor tidak boleh ada tanpa instruktur.
Instructor
Ketika entitas memiliki entitas terkaitOfficeAssignment
, setiap entitas memiliki referensi ke entitas lain di properti navigasinya.
Entitas Kursus
Perbarui Models/Course.cs
dengan kode berikut:
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<Instructor> Instructors { get; set; }
}
}
Entitas Course
memiliki properti DepartmentID
kunci asing (FK). DepartmentID
menunjuk ke entitas terkait Department
. Entitas Course
memiliki Department
properti navigasi.
EF Core tidak memerlukan properti kunci asing untuk model data saat model memiliki properti navigasi untuk entitas terkait. EF Core secara otomatis membuat FK dalam database di mana pun mereka diperlukan. EF Coremembuat properti bayangan untuk FK yang dibuat secara otomatis. Namun, secara eksplisit termasuk FK dalam model data dapat membuat pembaruan lebih sederhana dan lebih efisien. Misalnya, pertimbangkan model di mana properti DepartmentID
FK tidak disertakan. Saat entitas kursus diambil untuk diedit:
- Properti
Department
ini adalahnull
jika tidak dimuat secara eksplisit. - Untuk memperbarui entitas kursus,
Department
entitas harus terlebih dahulu diambil.
Saat properti DepartmentID
FK disertakan dalam model data, tidak perlu mengambil Department
entitas sebelum pembaruan.
Atribut DatabaseGenerated
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
menentukan bahwa PK disediakan oleh aplikasi daripada yang dihasilkan oleh database.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Secara default, EF Core mengasumsikan bahwa nilai PK dihasilkan oleh database. Database yang dihasilkan umumnya merupakan pendekatan terbaik. Untuk Course
entitas, pengguna menentukan PK. Misalnya, nomor kursus seperti seri 1000 untuk departemen matematika, seri 2000 untuk departemen bahasa Inggris.
Atribut DatabaseGenerated
juga dapat digunakan untuk menghasilkan nilai default. Misalnya, database dapat secara otomatis menghasilkan bidang tanggal untuk merekam tanggal baris dibuat atau diperbarui. Untuk informasi selengkapnya, lihat Properti yang Dihasilkan.
Properti kunci dan navigasi asing
Properti kunci asing (FK) dan properti navigasi dalam Course
entitas mencerminkan hubungan berikut:
Kursus ditugaskan ke satu departemen, jadi ada DepartmentID
FK dan Department
properti navigasi.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kursus dapat memiliki sejumlah siswa yang terdaftar di dalamnya, sehingga Enrollments
properti navigasi adalah koleksi:
public ICollection<Enrollment> Enrollments { get; set; }
Kursus dapat diajarkan oleh beberapa instruktur, sehingga Instructors
properti navigasi adalah koleksi:
public ICollection<Instructor> Instructors { get; set; }
Entitas Departemen
Buat Models/Department.cs
dengan kode berikut:
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; }
}
}
Atribut Kolom
Sebelumnya Column
atribut digunakan untuk mengubah pemetaan nama kolom. Dalam kode untuk Department
entitas, Column
atribut digunakan untuk mengubah pemetaan jenis data SQL. Kolom Budget
didefinisikan menggunakan jenis uang SQL Server dalam database:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Pemetaan kolom umumnya tidak diperlukan. EF Core memilih jenis data SQL Server yang sesuai berdasarkan jenis CLR untuk properti . Jenis CLR decimal
memetakan ke jenis SQL Server decimal
. Budget
adalah untuk mata uang, dan jenis data uang lebih sesuai untuk mata uang.
Properti kunci dan navigasi asing
Properti FK dan navigasi mencerminkan hubungan berikut:
- Departemen mungkin atau mungkin tidak memiliki administrator.
- Administrator selalu menjadi instruktur.
InstructorID
Oleh karena itu properti disertakan sebagai FK keInstructor
entitas.
Properti navigasi diberi nama Administrator
tetapi menyimpan Instructor
entitas:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Dalam ?
kode sebelumnya menentukan properti dapat diubah ke null.
Departemen mungkin memiliki banyak kursus, jadi ada properti navigasi Kursus:
public ICollection<Course> Courses { get; set; }
Menurut konvensi, EF Core memungkinkan penghapusan berjenjang untuk FK yang tidak dapat diubah ke null dan untuk hubungan banyak ke banyak. Perilaku default ini dapat mengakibatkan aturan penghapusan kaskade melingkar. Aturan penghapusan kaskade melingkar menyebabkan pengecualian saat migrasi ditambahkan.
Misalnya, jika Department.InstructorID
properti didefinisikan sebagai tidak dapat diubah ke null, EF Core akan mengonfigurasi aturan penghapusan berjenjang. Dalam hal ini, departemen akan dihapus ketika instruktur yang ditetapkan sebagai administratornya dihapus. Dalam skenario ini, aturan pembatasan akan lebih masuk akal. API fasih berikut akan menetapkan aturan pembatasan dan menonaktifkan penghapusan bertingkat.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Properti Kunci asing pendaftaran dan navigasi
Catatan pendaftaran adalah untuk satu kursus yang diambil oleh satu siswa.
Perbarui Models/Enrollment.cs
dengan kode berikut:
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; }
}
}
Properti FK dan properti navigasi mencerminkan hubungan berikut:
Catatan pendaftaran adalah untuk satu kursus, jadi ada CourseID
properti FK dan Course
properti navigasi:
public int CourseID { get; set; }
public Course Course { get; set; }
Catatan pendaftaran adalah untuk satu siswa, jadi ada StudentID
properti FK dan Student
properti navigasi:
public int StudentID { get; set; }
public Student Student { get; set; }
Hubungan Banyak ke Banyak
Ada hubungan banyak-ke-banyak antara Student
entitas dan Course
. Entitas Enrollment
berfungsi sebagai tabel gabungan banyak ke banyak dengan payload dalam database. Dengan payload berarti bahwa Enrollment
tabel berisi data tambahan selain FK untuk tabel yang digabungkan. Enrollment
Dalam entitas, data tambahan selain FK adalah PK dan Grade
.
Ilustrasi berikut menunjukkan seperti apa hubungan ini dalam diagram entitas. (Diagram ini dihasilkan menggunakan EF Power Tools untuk EF 6.x. Membuat diagram bukan bagian dari tutorial.)
Setiap garis hubungan memiliki 1 di satu ujung dan tanda bintang (*) di ujung lainnya, menunjukkan hubungan satu-ke-banyak.
Enrollment
Jika tabel tidak menyertakan informasi nilai, tabel hanya perlu berisi dua FK, CourseID
dan StudentID
. Tabel gabungan banyak ke banyak tanpa payload terkadang disebut tabel gabungan murni (PJT).
Entitas Instructor
dan Course
memiliki hubungan banyak ke banyak menggunakan PJT.
Memperbarui konteks database
Perbarui Data/SchoolContext.cs
dengan kode berikut:
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; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable(nameof(Course))
.HasMany(c => c.Instructors)
.WithMany(i => i.Courses);
modelBuilder.Entity<Student>().ToTable(nameof(Student));
modelBuilder.Entity<Instructor>().ToTable(nameof(Instructor));
}
}
}
Kode sebelumnya menambahkan entitas baru dan mengonfigurasi hubungan banyak ke banyak antara Instructor
entitas dan Course
.
Alternatif API fasih untuk atribut
Metode OnModelCreating
dalam kode sebelumnya menggunakan API fasih untuk mengonfigurasi EF Core perilaku. API disebut "fasih" karena sering digunakan dengan merangkai serangkaian panggilan metode bersama-sama ke dalam satu pernyataan. Kode berikut adalah contoh API fasih:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
Dalam tutorial ini, API fasih hanya digunakan untuk pemetaan database yang tidak dapat dilakukan dengan atribut. Namun, API fasih dapat menentukan sebagian besar aturan pemformatan, validasi, dan pemetaan yang dapat dilakukan dengan atribut.
Beberapa atribut seperti MinimumLength
tidak dapat diterapkan dengan API yang fasih. MinimumLength
tidak mengubah skema, itu hanya menerapkan aturan validasi panjang minimum.
Beberapa pengembang lebih suka menggunakan API yang fasih secara eksklusif sehingga mereka dapat menjaga kelas entitas mereka tetap bersih. Atribut dan API fasih dapat dicampur. Ada beberapa konfigurasi yang hanya dapat dilakukan dengan API yang fasih, misalnya, menentukan PK komposit. Ada beberapa konfigurasi yang hanya dapat dilakukan dengan atribut (MinimumLength
). Praktik yang direkomendasikan untuk menggunakan API atau atribut yang fasih:
- Pilih salah satu dari dua pendekatan ini.
- Gunakan pendekatan yang dipilih secara konsisten sebanyak mungkin.
Beberapa atribut yang digunakan dalam tutorial ini digunakan untuk:
- Validasi saja (misalnya,
MinimumLength
). - EF Core konfigurasi saja (misalnya,
HasKey
). - Validasi dan EF Core konfigurasi (misalnya,
[StringLength(50)]
).
Untuk informasi selengkapnya tentang atribut vs. API yang fasih, lihat Metode konfigurasi.
Seed database
Perbarui kode di Data/DbInitializer.cs
:
using ContosoUniversity.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var alexander = new Student
{
FirstMidName = "Carson",
LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01")
};
var alonso = new Student
{
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var anand = new Student
{
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var barzdukas = new Student
{
FirstMidName = "Gytis",
LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var li = new Student
{
FirstMidName = "Yan",
LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var justice = new Student
{
FirstMidName = "Peggy",
LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01")
};
var norman = new Student
{
FirstMidName = "Laura",
LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var olivetto = new Student
{
FirstMidName = "Nino",
LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01")
};
var students = new Student[]
{
alexander,
alonso,
anand,
barzdukas,
li,
justice,
norman,
olivetto
};
context.AddRange(students);
var abercrombie = new Instructor
{
FirstMidName = "Kim",
LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11")
};
var fakhouri = new Instructor
{
FirstMidName = "Fadi",
LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06")
};
var harui = new Instructor
{
FirstMidName = "Roger",
LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01")
};
var kapoor = new Instructor
{
FirstMidName = "Candace",
LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15")
};
var zheng = new Instructor
{
FirstMidName = "Roger",
LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12")
};
var instructors = new Instructor[]
{
abercrombie,
fakhouri,
harui,
kapoor,
zheng
};
context.AddRange(instructors);
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
Instructor = fakhouri,
Location = "Smith 17" },
new OfficeAssignment {
Instructor = harui,
Location = "Gowan 27" },
new OfficeAssignment {
Instructor = kapoor,
Location = "Thompson 304" }
};
context.AddRange(officeAssignments);
var english = new Department
{
Name = "English",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = abercrombie
};
var mathematics = new Department
{
Name = "Mathematics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = fakhouri
};
var engineering = new Department
{
Name = "Engineering",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = harui
};
var economics = new Department
{
Name = "Economics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = kapoor
};
var departments = new Department[]
{
english,
mathematics,
engineering,
economics
};
context.AddRange(departments);
var chemistry = new Course
{
CourseID = 1050,
Title = "Chemistry",
Credits = 3,
Department = engineering,
Instructors = new List<Instructor> { kapoor, harui }
};
var microeconomics = new Course
{
CourseID = 4022,
Title = "Microeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var macroeconmics = new Course
{
CourseID = 4041,
Title = "Macroeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var calculus = new Course
{
CourseID = 1045,
Title = "Calculus",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { fakhouri }
};
var trigonometry = new Course
{
CourseID = 3141,
Title = "Trigonometry",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { harui }
};
var composition = new Course
{
CourseID = 2021,
Title = "Composition",
Credits = 3,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var literature = new Course
{
CourseID = 2042,
Title = "Literature",
Credits = 4,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var courses = new Course[]
{
chemistry,
microeconomics,
macroeconmics,
calculus,
trigonometry,
composition,
literature
};
context.AddRange(courses);
var enrollments = new Enrollment[]
{
new Enrollment {
Student = alexander,
Course = chemistry,
Grade = Grade.A
},
new Enrollment {
Student = alexander,
Course = microeconomics,
Grade = Grade.C
},
new Enrollment {
Student = alexander,
Course = macroeconmics,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = calculus,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = trigonometry,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = anand,
Course = chemistry
},
new Enrollment {
Student = anand,
Course = microeconomics,
Grade = Grade.B
},
new Enrollment {
Student = barzdukas,
Course = chemistry,
Grade = Grade.B
},
new Enrollment {
Student = li,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = justice,
Course = literature,
Grade = Grade.B
}
};
context.AddRange(enrollments);
context.SaveChanges();
}
}
}
Kode sebelumnya menyediakan data benih untuk entitas baru. Sebagian besar kode ini membuat objek entitas baru dan memuat data sampel. Data sampel digunakan untuk pengujian.
Menerapkan migrasi atau menghilangkan dan membuat ulang
Dengan database yang ada, ada dua pendekatan untuk mengubah database:
- Hilangkan dan buat ulang database. Pilih bagian ini saat menggunakan SQLite.
- Terapkan migrasi ke database yang sudah ada. Instruksi di bagian ini hanya berfungsi untuk SQL Server, bukan untuk SQLite.
Salah satu pilihan berfungsi untuk SQL Server. Meskipun metode migrasi terapkan lebih kompleks dan memakan waktu, metode ini adalah pendekatan yang disukai untuk lingkungan produksi dunia nyata.
Menghilangkan dan membuat ulang database
Untuk memaksa EF Core membuat database baru, hilangkan dan perbarui database:
- Hapus folder Migrasi.
- Di Package Manager Console (PMC), jalankan perintah berikut:
Drop-Database
Add-Migration InitialCreate
Update-Database
Jalankan aplikasi. Menjalankan aplikasi menjalankan DbInitializer.Initialize
metode . Mengisi DbInitializer.Initialize
database baru.
Buka database di SSOX:
- Jika SSOX dibuka sebelumnya, klik tombol Refresh .
- Perluas simpul Tabel . Tabel yang dibuat ditampilkan.
Langkah berikutnya
Dua tutorial berikutnya menunjukkan cara membaca dan memperbarui data terkait.
Tutorial sebelumnya bekerja dengan model data dasar yang terdiri dari tiga entitas. Dalam tutorial ini:
- Lebih banyak entitas dan hubungan ditambahkan.
- Model data dikustomisasi dengan menentukan aturan pemformatan, validasi, dan pemetaan database.
Model data yang telah selesai ditampilkan dalam ilustrasi berikut:
Entitas Siswa
Ganti kode di Models/Student.cs
dengan kode berikut:
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
[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; }
}
}
Kode sebelumnya menambahkan FullName
properti dan menambahkan atribut berikut ke properti yang ada:
[DataType]
[DisplayFormat]
[StringLength]
[Column]
[Required]
[Display]
Properti terhitung FullName
FullName
adalah properti terhitung yang mengembalikan nilai yang dibuat dengan menggabungkan dua properti lainnya. FullName
tidak dapat diatur, sehingga hanya memiliki aksesor get. Tidak ada FullName
kolom yang dibuat dalam database.
Atribut DataType
[DataType(DataType.Date)]
Untuk tanggal pendaftaran siswa, semua halaman saat ini menampilkan waktu hari bersama dengan tanggal, meskipun hanya tanggal yang relevan. Dengan menggunakan atribut anotasi data, Anda dapat membuat satu perubahan kode yang akan memperbaiki format tampilan di setiap halaman yang menunjukkan data.
Atribut DataType menentukan jenis data yang lebih spesifik daripada jenis intrinsik database. Dalam hal ini hanya tanggal yang harus ditampilkan, bukan tanggal dan waktu. Enumerasi DataType menyediakan banyak jenis data, seperti Tanggal, Waktu, Nomor Telepon, Mata Uang, EmailAddress, dll. Atribut ini DataType
juga dapat memungkinkan aplikasi untuk secara otomatis menyediakan fitur khusus jenis. Contohnya:
mailto:
Tautan secara otomatis dibuat untukDataType.EmailAddress
.- Pemilih tanggal disediakan untuk
DataType.Date
sebagian besar browser.
Atribut memancarkan DataType
atribut HTML 5 data-
(tanda hubung data yang diucapkan). Atribut DataType
tidak memberikan validasi.
Atribut DisplayFormat
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
tidak menentukan format tanggal yang ditampilkan. Secara default, bidang tanggal ditampilkan sesuai dengan format default berdasarkan CultureInfo server.
Atribut DisplayFormat
digunakan untuk secara eksplisit menentukan format tanggal. Pengaturan ApplyFormatInEditMode
menentukan bahwa pemformatan juga harus diterapkan ke antarmuka pengguna edit. Beberapa bidang tidak boleh menggunakan ApplyFormatInEditMode
. Misalnya, simbol mata uang umumnya tidak boleh ditampilkan dalam kotak teks edit.
Atribut DisplayFormat
dapat digunakan dengan sendirinya. Umumnya merupakan ide yang baik untuk menggunakan DataType
atribut dengan DisplayFormat
atribut . Atribut DataType
menyampaikan semantik data dibandingkan dengan cara merendernya di layar. Atribut DataType
memberikan manfaat berikut yang tidak tersedia di DisplayFormat
:
- Browser dapat mengaktifkan fitur HTML5. Misalnya, tampilkan kontrol kalender, simbol mata uang yang sesuai lokal, tautan email, dan validasi input sisi klien.
- Secara default, browser merender data menggunakan format yang benar berdasarkan lokal.
Untuk informasi selengkapnya, lihat <dokumentasi Input> Tag Helper.
Atribut StringLength
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Aturan validasi data dan pesan kesalahan validasi dapat ditentukan dengan atribut. Atribut StringLength menentukan panjang minimum dan maksimum karakter yang diizinkan dalam bidang data. Kode yang ditampilkan membatasi nama hingga tidak lebih dari 50 karakter. Contoh yang mengatur panjang string minimum ditampilkan nanti.
Atribut ini StringLength
juga menyediakan validasi sisi klien dan sisi server. Nilai minimum tidak berdampak pada skema database.
Atribut StringLength
tidak mencegah pengguna memasukkan spasi kosong untuk nama. Atribut RegularExpression dapat digunakan untuk menerapkan pembatasan pada input. Misalnya, kode berikut mengharuskan karakter pertama menjadi huruf besar dan karakter yang tersisa menjadi alfabet:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Di SQL Server Object Explorer (SSOX), buka perancang tabel Siswa dengan mengklik dua kali tabel Siswa .
Gambar sebelumnya menunjukkan skema untuk Student
tabel. Bidang nama memiliki jenis nvarchar(MAX)
. Ketika migrasi dibuat dan diterapkan nanti dalam tutorial ini, bidang nama menjadi nvarchar(50)
sebagai hasil dari atribut panjang string.
Atribut Kolom
[Column("FirstName")]
public string FirstMidName { get; set; }
Atribut dapat mengontrol bagaimana kelas dan properti dipetakan ke database. Student
Dalam model, Column
atribut digunakan untuk memetakan nama FirstMidName
properti ke "FirstName" dalam database.
Saat database dibuat, nama properti pada model digunakan untuk nama kolom (kecuali saat Column
atribut digunakan). Model menggunakan Student
FirstMidName
untuk bidang nama depan karena bidang mungkin juga berisi nama tengah.
[Column]
Dengan atribut , Student.FirstMidName
dalam model data dipetakan ke FirstName
kolom Student
tabel. Penambahan Column
atribut mengubah model yang SchoolContext
mendukung . Model yang SchoolContext
mendukung tidak lagi cocok dengan database. Perbedaan tersebut akan diselesaikan dengan menambahkan migrasi nanti dalam tutorial ini.
Atribut Wajib
[Required]
Atribut Required
membuat properti nama menjadi bidang yang diperlukan. Atribut Required
tidak diperlukan untuk jenis yang tidak dapat diubah ke null seperti jenis nilai (misalnya, , DateTime
int
, dan double
). Jenis yang tidak boleh null secara otomatis diperlakukan sebagai bidang yang diperlukan.
Atribut Required
harus digunakan untuk MinimumLength
MinimumLength
diberlakukan.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength
dan Required
izinkan spasi kosong untuk memenuhi validasi. RegularExpression
Gunakan atribut untuk kontrol penuh atas string.
Atribut Tampilan
[Display(Name = "Last Name")]
Atribut Display
menentukan bahwa keterangan untuk kotak teks harus "Nama Depan", "Nama Belakang", "Nama Lengkap", dan "Tanggal Pendaftaran." Keterangan default tidak memiliki spasi membalik kata, misalnya "Nama Belakang."
Buat sebuah migrasi
Jalankan aplikasi dan buka halaman Siswa. Pengecualian dilemparkan. Atribut [Column]
menyebabkan EF berharap menemukan kolom bernama FirstName
, tetapi nama kolom dalam database masih FirstMidName
.
Pesan kesalahan mirip dengan contoh berikut:
SqlException: Invalid column name 'FirstName'.
Di PMC, masukkan perintah berikut untuk membuat migrasi baru dan perbarui database:
Add-Migration ColumnFirstName Update-Database
Perintah pertama dari perintah ini menghasilkan pesan peringatan berikut:
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Peringatan dihasilkan karena bidang nama sekarang dibatasi hingga 50 karakter. Jika nama dalam database memiliki lebih dari 50 karakter, karakter 51 hingga terakhir akan hilang.
Buka tabel Siswa di SSOX:
Sebelum migrasi diterapkan, kolom nama berjenis nvarchar(MAX). Kolom nama sekarang
nvarchar(50)
. Nama kolom telah berubah dariFirstMidName
menjadiFirstName
.
- Jalankan aplikasi dan buka halaman Siswa.
- Perhatikan bahwa waktu tidak input atau ditampilkan bersama dengan tanggal.
- Pilih Buat Baru, dan coba masukkan nama yang lebih panjang dari 50 karakter.
Catatan
Di bagian berikut, membangun aplikasi pada beberapa tahap menghasilkan kesalahan pengkompilasi. Instruksi menentukan kapan harus membuat aplikasi.
Entitas Instruktur
Buat Models/Instructor.cs
dengan kode berikut:
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; }
}
}
Beberapa atribut dapat berada di satu baris. Atribut HireDate
dapat ditulis sebagai berikut:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Properti navigasi
Properti CourseAssignments
dan OfficeAssignment
adalah properti navigasi.
Instruktur dapat mengajarkan sejumlah kursus, sehingga CourseAssignments
didefinisikan sebagai koleksi.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Instruktur dapat memiliki paling banyak satu kantor, sehingga OfficeAssignment
properti memiliki satu OfficeAssignment
entitas. OfficeAssignment
null jika tidak ada kantor yang ditetapkan.
public OfficeAssignment OfficeAssignment { get; set; }
Entitas OfficeAssignment
Buat Models/OfficeAssignment.cs
dengan kode berikut:
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; }
}
}
Atribut Kunci
Atribut [Key]
digunakan untuk mengidentifikasi properti sebagai kunci utama (PK) ketika nama properti adalah sesuatu selain classnameID atau ID.
Ada hubungan satu-ke-nol-atau-satu antara Instructor
entitas dan OfficeAssignment
. Penugasan kantor hanya ada dalam kaitannya dengan instruktur yang ditetapkannya. OfficeAssignment
PK juga merupakan kunci asingnya (FK) ke Instructor
entitas.
EF Coretidak dapat secara otomatis dikenali InstructorID
sebagai PK karena InstructorID
tidak mengikuti konvensi penamaan OfficeAssignment
ID atau classnameID. Oleh karena itu, Key
atribut digunakan untuk mengidentifikasi InstructorID
sebagai PK:
[Key]
public int InstructorID { get; set; }
Secara default, EF Core memperlakukan kunci sebagai non-database yang dihasilkan karena kolom adalah untuk hubungan identifikasi.
Properti navigasi Instruktur
Properti Instructor.OfficeAssignment
navigasi bisa null karena mungkin tidak OfficeAssignment
ada baris untuk instruktur tertentu. Instruktur mungkin tidak memiliki tugas kantor.
Properti OfficeAssignment.Instructor
navigasi akan selalu memiliki entitas instruktur karena jenis kunci InstructorID
asing adalah int
, jenis nilai yang tidak dapat diubah ke null. Penugasan kantor tidak boleh ada tanpa instruktur.
Instructor
Ketika entitas memiliki entitas terkaitOfficeAssignment
, setiap entitas memiliki referensi ke entitas lain di properti navigasinya.
Entitas Kursus
Perbarui Models/Course.cs
dengan kode berikut:
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; }
}
}
Entitas Course
memiliki properti DepartmentID
kunci asing (FK). DepartmentID
menunjuk ke entitas terkait Department
. Entitas Course
memiliki Department
properti navigasi.
EF Core tidak memerlukan properti kunci asing untuk model data saat model memiliki properti navigasi untuk entitas terkait. EF Core secara otomatis membuat FK dalam database di mana pun mereka diperlukan. EF Coremembuat properti bayangan untuk FK yang dibuat secara otomatis. Namun, secara eksplisit termasuk FK dalam model data dapat membuat pembaruan lebih sederhana dan lebih efisien. Misalnya, pertimbangkan model di mana properti DepartmentID
FK tidak disertakan. Saat entitas kursus diambil untuk diedit:
- Properti
Department
null jika tidak dimuat secara eksplisit. - Untuk memperbarui entitas kursus,
Department
entitas harus terlebih dahulu diambil.
Saat properti DepartmentID
FK disertakan dalam model data, tidak perlu mengambil Department
entitas sebelum pembaruan.
Atribut DatabaseGenerated
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
menentukan bahwa PK disediakan oleh aplikasi daripada yang dihasilkan oleh database.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Secara default, EF Core mengasumsikan bahwa nilai PK dihasilkan oleh database. Database yang dihasilkan umumnya merupakan pendekatan terbaik. Untuk Course
entitas, pengguna menentukan PK. Misalnya, nomor kursus seperti seri 1000 untuk departemen matematika, seri 2000 untuk departemen bahasa Inggris.
Atribut DatabaseGenerated
juga dapat digunakan untuk menghasilkan nilai default. Misalnya, database dapat secara otomatis menghasilkan bidang tanggal untuk merekam tanggal baris dibuat atau diperbarui. Untuk informasi selengkapnya, lihat Properti yang Dihasilkan.
Properti kunci dan navigasi asing
Properti kunci asing (FK) dan properti navigasi dalam Course
entitas mencerminkan hubungan berikut:
Kursus ditugaskan ke satu departemen, jadi ada DepartmentID
FK dan Department
properti navigasi.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kursus dapat memiliki sejumlah siswa yang terdaftar di dalamnya, sehingga Enrollments
properti navigasi adalah koleksi:
public ICollection<Enrollment> Enrollments { get; set; }
Kursus dapat diajarkan oleh beberapa instruktur, sehingga CourseAssignments
properti navigasi adalah koleksi:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
dijelaskan kemudian.
Entitas Departemen
Buat Models/Department.cs
dengan kode berikut:
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; }
}
}
Atribut Kolom
Sebelumnya Column
atribut digunakan untuk mengubah pemetaan nama kolom. Dalam kode untuk Department
entitas, Column
atribut digunakan untuk mengubah pemetaan jenis data SQL. Kolom Budget
didefinisikan menggunakan jenis uang SQL Server dalam database:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Pemetaan kolom umumnya tidak diperlukan. EF Core memilih jenis data SQL Server yang sesuai berdasarkan jenis CLR untuk properti . Jenis CLR decimal
memetakan ke jenis SQL Server decimal
. Budget
adalah untuk mata uang, dan jenis data uang lebih sesuai untuk mata uang.
Properti kunci dan navigasi asing
Properti FK dan navigasi mencerminkan hubungan berikut:
- Departemen mungkin atau mungkin tidak memiliki administrator.
- Administrator selalu menjadi instruktur.
InstructorID
Oleh karena itu properti disertakan sebagai FK keInstructor
entitas.
Properti navigasi diberi nama Administrator
tetapi menyimpan Instructor
entitas:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Tanda tanya (?) dalam kode sebelumnya menentukan properti dapat diubah ke null.
Departemen mungkin memiliki banyak kursus, jadi ada properti navigasi Kursus:
public ICollection<Course> Courses { get; set; }
Menurut konvensi, EF Core memungkinkan penghapusan berjenjang untuk FK yang tidak dapat diubah ke null dan untuk hubungan banyak ke banyak. Perilaku default ini dapat mengakibatkan aturan penghapusan kaskade melingkar. Aturan penghapusan kaskade melingkar menyebabkan pengecualian saat migrasi ditambahkan.
Misalnya, jika Department.InstructorID
properti didefinisikan sebagai tidak dapat diubah ke null, EF Core akan mengonfigurasi aturan penghapusan berjenjang. Dalam hal ini, departemen akan dihapus ketika instruktur yang ditetapkan sebagai administratornya dihapus. Dalam skenario ini, aturan pembatasan akan lebih masuk akal. API fasih berikut akan menetapkan aturan pembatasan dan menonaktifkan penghapusan bertingkat.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Entitas Pendaftaran
Catatan pendaftaran adalah untuk satu kursus yang diambil oleh satu siswa.
Perbarui Models/Enrollment.cs
dengan kode berikut:
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; }
}
}
Properti kunci dan navigasi asing
Properti FK dan properti navigasi mencerminkan hubungan berikut:
Catatan pendaftaran adalah untuk satu kursus, jadi ada CourseID
properti FK dan Course
properti navigasi:
public int CourseID { get; set; }
public Course Course { get; set; }
Catatan pendaftaran adalah untuk satu siswa, jadi ada StudentID
properti FK dan Student
properti navigasi:
public int StudentID { get; set; }
public Student Student { get; set; }
Hubungan Banyak ke Banyak
Ada hubungan banyak-ke-banyak antara Student
entitas dan Course
. Entitas Enrollment
berfungsi sebagai tabel gabungan banyak ke banyak dengan payload dalam database. "Dengan payload" berarti bahwa Enrollment
tabel berisi data tambahan selain FK untuk tabel yang digabungkan (dalam hal ini, PK dan Grade
).
Ilustrasi berikut menunjukkan seperti apa hubungan ini dalam diagram entitas. (Diagram ini dihasilkan menggunakan EF Power Tools untuk EF 6.x. Membuat diagram bukan bagian dari tutorial.)
Setiap garis hubungan memiliki 1 di satu ujung dan tanda bintang (*) di ujung lainnya, menunjukkan hubungan satu-ke-banyak.
Enrollment
Jika tabel tidak menyertakan informasi nilai, tabel hanya perlu berisi dua FK (CourseID
dan StudentID
). Tabel gabungan banyak ke banyak tanpa payload terkadang disebut tabel gabungan murni (PJT).
Entitas Instructor
dan Course
memiliki hubungan banyak ke banyak menggunakan tabel gabungan murni.
Catatan: EF 6.x mendukung tabel gabungan implisit untuk hubungan banyak ke banyak, tetapi EF Core tidak. Untuk informasi selengkapnya, lihat Hubungan banyak ke banyak di EF Core 2.0.
Entitas CourseAssignment
Buat Models/CourseAssignment.cs
dengan kode berikut:
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; }
}
}
Hubungan banyak ke banyak Kursus Instruktur memerlukan tabel gabungan, dan entitas untuk tabel gabungan tersebut adalah CourseAssignment.
Adalah umum untuk memberi nama entitas EntityName1EntityName2
gabungan . Misalnya, tabel gabungan Instruktur-ke-Kursus menggunakan pola ini adalah CourseInstructor
. Namun, sebaiknya gunakan nama yang menjelaskan hubungan.
Model data dimulai dengan sederhana dan berkembang. Menggabungkan tabel tanpa payload (PJT) sering berkembang untuk menyertakan payload. Dengan memulai dengan nama entitas deskriptif, nama tidak perlu berubah saat tabel gabungan berubah. Idealnya, entitas gabungan akan memiliki nama alami sendiri (mungkin kata tunggal) di domain bisnis. Misalnya, Buku dan Pelanggan dapat ditautkan dengan entitas gabungan yang disebut Peringkat. Untuk hubungan banyak ke banyak Kursus Instruktur, CourseAssignment
lebih disukai daripada CourseInstructor
.
Kunci komposit
Dua FK di CourseAssignment
(InstructorID
dan CourseID
) secara unik mengidentifikasi setiap baris CourseAssignment
tabel. CourseAssignment
tidak memerlukan PK khusus. Properti InstructorID
dan CourseID
berfungsi sebagai PK komposit. Satu-satunya cara untuk menentukan PK EF Core komposit adalah dengan API yang fasih. Bagian berikutnya menunjukkan cara mengonfigurasi PK komposit.
Kunci komposit memastikan bahwa:
- Beberapa baris diizinkan untuk satu kursus.
- Beberapa baris diizinkan untuk satu instruktur.
- Beberapa baris tidak diizinkan untuk instruktur dan kursus yang sama.
Entitas Enrollment
gabungan mendefinisikan PK sendiri, sehingga duplikat semacam ini dimungkinkan. Untuk mencegah duplikat tersebut:
- Menambahkan indeks unik pada bidang FK, atau
- Konfigurasikan
Enrollment
dengan kunci komposit utama yang miripCourseAssignment
dengan . Untuk informasi selengkapnya, lihat Indeks.
Memperbarui konteks database
Perbarui Data/SchoolContext.cs
dengan kode berikut:
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 });
}
}
}
Kode sebelumnya menambahkan entitas baru dan mengonfigurasi CourseAssignment
PK komposit entitas.
Alternatif API fasih untuk atribut
Metode OnModelCreating
dalam kode sebelumnya menggunakan API fasih untuk mengonfigurasi EF Core perilaku. API disebut "fasih" karena sering digunakan dengan merangkai serangkaian panggilan metode bersama-sama ke dalam satu pernyataan. Kode berikut adalah contoh API fasih:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
Dalam tutorial ini, API fasih hanya digunakan untuk pemetaan database yang tidak dapat dilakukan dengan atribut. Namun, API fasih dapat menentukan sebagian besar aturan pemformatan, validasi, dan pemetaan yang dapat dilakukan dengan atribut.
Beberapa atribut seperti MinimumLength
tidak dapat diterapkan dengan API yang fasih. MinimumLength
tidak mengubah skema, itu hanya menerapkan aturan validasi panjang minimum.
Beberapa pengembang lebih suka menggunakan API yang fasih secara eksklusif sehingga mereka dapat menjaga kelas entitas mereka tetap "bersih." Atribut dan API fasih dapat dicampur. Ada beberapa konfigurasi yang hanya dapat dilakukan dengan API yang fasih (menentukan PK komposit). Ada beberapa konfigurasi yang hanya dapat dilakukan dengan atribut (MinimumLength
). Praktik yang direkomendasikan untuk menggunakan API atau atribut yang fasih:
- Pilih salah satu dari dua pendekatan ini.
- Gunakan pendekatan yang dipilih secara konsisten sebanyak mungkin.
Beberapa atribut yang digunakan dalam tutorial ini digunakan untuk:
- Validasi saja (misalnya,
MinimumLength
). - EF Core konfigurasi saja (misalnya,
HasKey
). - Validasi dan EF Core konfigurasi (misalnya,
[StringLength(50)]
).
Untuk informasi selengkapnya tentang atribut vs. API yang fasih, lihat Metode konfigurasi.
Diagram entitas
Ilustrasi berikut menunjukkan diagram yang dibuat EF Power Tools untuk model Sekolah yang telah selesai.
Diagram sebelumnya menunjukkan:
- Beberapa garis hubungan satu ke banyak (1 hingga *).
- Garis hubungan satu-ke-nol-atau-satu (1 hingga 0,.1) antara
Instructor
entitas danOfficeAssignment
. - Garis hubungan nol atau satu-ke-banyak (0,.1 hingga *) antara
Instructor
entitas danDepartment
.
Seed database
Perbarui kode di Data/DbInitializer.cs
:
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("2016-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01") }
};
context.Students.AddRange(students);
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") }
};
context.Instructors.AddRange(instructors);
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 }
};
context.Departments.AddRange(departments);
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
},
};
context.Courses.AddRange(courses);
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" },
};
context.OfficeAssignments.AddRange(officeAssignments);
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
},
};
context.CourseAssignments.AddRange(courseInstructors);
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();
}
}
}
Kode sebelumnya menyediakan data benih untuk entitas baru. Sebagian besar kode ini membuat objek entitas baru dan memuat data sampel. Data sampel digunakan untuk pengujian. Lihat Enrollments
dan CourseAssignments
untuk contoh berapa banyak tabel gabungan yang dapat disemai.
Menambahkan migrasi
Bangun proyek.
Di PMC, jalankan perintah berikut.
Add-Migration ComplexDataModel
Perintah sebelumnya menampilkan peringatan tentang kemungkinan kehilangan data.
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
To undo this action, use 'ef migrations remove'
database update
Jika perintah dijalankan, kesalahan berikut dihasilkan:
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 'DepartmentID'.
Di bagian berikutnya, Anda akan melihat apa yang harus dilakukan tentang kesalahan ini.
Menerapkan migrasi atau menghilangkan dan membuat ulang
Sekarang setelah Anda memiliki database yang sudah ada, Anda perlu memikirkan cara menerapkan perubahan pada database tersebut. Tutorial ini menunjukkan dua alternatif:
- Hilangkan dan buat ulang database. Pilih bagian ini jika Anda menggunakan SQLite.
- Terapkan migrasi ke database yang sudah ada. Instruksi di bagian ini hanya berfungsi untuk SQL Server, bukan untuk SQLite.
Salah satu pilihan berfungsi untuk SQL Server. Meskipun metode migrasi terapkan lebih kompleks dan memakan waktu, metode ini adalah pendekatan yang disukai untuk lingkungan produksi dunia nyata.
Menghilangkan dan membuat ulang database
Lewati bagian ini jika Anda menggunakan SQL Server dan ingin melakukan pendekatan terapkan-migrasi di bagian berikut.
Untuk memaksa EF Core membuat database baru, hilangkan dan perbarui database:
Di Package Manager Console (PMC), jalankan perintah berikut:
Drop-Database
Hapus folder Migrasi, lalu jalankan perintah berikut:
Add-Migration InitialCreate Update-Database
Jalankan aplikasi. Menjalankan aplikasi menjalankan DbInitializer.Initialize
metode . Mengisi DbInitializer.Initialize
database baru.
Buka database di SSOX:
Jika SSOX dibuka sebelumnya, klik tombol Refresh .
Perluas simpul Tabel . Tabel yang dibuat ditampilkan.
Periksa tabel CourseAssignment:
- Klik kanan tabel CourseAssignment dan pilih Tampilkan Data.
- Verifikasi tabel CourseAssignment berisi data.
Menerapkan migrasi
Bagian ini bersifat opsional. Langkah-langkah ini hanya berfungsi untuk SQL Server LocalDB dan hanya jika Anda melewati bagian Jatuhkan dan buat ulang database sebelumnya.
Saat migrasi dijalankan dengan data yang ada, mungkin ada batasan FK yang tidak puas dengan data yang ada. Dengan data produksi, langkah-langkah harus diambil untuk memigrasikan data yang ada. Bagian ini menyediakan contoh memperbaiki pelanggaran batasan FK. Jangan membuat perubahan kode ini tanpa cadangan. Jangan membuat perubahan kode ini jika Anda menyelesaikan bagian Jatuhkan dan buat ulang database sebelumnya.
File {timestamp}_ComplexDataModel.cs
berisi kode berikut:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Kode sebelumnya menambahkan FK Course
yang tidak dapat DepartmentID
diubah ke tabel. Database dari tutorial sebelumnya berisi baris di Course
, sehingga tabel tidak dapat diperbarui oleh migrasi.
Untuk membuat ComplexDataModel
migrasi berfungsi dengan data yang ada:
- Ubah kode untuk memberi kolom baru (
DepartmentID
) nilai default. - Buat departemen palsu bernama "Temp" untuk bertindak sebagai departemen default.
Memperbaiki batasan kunci asing
ComplexDataModel
Di kelas migrasi, perbarui Up
metode :
- Buka file
{timestamp}_ComplexDataModel.cs
. - Komentari baris kode yang menambahkan
DepartmentID
kolom keCourse
tabel.
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);
Tambahkan kode yang disorot berikut. Kode baru berjalan setelah .CreateTable( name: "Department"
blok:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(type: "int", nullable: true),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", 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);
Dengan perubahan sebelumnya, baris yang ada Course
akan terkait dengan departemen "Temp" setelah ComplexDataModel.Up
metode berjalan.
Cara menangani situasi yang ditunjukkan di sini disederhanakan untuk tutorial ini. Aplikasi produksi akan:
- Sertakan kode atau skrip untuk menambahkan
Department
baris dan baris terkaitCourse
ke baris baruDepartment
. - Tidak menggunakan departemen "Temp" atau nilai default untuk
Course.DepartmentID
.
Di Package Manager Console (PMC), jalankan perintah berikut:
Update-Database
DbInitializer.Initialize
Karena metode ini dirancang untuk hanya berfungsi dengan database kosong, gunakan SSOX untuk menghapus semua baris dalam tabel Siswa dan Kursus. (Penghapusan kaskade akan mengurus tabel Pendaftaran.)
Jalankan aplikasi. Menjalankan aplikasi menjalankan DbInitializer.Initialize
metode . Mengisi DbInitializer.Initialize
database baru.
Langkah berikutnya
Dua tutorial berikutnya menunjukkan cara membaca dan memperbarui data terkait.
Tutorial sebelumnya bekerja dengan model data dasar yang terdiri dari tiga entitas. Dalam tutorial ini:
- Lebih banyak entitas dan hubungan ditambahkan.
- Model data dikustomisasi dengan menentukan aturan pemformatan, validasi, dan pemetaan database.
Kelas entitas untuk model data yang telah selesai ditampilkan dalam ilustrasi berikut:
Jika Anda mengalami masalah yang tidak dapat Anda selesaikan, unduh aplikasi yang telah selesai.
Mengkustomisasi model data dengan atribut
Di bagian ini, model data disesuaikan menggunakan atribut.
Atribut DataType
Halaman siswa saat ini menampilkan waktu tanggal pendaftaran. Biasanya, bidang tanggal hanya memperlihatkan tanggal dan bukan waktunya.
Perbarui Models/Student.cs
dengan kode yang disorot berikut:
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; }
}
}
Atribut DataType menentukan jenis data yang lebih spesifik daripada jenis intrinsik database. Dalam hal ini hanya tanggal yang harus ditampilkan, bukan tanggal dan waktu. Enumerasi DataType menyediakan banyak jenis data, seperti Tanggal, Waktu, Nomor Telepon, Mata Uang, EmailAddress, dll. Atribut ini DataType
juga dapat memungkinkan aplikasi untuk secara otomatis menyediakan fitur khusus jenis. Contohnya:
mailto:
Tautan secara otomatis dibuat untukDataType.EmailAddress
.- Pemilih tanggal disediakan untuk
DataType.Date
sebagian besar browser.
Atribut ini DataType
memancarkan atribut HTML 5 data-
(tanda hubung data yang diucapkan) yang digunakan browser HTML 5. Atribut DataType
tidak memberikan validasi.
DataType.Date
tidak menentukan format tanggal yang ditampilkan. Secara default, bidang tanggal ditampilkan sesuai dengan format default berdasarkan CultureInfo server.
Atribut DisplayFormat
digunakan untuk secara eksplisit menentukan format tanggal:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Pengaturan ApplyFormatInEditMode
menentukan bahwa pemformatan juga harus diterapkan ke antarmuka pengguna edit. Beberapa bidang tidak boleh menggunakan ApplyFormatInEditMode
. Misalnya, simbol mata uang umumnya tidak boleh ditampilkan dalam kotak teks edit.
Atribut DisplayFormat
dapat digunakan dengan sendirinya. Umumnya merupakan ide yang baik untuk menggunakan DataType
atribut dengan DisplayFormat
atribut . Atribut DataType
menyampaikan semantik data dibandingkan dengan cara merendernya di layar. Atribut DataType
memberikan manfaat berikut yang tidak tersedia di DisplayFormat
:
- Browser dapat mengaktifkan fitur HTML5. Misalnya, tampilkan kontrol kalender, simbol mata uang yang sesuai lokal, tautan email, validasi input sisi klien, dll.
- Secara default, browser merender data menggunakan format yang benar berdasarkan lokal.
Untuk informasi selengkapnya, lihat <dokumentasi Input> Tag Helper.
Jalankan aplikasi. Navigasi ke halaman Indeks Siswa. Waktu tidak lagi ditampilkan. Setiap tampilan yang menggunakan Student
model menampilkan tanggal tanpa waktu.
Atribut StringLength
Aturan validasi data dan pesan kesalahan validasi dapat ditentukan dengan atribut. Atribut StringLength menentukan panjang minimum dan maksimum karakter yang diizinkan dalam bidang data. Atribut ini StringLength
juga menyediakan validasi sisi klien dan sisi server. Nilai minimum tidak berdampak pada skema database.
Student
Perbarui model dengan kode berikut:
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
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; }
}
}
Kode sebelumnya membatasi nama hingga tidak lebih dari 50 karakter. Atribut StringLength
tidak mencegah pengguna memasukkan spasi kosong untuk nama. Atribut RegularExpression digunakan untuk menerapkan pembatasan pada input. Misalnya, kode berikut mengharuskan karakter pertama menjadi huruf besar dan karakter yang tersisa menjadi alfabet:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Jalankan aplikasi:
- Navigasi ke halaman Siswa.
- Pilih Buat Baru, dan masukkan nama yang lebih panjang dari 50 karakter.
- Pilih Buat, validasi sisi klien memperlihatkan pesan kesalahan.
Di SQL Server Object Explorer (SSOX), buka perancang tabel Siswa dengan mengklik dua kali tabel Siswa .
Gambar sebelumnya menunjukkan skema untuk Student
tabel. Bidang nama memiliki jenis nvarchar(MAX)
karena migrasi belum dijalankan pada DB. Ketika migrasi dijalankan nanti dalam tutorial ini, bidang nama menjadi nvarchar(50)
.
Atribut Kolom
Atribut dapat mengontrol bagaimana kelas dan properti dipetakan ke database. Di bagian ini, Column
atribut digunakan untuk memetakan nama FirstMidName
properti ke "FirstName" di DB.
Saat DB dibuat, nama properti pada model digunakan untuk nama kolom (kecuali saat Column
atribut digunakan).
Model menggunakan Student
FirstMidName
untuk bidang nama depan karena bidang mungkin juga berisi nama tengah.
Student.cs
Perbarui file dengan kode yang disorot berikut:
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
[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; }
}
}
Dengan perubahan sebelumnya, Student.FirstMidName
di peta aplikasi ke FirstName
kolom Student
tabel.
Penambahan Column
atribut mengubah model yang SchoolContext
mendukung . Model yang SchoolContext
mendukung tidak lagi cocok dengan database. Jika aplikasi dijalankan sebelum menerapkan migrasi, pengecualian berikut dibuat:
SqlException: Invalid column name 'FirstName'.
Untuk memperbarui DB:
- Bangun proyek.
- Buka jendela perintah di folder proyek. Masukkan perintah berikut untuk membuat migrasi baru dan perbarui DB:
Add-Migration ColumnFirstName
Update-Database
Perintah migrations add ColumnFirstName
menghasilkan pesan peringatan berikut:
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Peringatan dihasilkan karena bidang nama sekarang dibatasi hingga 50 karakter. Jika nama dalam DB memiliki lebih dari 50 karakter, karakter 51 hingga terakhir akan hilang.
- Menguji aplikasi.
Buka tabel Siswa di SSOX:
Sebelum migrasi diterapkan, kolom nama berjenis nvarchar(MAX). Kolom nama sekarang nvarchar(50)
. Nama kolom telah berubah dari FirstMidName
menjadi FirstName
.
Catatan
Di bagian berikut, membangun aplikasi pada beberapa tahap menghasilkan kesalahan pengkompilasi. Instruksi menentukan kapan harus membuat aplikasi.
Pembaruan entitas siswa
Perbarui Models/Student.cs
dengan kode berikut:
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, ErrorMessage = "First name cannot be longer than 50 characters.")]
[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; }
}
}
Atribut Wajib
Atribut Required
membuat properti nama menjadi bidang yang diperlukan. Atribut Required
tidak diperlukan untuk jenis yang tidak dapat diubah ke null seperti jenis nilai (DateTime
, , int
double
, dll.). Jenis yang tidak boleh null secara otomatis diperlakukan sebagai bidang yang diperlukan.
Atribut Required
dapat diganti dengan parameter panjang minimum dalam StringLength
atribut :
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
Atribut Tampilan
Atribut Display
menentukan bahwa keterangan untuk kotak teks harus "Nama Depan", "Nama Belakang", "Nama Lengkap", dan "Tanggal Pendaftaran." Keterangan default tidak memiliki spasi membalik kata, misalnya "Nama Belakang."
Properti terhitung FullName
FullName
adalah properti terhitung yang mengembalikan nilai yang dibuat dengan menggabungkan dua properti lainnya. FullName
tidak dapat diatur, hanya memiliki aksesor get. Tidak ada FullName
kolom yang dibuat dalam database.
Membuat Entitas Instruktur
Buat Models/Instructor.cs
dengan kode berikut:
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; }
}
}
Beberapa atribut dapat berada di satu baris. Atribut HireDate
dapat ditulis sebagai berikut:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Properti navigasi CourseAssignments dan OfficeAssignment
Properti CourseAssignments
dan OfficeAssignment
adalah properti navigasi.
Instruktur dapat mengajarkan sejumlah kursus, sehingga CourseAssignments
didefinisikan sebagai koleksi.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Jika properti navigasi menyimpan beberapa entitas:
- Ini harus berupa tipe daftar tempat entri dapat ditambahkan, dihapus, dan diperbarui.
Jenis properti navigasi meliputi:
ICollection<T>
List<T>
HashSet<T>
Jika ICollection<T>
ditentukan, EF Core membuat HashSet<T>
koleksi secara default.
Entitas CourseAssignment
dijelaskan di bagian tentang hubungan banyak ke banyak.
Aturan bisnis Contoso University menyatakan bahwa instruktur dapat memiliki paling banyak satu kantor. Properti OfficeAssignment
menyimpan satu OfficeAssignment
entitas. OfficeAssignment
null jika tidak ada kantor yang ditetapkan.
public OfficeAssignment OfficeAssignment { get; set; }
Membuat entitas OfficeAssignment
Buat Models/OfficeAssignment.cs
dengan kode berikut:
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; }
}
}
Atribut Kunci
Atribut [Key]
digunakan untuk mengidentifikasi properti sebagai kunci utama (PK) ketika nama properti adalah sesuatu selain classnameID atau ID.
Ada hubungan satu-ke-nol-atau-satu antara Instructor
entitas dan OfficeAssignment
. Penugasan kantor hanya ada dalam kaitannya dengan instruktur yang ditetapkannya. OfficeAssignment
PK juga merupakan kunci asingnya (FK) ke Instructor
entitas. EF Core tidak dapat secara otomatis dikenali InstructorID
sebagai PK karena OfficeAssignment
:
InstructorID
tidak mengikuti konvensi penamaan ID atau classnameID.
Oleh karena itu, Key
atribut digunakan untuk mengidentifikasi InstructorID
sebagai PK:
[Key]
public int InstructorID { get; set; }
Secara default, EF Core memperlakukan kunci sebagai non-database yang dihasilkan karena kolom adalah untuk hubungan identifikasi.
Properti navigasi Instruktur
Properti OfficeAssignment
navigasi untuk Instructor
entitas dapat diubah ke null karena:
- Jenis referensi (seperti kelas dapat diubah ke null).
- Instruktur mungkin tidak memiliki tugas kantor.
Entitas OfficeAssignment
memiliki properti navigasi yang tidak dapat diubah ke Instructor
null karena:
InstructorID
tidak dapat diubah ke null.- Penugasan kantor tidak boleh ada tanpa instruktur.
Instructor
Ketika entitas memiliki entitas terkaitOfficeAssignment
, setiap entitas memiliki referensi ke entitas lain di properti navigasinya.
Atribut [Required]
dapat diterapkan ke Instructor
properti navigasi:
[Required]
public Instructor Instructor { get; set; }
Kode sebelumnya menentukan bahwa harus ada instruktur terkait. Kode sebelumnya tidak perlu karena InstructorID
kunci asing (yang juga PK) tidak dapat diubah ke null.
Mengubah Entitas Kursus
Perbarui Models/Course.cs
dengan kode berikut:
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; }
}
}
Entitas Course
memiliki properti DepartmentID
kunci asing (FK). DepartmentID
menunjuk ke entitas terkait Department
. Entitas Course
memiliki Department
properti navigasi.
EF Core tidak memerlukan properti FK untuk model data saat model memiliki properti navigasi untuk entitas terkait.
EF Core secara otomatis membuat FK dalam database di mana pun mereka diperlukan. EF Coremembuat properti bayangan untuk FK yang dibuat secara otomatis. Memiliki FK dalam model data dapat membuat pembaruan lebih sederhana dan lebih efisien. Misalnya, pertimbangkan model di mana properti DepartmentID
FK tidak disertakan. Saat entitas kursus diambil untuk diedit:
- Entitas
Department
null jika tidak dimuat secara eksplisit. - Untuk memperbarui entitas kursus,
Department
entitas harus terlebih dahulu diambil.
Saat properti DepartmentID
FK disertakan dalam model data, tidak perlu mengambil Department
entitas sebelum pembaruan.
Atribut DatabaseGenerated
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
menentukan bahwa PK disediakan oleh aplikasi daripada yang dihasilkan oleh database.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Secara default, EF Core mengasumsikan bahwa nilai PK dihasilkan oleh DB. Nilai PK yang dihasilkan DB umumnya merupakan pendekatan terbaik. Untuk Course
entitas, pengguna menentukan PK. Misalnya, nomor kursus seperti seri 1000 untuk departemen matematika, seri 2000 untuk departemen bahasa Inggris.
Atribut DatabaseGenerated
juga dapat digunakan untuk menghasilkan nilai default. Misalnya, DB dapat secara otomatis menghasilkan bidang tanggal untuk merekam tanggal baris dibuat atau diperbarui. Untuk informasi selengkapnya, lihat Properti yang Dihasilkan.
Properti kunci dan navigasi asing
Properti kunci asing (FK) dan properti navigasi dalam Course
entitas mencerminkan hubungan berikut:
Kursus ditugaskan ke satu departemen, jadi ada DepartmentID
FK dan Department
properti navigasi.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kursus dapat memiliki sejumlah siswa yang terdaftar di dalamnya, sehingga Enrollments
properti navigasi adalah koleksi:
public ICollection<Enrollment> Enrollments { get; set; }
Kursus dapat diajarkan oleh beberapa instruktur, sehingga CourseAssignments
properti navigasi adalah koleksi:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
dijelaskan kemudian.
Membuat entitas Departemen
Buat Models/Department.cs
dengan kode berikut:
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; }
}
}
Atribut Kolom
Sebelumnya Column
atribut digunakan untuk mengubah pemetaan nama kolom. Dalam kode untuk Department
entitas, Column
atribut digunakan untuk mengubah pemetaan jenis data SQL. Kolom Budget
didefinisikan menggunakan jenis uang SQL Server di DB:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Pemetaan kolom umumnya tidak diperlukan. EF Core umumnya memilih jenis data SQL Server yang sesuai berdasarkan jenis CLR untuk properti . Jenis CLR decimal
memetakan ke jenis SQL Server decimal
. Budget
adalah untuk mata uang, dan jenis data uang lebih sesuai untuk mata uang.
Properti kunci dan navigasi asing
Properti FK dan navigasi mencerminkan hubungan berikut:
- Departemen mungkin atau mungkin tidak memiliki administrator.
- Administrator selalu menjadi instruktur.
InstructorID
Oleh karena itu properti disertakan sebagai FK keInstructor
entitas.
Properti navigasi diberi nama Administrator
tetapi menyimpan Instructor
entitas:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Tanda tanya (?) dalam kode sebelumnya menentukan properti dapat diubah ke null.
Departemen mungkin memiliki banyak kursus, jadi ada properti navigasi Kursus:
public ICollection<Course> Courses { get; set; }
Catatan: Menurut konvensi, EF Core memungkinkan penghapusan berjenjang untuk FK yang tidak dapat diubah ke null dan untuk hubungan banyak ke banyak. Penghapusan berskala dapat mengakibatkan aturan penghapusan kaskade melingkar. Aturan penghapusan kaskade melingkar menyebabkan pengecualian saat migrasi ditambahkan.
Misalnya, jika Department.InstructorID
properti didefinisikan sebagai tidak dapat diubah ke null:
EF Core mengonfigurasi aturan penghapusan kaskade untuk menghapus departemen saat instruktur dihapus.
Menghapus departemen ketika instruktur dihapus bukanlah perilaku yang dimaksudkan.
API fasih berikut akan menetapkan aturan pembatasan alih-alih bertingkat.
modelBuilder.Entity<Department>() .HasOne(d => d.Administrator) .WithMany() .OnDelete(DeleteBehavior.Restrict)
Kode sebelumnya menonaktifkan penghapusan kaskade pada hubungan instruktur departemen.
Memperbarui entitas Pendaftaran
Catatan pendaftaran adalah untuk satu kursus yang diambil oleh satu siswa.
Perbarui Models/Enrollment.cs
dengan kode berikut:
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; }
}
}
Properti kunci dan navigasi asing
Properti FK dan properti navigasi mencerminkan hubungan berikut:
Catatan pendaftaran adalah untuk satu kursus, jadi ada CourseID
properti FK dan Course
properti navigasi:
public int CourseID { get; set; }
public Course Course { get; set; }
Catatan pendaftaran adalah untuk satu siswa, jadi ada StudentID
properti FK dan Student
properti navigasi:
public int StudentID { get; set; }
public Student Student { get; set; }
Hubungan Banyak ke Banyak
Ada hubungan banyak-ke-banyak antara Student
entitas dan Course
. Entitas Enrollment
berfungsi sebagai tabel gabungan banyak ke banyak dengan payload dalam database. "Dengan payload" berarti bahwa Enrollment
tabel berisi data tambahan selain FK untuk tabel yang digabungkan (dalam hal ini, PK dan Grade
).
Ilustrasi berikut menunjukkan seperti apa hubungan ini dalam diagram entitas. (Diagram ini dihasilkan menggunakan EF Power Tools untuk EF 6.x. Membuat diagram bukan bagian dari tutorial.)
Setiap garis hubungan memiliki 1 di satu ujung dan tanda bintang (*) di ujung lainnya, menunjukkan hubungan satu-ke-banyak.
Enrollment
Jika tabel tidak menyertakan informasi nilai, tabel hanya perlu berisi dua FK (CourseID
dan StudentID
). Tabel gabungan banyak ke banyak tanpa payload terkadang disebut tabel gabungan murni (PJT).
Entitas Instructor
dan Course
memiliki hubungan banyak ke banyak menggunakan tabel gabungan murni.
Catatan: EF 6.x mendukung tabel gabungan implisit untuk hubungan banyak ke banyak, tetapi EF Core tidak. Untuk informasi selengkapnya, lihat Hubungan banyak ke banyak di EF Core 2.0.
Entitas CourseAssignment
Buat Models/CourseAssignment.cs
dengan kode berikut:
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; }
}
}
Instruktur ke Kursus
Hubungan banyak ke banyak Kursus Instruktur:
- Memerlukan tabel gabungan yang harus diwakili oleh kumpulan entitas.
- Adalah tabel gabungan murni (tabel tanpa payload).
Adalah umum untuk memberi nama entitas EntityName1EntityName2
gabungan . Misalnya, tabel gabungan Instruktur-ke-Kursus menggunakan pola ini adalah CourseInstructor
. Namun, sebaiknya gunakan nama yang menjelaskan hubungan.
Model data dimulai dengan sederhana dan berkembang. Gabungan tanpa payload (PJT) sering berkembang untuk menyertakan payload. Dengan memulai dengan nama entitas deskriptif, nama tidak perlu berubah saat tabel gabungan berubah. Idealnya, entitas gabungan akan memiliki nama alami sendiri (mungkin kata tunggal) di domain bisnis. Misalnya, Buku dan Pelanggan dapat ditautkan dengan entitas gabungan yang disebut Peringkat. Untuk hubungan banyak ke banyak Kursus Instruktur, CourseAssignment
lebih disukai daripada CourseInstructor
.
Kunci komposit
FK tidak dapat diubah ke null. Dua FK di CourseAssignment
(InstructorID
dan CourseID
) secara unik mengidentifikasi setiap baris CourseAssignment
tabel. CourseAssignment
tidak memerlukan PK khusus. Properti InstructorID
dan CourseID
berfungsi sebagai PK komposit. Satu-satunya cara untuk menentukan PK EF Core komposit adalah dengan API yang fasih. Bagian berikutnya menunjukkan cara mengonfigurasi PK komposit.
Kunci komposit memastikan:
- Beberapa baris diizinkan untuk satu kursus.
- Beberapa baris diizinkan untuk satu instruktur.
- Beberapa baris untuk instruktur dan kursus yang sama tidak diizinkan.
Entitas Enrollment
gabungan mendefinisikan PK sendiri, sehingga duplikat semacam ini dimungkinkan. Untuk mencegah duplikat tersebut:
- Menambahkan indeks unik pada bidang FK, atau
- Konfigurasikan
Enrollment
dengan kunci komposit utama yang miripCourseAssignment
dengan . Untuk informasi selengkapnya, lihat Indeks.
Memperbarui konteks DB
Tambahkan kode yang disorot berikut ke Data/SchoolContext.cs
:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Models
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollment { get; set; }
public DbSet<Student> Student { 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 });
}
}
}
Kode sebelumnya menambahkan entitas baru dan mengonfigurasi CourseAssignment
PK komposit entitas.
Alternatif API fasih untuk atribut
Metode OnModelCreating
dalam kode sebelumnya menggunakan API fasih untuk mengonfigurasi EF Core perilaku. API disebut "fasih" karena sering digunakan dengan merangkai serangkaian panggilan metode bersama-sama ke dalam satu pernyataan. Kode berikut adalah contoh API fasih:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
Dalam tutorial ini, API fasih hanya digunakan untuk pemetaan DB yang tidak dapat dilakukan dengan atribut. Namun, API fasih dapat menentukan sebagian besar aturan pemformatan, validasi, dan pemetaan yang dapat dilakukan dengan atribut.
Beberapa atribut seperti MinimumLength
tidak dapat diterapkan dengan API yang fasih. MinimumLength
tidak mengubah skema, itu hanya menerapkan aturan validasi panjang minimum.
Beberapa pengembang lebih suka menggunakan API yang fasih secara eksklusif sehingga mereka dapat menjaga kelas entitas mereka tetap "bersih." Atribut dan API fasih dapat dicampur. Ada beberapa konfigurasi yang hanya dapat dilakukan dengan API yang fasih (menentukan PK komposit). Ada beberapa konfigurasi yang hanya dapat dilakukan dengan atribut (MinimumLength
). Praktik yang direkomendasikan untuk menggunakan API atau atribut yang fasih:
- Pilih salah satu dari dua pendekatan ini.
- Gunakan pendekatan yang dipilih secara konsisten sebanyak mungkin.
Beberapa atribut yang digunakan dalam tutorial ini digunakan untuk:
- Validasi saja (misalnya,
MinimumLength
). - EF Core konfigurasi saja (misalnya,
HasKey
). - Validasi dan EF Core konfigurasi (misalnya,
[StringLength(50)]
).
Untuk informasi selengkapnya tentang atribut vs. API yang fasih, lihat Metode konfigurasi.
Diagram Entitas Memperlihatkan Hubungan
Ilustrasi berikut menunjukkan diagram yang dibuat EF Power Tools untuk model Sekolah yang telah selesai.
Diagram sebelumnya menunjukkan:
- Beberapa garis hubungan satu ke banyak (1 hingga *).
- Garis hubungan satu-ke-nol-atau-satu (1 hingga 0,.1) antara
Instructor
entitas danOfficeAssignment
. - Garis hubungan nol atau satu-ke-banyak (0,.1 hingga *) antara
Instructor
entitas danDepartment
.
Seed DB dengan Data Pengujian
Perbarui kode di Data/DbInitializer.cs
:
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.Student.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.Student.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.Enrollment.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollment.Add(e);
}
}
context.SaveChanges();
}
}
}
Kode sebelumnya menyediakan data benih untuk entitas baru. Sebagian besar kode ini membuat objek entitas baru dan memuat data sampel. Data sampel digunakan untuk pengujian. Lihat Enrollments
dan CourseAssignments
untuk contoh berapa banyak tabel gabungan yang dapat disemai.
Menambahkan migrasi
Bangun proyek.
Add-Migration ComplexDataModel
Perintah sebelumnya menampilkan peringatan tentang kemungkinan kehilangan data.
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'
database update
Jika perintah dijalankan, kesalahan berikut dihasilkan:
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 'DepartmentID'.
Menerapkan migrasi
Sekarang setelah Anda memiliki database yang sudah ada, Anda perlu memikirkan cara menerapkan perubahan di masa mendatang. Tutorial ini menunjukkan dua pendekatan:
- Menghilangkan dan membuat ulang database
- Terapkan migrasi ke database yang sudah ada. Meskipun metode ini lebih kompleks dan memakan waktu, metode ini adalah pendekatan yang disukai untuk lingkungan produksi dunia nyata. Catatan: Ini adalah bagian opsional dari tutorial. Anda dapat melakukan langkah-langkah drop dan re-create dan melewati bagian ini. Jika Anda ingin mengikuti langkah-langkah di bagian ini, jangan lakukan langkah-langkah drop dan buat ulang.
Menghilangkan dan membuat ulang database
Kode dalam yang diperbarui DbInitializer
menambahkan data benih untuk entitas baru. Untuk memaksa EF Core membuat DB baru, hilangkan dan perbarui DB:
Di Package Manager Console (PMC), jalankan perintah berikut:
Drop-Database
Update-Database
Jalankan Get-Help about_EntityFrameworkCore
dari PMC untuk mendapatkan informasi bantuan.
Jalankan aplikasi. Menjalankan aplikasi menjalankan DbInitializer.Initialize
metode . Mengisi DbInitializer.Initialize
DB baru.
Buka DB di SSOX:
- Jika SSOX dibuka sebelumnya, klik tombol Refresh .
- Perluas simpul Tabel . Tabel yang dibuat ditampilkan.
Periksa tabel CourseAssignment:
- Klik kanan tabel CourseAssignment dan pilih Tampilkan Data.
- Verifikasi tabel CourseAssignment berisi data.
Menerapkan migrasi ke database yang sudah ada
Bagian ini bersifat opsional. Langkah-langkah ini hanya berfungsi jika Anda melewati bagian Jatuhkan dan buat ulang database sebelumnya.
Saat migrasi dijalankan dengan data yang ada, mungkin ada batasan FK yang tidak puas dengan data yang ada. Dengan data produksi, langkah-langkah harus diambil untuk memigrasikan data yang ada. Bagian ini menyediakan contoh memperbaiki pelanggaran batasan FK. Jangan membuat perubahan kode ini tanpa cadangan. Jangan membuat perubahan kode ini jika Anda menyelesaikan bagian sebelumnya dan memperbarui database.
File {timestamp}_ComplexDataModel.cs
berisi kode berikut:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Kode sebelumnya menambahkan FK Course
yang tidak dapat DepartmentID
diubah ke tabel. DB dari tutorial sebelumnya berisi baris di Course
, sehingga tabel tidak dapat diperbarui oleh migrasi.
Untuk membuat ComplexDataModel
migrasi berfungsi dengan data yang ada:
- Ubah kode untuk memberi kolom baru (
DepartmentID
) nilai default. - Buat departemen palsu bernama "Temp" untuk bertindak sebagai departemen default.
Memperbaiki batasan kunci asing
ComplexDataModel
Perbarui metode kelasUp
:
- Buka file
{timestamp}_ComplexDataModel.cs
. - Komentari baris kode yang menambahkan
DepartmentID
kolom keCourse
tabel.
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);
Tambahkan kode yang disorot berikut. Kode baru berjalan setelah .CreateTable( name: "Department"
blok:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(type: "int", nullable: true),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", 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);
Dengan perubahan sebelumnya, baris yang ada Course
akan terkait dengan departemen "Temp" setelah ComplexDataModel
Up
metode berjalan.
Aplikasi produksi akan:
- Sertakan kode atau skrip untuk menambahkan
Department
baris dan baris terkaitCourse
ke baris baruDepartment
. - Tidak menggunakan departemen "Temp" atau nilai default untuk
Course.DepartmentID
.
Tutorial berikutnya mencakup data terkait.
Sumber Daya Tambahan:
ASP.NET Core