Samouczek: tworzenie złożonego modelu danych — ASP.NET MVC za pomocą polecenia EF Core
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 formatowanie, walidację i reguły mapowania bazy danych.
Po zakończeniu klasy jednostek składają się na ukończony model danych pokazany na poniższej ilustracji:
W tym samouczku zostały wykonane następujące czynności:
- Dostosowywanie modelu danych
- Wprowadzanie zmian w jednostce Student
- Tworzenie jednostki instruktora
- Tworzenie jednostki OfficeAssignment
- Modyfikowanie jednostki Course
- Tworzenie jednostki Dział
- Modyfikowanie jednostki Rejestracji
- Aktualizowanie kontekstu bazy danych
- Inicjuj bazę danych z danymi testowymi
- Dodawanie migracji
- Zmienianie parametry połączenia
- Aktualizowanie bazy danych
Wymagania wstępne
Dostosowywanie modelu danych
W tej sekcji dowiesz się, jak dostosować model danych przy użyciu atrybutów określających reguły formatowania, walidacji i mapowania bazy danych. Następnie w kilku poniższych sekcjach utworzysz kompletny model danych Szkoły, 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 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 Models/Student.cs
pliku dodaj instrukcję using
dla System.ComponentModel.DataAnnotations
przestrzeni nazw i dodaj DataType
atrybuty i DisplayFormat
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 ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
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, Telefon Number, 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 DataType.EmailAddress
elementu , a selektor dat można udostępnić DataType.Date
w przeglądarkach obsługujących kod HTML5. Atrybut DataType
emituje atrybuty HTML 5 data-
(wymawiane kreska 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)]
Ustawienie ApplyFormatInEditMode
określa, że 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).
Można użyć atrybutu DisplayFormat
samodzielnie, ale zazwyczaj dobrym pomysłem jest również użycie 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, weryfikacji danych wejściowych po stronie klienta itp.).
Domyślnie przeglądarka będzie renderować dane przy użyciu poprawnego formatu na podstawie ustawień regionalnych.
Aby uzyskać więcej informacji, zobacz dokumentację pomocnika tagów <wejściowych>.
Uruchom aplikację, przejdź do strony 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 modelu Student.
Atrybut StringLength
Można również określić reguły walidacji danych i komunikaty o błędach walidacji przy użyciu atrybutów. Atrybut StringLength
ustawia maksymalną długość bazy danych i zapewnia weryfikację po stronie klienta i po stronie serwera dla ASP.NET Core 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.
Załóżmy, że chcesz mieć pewność, że użytkownicy nie wprowadzają więcej niż 50 znaków dla nazwy. Aby dodać to ograniczenie, dodaj StringLength
atrybuty do LastName
właściwości i FirstMidName
, jak pokazano w poniższym przykładzie:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
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ą postać alfabetyczną:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Atrybut MaxLength
zapewnia funkcjonalność podobną do atrybutu StringLength
, ale nie zapewnia weryfikacji po stronie klienta.
Model bazy danych zmienił się teraz w sposób, który wymaga zmiany schematu bazy danych. Użyjesz migracji, aby zaktualizować schemat bez utraty danych, które mogły zostać dodane do bazy danych przy użyciu interfejsu użytkownika aplikacji.
Zapisz zmiany i skompiluj projekt. Następnie otwórz okno polecenia w folderze projektu i wprowadź następujące polecenia:
dotnet ef migrations add MaxLengthOnNames
dotnet ef database update
Polecenie migrations add
ostrzega, że może wystąpić utrata danych, ponieważ zmiana sprawia, że maksymalna długość jest krótsza dla dwóch kolumn. Migracje tworzą plik o nazwie <timeStamp>_MaxLengthOnNames.cs
. Ten plik zawiera kod w metodzie Up
, która zaktualizuje bazę danych tak, aby odpowiadała bieżącemu modelowi danych. Polecenie database update
uruchomiło ten kod.
Sygnatura czasowa poprzedzona nazwą pliku migracji jest używana przez program Entity Framework do zamawiania migracji. Można utworzyć wiele migracji przed uruchomieniem polecenia update-database, a następnie wszystkie migracje są stosowane w kolejności, w której zostały utworzone.
Uruchom aplikację, wybierz kartę Uczniowie , kliknij pozycję Utwórz nowy i spróbuj wprowadzić nazwę dłuższą niż 50 znaków. Aplikacja powinna uniemożliwić wykonanie tej czynności.
Atrybut Kolumna
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.
Student.cs
W pliku dodaj instrukcję using
i System.ComponentModel.DataAnnotations.Schema
dodaj 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 ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50)]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Dodanie atrybutu Column
zmienia model kopii zapasowej SchoolContext
elementu , aby nie był zgodny z bazą danych.
Zapisz zmiany i skompiluj projekt. Następnie otwórz okno polecenia w folderze projektu i wprowadź następujące polecenia, aby utworzyć inną migrację:
dotnet ef migrations add ColumnFirstName
dotnet ef database update
W programie SQL Server Eksplorator obiektów otwórz projektanta tabeli Student, klikając dwukrotnie tabelę Student.
Przed zastosowaniem dwóch pierwszych migracji kolumny nazw były typu nvarchar(MAX). Są teraz nvarchar(50), a nazwa kolumny zmieniła się z FirstMidName na FirstName.
Uwaga
Jeśli spróbujesz skompilować przed zakończeniem tworzenia wszystkich klas jednostek w poniższych sekcjach, mogą wystąpić błędy kompilatora.
Zmiany w jednostce Student
W Models/Student.cs
pliku zastąp kod dodany wcześniej następującym kodem. Zmiany są wyróżnione.
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50)]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Wymagany atrybut
Atrybut Required
sprawia, że właściwości nazwy są wymagane pola. Atrybut Required
nie jest wymagany w przypadku typów bez wartości null, takich jak typy wartości (DateTime, int, double, float itp.). Typy, które nie mogą mieć wartości null, są automatycznie traktowane jako wymagane pola.
Atrybut Required
musi być używany z elementem MinimumLength
MinimumLength
, aby można było wymusić.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
Atrybut Wyświetlania
Atrybut Display
określa, że podpis pól tekstowych powinny być "Imię", "Nazwisko", "Imię", "Imię", "Pełna nazwa" i "Data rejestracji" zamiast nazwy właściwości w każdym wystąpieniu (które nie ma spacji dzielącej wyrazy).
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ę pobierania, a w bazie danych nie FullName
zostanie wygenerowana żadna kolumna.
Tworzenie jednostki instruktora
Utwórz Models/Instructor.cs
plik , zastępując kod szablonu następującym kodem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<CourseAssignment> CourseAssignments { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
Zwróć uwagę, że kilka właściwości jest takich samych w jednostkach Student i Instruktor. W samouczku Implementowanie dziedziczenia w dalszej części tej serii refaktoryzujesz ten kod, aby wyeliminować nadmiarowość.
Można umieścić wiele atrybutów w jednym wierszu, aby można było również napisać HireDate
atrybuty w następujący sposób:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Właściwości nawigacji CourseAssignments i OfficeAssignment
Właściwości CourseAssignments
i OfficeAssignment
to właściwości nawigacji.
Instruktor może uczyć dowolną liczbę kursów, dlatego CourseAssignments
jest definiowany jako kolekcja.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Jeśli właściwość nawigacji może zawierać wiele jednostek, jej typ musi być listą, w której można dodawać, usuwać i aktualizować wpisy. Można określić ICollection<T>
lub typ, taki jak List<T>
lub HashSet<T>
. Jeśli określisz ICollection<T>
wartość , program EF domyślnie tworzy HashSet<T>
kolekcję.
Powód, dla którego te jednostki zostały CourseAssignment
wyjaśnione poniżej w sekcji dotyczącej relacji wiele-do-wielu.
Reguły biznesowe firmy Contoso University stwierdzają, że instruktor może mieć tylko jedno biuro, więc OfficeAssignment
właściwość posiada pojedynczą jednostkę OfficeAssignment (która może mieć wartość null, jeśli nie przypisano urzędu).
public OfficeAssignment OfficeAssignment { get; set; }
Tworzenie jednostki OfficeAssignment
Utwórz Models/OfficeAssignment.cs
za pomocą następującego kodu:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor Instructor { get; set; }
}
}
Atrybut Klucz
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]
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.
Właściwość nawigacji instruktora
Jednostka Instruktor ma właściwość nawigacji dopuszczającej OfficeAssignment
wartość null (ponieważ instruktor może nie mieć przypisania pakietu Office), a jednostka OfficeAssignment ma właściwość nawigacji bez wartości null Instructor
(ponieważ przypisanie pakietu Office nie może istnieć bez instruktora — InstructorID
jest niepuste). Gdy jednostka Instruktor ma powiązaną jednostkę OfficeAssignment, każda jednostka będzie mieć odwołanie do drugiej w jej 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ż InstructorID
klucz obcy (który jest również kluczem do tej tabeli) nie może mieć wartości null.
Modyfikowanie jednostki Course
W Models/Course.cs
pliku zastąp kod dodany wcześniej następującym kodem. Zmiany są wyróżnione.
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}
Jednostka kursu ma właściwość DepartmentID
klucza obcego, która wskazuje powiązaną Department
jednostkę Department 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 niezależnie od potrzeb i tworzy dla nich właściwości w tle. Jednak posiadanie klucza obcego w modelu danych może sprawić, że aktualizacje będą prostsze i bardziej wydajne. Na przykład podczas pobierania Course
jednostki do edycji jednostka ma wartość null, Department
jeśli nie zostanie załadowana, więc podczas aktualizowania Course
jednostki 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
właściwości CourseID
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.
Atrybut DatabaseGenerated
może również służyć do generowania wartości domyślnych, jak w przypadku kolumn bazy danych używanych do rejestrowania daty utworzenia lub zaktualizowania wiersza. Aby uzyskać więcej informacji, zobacz Wygenerowane właściwości.
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 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 ICollection<Enrollment> Enrollments { get; set; }
Kurs może być nauczany przez wielu instruktorów, więc CourseAssignments
właściwość nawigacji jest kolekcją (typ CourseAssignment
jest wyjaśniony później):
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Tworzenie jednostki Dział
Utwórz Models/Department.cs
za pomocą 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)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Atrybut Kolumna
Wcześniej użyto atrybutu Column
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 programu SQL Server money
w bazie danych:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapowanie kolumn nie jest zwykle wymagane, ponieważ program Entity Framework 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 do jednostki Instruktor, 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 jednostkę Instruktor:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Dział może mieć wiele kursów, więc istnieje właściwość nawigacji Kursy:
public 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 próby dodania migracji. Jeśli na przykład właściwość nie zostanie zdefiniowana Department.InstructorID
jako dopuszczana do wartości null, program EF skonfiguruje regułę usuwania kaskadowego, aby usunąć dział po usunięciu instruktora, co nie jest tym, co ma się zdarzyć. Jeśli reguły biznesowe wymagały InstructorID
, aby właściwość nie mogła mieć wartości null, należy użyć następującej instrukcji płynnego interfejsu API, aby wyłączyć usuwanie kaskadowe w relacji:
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Modyfikowanie jednostki Rejestracji
W Models/Enrollment.cs
pliku zastąp kod dodany wcześniej następującym kodem:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
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 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 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. "Z ładunkiem" oznacza, ż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 for EF 6.x; tworzenie diagramu nie jest częścią tego samouczka. Jest on po prostu używany 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 byłaby to tabela sprzężenia wiele-do-wielu bez ładunku (lub czystej tabeli sprzężeń) w bazie danych. Jednostki Instructor
i Course
mają taką relację wiele do wielu, a następnym krokiem jest utworzenie klasy jednostki, która będzie działać jako tabela sprzężenia bez ładunku.
EF Core obsługuje niejawne tabele sprzężenia dla relacji wiele-do-wielu, ale ten korepetytor nie został zaktualizowany do używania niejawnej tabeli sprzężenia. Zobacz Relacje wiele-do-wielu— wersja strony tego samouczka, Razor która została zaktualizowana.
Jednostka CourseAssignment
Utwórz Models/CourseAssignment.cs
za pomocą następującego kodu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}
Nazwy jednostek sprzężenia
Tabela sprzężenia jest wymagana w bazie danych dla relacji Instruktor-to-Courses wiele-do-wielu i musi być reprezentowana przez zestaw jednostek. Często nazywa się jednostkę EntityName1EntityName2
sprzężenia , która w tym przypadku będzie miała wartość CourseInstructor
. Zalecamy jednak wybranie nazwy, która opisuje relację. Modele danych zaczynają się proste i rosną, a sprzężenia bez ładunku często są ładowane później. Jeśli zaczniesz od nazwy jednostki opisowej, nie musisz później zmieniać nazwy. Najlepiej, aby jednostka sprzężenia miała własną naturalną (prawdopodobnie pojedynczą nazwę) w domenie biznesowej. Na przykład książki i klienci mogą być połączone za pośrednictwem klasyfikacji. W przypadku tej relacji CourseAssignment
jest lepszym wyborem niż CourseInstructor
.
Klucz złożony
Ponieważ klucze obce nie są dopuszczane do wartości null i razem jednoznacznie identyfikują każdy wiersz tabeli, nie ma potrzeby oddzielnego klucza podstawowego. Właściwości InstructorID
i CourseID
powinny działać jako złożony klucz podstawowy. Jedynym sposobem identyfikowania złożonych kluczy podstawowych do platformy EF jest użycie płynnego interfejsu API (nie można go wykonać przy użyciu atrybutów). W następnej sekcji zobaczysz, jak skonfigurować złożony klucz podstawowy.
Klucz złożony gwarantuje, że chociaż można mieć wiele wierszy dla jednego kursu i wiele wierszy dla jednego instruktora, nie można mieć wielu wierszy dla tego samego instruktora i kursu. Jednostka Enrollment
sprzężenia definiuje własny klucz podstawowy, więc możliwe są duplikaty tego rodzaju. Aby zapobiec takim duplikatom, można dodać unikatowy indeks w polach klucza obcego lub skonfigurować przy użyciu Enrollment
podstawowego klucza złożonego podobnego do CourseAssignment
. Aby uzyskać więcej informacji, zobacz Indeksy.
Aktualizowanie kontekstu bazy danych
Dodaj następujący wyróżniony kod do Data/SchoolContext.cs
pliku:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Ten kod dodaje nowe jednostki i konfiguruje złożony klucz podstawowy jednostki CourseAssignment.
Informacje o alternatywnej wersji interfejsu API
Kod w OnModelCreating
metodzie DbContext
klasy używa płynnego interfejsu API do konfigurowania zachowania platformy EF. Interfejs API jest nazywany "fluent", ponieważ jest często używany przez ciągowanie serii wywołań metod w jedną instrukcję, jak w tym przykładzie EF Core z dokumentacji:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
W tym samouczku używasz 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. Jeśli używasz obu tych elementów, należy pamiętać, że wszędzie tam, gdzie występuje konflikt, interfejs API Fluent zastępuje atrybuty.
Aby uzyskać więcej informacji na temat atrybutów i płynnego interfejsu API, zobacz Metody konfiguracji.
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 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ł.
Inicjuj bazę danych z danymi testowymi
Zastąp kod w Data/DbInitializer.cs
pliku następującym kodem, aby podać dane inicjuj dla nowo utworzonych jednostek.
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};
foreach (Student s in students)
{
context.Students.Add(s);
}
context.SaveChanges();
var instructors = new Instructor[]
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
foreach (Instructor i in instructors)
{
context.Instructors.Add(i);
}
context.SaveChanges();
var departments = new Department[]
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};
foreach (Department d in departments)
{
context.Departments.Add(d);
}
context.SaveChanges();
var courses = new Course[]
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};
foreach (OfficeAssignment o in officeAssignments)
{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();
var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};
foreach (CourseAssignment ci in courseInstructors)
{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}
Jak pokazano w pierwszym samouczku, większość tego kodu po prostu tworzy nowe obiekty jednostki i ładuje przykładowe dane do właściwości zgodnie z wymaganiami dotyczącymi testowania. Zwróć uwagę, jak są obsługiwane relacje wiele-do-wielu: kod tworzy relacje, tworząc jednostki w Enrollments
zestawach jednostek i CourseAssignment
sprzężenia.
Dodawanie migracji
Zapisz zmiany i skompiluj projekt. Następnie otwórz okno polecenia w folderze projektu i wprowadź migrations add
polecenie (nie wykonaj jeszcze polecenia update-database):
dotnet ef migrations add ComplexDataModel
Zostanie wyświetlone ostrzeżenie o możliwej utracie danych.
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'
Jeśli próbowano uruchomić database update
polecenie w tym momencie (jeszcze tego nie zrobisz), zostanie wyświetlony 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".
Czasami podczas wykonywania migracji z istniejącymi danymi należy wstawić dane wycinkowe do bazy danych, aby spełnić ograniczenia klucza obcego. Wygenerowany kod w metodzie Up
dodaje do Course
tabeli niepusty DepartmentID
klucz obcy. Jeśli w tabeli Course istnieją już wiersze po uruchomieniu kodu, operacja koń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. Na potrzeby tego samouczka uruchomisz migrację w nowej bazie danych, ale w aplikacji produkcyjnej trzeba będzie przeprowadzić migrację do istniejących danych, więc w poniższych kierunkach pokazano przykład tego, jak to zrobić.
Aby migracja działała z istniejącymi danymi, musisz zmienić kod, aby nadać nowej kolumnie wartość domyślną, i utworzyć dział wycinków o nazwie "Temp", aby działał jako domyślny dział. W związku z tym istniejące wiersze kursu będą powiązane z działem "Temp" po uruchomieniu Up
metody.
Otwórz plik
{timestamp}_ComplexDataModel.cs
.Oznacz jako komentarz wiersz kodu, który dodaje kolumnę DepartmentID do tabeli Course.
migrationBuilder.AlterColumn<string>( name: "Title", table: "Course", maxLength: 50, nullable: true, oldClrType: typeof(string), oldNullable: true); //migrationBuilder.AddColumn<int>( // name: "DepartmentID", // table: "Course", // nullable: false, // defaultValue: 0);
Dodaj następujący wyróżniony kod po kodzie, który tworzy tabelę Dział:
migrationBuilder.CreateTable( name: "Department", columns: table => new { DepartmentID = table.Column<int>(nullable: false) .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), Budget = table.Column<decimal>(type: "money", nullable: false), InstructorID = table.Column<int>(nullable: true), Name = table.Column<string>(maxLength: 50, nullable: true), StartDate = table.Column<DateTime>(nullable: false) }, constraints: table => { table.PrimaryKey("PK_Department", x => x.DepartmentID); table.ForeignKey( name: "FK_Department_Instructor_InstructorID", column: x => x.InstructorID, principalTable: "Instructor", principalColumn: "ID", onDelete: ReferentialAction.Restrict); }); migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())"); // Default value for FK points to department created above, with // defaultValue changed to 1 in following AddColumn statement. migrationBuilder.AddColumn<int>( name: "DepartmentID", table: "Course", nullable: false, defaultValue: 1);
W aplikacji produkcyjnej napiszesz kod lub skrypty, aby dodać wiersze działu i powiązać wiersze kursu z nowymi wierszami działu. Nie potrzebujesz już działu "Temp" ani wartości domyślnej w kolumnie Course.DepartmentID
.
Zapisz zmiany i skompiluj projekt.
Zmienianie parametry połączenia
Teraz masz nowy kod w klasie, który dodaje dane inicjujące DbInitializer
dla nowych jednostek do pustej bazy danych. Aby program EF utworzył nową pustą bazę danych, zmień nazwę bazy danych w parametry połączenia na appsettings.json
ContosoUniversity3 lub inną nazwę, której nie użyto na komputerze, którego używasz.
{
"ConnectionStrings": {
"DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=ContosoUniversity3;Trusted_Connection=True;MultipleActiveResultSets=true"
},
Zapisz zmianę na appsettings.json
.
Uwaga
Alternatywą dla zmiany nazwy bazy danych jest usunięcie bazy danych. Użyj programu SQL Server Eksplorator obiektów (SSOX) lub polecenia interfejsu database drop
wiersza polecenia:
dotnet ef database drop
Aktualizowanie bazy danych
Po zmianie nazwy bazy danych lub usunięciu bazy danych uruchom database update
polecenie w oknie poleceń, aby wykonać migracje.
dotnet ef database update
Uruchom aplikację, aby spowodować DbInitializer.Initialize
uruchomienie metody i wypełnienie nowej bazy danych.
Otwórz bazę danych w systemie SSOX, tak jak wcześniej, i rozwiń węzeł Tabele , aby zobaczyć, że wszystkie tabele zostały utworzone. (Jeśli nadal masz otwarte rozwiązanie SSOX z wcześniejszego czasu, kliknij przycisk Przycisk Odśwież ).
Uruchom aplikację, aby wyzwolić kod inicjatora, który wysieje bazę danych.
Kliknij prawym przyciskiem myszy tabelę CourseAssignment i wybierz pozycję Wyświetl dane , aby sprawdzić, czy zawiera ona dane.
Uzyskiwanie kodu
Pobierz lub wyświetl ukończoną aplikację.
Następne kroki
W tym samouczku zostały wykonane następujące czynności:
- Dostosowany model danych
- Wprowadzono zmiany w jednostce Student
- Utworzono jednostkę instruktora
- Utworzono jednostkę OfficeAssignment
- Zmodyfikowana jednostka Kursu
- Utworzona jednostka Działu
- Zmodyfikowana jednostka rejestracji
- Zaktualizowano kontekst bazy danych
- Baza danych rozstawiona z danymi testowymi
- Dodano migrację
- Zmieniono parametry połączenia
- Zaktualizowano bazę danych
Przejdź do następnego samouczka, aby dowiedzieć się więcej na temat uzyskiwania dostępu do powiązanych danych.