Bagikan melalui


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:

Entity diagram

Diagram database berikut dibuat dengan Dataedo:

Dataedo diagram

Untuk membuat diagram database dengan Dataedo:

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, Telepon Number, Mata Uang, EmailAddress, dll. Atribut ini DataType juga dapat memungkinkan aplikasi untuk secara otomatis menyediakan fitur khusus jenis. Contohnya:

  • mailto: Tautan secara otomatis dibuat untuk DataType.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 .

Students table in SSOX before migrations

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 StudentFirstMidName 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 SchoolContextmendukung . 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, , DateTimeint, dan double). Jenis yang tidak boleh null secara otomatis diperlakukan sebagai bidang yang diperlukan.

Atribut Required harus digunakan untuk MinimumLengthMinimumLength 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:

    Students table in SSOX after migrations

    Sebelum migrasi diterapkan, kolom nama berjenis nvarchar(MAX). Kolom nama sekarang nvarchar(50). Nama kolom telah berubah dari FirstMidName menjadi FirstName.

  • 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 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

OfficeAssignment entity

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 DepartmentIDkunci 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 DepartmentIDFK tidak disertakan. Saat entitas kursus diambil untuk diedit:

  • Properti Department ini adalah 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 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 ke Instructor 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.

Enrollment entity

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

Student-Course many to many relationship

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:

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:

Entity diagram

Entitas Siswa

Student entity

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, Telepon Number, Mata Uang, EmailAddress, dll. Atribut ini DataType juga dapat memungkinkan aplikasi untuk secara otomatis menyediakan fitur khusus jenis. Contohnya:

  • mailto: Tautan secara otomatis dibuat untuk DataType.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 .

Students table in SSOX before migrations

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 StudentFirstMidName 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 SchoolContextmendukung . 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, , DateTimeint, dan double). Jenis yang tidak boleh null secara otomatis diperlakukan sebagai bidang yang diperlukan.

Atribut Required harus digunakan untuk MinimumLengthMinimumLength 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:

    Students table in SSOX after migrations

    Sebelum migrasi diterapkan, kolom nama berjenis nvarchar(MAX). Kolom nama sekarang nvarchar(50). Nama kolom telah berubah dari FirstMidName menjadi FirstName.

  • 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

Instructor entity

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

OfficeAssignment entity

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

Course entity

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 DepartmentIDkunci 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 DepartmentIDFK 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

Department entity

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 ke Instructor 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.

Enrollment entity

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

Student-Course many to many relationship

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

CourseAssignment entity

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.

Instructor-to-Courses m:M

Adalah umum untuk memberi nama entitas EntityName1EntityName2gabungan . 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 mirip CourseAssignmentdengan . 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.

Entity diagram

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 dan OfficeAssignment .
  • Garis hubungan nol atau satu-ke-banyak (0,.1 hingga *) antara Instructor entitas dan Department .

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:

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.

    Tables in SSOX

  • Periksa tabel CourseAssignment:

    • Klik kanan tabel CourseAssignment dan pilih Tampilkan Data.
    • Verifikasi tabel CourseAssignment berisi data.

    CourseAssignment data in SSOX

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 ke Course 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 terkait Course ke baris baru Department .
  • 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:

Entity diagram

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, Telepon Number, Mata Uang, EmailAddress, dll. Atribut ini DataType juga dapat memungkinkan aplikasi untuk secara otomatis menyediakan fitur khusus jenis. Contohnya:

  • mailto: Tautan secara otomatis dibuat untuk DataType.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.

Students index page showing dates without times

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.

Students index page showing string length errors

Di SQL Server Object Explorer (SSOX), buka perancang tabel Siswa dengan mengklik dua kali tabel Siswa .

Students table in SSOX before migrations

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 StudentFirstMidName 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 SchoolContextmendukung . 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:

Students table in SSOX after migrations

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

Student entity

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, , intdouble, 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

Instructor entity

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

OfficeAssignment entity

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

Course entity

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 DepartmentIDkunci 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 DepartmentIDFK 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

Department entity

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 ke Instructor 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.

Enrollment entity

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

Student-Course many to many relationship

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

CourseAssignment entity

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

Instructor-to-Courses m:M

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 EntityName1EntityName2gabungan . 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 mirip CourseAssignmentdengan . 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.

Entity diagram

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 dan OfficeAssignment .
  • Garis hubungan nol atau satu-ke-banyak (0,.1 hingga *) antara Instructor entitas dan Department .

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.

Tables in SSOX

Periksa tabel CourseAssignment:

  • Klik kanan tabel CourseAssignment dan pilih Tampilkan Data.
  • Verifikasi tabel CourseAssignment berisi data.

CourseAssignment data in SSOX

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 ke Course 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 ComplexDataModelUp metode berjalan.

Aplikasi produksi akan:

  • Sertakan kode atau skrip untuk menambahkan Department baris dan baris terkait Course ke baris baru Department .
  • Tidak menggunakan departemen "Temp" atau nilai default untuk Course.DepartmentID.

Tutorial berikutnya mencakup data terkait.

Sumber Daya Tambahan: