Samouczek: tworzenie bardziej złożonego modelu danych dla aplikacji MVC ASP.NET

W poprzednich samouczkach pracowaliśmy z prostym modelem danych składającym się z trzech jednostek. W tym samouczku dodasz więcej jednostek i relacji i dostosujesz model danych, określając reguły formatowania, walidacji i mapowania bazy danych. W tym artykule przedstawiono dwa sposoby dostosowywania modelu danych: dodając atrybuty do klas jednostek i dodając kod do klasy kontekstu bazy danych.

Po zakończeniu klasy jednostek składają się na ukończony model danych pokazany na poniższej ilustracji:

School_class_diagram

W tym samouczku zostały wykonane następujące czynności:

  • Dostosowywanie modelu danych
  • Aktualizowanie jednostki Student
  • Tworzenie jednostki instruktora
  • Tworzenie jednostki OfficeAssignment
  • Modyfikowanie jednostki Course
  • Tworzenie jednostki Dział
  • Modyfikowanie jednostki Enrollment
  • Dodawanie kodu do kontekstu bazy danych
  • Inicjuj bazę danych z danymi testowymi
  • Dodawanie migracji
  • Aktualizowanie bazy danych

Wymagania wstępne

Dostosowywanie modelu danych

W tej sekcji dowiesz się, jak dostosować model danych przy użyciu atrybutów określających reguły formatowania, walidacji i mapowania bazy danych. Następnie w kilku poniższych sekcjach utworzysz kompletny School model danych, dodając atrybuty do utworzonych już klas i tworząc nowe klasy dla pozostałych typów jednostek w modelu.

Atrybut DataType

W przypadku dat rejestracji uczniów wszystkie strony internetowe są obecnie wyświetlane wraz z datą, chociaż wszystko, o co dbasz o to pole, jest datą. Za pomocą atrybutów adnotacji danych można wprowadzić jedną zmianę kodu, która naprawi format wyświetlania w każdym widoku, który pokazuje dane. Aby zobaczyć przykład tego, jak to zrobić, dodasz atrybut do EnrollmentDate właściwości w Student klasie.

W pliku Models\Student.cs dodaj instrukcję using dla System.ComponentModel.DataAnnotations przestrzeni nazw i dodaj DataType atrybuty do DisplayFormatEnrollmentDate właściwości, jak pokazano w poniższym przykładzie:

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 virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Atrybut DataType służy do określania typu danych, który jest bardziej specyficzny niż typ wewnętrzny bazy danych. W tym przypadku chcemy śledzić tylko datę, a nie datę i godzinę. Wyliczenie DataType zawiera wiele typów danych, takich jak Data, Godzina, Liczba telefonów, Waluta, Adres e-mail i inne. Atrybut DataType może również umożliwić aplikacji automatyczne udostępnianie funkcji specyficznych dla typu. Na przykład mailto: można utworzyć link dla elementu DataType.EmailAddress, a selektor dat można podać dla elementu DataType.Date w przeglądarkach, które obsługują kod HTML5. Atrybuty DataType emitują atrybuty HTML 5 data- (wymawiane kreski danych), które mogą zrozumieć przeglądarki HTML 5. Atrybuty DataType nie zapewniają żadnej weryfikacji.

DataType.Date nie określa formatu wyświetlanej daty. Domyślnie pole danych jest wyświetlane zgodnie z domyślnymi formatami na podstawie informacji o kulturze serwera.

Atrybut DisplayFormat jest używany do jawnego określenia formatu daty:

[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]

Ustawienie ApplyFormatInEditMode określa, że określone formatowanie powinno być również stosowane, gdy wartość jest wyświetlana w polu tekstowym do edycji. (Możesz tego nie chcieć w przypadku niektórych pól — na przykład w przypadku wartości waluty możesz nie chcieć symbolu waluty w polu tekstowym do edycji).

Atrybut DisplayFormat można użyć sam w sobie, ale zazwyczaj warto użyć atrybutu DataType . Atrybut DataType przekazuje semantyka danych w przeciwieństwie do sposobu renderowania ich na ekranie i zapewnia następujące korzyści, których nie otrzymujesz za DisplayFormatpomocą polecenia :

  • Przeglądarka może włączyć funkcje HTML5 (na przykład w celu wyświetlenia kontrolki kalendarza, symbolu waluty odpowiedniego dla ustawień regionalnych, linków poczty e-mail, weryfikacji danych wejściowych po stronie klienta itp.).
  • Domyślnie przeglądarka renderuje dane przy użyciu poprawnego formatu na podstawie ustawień regionalnych.
  • Atrybut DataType może umożliwić MVC wybranie odpowiedniego szablonu pola do renderowania danych ( DisplayFormat używa szablonu ciągu). Aby uzyskać więcej informacji, zobacz szablony ASP.NET MVC 2 Brada Wilsona. (Chociaż został napisany dla MVC 2, ten artykuł nadal dotyczy bieżącej wersji ASP.NET MVC).

Jeśli używasz atrybutu DataType z polem daty, musisz również określić DisplayFormat atrybut, aby upewnić się, że pole jest poprawnie renderowane w przeglądarkach chrome. Aby uzyskać więcej informacji, zobacz ten wątek StackOverflow.

Aby uzyskać więcej informacji na temat obsługi innych formatów dat w programie MVC, przejdź do tematu MVC 5 Introduction: Examining the Edit Methods and Edit View and search in the page for "internationalization" (Wprowadzenie do wzorca MVC: Badanie metod edycji i widoku edycji i wyszukiwanie na stronie pod kątem "internationalization").

Uruchom ponownie stronę Indeks uczniów i zwróć uwagę, że czasy nie są już wyświetlane dla dat rejestracji. To samo będzie dotyczyć każdego widoku, który korzysta z Student modelu.

Students_index_page_with_formatted_date

CiągLengthAttribute

Można również określić reguły walidacji danych i komunikaty o błędach walidacji przy użyciu atrybutów. Atrybut StringLength ustawia maksymalną długość bazy danych i zapewnia weryfikację po stronie klienta i serwera dla ASP.NET MVC. Możesz również określić minimalną długość ciągu w tym atrybucie, ale minimalna wartość nie ma wpływu na schemat bazy danych.

Załóżmy, że chcesz mieć pewność, że użytkownicy nie wprowadzają więcej niż 50 znaków dla nazwy. Aby dodać to ograniczenie, dodaj atrybuty StringLength do LastName właściwości i FirstMidName , jak pokazano w poniższym przykładzie:

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 virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Atrybut StringLength nie uniemożliwi użytkownikowi wprowadzenia białego odstępu dla nazwy. Możesz użyć atrybutu RegularExpression , aby zastosować ograniczenia do danych wejściowych. Na przykład poniższy kod wymaga, aby pierwszy znak był wielkimi literami, a pozostałe znaki mają być alfabetyczne:

[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]

Atrybut MaxLength zapewnia podobną funkcjonalność do atrybutu StringLength , ale nie zapewnia weryfikacji po stronie klienta.

Uruchom aplikację i kliknij kartę Uczniowie . Zostanie wyświetlony następujący błąd:

Model tworzący kopię zapasową kontekstu "SchoolContext" zmienił się od czasu utworzenia bazy danych. Rozważ użycie Migracje Code First do zaktualizowania bazy danych (https://go.microsoft.com/fwlink/?LinkId=238269).

Model bazy danych zmienił się w sposób, który wymaga zmiany schematu bazy danych, a program Entity Framework wykrył, że. Użyjesz migracji, aby zaktualizować schemat bez utraty danych dodanych do bazy danych przy użyciu interfejsu użytkownika. Jeśli zmieniono dane utworzone przez metodę Seed , zostanie ona zmieniona z powrotem na jej oryginalny stan ze względu na metodę AddOrUpdate , której używasz w metodzie Seed . (AddOrUpdate jest odpowiednikiem operacji "upsert" z terminologii bazy danych).

W konsoli Menedżera pakietów (PMC) wprowadź następujące polecenia:

add-migration MaxLengthOnNames
update-database

Polecenie add-migration tworzy plik o nazwie <timeStamp>_MaxLengthOnNames.cs. Ten plik zawiera kod w metodzie Up , która zaktualizuje bazę danych tak, aby odpowiadała bieżącemu modelowi danych. Polecenie update-database uruchomiło ten kod.

Sygnatura czasowa poprzedzona nazwą pliku migracji jest używana przez program Entity Framework do zamawiania migracji. Przed uruchomieniem update-database polecenia można utworzyć wiele migracji, a następnie wszystkie migracje są stosowane w kolejności, w której zostały utworzone.

Uruchom stronę Tworzenie i wprowadź nazwę dłuższą niż 50 znaków. Po kliknięciu przycisku Utwórz po stronie klienta zostanie wyświetlony komunikat o błędzie: Pole LastName musi być ciągiem o maksymalnej długości 50.

Atrybut kolumny

Można również użyć atrybutów, aby kontrolować sposób mapowania klas i właściwości na bazę danych. Załóżmy, że użyto nazwy FirstMidName pola imię, ponieważ pole może również zawierać średnią nazwę. Jednak chcesz, aby kolumna bazy danych miała nazwę FirstName, ponieważ użytkownicy, którzy będą pisać zapytania ad hoc względem bazy danych, są przyzwyczajeni do tej nazwy. Aby wykonać to mapowanie, możesz użyć atrybutu Column .

Atrybut Column określa, że po utworzeniu bazy danych kolumna Student tabeli, która mapuje na FirstMidName właściwość, będzie mieć nazwę FirstName. Innymi słowy, gdy kod odwołuje się do Student.FirstMidNameelementu , dane pochodzą lub zostaną zaktualizowane w FirstName kolumnie Student tabeli. Jeśli nie określisz nazw kolumn, mają taką samą nazwę jak nazwa właściwości.

W pliku Student.cs dodaj instrukcję usingDla elementu System.ComponentModel.DataAnnotations.Schema i dodaj atrybut nazwy kolumny do FirstMidName właściwości, jak pokazano w następującym wyróżnionym kodzie:

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 virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Dodanie atrybutu Kolumna zmienia model kopii zapasowej obiektu SchoolContext, aby nie był zgodny z bazą danych. Wprowadź następujące polecenia w usłudze PMC, aby utworzyć inną migrację:

add-migration ColumnFirstName
update-database

W Eksploratorze serwera otwórz projektanta tabeli Student , klikając dwukrotnie tabelę Student .

Na poniższej ilustracji przedstawiono oryginalną nazwę kolumny, tak jak przed zastosowaniem dwóch pierwszych migracji. Oprócz nazwy kolumny zmieniającej się z FirstMidName na FirstName, dwie kolumny nazw zmieniły się z MAX długości na 50 znaków.

Dwa zrzuty ekranu pokazujące różnice w nazwach i typach danych dwóch tabel Student.

Możesz również wprowadzić zmiany mapowania bazy danych przy użyciu interfejsu API Fluent, jak zobaczysz w dalszej części tego samouczka.

Uwaga

Jeśli spróbujesz skompilować przed zakończeniem tworzenia wszystkich klas jednostek w poniższych sekcjach, mogą wystąpić błędy kompilatora.

Aktualizowanie jednostki Student

W pliku Models\Student.cs zastąp kod dodany wcześniej następującym kodem. Zmiany są wyróżnione.

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 virtual ICollection<Enrollment> Enrollments { get; set; }
    }
}

Wymagany atrybut

Atrybut Wymagany sprawia, że wymagane pola właściwości nazwy. Parametr Required attribute nie jest wymagany dla typów wartości, takich jak DateTime, int, double i float. Typy wartości nie mogą mieć przypisanej wartości null, dlatego są one z natury traktowane jako wymagane pola.

Atrybut Required musi być używany z MinimumLength atrybutem MinimumLength , aby można było wymusić.

[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }

MinimumLength i Required zezwalaj na białe znaki w celu spełnienia walidacji. Użyj atrybutu , aby RegularExpression uzyskać pełną kontrolę nad ciągiem.

Atrybut wyświetlania

Atrybut Display określa, że podpis dla pól tekstowych powinny być "Imię", "Nazwisko", "Imię", "Imię", "Pełna nazwa" i "Data rejestracji" zamiast nazwy właściwości w każdym wystąpieniu (które nie ma przestrzeni dzielącej wyrazy).

Właściwość obliczeniowa FullName

FullName to właściwość obliczeniowa zwracająca wartość utworzoną przez połączenie dwóch innych właściwości. W związku z tym ma ona tylko metodę get dostępu i żadna kolumna nie FullName zostanie wygenerowana w bazie danych.

Tworzenie jednostki Instruktor

Utwórz plik Models\Instructor.cs, zastępując kod szablonu następującym kodem:

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 virtual ICollection<Course> Courses { get; set; }
        public virtual OfficeAssignment OfficeAssignment { get; set; }
    }
}

Zwróć uwagę, że kilka właściwości jest takich samych w jednostkach Student i Instructor . W samouczku Implementowanie dziedziczenia w dalszej części tej serii refaktoryzujesz ten kod, aby wyeliminować nadmiarowość.

W jednym wierszu można umieścić wiele atrybutów, aby można było również napisać klasę instruktora w następujący sposób:

public class Instructor
{
   public int ID { get; set; }

   [Display(Name = "Last Name"),StringLength(50, MinimumLength=1)]
   public string LastName { get; set; }

   [Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
   public string FirstMidName { get; set; }

   [DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
   public DateTime HireDate { get; set; }

   [Display(Name = "Full Name")]
   public string FullName
   {
      get { return LastName + ", " + FirstMidName; }
   }

   public virtual ICollection<Course> Courses { get; set; }
   public virtual OfficeAssignment OfficeAssignment { get; set; }
}

Właściwości nawigacji Courses i OfficeAssignment

Właściwości Courses i OfficeAssignment są właściwościami nawigacji. Jak wyjaśniono wcześniej, są one zwykle definiowane jako wirtualne , dzięki czemu mogą korzystać z funkcji platformy Entity Framework nazywanej ładowaniem leniwym. Ponadto jeśli właściwość nawigacji może przechowywać wiele jednostek, jej typ musi implementować interfejs ICollection<T> . Na przykład IList<T> kwalifikuje się, ale nie IEnumerable<T> , ponieważ IEnumerable<T> nie implementuje polecenia Dodaj.

Instruktor może uczyć dowolną liczbę kursów, dlatego Courses jest definiowany jako kolekcja Course jednostek.

public virtual ICollection<Course> Courses { get; set; }

Nasze reguły biznesowe zawierają instruktora może mieć co najwyżej jedno biuro, dlatego OfficeAssignment jest definiowana jako pojedyncza OfficeAssignment jednostka (która może być null , jeśli nie ma przypisanego biura).

public virtual OfficeAssignment OfficeAssignment { get; set; }

Tworzenie jednostki OfficeAssignment

Utwórz plik Models\OfficeAssignment.cs z następującym kodem:

using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;

namespace ContosoUniversity.Models
{
    public class OfficeAssignment
    {
        [Key]
        [ForeignKey("Instructor")]
        public int InstructorID { get; set; }
        [StringLength(50)]
        [Display(Name = "Office Location")]
        public string Location { get; set; }

        public virtual Instructor Instructor { get; set; }
    }
}

Skompiluj projekt, który zapisuje zmiany i sprawdza, czy nie wprowadzono żadnych błędów kopiowania i wklejania, które kompilator może przechwycić.

Atrybut klucza

Istnieje relacja jeden do zera lub jednego między jednostkami Instructor i OfficeAssignment . Przypisanie biura istnieje tylko w odniesieniu do instruktora, do której jest przypisany, a zatem jego klucz podstawowy jest również kluczem obcym Instructor jednostki. Jednak platforma Entity Framework nie może automatycznie rozpoznać InstructorID jako klucza podstawowego tej jednostki, ponieważ jej nazwa nie jest zgodna z konwencją ID nazewnictwa nazwID klas ani . W związku z tym Key atrybut jest używany do identyfikowania go jako klucza:

[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }

Można również użyć atrybutu Key , jeśli jednostka ma własny klucz podstawowy, ale chcesz nazwać właściwość inną niż classnameID lub ID. Domyślnie program EF traktuje klucz jako niegenerowany przez bazę danych, ponieważ kolumna służy do identyfikowania relacji.

Atrybut ForeignKey

Jeśli istnieje relacja jeden do zera lub jeden albo relacja jeden do jednego między dwiema jednostkami (na przykład między OfficeAssignment i Instructor), program EF nie może ustalić, który koniec relacji jest podmiotem zabezpieczeń i który koniec jest zależny. Relacje jeden do jednego mają właściwość nawigacji referencyjnej w każdej klasie do innej klasy. Atrybut ForeignKey można zastosować do klasy zależnej w celu ustanowienia relacji. Jeśli pominięto atrybut ForeignKey, podczas próby utworzenia migracji wystąpi następujący błąd:

Nie można określić głównego końca skojarzenia między typami "ContosoUniversity.Models.OfficeAssignment" i "ContosoUniversity.Models.Instructor". Główny koniec tego skojarzenia musi być jawnie skonfigurowany przy użyciu płynnego interfejsu API relacji lub adnotacji danych.

W dalszej części samouczka zobaczysz, jak skonfigurować tę relację z płynnym interfejsem API.

Właściwość nawigacji instruktora

Jednostka Instructor ma właściwość nawigacji dopuszczającej OfficeAssignment wartość null (ponieważ instruktor może nie mieć przypisania do pakietu Office), a OfficeAssignment jednostka ma właściwość nawigacji bez wartości null Instructor (ponieważ przypisanie pakietu Office nie może istnieć bez instruktora — InstructorID jest niepuste). Instructor Jeśli jednostka ma powiązaną OfficeAssignment jednostkę, każda jednostka będzie mieć odwołanie do drugiej jednostki we właściwości nawigacji.

Możesz umieścić [Required] atrybut we właściwości nawigacji Instruktor, aby określić, że musi istnieć powiązany instruktor, ale nie musisz tego robić, ponieważ klucz obcy InstructorID (który jest również kluczem do tej tabeli) jest niepusty.

Modyfikowanie jednostki Course

W pliku Models\Course.cs zastąp dodany wcześniej kod następującym kodem:

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 virtual Department Department { get; set; }
      public virtual ICollection<Enrollment> Enrollments { get; set; }
      public virtual ICollection<Instructor> Instructors { get; set; }
   }
}

Jednostka kursu ma właściwość DepartmentID klucza obcego, która wskazuje powiązaną DepartmentDepartment jednostkę i ma właściwość nawigacji. Platforma Entity Framework nie wymaga dodania właściwości klucza obcego do modelu danych, jeśli masz właściwość nawigacji dla powiązanej jednostki. Program EF automatycznie tworzy klucze obce w bazie danych wszędzie tam, gdzie są potrzebne. Jednak posiadanie klucza obcego w modelu danych może sprawić, że aktualizacje będą prostsze i bardziej wydajne. Na przykład podczas pobierania jednostki kursu do edycji jednostka ma wartość null, Department jeśli jej nie załadowasz, więc podczas aktualizowania jednostki kursu konieczne będzie najpierw pobranie Department jednostki. Jeśli właściwość DepartmentID klucza obcego jest uwzględniona w modelu danych, nie musisz pobierać Department jednostki przed aktualizacją.

Atrybut DatabaseGenerated

Atrybut DatabaseGenerated z parametrem None we CourseID właściwości określa, że wartości klucza podstawowego są dostarczane przez użytkownika, a nie generowane przez bazę danych.

[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }

Domyślnie platforma Entity Framework zakłada, że wartości klucza podstawowego są generowane przez bazę danych. To jest to, czego potrzebujesz w większości scenariuszy. Jednak w przypadku Course jednostek użyjesz numeru kursu określonego przez użytkownika, takiego jak seria 1000 dla jednego działu, serii 2000 dla innego działu itd.

Właściwości klucza obcego i nawigacji

Właściwości klucza obcego i właściwości nawigacji w jednostce Course odzwierciedlają następujące relacje:

  • Kurs jest przypisywany do jednego działu, więc istnieje DepartmentID klucz obcy i Department właściwość nawigacji z powodów wymienionych powyżej.

    public int DepartmentID { get; set; }
    public virtual Department Department { get; set; }
    
  • Kurs może mieć dowolną liczbę uczniów zarejestrowanych w nim, więc Enrollments właściwość nawigacji jest kolekcją:

    public virtual ICollection<Enrollment> Enrollments { get; set; }
    
  • Kurs może być prowadzony przez wielu instruktorów, więc Instructors właściwość nawigacji jest kolekcją:

    public virtual ICollection<Instructor> Instructors { get; set; }
    

Tworzenie jednostki Dział

Utwórz plik Models\Department.cs z następującym kodem:

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 virtual Instructor Administrator { get; set; }
      public virtual ICollection<Course> Courses { get; set; }
   }
}

Atrybut kolumny

Wcześniej użyto atrybutu Kolumna do zmiany mapowania nazw kolumn. W kodzie Department jednostki atrybut jest używany do zmiany mapowania typu danych SQL, Column aby kolumna została zdefiniowana przy użyciu SQL Server typu pieniędzy w bazie danych:

[Column(TypeName="money")]
public decimal Budget { get; set; }

Mapowanie kolumn zwykle nie jest wymagane, ponieważ platforma Entity Framework zazwyczaj wybiera odpowiedni typ danych SQL Server na podstawie typu CLR zdefiniowanego dla właściwości. Typ CLR decimal jest mapowy na typ SQL Serverdecimal. Ale w tym przypadku wiadomo, że kolumna będzie przechowywać kwoty waluty, a typ danych pieniędzy jest bardziej odpowiedni dla tego. Aby uzyskać więcej informacji na temat typów danych CLR i ich dopasowania do typów danych SQL Server, zobacz SqlClient for Entity FrameworkTypes.

Właściwości klucza obcego i nawigacji

Właściwości klucza obcego i nawigacji odzwierciedlają następujące relacje:

  • Dział może lub nie ma administratora, a administrator jest zawsze instruktorem. InstructorID W związku z tym właściwość jest dołączana jako klucz obcy do Instructor jednostki, a znak zapytania jest dodawany po int oznaczeniu typu, aby oznaczyć właściwość jako dopuszczaną wartość null. Właściwość nawigacji ma nazwę Administrator , ale zawiera Instructor jednostkę:

    public int? InstructorID { get; set; }
    public virtual Instructor Administrator { get; set; }
    
  • Dział może mieć wiele kursów, dlatego istnieje Courses właściwość nawigacji:

    public virtual ICollection<Course> Courses { get; set; }
    

    Uwaga

    Zgodnie z konwencją platforma Entity Framework umożliwia kaskadowe usuwanie kluczy obcych bez wartości null oraz relacje wiele-do-wielu. Może to spowodować cykliczne reguły usuwania kaskadowego, co spowoduje wyjątek podczas próby dodania migracji. Jeśli na przykład właściwość nie zostanie zdefiniowana Department.InstructorID jako dopuszczana do wartości null, zostanie wyświetlony następujący komunikat o wyjątku: "Relacja referencyjna spowoduje cykliczne odwołanie, które jest niedozwolone". Jeśli reguły biznesowe wymagały InstructorID , aby właściwość nie dopuszczała wartości null, należy użyć następującej instrukcji płynnego interfejsu API, aby wyłączyć usuwanie kaskadowe w relacji:

modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);

Modyfikowanie jednostki Enrollment

W pliku Models\Enrollment.cs zastąp kod dodany wcześniej następującym kodem

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 virtual Course Course { get; set; }
        public virtual Student Student { get; set; }
    }
}

Właściwości klucza obcego i nawigacji

Właściwości klucza obcego i właściwości nawigacji odzwierciedlają następujące relacje:

  • Rekord rejestracji jest przeznaczony dla jednego kursu, dlatego istnieje właściwość klucza obcego CourseIDCourse i właściwość nawigacji:

    public int CourseID { get; set; }
    public virtual Course Course { get; set; }
    
  • Rekord rejestracji dotyczy jednego ucznia, więc istnieje właściwość klucza obcego StudentIDStudent i właściwość nawigacji:

    public int StudentID { get; set; }
    public virtual Student Student { get; set; }
    

Relacje wiele-do-wielu

Istnieje relacja wiele-do-wielu między jednostkami Student i Course , a Enrollment jednostka działa jako tabela sprzężenia wiele-do-wielu z ładunkiem w bazie danych. Oznacza to, że Enrollment tabela zawiera dodatkowe dane oprócz kluczy obcych dla tabel sprzężonych (w tym przypadku klucz podstawowy i Grade właściwość).

Poniższa ilustracja przedstawia wygląd tych relacji na diagramie jednostki. (Ten diagram został wygenerowany przy użyciu narzędzi Entity Framework Power Tools; tworzenie diagramu nie jest częścią samouczka, jest ono po prostu używane tutaj jako ilustracja).

Student-Course_many-many_relationship

Każda linia relacji ma wartość 1 na jednym końcu i gwiazdkę (*) z drugiej wskazującą relację jeden do wielu.

Enrollment Jeśli tabela nie zawierała informacji o klasie, musiałaby zawierać tylko dwa klucze CourseID obce i StudentID. W takim przypadku będzie odpowiadać tabeli sprzężenia wiele-do-wielu bez ładunku (lub czystej tabeli sprzężenia) w bazie danych i nie trzeba byłoby w ogóle tworzyć dla niej klasy modelu. Jednostki Instructor i Course mają taką relację wiele do wielu, a jak widać, między nimi nie ma żadnej klasy jednostek:

Instruktor Course_many-many_relationship

Tabela sprzężenia jest jednak wymagana w bazie danych, jak pokazano na poniższym diagramie bazy danych:

Instruktor Course_many-many_relationship_tables

Program Entity Framework automatycznie tworzy tabelę CourseInstructor i odczytasz ją i zaktualizujesz pośrednio, odczytując i aktualizując Instructor.Courses właściwości nawigacji i .Course.Instructors

Diagram relacji jednostki

Na poniższej ilustracji przedstawiono diagram utworzony przez narzędzia Entity Framework Power Tools dla ukończonego modelu szkoły.

School_data_model_diagram

Oprócz linii relacji wiele-do-wielu (* do *) i wierszy relacji jeden-do-wielu (od 1 do *), można zobaczyć tutaj wiersz relacji jeden do zera lub jeden (od 1 do 0,1) między jednostkami i i OfficeAssignment wierszem relacji zero lub jeden do wielu (od 0,1 do *) między Instructor jednostkami instruktora i działu.

Dodawanie kodu do kontekstu bazy danych

Następnie dodasz nowe jednostki do SchoolContext klasy i dostosujesz niektóre mapowanie przy użyciu płynnych wywołań interfejsu API . Interfejs API jest "płynny", ponieważ jest często używany przez ciągowanie serii metod wywoływanych razem w jedną instrukcję, jak w poniższym przykładzie:

modelBuilder.Entity<Course>()
     .HasMany(c => c.Instructors).WithMany(i => i.Courses)
     .Map(t => t.MapLeftKey("CourseID")
         .MapRightKey("InstructorID")
         .ToTable("CourseInstructor"));

W tym samouczku użyjesz płynnego interfejsu API tylko do mapowania bazy danych, których nie można wykonać z atrybutami. Możesz jednak użyć płynnego interfejsu API, aby określić większość reguł formatowania, walidacji i mapowania, które można wykonać za pomocą atrybutów. Niektóre atrybuty, takie jak MinimumLength nie można zastosować za pomocą płynnego interfejsu API. Jak wspomniano wcześniej, MinimumLength nie zmienia schematu, stosuje tylko regułę weryfikacji po stronie klienta i serwera

Niektórzy deweloperzy wolą korzystać wyłącznie z płynnego interfejsu API, aby mogli zachować "czyste" klasy jednostek. Możesz mieszać atrybuty i płynny interfejs API, jeśli chcesz, i istnieje kilka dostosowań, które można wykonać tylko przy użyciu płynnego interfejsu API, ale ogólnie zalecanym rozwiązaniem jest wybranie jednego z tych dwóch podejść i użycie tej spójnej możliwie największej ilości.

Aby dodać nowe jednostki do modelu danych i wykonać mapowanie bazy danych, które nie zostało wykonane przy użyciu atrybutów, zastąp kod w pliku DAL\SchoolContext.cs następującym kodem:

using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace ContosoUniversity.DAL
{
   public class SchoolContext : DbContext
   {
      public DbSet<Course> Courses { get; set; }
      public DbSet<Department> Departments { get; set; }
      public DbSet<Enrollment> Enrollments { get; set; }
      public DbSet<Instructor> Instructors { get; set; }
      public DbSet<Student> Students { get; set; }
      public DbSet<OfficeAssignment> OfficeAssignments { get; set; }

      protected override void OnModelCreating(DbModelBuilder modelBuilder)
      {
         modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

         modelBuilder.Entity<Course>()
             .HasMany(c => c.Instructors).WithMany(i => i.Courses)
             .Map(t => t.MapLeftKey("CourseID")
                 .MapRightKey("InstructorID")
                 .ToTable("CourseInstructor"));
      }
   }
}

Nowa instrukcja w metodzie OnModelCreating konfiguruje tabelę sprzężenia wiele-do-wielu:

  • W przypadku relacji wiele-do-wielu między jednostkami Instructor i Course kod określa nazwy tabel i kolumn dla tabeli sprzężenia. Code First może skonfigurować relację wiele-do-wielu dla Ciebie bez tego kodu, ale jeśli go nie wywołasz, otrzymasz nazwy domyślne, takie jak InstructorInstructorID dla InstructorID kolumny.

    modelBuilder.Entity<Course>()
        .HasMany(c => c.Instructors).WithMany(i => i.Courses)
        .Map(t => t.MapLeftKey("CourseID")
            .MapRightKey("InstructorID")
            .ToTable("CourseInstructor"));
    

Poniższy kod zawiera przykład użycia płynnego interfejsu API zamiast atrybutów w celu określenia relacji między jednostkami Instructor i OfficeAssignment :

modelBuilder.Entity<Instructor>()
    .HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);

Aby uzyskać informacje o tym, jakie instrukcje "fluent API" działają w tle, zobacz wpis w blogu interfejsu API Fluent .

Inicjuj bazę danych z danymi testowymi

Zastąp kod w pliku Migrations\Configuration.cs następującym kodem, aby podać dane początkowe dla nowo utworzonych jednostek.

namespace ContosoUniversity.Migrations
{
    using ContosoUniversity.Models;
    using ContosoUniversity.DAL;
    using System;
    using System.Collections.Generic;
    using System.Data.Entity;
    using System.Data.Entity.Migrations;
    using System.Linq;
    
    internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext>
    {
        public Configuration()
        {
            AutomaticMigrationsEnabled = false;
        }

        protected override void Seed(SchoolContext context)
        {
            var students = new List<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") }
            };

            students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var instructors = new List<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") }
            };
            instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
            context.SaveChanges();

            var departments = new List<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 }
            };
            departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s));
            context.SaveChanges();

            var courses = new List<Course>
            {
                new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 1045, Title = "Calculus",       Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 3141, Title = "Trigonometry",   Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2021, Title = "Composition",    Credits = 3,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
                new Course {CourseID = 2042, Title = "Literature",     Credits = 4,
                  DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
                  Instructors = new List<Instructor>() 
                },
            };
            courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
            context.SaveChanges();

            var officeAssignments = new List<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" },
            };
            officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.InstructorID, s));
            context.SaveChanges();

            AddOrUpdateInstructor(context, "Chemistry", "Kapoor");
            AddOrUpdateInstructor(context, "Chemistry", "Harui");
            AddOrUpdateInstructor(context, "Microeconomics", "Zheng");
            AddOrUpdateInstructor(context, "Macroeconomics", "Zheng");

            AddOrUpdateInstructor(context, "Calculus", "Fakhouri");
            AddOrUpdateInstructor(context, "Trigonometry", "Harui");
            AddOrUpdateInstructor(context, "Composition", "Abercrombie");
            AddOrUpdateInstructor(context, "Literature", "Abercrombie");

            context.SaveChanges();

            var enrollments = new List<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();
        }

        void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName)
        {
            var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle);
            var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName);
            if (inst == null)
                crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName));
        }
    }
}

Jak pokazano w pierwszym samouczku, większość tego kodu po prostu aktualizuje lub tworzy nowe obiekty jednostki i ładuje przykładowe dane do właściwości zgodnie z wymaganiami do testowania. Zauważ jednak, że Course jednostka, która ma relację wiele do wielu z jednostką Instructor , jest obsługiwana:

var courses = new List<Course>
{
    new Course {CourseID = 1050, Title = "Chemistry",      Credits = 3,
      DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
      Instructors = new List<Instructor>() 
    },
    ...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();

Podczas tworzenia Course obiektu należy zainicjować Instructors właściwość nawigacji jako pustą kolekcję przy użyciu kodu Instructors = new List<Instructor>(). Dzięki temu można dodawać Instructor jednostki powiązane z tym Course za pomocą Instructors.Add metody . Jeśli nie utworzono pustej listy, nie można dodać tych relacji, ponieważ Instructors właściwość będzie mieć wartość null i nie będzie miała Add metody. Można również dodać inicjowanie listy do konstruktora.

Dodawanie migracji

W usłudze PMC wprowadź add-migration polecenie (jeszcze nie wykonaj update-database polecenia):

add-Migration ComplexDataModel

Jeśli próbowano uruchomić update-database polecenie w tym momencie (jeszcze tego nie zrobisz), zostanie wyświetlony następujący błąd:

Instrukcja ALTER TABLE powoduje konflikt z ograniczeniem KLUCZA OBCEgo "FK_dbo. Course_dbo. Department_DepartmentID". Konflikt wystąpił w bazie danych "ContosoUniversity", tabeli "dbo". Dział", kolumna "DepartmentID".

Czasami podczas wykonywania migracji z istniejącymi danymi należy wstawić dane wycinkowe do bazy danych w celu spełnienia ograniczeń klucza obcego i to właśnie teraz trzeba zrobić. Wygenerowany kod w metodzie ComplexDataModel Up dodaje do Course tabeli niepusty DepartmentID klucz obcy. Ponieważ w tabeli znajdują się już wiersze po uruchomieniu Course kodu, operacja zakończy się niepowodzeniem, AddColumn ponieważ SQL Server nie wie, jaką wartość należy umieścić w kolumnie, która nie może mieć wartości null. W związku z tym należy zmienić kod, aby nadać nowej kolumnie wartość domyślną, i utworzyć dział wycinkowy o nazwie "Temp", aby działał jako dział domyślny. W związku z tym istniejące Course wiersze będą powiązane z działem "Temp" po uruchomieniu Up metody. Można je powiązać z właściwymi działami w metodzie Seed .

< Edytuj plik znacznika> czasu_ComplexDataModel.cs, oznacz jako komentarz wiersz kodu, który dodaje kolumnę DepartmentID do tabeli Course i dodaj następujący wyróżniony kod (wyróżniony jest również wiersz komentarza):

CreateTable(
        "dbo.CourseInstructor",
        c => new
            {
                CourseID = c.Int(nullable: false),
                InstructorID = c.Int(nullable: false),
            })
        .PrimaryKey(t => new { t.CourseID, t.InstructorID })
        .ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
        .ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true)
        .Index(t => t.CourseID)
        .Index(t => t.InstructorID);

    // Create  a department for course to point to.
    Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
    //  default value for FK points to department created above.
    AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1)); 
    //AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));

    AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));

Po uruchomieniu Seed metody wstawi wiersze w Department tabeli i będzie odnosić istniejące Course wiersze do tych nowych Department wierszy. Jeśli nie dodano żadnych kursów w interfejsie użytkownika, nie potrzebujesz już działu "Temp" ani wartości domyślnej w kolumnie Course.DepartmentID . Aby umożliwić osobie dodanie kursów przy użyciu aplikacji, należy również zaktualizować Seed kod metody, aby upewnić się, że wszystkie Course wiersze (nie tylko te wstawione przez wcześniejsze uruchomienia Seed metody) mają prawidłowe DepartmentID wartości przed usunięciem wartości domyślnej z kolumny i usunięciem działu "Temp".

Aktualizowanie bazy danych

Po zakończeniu edytowania < pliku timestamp>_ComplexDataModel.cs wprowadź update-database polecenie w pmC, aby wykonać migrację.

update-database

Uwaga

Podczas migrowania danych i wprowadzania zmian schematu można uzyskać inne błędy. Jeśli nie możesz rozpoznać błędów migracji, możesz zmienić nazwę bazy danych w parametrach połączenia lub usunąć bazę danych. Najprostszym podejściem jest zmiana nazwy bazy danych w plikuWeb.config . W poniższym przykładzie przedstawiono nazwę zmienioną na CU_Test:

<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;" 
      providerName="System.Data.SqlClient" />

W przypadku nowej bazy danych nie ma danych do migracji, a update-database polecenie jest znacznie bardziej prawdopodobne, aby ukończyć bez błędów. Aby uzyskać instrukcje dotyczące usuwania bazy danych, zobacz Jak usunąć bazę danych z programu Visual Studio 2012.

Jeśli to się nie powiedzie, kolejną rzeczą, którą można spróbować, jest ponowne zainicjowanie bazy danych, wprowadzając następujące polecenie w pmC:

update-database -TargetMigration:0

Otwórz bazę danych w Eksploratorze serwera , tak jak wcześniej, i rozwiń węzeł Tabele , aby zobaczyć, że wszystkie tabele zostały utworzone. (Jeśli eksplorator serwera jest nadal otwarty we wcześniejszej chwili, kliknij przycisk Odśwież ).

Zrzut ekranu przedstawiający okno Eksplorator serwera. Folder Tables w obszarze Kontekst szkoły jest otwarty.

Nie utworzono klasy modelu dla CourseInstructor tabeli. Jak wyjaśniono wcześniej, jest to tabela sprzężenia dla relacji wiele-do-wielu między jednostkami Instructor i Course .

Kliknij prawym przyciskiem myszy tabelę CourseInstructor i wybierz polecenie Pokaż dane tabeli , aby sprawdzić, czy zawiera ona dane w wyniku jednostek dodanych Instructor do Course.Instructors właściwości nawigacji.

Table_data_in_CourseInstructor_table

Uzyskiwanie kodu

Pobieranie ukończonego projektu

Dodatkowe zasoby

Linki do innych zasobów platformy Entity Framework można znaleźć w ASP.NET Dostęp do danych — zalecane zasoby.

Następne kroki

W tym samouczku zostały wykonane następujące czynności:

  • Dostosowywanie modelu danych
  • Zaktualizowana jednostka Student
  • Utworzona jednostka instruktora
  • Utworzono jednostkę OfficeAssignment
  • Zmodyfikowano jednostkę Course
  • Utworzono jednostkę Dział
  • Zmodyfikowano jednostkę Enrollment
  • Dodano kod do kontekstu bazy danych
  • Rozstawiona baza danych z danymi testowymi
  • Dodano migrację
  • Zaktualizowano bazę danych

Przejdź do następnego artykułu, aby dowiedzieć się, jak odczytywać i wyświetlać powiązane dane ładowane przez program Entity Framework do właściwości nawigacji.