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 tworzyć 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, który składał 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:
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 już utworzonych 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 aktualnie wyświetlają godzinę wraz z datą, chociaż wszystko, o co chodzi w tym polu, to data. 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 obszarze Models\Student.cs dodaj instrukcję using
dla System.ComponentModel.DataAnnotations
przestrzeni nazw i dodaj DataType
i DisplayFormat
atrybuty do EnrollmentDate
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 szczegółowy 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, Numer telefonu, 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 przeglądarki HTML 5 mogą zrozumieć. Atrybuty DataType nie zapewniają żadnej walidacji.
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 nie chcieć tego dla niektórych pól — na przykład w przypadku wartości walutowych symbol waluty w polu tekstowym do edycji).
Atrybut DisplayFormat można użyć samodzielnie, 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 pomocą DisplayFormat
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 będzie renderować 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 , 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.
Ponownie uruchom 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 korzystającego z Student
modelu.
Atrybut StringLengthAttribute
Można również określić reguły weryfikacji 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 wprowadzania białych znaków 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 podobne funkcje 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ł to. Za pomocą migracji zaktualizujesz schemat bez utraty danych dodanych do bazy danych przy użyciu interfejsu użytkownika. Jeśli zmieniono dane utworzone przez Seed
metodę, zostaną zmienione 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żer 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 migracji wszystkie migracje zostaną zastosowane w kolejności, w jakiej zostały utworzone.
Uruchom stronę Tworzenie i wprowadź nazwę dłuższą niż 50 znaków. Po przekroczeniu 50 znaków walidacja po stronie klienta natychmiast wyświetla komunikat o błędzie.
Atrybut kolumny
Możesz 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ć nazwę środkową. 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 ustawić to mapowanie, możesz użyć atrybutu Column
.
Atrybut Column
określa, że po utworzeniu bazy danych kolumna Student
tabeli mapowania na FirstMidName
właściwość będzie mieć nazwę FirstName
. Innymi słowy, gdy kod odwołuje się do Student.FirstMidName
, dane pochodzą z tabeli lub zostaną zaktualizowane w FirstName
kolumnie Student
tabeli. Jeśli nie określisz nazw kolumn, mają one 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 poniższym 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, więc nie będzie 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 baz danych, jeśli używasz programu Express dla sieci Web), kliknij dwukrotnie tabelę Student.
Na poniższej ilustracji przedstawiono oryginalną nazwę kolumny, tak jak przed zastosowaniem dwóch pierwszych migracji. Oprócz zmiany nazwy kolumny z FirstMidName
na FirstName
kolumna dwie kolumny nazw zmieniły się z MAX
długości na 50 znaków.
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
Utwórz modele\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 LastName
właściwości określają, że jest to wymagane pole, że podpis pola tekstowego powinien mieć wartość "Nazwisko" (zamiast nazwy właściwości, czyli "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 po stronie serwera dla ASP.NET MVC. Można 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. Nie można przypisać wartości null typów wartości, 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; }
Możesz umieścić wiele atrybutów w jednym wierszu, 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
jest właściwością obliczeniową, która zwraca wartość utworzoną przez łą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 Kursy i OfficeAssignment
Właściwości Courses
i OfficeAssignment
to właściwości nawigacji. Jak wyjaśniono wcześniej, są one zwykle definiowane jako wirtualne , dzięki czemu mogą korzystać z funkcji Entity Framework nazywanej leniwym ładowaniem. Ponadto jeśli właściwość nawigacji może przechowywać wiele jednostek, jej typ musi zaimplementować 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 określają, że instruktor może mieć tylko jedno biuro, więc OfficeAssignment
jest definiowany jako jedna 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
Utwórz modele\OfficeAssignment.cs przy użyciu następującego kodu:
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 program Entity Framework nie może automatycznie rozpoznać InstructorID
jako klucza podstawowego tej jednostki, ponieważ jego nazwa nie jest zgodna z konwencją ID
nazewnictwa ani classnameID
. W związku z tym Key
atrybut jest używany do identyfikowania go jako klucza:
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
Możesz 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 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
Gdy 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 instruktora, 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) nie może mieć wartości null.
Modyfikowanie jednostki kursu
W obszarze Models\Course.cs zastąp kod dodany wcześniej 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ą Department
Department
jednostkę i ma właściwość nawigacji. Program 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 nie zostanie załadowana, więc podczas aktualizowania jednostki kursu trzeba będzie najpierw pobrać Department
jednostkę. 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 program 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 określonego przez użytkownika numeru kursu, 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 iDepartment
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ę zarejestrowanych w nim uczniów, więc
Enrollments
właściwość nawigacji jest kolekcją:public virtual ICollection<Enrollment> Enrollments { get; set; }
Kurs może być nauczany przez wielu instruktorów, więc
Instructors
właściwość nawigacji jest kolekcją:public virtual ICollection<Instructor> Instructors { get; set; }
Tworzenie jednostki działu
Utwórz modele\Department.cs przy użyciu następującego kodu:
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 typu pieniędzy programu SQL Server w bazie danych:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapowanie kolumn nie jest zwykle wymagane, ponieważ program Entity Framework zwykle wybiera odpowiedni typ danych programu SQL Server na podstawie typu CLR zdefiniowanego dla właściwości. Typ CLR decimal
jest mapowy na typ programu SQL Server decimal
. Ale w tym przypadku wiesz, że kolumna będzie przechowywać kwoty waluty, a typ danych pieniężnych 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 doInstructor
jednostki, a znak zapytania jest dodawany point
oznaczeniu typu, aby oznaczyć właściwość jako dopuszczaną wartość null. Właściwość nawigacji ma nazwęAdministrator
, ale zawieraInstructor
jednostkę:public int? InstructorID { get; set; } public virtual Instructor Administrator { get; set; }
Dział może mieć wiele kursów, więc 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 i relacje wiele-do-wielu. Może to spowodować cykliczne reguły usuwania kaskadowego, 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 otrzymasz następujący komunikat o wyjątku: "Relacja referencyjna spowoduje cykliczne odwołanie, które jest niedozwolone". Jeśli reguły biznesowe wymagałyInstructorID
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
W obszarze 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 obszarze 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 dotyczy jednego kursu, więc istnieje właściwość klucza obcego
CourseID
Course
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
StudentID
Student
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 elementu Narzędzia Entity Framework Power Tools; tworzenie diagramu nie jest częścią tego samouczka— jest ono używane tylko tutaj jako ilustracja).
Każda linia relacji ma wartość 1 na jednym końcu i gwiazdkę (*) z drugiej strony wskazującą relację jeden do wielu.
Enrollment
Jeśli tabela nie zawierała informacji o klasie, musi zawierać tylko dwa klucze CourseID
obce i StudentID
. W takim przypadku będzie ona odpowiadać tabeli sprzężenia wiele-do-wielu bez ładunku (lub czystej tabeli sprzężenia) w bazie danych i nie trzeba by 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:
Tabela sprzężenia jest jednak wymagana w bazie danych, jak pokazano na poniższym diagramie bazy danych:
Program Entity Framework automatycznie tworzy tabelę CourseInstructor
i odczytaj ją i zaktualizujesz 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.
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 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
jednostkami 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ągowanie serii wywołań metod 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ć klasy jednostek "czyste". 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 zalecaną praktyką jest wybranie jednego z tych dwóch podejść i użycie tej spójnej ilości, jak to możliwe.
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 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
iCourse
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, otrzymasz nazwy domyślne, takie jakInstructorInstructorID
dlaInstructorID
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 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 inicjowania 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 dotyczącymi 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 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 usłudze 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". Dział", kolumna "DepartmentID".
<Edytuj znacznik> czasu_Chap4.cs plik 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()
{
(Pamiętaj, aby oznaczyć komentarz lub usunąć istniejący AddColumn
wiersz podczas dodawania nowego wiersza albo podczas wprowadzania update-database
polecenia wystąpi błąd).
Czasami podczas wykonywania migracji z istniejącymi danymi należy wstawić dane wycinkowe 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ż program 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 domyślny dział. W związku z tym, jeśli podczas Course
uruchamiania tego kodu istnieją wiersze, wszystkie te elementy będą powiązane z działem "Temp".
Gdy metoda zostanie uruchomiona Seed
, 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 znacznika <czasu>_Chap4.cs pliku 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żesz 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 pliku Web.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 nadal masz Eksplorator serwera otwarty we wcześniejszej chwili, kliknij przycisk Odśwież .
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.
Podsumowanie
Masz teraz bardziej złożony model danych i odpowiednią bazę danych. W poniższym samouczku dowiesz się więcej na temat różnych sposobów 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.