Tworzenie bardziej złożonego modelu danych dla aplikacji MVC ASP.NET (4 z 10)

Autor : Tom Dykstra

Przykładowa aplikacja internetowa Contoso University pokazuje, jak utworzyć aplikacje ASP.NET MVC 4 przy użyciu programu Entity Framework 5 Code First i Visual Studio 2012. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek z serii.

Uwaga

Jeśli napotkasz problem, którego nie możesz rozwiązać, pobierz ukończony rozdział i spróbuj odtworzyć problem. Zazwyczaj rozwiązanie problemu można znaleźć, porównując kod z ukończonym kodem. Aby uzyskać informacje o niektórych typowych błędach i sposobach ich rozwiązywania, zobacz Błędy i obejścia.

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, a następnie dostosujesz model danych, określając reguły formatowania, walidacji i mapowania bazy danych. Zobaczysz 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

Dostosowywanie modelu danych przy użyciu atrybutów

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 StudentID { 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)]
public DateTime EnrollmentDate { get; set; }

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 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 ( format DisplayFormat , jeśli jest używany przez siebie przy użyciu 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.

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 przy użyciu atrybutów. 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 StudentID { 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 MaxLengthOnNames tworzy plik o nazwie <timeStamp>_MaxLengthOnNames.cs. Ten plik zawiera kod, który zaktualizuje bazę danych w celu dopasowania do bieżącego modelu danych. Sygnatura czasowa poprzedzona nazwą pliku migracji jest używana przez program Entity Framework do zamawiania migracji. Po utworzeniu wielu migracji po usunięciu bazy danych lub wdrożeniu projektu przy użyciu funkcji Migrations wszystkie migracje są stosowane w kolejności, w jakiej zostały utworzone.

Uruchom stronę Tworzenie i wprowadź nazwę dłuższą niż 50 znaków. Gdy tylko przekroczysz 50 znaków, walidacja po stronie klienta natychmiast wyświetli komunikat o błędzie.

błąd val po stronie klienta

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.

Dodaj instrukcję using dla elementu System.ComponentModel.DataAnnotations.Schema i 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 StudentID { 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 (Eksploratorze bazy danych , jeśli używasz programu Express for Web), kliknij dwukrotnie tabelę Student .

Zrzut ekranu przedstawiający tabelę Student w Eksploratorze serwera.

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.

Zrzut ekranu przedstawiający tabelę Student w Eksploratorze serwera. Wiersz First Name (Imię) na poprzednim zrzucie ekranu został zmieniony tak, aby był odczytywany jako Imię i nazwisko.

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 tych klas jednostek, mogą wystąpić błędy kompilatora.

Tworzenie jednostki instruktora

Instructor_entity

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 InstructorID { 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; }

        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 przy użyciu dziedziczenia, aby wyeliminować tę nadmiarowość.

Wymagane i wyświetlane atrybuty

Atrybuty właściwości LastName określają, że jest to wymagane pole, że podpis dla pola tekstowego powinna być "Nazwisko" (zamiast nazwy właściwości, która byłaby "LastName" bez spacji) i że wartość nie może być dłuższa niż 50 znaków.

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

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. Atrybut Wymagany 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 wymagane. Można usunąć wymagany atrybut i zastąpić go parametrem minimalnej długości atrybutu StringLength :

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

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 InstructorID { 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; }

   public string FullName
   {
      get { return LastName + ", " + FirstMidName; }
   }

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

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.

public string FullName
{
    get { return LastName + ", " + FirstMidName; }
}

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

Instruktor może uczyć dowolną liczbę kursów, dlatego Courses jest definiowany jako kolekcja Course jednostek. 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 ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }

Tworzenie jednostki OfficeAssignment

OfficeAssignment_entity

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 pokażemy, 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 Kursu

Course_entity

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

      [Display(Name = "Department")]
      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łu

Department_entity

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)]
      public DateTime StartDate { get; set; }

      [Display(Name = "Administrator")]
      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.

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ć powstanie reguł usuwania kaskadowego cyklicznego, co spowoduje wyjątek podczas uruchamiania kodu inicjatora. Jeśli na przykład właściwość nie została zdefiniowana Department.InstructorID jako dopuszczana do wartości null, podczas uruchamiania inicjatora 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 właściwości jako niepustej, należy użyć następującego płynnego interfejsu API, aby wyłączyć usuwanie kaskadowe w relacji:

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

Modyfikowanie jednostki Student

Student_entity

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 StudentID { get; set; }

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

      [StringLength(50, MinimumLength = 1, 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)]
      [Display(Name = "Enrollment Date")]
      public DateTime EnrollmentDate { get; set; }

      public string FullName
      {
         get { return LastName + ", " + FirstMidName; }
      }

      public virtual ICollection<Enrollment> Enrollments { get; set; }
   }
}

Jednostka rejestracji

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

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

Instruktor Course_many do many_relationship_tables

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

Diagram jednostki przedstawiający relacje

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

School_data_model_diagram

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

Dostosowywanie modelu danych przez dodanie kodu do kontekstu bazy danych

Następnie dodasz nowe jednostki do SchoolContext klasy i dostosujesz niektóre mapowania przy użyciu płynnych wywołań interfejsu API . (Interfejs API jest "płynny", ponieważ jest często używany przez ciągi serii wywołań metody w jedną instrukcję).

W tym samouczku użyjesz płynnego interfejsu API tylko do mapowania bazy danych, których nie można wykonać z atrybutami. Można jednak również użyć płynnego interfejsu API, aby określić większość reguł formatowania, walidacji i mapowania, które można wykonać przy użyciu 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ą używać płynnego interfejsu API wyłącznie tak, aby mogli zachować "czyste" klasy jednostek. Jeśli chcesz, możesz mieszać atrybuty i płynny interfejs API. 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 bez tego kodu, ale jeśli go nie wywołasz, uzyskasz 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 przedstawia 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 Fluent API .

Rozmieszczanie bazy danych przy użyciu danych testowych

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

namespace ContosoUniversity.Migrations
{
   using System;
   using System.Collections.Generic;
   using System.Data.Entity;
   using System.Data.Entity.Migrations;
   using System.Linq;
   using ContosoUniversity.Models;
   using ContosoUniversity.DAL;

   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").InstructorID },
                new Department { Name = "Mathematics", Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Fakhouri").InstructorID },
                new Department { Name = "Engineering", Budget = 350000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Harui").InstructorID },
                new Department { Name = "Economics",   Budget = 100000, 
                    StartDate = DateTime.Parse("2007-09-01"), 
                    InstructorID  = instructors.Single( i => i.LastName == "Kapoor").InstructorID }
            };
         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").InstructorID, 
                    Location = "Smith 17" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID, 
                    Location = "Gowan 27" },
                new OfficeAssignment { 
                    InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID, 
                    Location = "Thompson 304" },
            };
         officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.Location, 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").StudentID, 
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID, 
                    Grade = Grade.A 
                },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID, 
                    Grade = Grade.C 
                 },                            
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
                    CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID, 
                    Grade = Grade.B
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                     StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment {
                    StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
                    CourseID = courses.Single(c => c.Title == "Composition" ).CourseID, 
                    Grade = Grade.B 
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").StudentID,
                    CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Anand").StudentID,
                    CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
                    Grade = Grade.B         
                 },
                new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Barzdukas").StudentID,
                    CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Li").StudentID,
                    CourseID = courses.Single(c => c.Title == "Composition").CourseID,
                    Grade = Grade.B         
                 },
                 new Enrollment { 
                    StudentID = students.Single(s => s.LastName == "Justice").StudentID,
                    CourseID = courses.Single(c => c.Title == "Literature").CourseID,
                    Grade = Grade.B         
                 }
            };

         foreach (Enrollment e in enrollments)
         {
            var enrollmentInDataBase = context.Enrollments.Where(
                s =>
                     s.Student.StudentID == 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 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,
       Department = departments.Single( s => s.Name == "Engineering"),
       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 będzie 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 i aktualizowanie bazy danych

W witrynie PMC wprowadź add-migration polecenie:

PM> add-Migration Chap4

Jeśli spróbujesz zaktualizować bazę danych w tym momencie, wystąpi następujący błąd:

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

< Edytuj plik timestamp>_Chap4.cs i wprowadź następujące zmiany w kodzie (dodasz instrukcję SQL i zmodyfikujesz instrukcję AddColumn ):

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));
    AddForeignKey("dbo.Course", "DepartmentID", "dbo.Department", "DepartmentID", cascadeDelete: true);
    CreateIndex("dbo.Course", "DepartmentID");
}

public override void Down()
{

(Upewnij się, że komentarz lub usunięcie istniejącego AddColumn wiersza podczas dodawania nowego wiersza jest wyświetlany komunikat o błędzie podczas wprowadzania update-database polecenia).

Czasami podczas wykonywania migracji z istniejącymi danymi należy wstawić dane wycinka do bazy danych, aby spełnić ograniczenia klucza obcego i to właśnie teraz wykonujesz. Wygenerowany kod dodaje do Course tabeli niepusty DepartmentID klucz obcy. Jeśli 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 zmieniono kod, aby nadać nowej kolumnie wartość domyślną, i utworzono dział wycinków o nazwie "Temp", aby działał jako dział domyślny. W związku z tym, jeśli podczas Course uruchamiania tego kodu istnieją wiersze, wszystkie będą powiązane z działem "Temp".

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 zezwolić na możliwość dodania kursów przez kogoś 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".

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

Uwaga

Podczas migrowania danych i wprowadzania zmian schematu można uzyskać inne błędy. Jeśli wystąpią błędy migracji, których nie można rozwiązać, możesz zmienić parametry połączenia w pliku Web.config lub usunąć bazę danych. Najprostszym podejściem jest zmiana nazwy bazy danych w plikuWeb.config . Na przykład zmień nazwę bazy danych na CU_test, jak pokazano poniżej:

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

W przypadku nowej bazy danych nie ma danych do zmigrowania, 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.

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 wcześniej, kliknij przycisk Odśwież ).

Zrzut ekranu przedstawiający bazę danych Eksploratora serwera. Węzeł Tabele jest rozwinięty.

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 on dane w wyniku jednostek dodanych Instructor do Course.Instructors właściwości nawigacji.

Table_data_in_CourseInstructor_table

Podsumowanie

Masz teraz bardziej złożony model danych i odpowiednią bazę danych. W poniższym samouczku dowiesz się więcej o różnych sposobach uzyskiwania dostępu do powiązanych danych.

Linki do innych zasobów programu Entity Framework można znaleźć na mapie zawartości dostępu do danych ASP.NET.