Część 5, Razor strony z EF Core ASP.NET Core — model danych
Przez Tom Dykstra, Jeremy Likness i Jon P Smith
Aplikacja internetowa Contoso University pokazuje, jak tworzyć Razor aplikacje internetowe stron przy użyciu programu EF Core Visual Studio. Aby uzyskać informacje na temat serii samouczków, zobacz pierwszy samouczek.
Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację i porównaj ten kod z utworzonymi elementami, wykonując czynności opisane w samouczku.
Poprzednie samouczki współpracowały z podstawowym modelem danych składającym się z trzech jednostek. W tym samouczku:
- Dodano więcej jednostek i relacji.
- Model danych jest dostosowywany przez określenie reguł formatowania, walidacji i mapowania bazy danych.
Ukończony model danych przedstawiono na poniższej ilustracji:
Następujący diagram bazy danych został wykonany z usługą Dataedo:
Aby utworzyć diagram bazy danych za pomocą usługi Dataedo:
- Wdrażanie aplikacji na platformie Azure
- Pobierz i zainstaluj urządzenie Dataedo na komputerze.
- Postępuj zgodnie z instrukcjami Generowanie dokumentacji dla usługi Azure SQL Database w ciągu 5 minut
Na powyższym diagramie CourseInstructor
Dataedo jest to tabela sprzężenia utworzona przez program Entity Framework. Aby uzyskać więcej informacji, zobacz Wiele do wielu
Jednostka Student
Zastąp kod w pliku Models/Student.cs
następującym kodem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Powyższy kod dodaje FullName
właściwość i dodaje następujące atrybuty do istniejących właściwości:
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. FullName
nie można ustawić, więc ma tylko metodę dostępu. W bazie danych nie FullName
jest tworzona żadna kolumna.
Atrybut DataType
[DataType(DataType.Date)]
W przypadku dat rejestracji uczniów wszystkie strony aktualnie wyświetlają godzinę dnia wraz z datą, chociaż tylko data jest odpowiednia. Za pomocą atrybutów adnotacji danych można wprowadzić jedną zmianę kodu, która naprawi format wyświetlania na każdej stronie, na której są wyświetlane dane.
Atrybut DataType określa typ danych, który jest bardziej szczegółowy niż typ wewnętrzny bazy danych. W takim przypadku powinna być wyświetlana tylko data, a nie data i godzina. Wyliczenie DataType zawiera wiele typów danych, takich jak Data, Godzina, Numer telefonu, Waluta, Adres e-mail itp. Atrybut DataType
może również umożliwić aplikacji automatyczne udostępnianie funkcji specyficznych dla typu. Na przykład:
- Link
mailto:
jest tworzony automatycznie dla elementuDataType.EmailAddress
. - Selektor dat jest udostępniany
DataType.Date
w większości przeglądarek.
Atrybut DataType
emituje atrybuty HTML 5 data-
(wymawiane kreska danych). Atrybuty DataType
nie zapewniają walidacji.
Atrybut DisplayFormat
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
nie określa formatu wyświetlanej daty. Domyślnie pole daty 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. Ustawienie ApplyFormatInEditMode
określa, że formatowanie powinno być również stosowane do interfejsu użytkownika edycji. Niektóre pola nie powinny używać elementu ApplyFormatInEditMode
. Na przykład symbol waluty zazwyczaj nie powinien być wyświetlany w polu tekstowym edycji.
Atrybut DisplayFormat
może być używany samodzielnie. Zazwyczaj dobrym pomysłem jest użycie atrybutu DataType
z atrybutem DisplayFormat
. Atrybut DataType
przekazuje semantyka danych, w przeciwieństwie do sposobu renderowania ich na ekranie. Atrybut DataType
zapewnia następujące korzyści, które nie są dostępne w programie DisplayFormat
:
- Przeglądarka może włączyć funkcje HTML5. Na przykład pokaż kontrolkę kalendarza, symbol waluty odpowiedniej dla ustawień regionalnych, linki poczty e-mail i walidację danych wejściowych po stronie klienta.
- Domyślnie przeglądarka renderuje dane przy użyciu poprawnego formatu na podstawie ustawień regionalnych.
Aby uzyskać więcej informacji, zobacz dokumentację pomocnika tagów <wejściowych>.
Atrybut StringLength
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Reguły walidacji danych i komunikaty o błędach walidacji można określić za pomocą atrybutów. Atrybut StringLength określa minimalną i maksymalną długość znaków dozwolonych w polu danych. Pokazany kod ogranicza nazwy do nie więcej niż 50 znaków. Przykład ustawiający minimalną długość ciągu jest wyświetlany później.
Atrybut StringLength
zapewnia również weryfikację po stronie klienta i po stronie serwera. Minimalna wartość nie ma wpływu na schemat bazy danych.
Atrybut StringLength
nie uniemożliwia użytkownikowi wprowadzania białych znaków dla nazwy. Atrybut RegularExpression może służyć do stosowania ograniczeń 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]*$")]
W programie SQL Server Eksplorator obiektów (SSOX) otwórz projektanta tabeli Student, klikając dwukrotnie tabelę Student.
Na powyższym obrazie przedstawiono schemat tabeli Student
. Pola nazw mają typ nvarchar(MAX)
. Po utworzeniu i zastosowaniu migracji w dalszej części tego samouczka pola nazw stają się nvarchar(50)
wynikiem atrybutów długości ciągu.
Atrybut Kolumna
[Column("FirstName")]
public string FirstMidName { get; set; }
Atrybuty mogą kontrolować sposób mapowania klas i właściwości na bazę danych. Student
W modelu Column
atrybut jest używany do mapowania nazwy FirstMidName
właściwości na "FirstName" w bazie danych.
Podczas tworzenia bazy danych nazwy właściwości w modelu są używane dla nazw kolumn (z wyjątkiem przypadków użycia atrybutu Column
). Model Student
używa FirstMidName
pola imię, ponieważ pole może również zawierać nazwę środkową.
Za pomocą atrybutu [Column]
Student.FirstMidName
w modelu danych mapuje się na kolumnę FirstName
Student
tabeli. Dodanie atrybutu Column
zmienia model kopii zapasowej SchoolContext
elementu . Model kopii zapasowej SchoolContext
bazy danych nie jest już zgodny z bazą danych. Ta rozbieżność zostanie rozwiązana przez dodanie migracji w dalszej części tego samouczka.
Wymagany atrybut
[Required]
Atrybut Required
sprawia, że właściwości nazwy są wymagane pola. Atrybut Required
nie jest wymagany w przypadku typów innych niż null, DateTime
takich jak typy wartości (na przykład , , int
i double
). 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; }
MinimumLength
i Required
zezwalaj na sprawdzanie poprawności przez biały znak. Użyj atrybutu , RegularExpression
aby uzyskać pełną kontrolę nad ciągiem.
Atrybut Wyświetlania
[Display(Name = "Last Name")]
Atrybut Display
określa, że podpis pól tekstowych powinien mieć wartość "Imię", "Nazwisko", "Imię", "Pełna nazwa" i "Data rejestracji". Domyślne napisy nie miały spacji dzielącej wyrazy, na przykład "Lastname".
Tworzenie migracji
Uruchom aplikację i przejdź do strony Uczniowie. Zgłaszany jest wyjątek. Atrybut [Column]
powoduje, że program EF oczekuje znalezienia kolumny o nazwie FirstName
, ale nazwa kolumny w bazie danych nadal FirstMidName
ma wartość .
Komunikat o błędzie jest podobny do następującego przykładu:
SqlException: Invalid column name 'FirstName'.
There are pending model changes
Pending model changes are detected in the following:
SchoolContext
W usłudze PMC wprowadź następujące polecenia, aby utworzyć nową migrację i zaktualizować bazę danych:
Add-Migration ColumnFirstName Update-Database
Pierwsze z tych poleceń generuje następujący komunikat ostrzegawczy:
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Ostrzeżenie jest generowane, ponieważ pola nazw są teraz ograniczone do 50 znaków. Jeśli nazwa w bazie danych miała więcej niż 50 znaków, utracono znak od 51 do ostatniego.
Otwórz tabelę Student w programie SSOX:
Przed zastosowaniem migracji kolumny nazw były typu nvarchar(MAX). Kolumny nazw to teraz
nvarchar(50)
. Nazwa kolumny zmieniła się zFirstMidName
naFirstName
.
- Uruchom aplikację i przejdź do strony Uczniowie.
- Zwróć uwagę, że czasy nie są wejściowe lub wyświetlane wraz z datami.
- Wybierz pozycję Utwórz nowy i spróbuj wprowadzić nazwę dłuższą niż 50 znaków.
Uwaga
W poniższych sekcjach kompilowanie aplikacji na niektórych etapach generuje błędy kompilatora. Instrukcje określają, kiedy należy skompilować aplikację.
Jednostka instruktora
Utwórz Models/Instructor.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 Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<Course> Courses { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
Wiele atrybutów może znajdować się w jednym wierszu. Atrybuty HireDate
można napisać 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
Właściwości Courses
i OfficeAssignment
to właściwości nawigacji.
Instruktor może uczyć dowolną liczbę kursów, dlatego Courses
jest definiowany jako kolekcja.
public ICollection<Course> Courses { get; set; }
Instruktor może mieć co najwyżej jedno biuro, więc OfficeAssignment
nieruchomość posiada jedną OfficeAssignment
jednostkę. OfficeAssignment
parametr ma wartość null, jeśli nie przypisano żadnego pakietu Office.
public OfficeAssignment OfficeAssignment { get; set; }
Jednostka 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
Atrybut [Key]
służy do identyfikowania właściwości jako klucza podstawowego (PK), gdy nazwa właściwości jest inna niż classnameID
lub ID
.
Istnieje relacja jeden do zera lub jednego między jednostkami Instructor
i OfficeAssignment
. Przypisanie biura istnieje tylko w odniesieniu do instruktora, do niego przypisano. Klucz OfficeAssignment
PK jest również jego kluczem obcym (FK) do Instructor
jednostki. Relacja jeden do zera lub jednego występuje, gdy klucz PK w jednej tabeli jest zarówno kluczem PK, jak i kluczem FK w innej tabeli.
EF Core Program nie może automatycznie rozpoznać InstructorID
klucza PK, OfficeAssignment
ponieważ InstructorID
nie jest zgodna z konwencją nazewnictwa identyfikatora ani identyfikatora klasy. W związku z tym Key
atrybut jest używany do identyfikowania InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Domyślnie klucz jest traktowany jako niegenerowany przez bazę danych, EF Core ponieważ kolumna służy do identyfikowania relacji. Aby uzyskać więcej informacji, zobacz Ef Keys (Klucze EF).
Właściwość nawigacji instruktora
Właściwość Instructor.OfficeAssignment
nawigacji może mieć wartość null, ponieważ może nie być OfficeAssignment
wiersz dla danego instruktora. Instruktor może nie mieć przypisania biura.
Właściwość OfficeAssignment.Instructor
nawigacji zawsze będzie mieć jednostkę instruktora, ponieważ typ klucza InstructorID
obcego to int
, typ wartości innej niż null. Przypisanie biura nie może istnieć bez instruktora.
Instructor
Gdy jednostka ma powiązaną OfficeAssignment
jednostkę, każda jednostka ma odwołanie do drugiej jednostki we właściwości nawigacji.
Jednostka kursu
Zaktualizuj Models/Course.cs
za pomocą następującego kodu:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<Instructor> Instructors { get; set; }
}
}
Jednostka Course
ma właściwość DepartmentID
klucza obcego (FK). DepartmentID
wskazuje powiązaną Department
jednostkę. Jednostka Course
ma Department
właściwość nawigacji.
EF Core nie wymaga właściwości klucza obcego dla modelu danych, gdy model ma właściwość nawigacji dla powiązanej jednostki. EF Core automatycznie tworzy zestawy FKs w bazie danych wszędzie tam, gdzie są potrzebne. EF Core Tworzy właściwości w tle dla automatycznie utworzonych zestawów FKs. Jednak jawne uwzględnienie klucza FK w modelu danych może sprawić, że aktualizacje będą prostsze i bardziej wydajne. Rozważmy na przykład model, w którym właściwość DepartmentID
FK nie jest uwzględniona. Po pobraniu jednostki kursu do edycji:
- Właściwość
Department
jestnull
, jeśli nie jest jawnie załadowana. - Aby zaktualizować jednostkę kursu,
Department
należy najpierw pobrać jednostkę.
Jeśli właściwość DepartmentID
FK jest uwzględniona w modelu danych, nie ma potrzeby pobierania Department
jednostki przed aktualizacją.
Atrybut DatabaseGenerated
Atrybut [DatabaseGenerated(DatabaseGeneratedOption.None)]
określa, że klucz PK jest dostarczany przez aplikację, a nie generowany przez bazę danych.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Domyślnie zakłada się, EF Core że wartości PK są generowane przez bazę danych. Generowanie bazy danych jest zazwyczaj najlepszym rozwiązaniem. W przypadku Course
jednostek użytkownik określa klucz PK. Na przykład numer kursu, taki jak seria 1000 dla działu matematyki, seria 2000 dla działu angielskiego.
Atrybut może być również używany do generowania DatabaseGenerated
wartości domyślnych. Na przykład baza danych może automatycznie wygenerować pole daty, aby zarejestrować datę 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 FK i Department
właściwość nawigacji.
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 Instructors
właściwość nawigacji jest kolekcją:
public ICollection<Instructor> Instructors { get; set; }
Jednostka 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
Column
Wcześniej atrybut był używany do zmiany mapowania nazw kolumn. W kodzie Department
jednostki Column
atrybut jest używany do zmiany mapowania typu danych SQL. Kolumna jest definiowana Budget
przy użyciu typu pieniędzy programu SQL Server w bazie danych:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapowanie kolumn zwykle nie jest wymagane. EF Core wybiera odpowiedni typ danych programu SQL Server na podstawie typu CLR dla właściwości . Typ CLR decimal
jest mapowy na typ programu SQL Server decimal
. Budget
jest dla waluty, a typ danych pieniężnych jest bardziej odpowiedni dla waluty.
Właściwości klucza obcego i nawigacji
Właściwości FK i nawigacji odzwierciedlają następujące relacje:
- Dział może lub nie ma administratora.
- Administrator jest zawsze instruktorem. W związku z
InstructorID
tym właściwość jest dołączana jako klucz FK doInstructor
jednostki.
Właściwość nawigacji ma nazwę Administrator
, ale zawiera Instructor
jednostkę:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
W ?
poprzednim kodzie określa, że właściwość jest dopuszczana do wartości null.
Dział może mieć wiele kursów, więc istnieje właściwość nawigacji Kursy:
public ICollection<Course> Courses { get; set; }
Zgodnie z konwencją umożliwia EF Core usuwanie kaskadowe dla niepustych zestawów FKs i relacji wiele-do-wielu. To domyślne zachowanie może spowodować cykliczne reguły usuwania kaskadowego. Reguły usuwania kaskadowego cykliczne powodują wyjątek podczas dodawania migracji.
Jeśli na przykład Department.InstructorID
właściwość została zdefiniowana jako niepusta, EF Core skonfiguruje regułę usuwania kaskadowego. W takim przypadku dział zostanie usunięty po usunięciu instruktora przypisanego jako jego administrator. W tym scenariuszu reguła ograniczeń będzie bardziej zrozumiała. Następujący płynny interfejs API ustawi regułę ograniczania i wyłączy kaskadowe usuwanie.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Właściwości klucza obcego i nawigacji Rejestracji
Rekord rejestracji dotyczy jednego kursu podjętego przez jednego ucznia.
Zaktualizuj Models/Enrollment.cs
za pomocą następującego kodu:
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Właściwości klucza FK i właściwości nawigacji odzwierciedlają następujące relacje:
Rekord rejestracji dotyczy jednego kursu, więc istnieje CourseID
właściwość FK i Course
właściwość nawigacji:
public int CourseID { get; set; }
public Course Course { get; set; }
Rekord rejestracji jest przeznaczony dla jednego ucznia, więc istnieje StudentID
właściwość FK i Student
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
. Jednostka Enrollment
pełni funkcję tabeli sprzężenia wiele-do-wielu z ładunkiem w bazie danych. Z ładunkiem oznacza, że Enrollment
tabela zawiera dodatkowe dane oprócz zestawów FKs dla tabel sprzężonych. W jednostce Enrollment
dodatkowe dane oprócz zestawów FK to PK i Grade
.
Poniższa ilustracja przedstawia wygląd tych relacji na diagramie jednostki. (Ten diagram został wygenerowany przy użyciu polecenia Narzędzia EF Power Tools for EF 6.x. Tworzenie diagramu nie jest częścią samouczka).
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, będzie ona musiała zawierać tylko dwa zestawy FKs i CourseID
StudentID
. Tabela sprzężenia wiele-do-wielu bez ładunku jest czasami nazywana czystą tabelą sprzężenia (PJT).
Jednostki Instructor
i Course
mają relację wiele do wielu przy użyciu protokołu PJT.
Aktualizowanie kontekstu bazy danych
Zaktualizuj Data/SchoolContext.cs
za pomocą następującego kodu:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable(nameof(Course))
.HasMany(c => c.Instructors)
.WithMany(i => i.Courses);
modelBuilder.Entity<Student>().ToTable(nameof(Student));
modelBuilder.Entity<Instructor>().ToTable(nameof(Instructor));
}
}
}
Powyższy kod dodaje nowe jednostki i konfiguruje relację wiele-do-wielu między jednostkami Instructor
i Course
.
Fluent API alternatywa dla atrybutów
Metoda OnModelCreating
w poprzednim kodzie używa płynnego interfejsu API do konfigurowania EF Core zachowania. Interfejs API jest nazywany "płynnym", ponieważ jest często używany przez ciągowanie serii wywołań metod w jedną instrukcję. Poniższy kod jest przykładem płynnego interfejsu API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
W tym samouczku płynny interfejs API jest używany tylko do mapowania bazy danych, których nie można wykonać za pomocą atrybutów. Jednak płynny interfejs API może określać większość reguł formatowania, walidacji i mapowania, które można wykonać za pomocą atrybutów.
Niektóre atrybuty, takie jak MinimumLength
nie można zastosować za pomocą płynnego interfejsu API. MinimumLength
nie zmienia schematu, ale stosuje tylko regułę weryfikacji minimalnej długości.
Niektórzy deweloperzy wolą używać płynnego interfejsu API wyłącznie tak, aby mogli zachować czyste klasy jednostek. Atrybuty i płynny interfejs API można mieszać. Istnieją pewne konfiguracje, które można wykonać tylko za pomocą płynnego interfejsu API, na przykład określając złożony klucz PK. Istnieją pewne konfiguracje, które można wykonać tylko za pomocą atrybutów (MinimumLength
). Zalecana praktyka korzystania z płynnego interfejsu API lub atrybutów:
- Wybierz jedno z tych dwóch podejść.
- Użyj wybranego podejścia spójnie, jak najwięcej.
Niektóre atrybuty używane w tym samouczku są używane w następujących celach:
- Tylko walidacja (na przykład
MinimumLength
). - EF Core tylko konfiguracja (na przykład
HasKey
). - Walidacja i EF Core konfiguracja (na przykład
[StringLength(50)]
).
Aby uzyskać więcej informacji na temat atrybutów i płynnego interfejsu API, zobacz Metody konfiguracji.
Inicjowanie bazy danych
Zaktualizuj kod w pliku Data/DbInitializer.cs
:
using ContosoUniversity.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var alexander = new Student
{
FirstMidName = "Carson",
LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01")
};
var alonso = new Student
{
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var anand = new Student
{
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var barzdukas = new Student
{
FirstMidName = "Gytis",
LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var li = new Student
{
FirstMidName = "Yan",
LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var justice = new Student
{
FirstMidName = "Peggy",
LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01")
};
var norman = new Student
{
FirstMidName = "Laura",
LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var olivetto = new Student
{
FirstMidName = "Nino",
LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01")
};
var students = new Student[]
{
alexander,
alonso,
anand,
barzdukas,
li,
justice,
norman,
olivetto
};
context.AddRange(students);
var abercrombie = new Instructor
{
FirstMidName = "Kim",
LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11")
};
var fakhouri = new Instructor
{
FirstMidName = "Fadi",
LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06")
};
var harui = new Instructor
{
FirstMidName = "Roger",
LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01")
};
var kapoor = new Instructor
{
FirstMidName = "Candace",
LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15")
};
var zheng = new Instructor
{
FirstMidName = "Roger",
LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12")
};
var instructors = new Instructor[]
{
abercrombie,
fakhouri,
harui,
kapoor,
zheng
};
context.AddRange(instructors);
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
Instructor = fakhouri,
Location = "Smith 17" },
new OfficeAssignment {
Instructor = harui,
Location = "Gowan 27" },
new OfficeAssignment {
Instructor = kapoor,
Location = "Thompson 304" }
};
context.AddRange(officeAssignments);
var english = new Department
{
Name = "English",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = abercrombie
};
var mathematics = new Department
{
Name = "Mathematics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = fakhouri
};
var engineering = new Department
{
Name = "Engineering",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = harui
};
var economics = new Department
{
Name = "Economics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = kapoor
};
var departments = new Department[]
{
english,
mathematics,
engineering,
economics
};
context.AddRange(departments);
var chemistry = new Course
{
CourseID = 1050,
Title = "Chemistry",
Credits = 3,
Department = engineering,
Instructors = new List<Instructor> { kapoor, harui }
};
var microeconomics = new Course
{
CourseID = 4022,
Title = "Microeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var macroeconmics = new Course
{
CourseID = 4041,
Title = "Macroeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var calculus = new Course
{
CourseID = 1045,
Title = "Calculus",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { fakhouri }
};
var trigonometry = new Course
{
CourseID = 3141,
Title = "Trigonometry",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { harui }
};
var composition = new Course
{
CourseID = 2021,
Title = "Composition",
Credits = 3,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var literature = new Course
{
CourseID = 2042,
Title = "Literature",
Credits = 4,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var courses = new Course[]
{
chemistry,
microeconomics,
macroeconmics,
calculus,
trigonometry,
composition,
literature
};
context.AddRange(courses);
var enrollments = new Enrollment[]
{
new Enrollment {
Student = alexander,
Course = chemistry,
Grade = Grade.A
},
new Enrollment {
Student = alexander,
Course = microeconomics,
Grade = Grade.C
},
new Enrollment {
Student = alexander,
Course = macroeconmics,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = calculus,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = trigonometry,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = anand,
Course = chemistry
},
new Enrollment {
Student = anand,
Course = microeconomics,
Grade = Grade.B
},
new Enrollment {
Student = barzdukas,
Course = chemistry,
Grade = Grade.B
},
new Enrollment {
Student = li,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = justice,
Course = literature,
Grade = Grade.B
}
};
context.AddRange(enrollments);
context.SaveChanges();
}
}
}
Powyższy kod zawiera dane inicjujące dla nowych jednostek. Większość tego kodu tworzy nowe obiekty jednostek i ładuje przykładowe dane. Przykładowe dane są używane do testowania.
Zastosuj migrację lub upuść i utwórz ponownie
W przypadku istniejącej bazy danych istnieją dwa podejścia do zmiany bazy danych:
- Upuść i ponownie utwórz bazę danych. Wybierz tę sekcję podczas korzystania z biblioteki SQLite.
- Zastosuj migrację do istniejącej bazy danych. Instrukcje w tej sekcji działają tylko dla programu SQL Server, a nie dla sqLite.
Wybór działa w przypadku programu SQL Server. Chociaż metoda apply-migration jest bardziej złożona i czasochłonna, jest to preferowane podejście dla rzeczywistych środowisk produkcyjnych.
Usuwanie i ponowne tworzenie bazy danych
Aby wymusić EF Core utworzenie nowej bazy danych, upuść i zaktualizować bazę danych:
- Usuń folder Migracje.
- W konsoli Menedżer pakietów (PMC) uruchom następujące polecenia:
Drop-Database
Add-Migration InitialCreate
Update-Database
Uruchom aplikację. Uruchomienie aplikacji powoduje uruchomienie DbInitializer.Initialize
metody . Obiekt DbInitializer.Initialize
wypełnia nową bazę danych.
Otwórz bazę danych w programie SSOX:
- Jeśli system SSOX został wcześniej otwarty, kliknij przycisk Odśwież .
- Rozwiń węzeł Tabele. Zostaną wyświetlone utworzone tabele.
Następne kroki
W kolejnych dwóch samouczkach pokazano, jak odczytywać i aktualizować powiązane dane.
Poprzednie samouczki współpracowały z podstawowym modelem danych składającym się z trzech jednostek. W tym samouczku:
- Dodano więcej jednostek i relacji.
- Model danych jest dostosowywany przez określenie reguł formatowania, walidacji i mapowania bazy danych.
Ukończony model danych przedstawiono na poniższej ilustracji:
Jednostka Student
Zastąp kod w pliku Models/Student.cs
następującym kodem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Powyższy kod dodaje FullName
właściwość i dodaje następujące atrybuty do istniejących właściwości:
[DataType]
[DisplayFormat]
[StringLength]
[Column]
[Required]
[Display]
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. FullName
nie można ustawić, więc ma tylko metodę dostępu. W bazie danych nie FullName
jest tworzona żadna kolumna.
Atrybut DataType
[DataType(DataType.Date)]
W przypadku dat rejestracji uczniów wszystkie strony aktualnie wyświetlają godzinę dnia wraz z datą, chociaż tylko data jest odpowiednia. Za pomocą atrybutów adnotacji danych można wprowadzić jedną zmianę kodu, która naprawi format wyświetlania na każdej stronie, na której są wyświetlane dane.
Atrybut DataType określa typ danych, który jest bardziej szczegółowy niż typ wewnętrzny bazy danych. W takim przypadku powinna być wyświetlana tylko data, a nie data i godzina. Wyliczenie DataType zawiera wiele typów danych, takich jak Data, Godzina, Numer telefonu, Waluta, Adres e-mail itp. Atrybut DataType
może również umożliwić aplikacji automatyczne udostępnianie funkcji specyficznych dla typu. Na przykład:
- Link
mailto:
jest tworzony automatycznie dla elementuDataType.EmailAddress
. - Selektor dat jest udostępniany
DataType.Date
w większości przeglądarek.
Atrybut DataType
emituje atrybuty HTML 5 data-
(wymawiane kreska danych). Atrybuty DataType
nie zapewniają walidacji.
Atrybut DisplayFormat
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
nie określa formatu wyświetlanej daty. Domyślnie pole daty 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. Ustawienie ApplyFormatInEditMode
określa, że formatowanie powinno być również stosowane do interfejsu użytkownika edycji. Niektóre pola nie powinny używać elementu ApplyFormatInEditMode
. Na przykład symbol waluty zazwyczaj nie powinien być wyświetlany w polu tekstowym edycji.
Atrybut DisplayFormat
może być używany samodzielnie. Zazwyczaj dobrym pomysłem jest użycie atrybutu DataType
z atrybutem DisplayFormat
. Atrybut DataType
przekazuje semantyka danych, w przeciwieństwie do sposobu renderowania ich na ekranie. Atrybut DataType
zapewnia następujące korzyści, które nie są dostępne w programie DisplayFormat
:
- Przeglądarka może włączyć funkcje HTML5. Na przykład pokaż kontrolkę kalendarza, symbol waluty odpowiedniej dla ustawień regionalnych, linki poczty e-mail i walidację danych wejściowych po stronie klienta.
- Domyślnie przeglądarka renderuje dane przy użyciu poprawnego formatu na podstawie ustawień regionalnych.
Aby uzyskać więcej informacji, zobacz dokumentację pomocnika tagów <wejściowych>.
Atrybut StringLength
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Reguły walidacji danych i komunikaty o błędach walidacji można określić za pomocą atrybutów. Atrybut StringLength określa minimalną i maksymalną długość znaków dozwolonych w polu danych. Pokazany kod ogranicza nazwy do nie więcej niż 50 znaków. Przykład ustawiający minimalną długość ciągu jest wyświetlany później.
Atrybut StringLength
zapewnia również weryfikację po stronie klienta i po stronie serwera. Minimalna wartość nie ma wpływu na schemat bazy danych.
Atrybut StringLength
nie uniemożliwia użytkownikowi wprowadzania białych znaków dla nazwy. Atrybut RegularExpression może służyć do stosowania ograniczeń 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]*$")]
W programie SQL Server Eksplorator obiektów (SSOX) otwórz projektanta tabeli Student, klikając dwukrotnie tabelę Student.
Na powyższym obrazie przedstawiono schemat tabeli Student
. Pola nazw mają typ nvarchar(MAX)
. Po utworzeniu i zastosowaniu migracji w dalszej części tego samouczka pola nazw stają się nvarchar(50)
wynikiem atrybutów długości ciągu.
Atrybut Kolumna
[Column("FirstName")]
public string FirstMidName { get; set; }
Atrybuty mogą kontrolować sposób mapowania klas i właściwości na bazę danych. Student
W modelu Column
atrybut jest używany do mapowania nazwy FirstMidName
właściwości na "FirstName" w bazie danych.
Podczas tworzenia bazy danych nazwy właściwości w modelu są używane dla nazw kolumn (z wyjątkiem przypadków użycia atrybutu Column
). Model Student
używa FirstMidName
pola imię, ponieważ pole może również zawierać nazwę środkową.
Za pomocą atrybutu [Column]
Student.FirstMidName
w modelu danych mapuje się na kolumnę FirstName
Student
tabeli. Dodanie atrybutu Column
zmienia model kopii zapasowej SchoolContext
elementu . Model kopii zapasowej SchoolContext
bazy danych nie jest już zgodny z bazą danych. Ta rozbieżność zostanie rozwiązana przez dodanie migracji w dalszej części tego samouczka.
Wymagany atrybut
[Required]
Atrybut Required
sprawia, że właściwości nazwy są wymagane pola. Atrybut Required
nie jest wymagany w przypadku typów innych niż null, DateTime
takich jak typy wartości (na przykład , , int
i double
). 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; }
MinimumLength
i Required
zezwalaj na sprawdzanie poprawności przez biały znak. Użyj atrybutu , RegularExpression
aby uzyskać pełną kontrolę nad ciągiem.
Atrybut Wyświetlania
[Display(Name = "Last Name")]
Atrybut Display
określa, że podpis pól tekstowych powinien mieć wartość "Imię", "Nazwisko", "Imię", "Pełna nazwa" i "Data rejestracji". Domyślne napisy nie miały spacji dzielącej wyrazy, na przykład "Lastname".
Tworzenie migracji
Uruchom aplikację i przejdź do strony Uczniowie. Zgłaszany jest wyjątek. Atrybut [Column]
powoduje, że program EF oczekuje znalezienia kolumny o nazwie FirstName
, ale nazwa kolumny w bazie danych nadal FirstMidName
ma wartość .
Komunikat o błędzie jest podobny do następującego przykładu:
SqlException: Invalid column name 'FirstName'.
W usłudze PMC wprowadź następujące polecenia, aby utworzyć nową migrację i zaktualizować bazę danych:
Add-Migration ColumnFirstName Update-Database
Pierwsze z tych poleceń generuje następujący komunikat ostrzegawczy:
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Ostrzeżenie jest generowane, ponieważ pola nazw są teraz ograniczone do 50 znaków. Jeśli nazwa w bazie danych miała więcej niż 50 znaków, utracono znak od 51 do ostatniego.
Otwórz tabelę Student w programie SSOX:
Przed zastosowaniem migracji kolumny nazw były typu nvarchar(MAX). Kolumny nazw to teraz
nvarchar(50)
. Nazwa kolumny zmieniła się zFirstMidName
naFirstName
.
- Uruchom aplikację i przejdź do strony Uczniowie.
- Zwróć uwagę, że czasy nie są wejściowe lub wyświetlane wraz z datami.
- Wybierz pozycję Utwórz nowy i spróbuj wprowadzić nazwę dłuższą niż 50 znaków.
Uwaga
W poniższych sekcjach kompilowanie aplikacji na niektórych etapach generuje błędy kompilatora. Instrukcje określają, kiedy należy skompilować aplikację.
Jednostka instruktora
Utwórz Models/Instructor.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 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; }
}
}
Wiele atrybutów może znajdować się w jednym wierszu. Atrybuty HireDate
można napisać 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
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; }
Instruktor może mieć co najwyżej jedno biuro, więc OfficeAssignment
nieruchomość posiada jedną OfficeAssignment
jednostkę. OfficeAssignment
parametr ma wartość null, jeśli nie przypisano żadnego pakietu Office.
public OfficeAssignment OfficeAssignment { get; set; }
Jednostka 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
Atrybut [Key]
służy do identyfikowania właściwości jako klucza podstawowego (PK), gdy nazwa właściwości jest czymś innym niż classnameID lub ID.
Istnieje relacja jeden do zera lub jednego między jednostkami Instructor
i OfficeAssignment
. Przypisanie biura istnieje tylko w odniesieniu do instruktora, do niego przypisano. Klucz OfficeAssignment
PK jest również jego kluczem obcym (FK) do Instructor
jednostki.
EF Core Program nie może automatycznie rozpoznać InstructorID
klucza PK, OfficeAssignment
ponieważ InstructorID
nie jest zgodna z konwencją nazewnictwa identyfikatora ani identyfikatora klasy. W związku z tym Key
atrybut jest używany do identyfikowania InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Domyślnie klucz jest traktowany jako niegenerowany przez bazę danych, EF Core ponieważ kolumna służy do identyfikowania relacji.
Właściwość nawigacji instruktora
Właściwość Instructor.OfficeAssignment
nawigacji może mieć wartość null, ponieważ może nie być OfficeAssignment
wiersz dla danego instruktora. Instruktor może nie mieć przypisania biura.
Właściwość OfficeAssignment.Instructor
nawigacji zawsze będzie mieć jednostkę instruktora, ponieważ typ klucza InstructorID
obcego to int
, typ wartości innej niż null. Przypisanie biura nie może istnieć bez instruktora.
Instructor
Gdy jednostka ma powiązaną OfficeAssignment
jednostkę, każda jednostka ma odwołanie do drugiej jednostki we właściwości nawigacji.
Jednostka kursu
Zaktualizuj Models/Course.cs
za pomocą następującego kodu:
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 Course
ma właściwość DepartmentID
klucza obcego (FK). DepartmentID
wskazuje powiązaną Department
jednostkę. Jednostka Course
ma Department
właściwość nawigacji.
EF Core nie wymaga właściwości klucza obcego dla modelu danych, gdy model ma właściwość nawigacji dla powiązanej jednostki. EF Core automatycznie tworzy zestawy FKs w bazie danych wszędzie tam, gdzie są potrzebne. EF Core Tworzy właściwości w tle dla automatycznie utworzonych zestawów FKs. Jednak jawne uwzględnienie klucza FK w modelu danych może sprawić, że aktualizacje będą prostsze i bardziej wydajne. Rozważmy na przykład model, w którym właściwość DepartmentID
FK nie jest uwzględniona. Po pobraniu jednostki kursu do edycji:
- Właściwość
Department
ma wartość null, jeśli nie jest jawnie załadowana. - Aby zaktualizować jednostkę kursu,
Department
należy najpierw pobrać jednostkę.
Jeśli właściwość DepartmentID
FK jest uwzględniona w modelu danych, nie ma potrzeby pobierania Department
jednostki przed aktualizacją.
Atrybut DatabaseGenerated
Atrybut [DatabaseGenerated(DatabaseGeneratedOption.None)]
określa, że klucz PK jest dostarczany przez aplikację, a nie generowany przez bazę danych.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Domyślnie zakłada się, EF Core że wartości PK są generowane przez bazę danych. Generowanie bazy danych jest zazwyczaj najlepszym rozwiązaniem. W przypadku Course
jednostek użytkownik określa klucz PK. Na przykład numer kursu, taki jak seria 1000 dla działu matematyki, seria 2000 dla działu angielskiego.
Atrybut może być również używany do generowania DatabaseGenerated
wartości domyślnych. Na przykład baza danych może automatycznie wygenerować pole daty, aby zarejestrować datę 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 FK i Department
właściwość nawigacji.
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ą:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
jest wyjaśniony później.
Jednostka 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
Column
Wcześniej atrybut był używany do zmiany mapowania nazw kolumn. W kodzie Department
jednostki Column
atrybut jest używany do zmiany mapowania typu danych SQL. Kolumna jest definiowana Budget
przy użyciu typu pieniędzy programu SQL Server w bazie danych:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapowanie kolumn zwykle nie jest wymagane. EF Core wybiera odpowiedni typ danych programu SQL Server na podstawie typu CLR dla właściwości . Typ CLR decimal
jest mapowy na typ programu SQL Server decimal
. Budget
jest dla waluty, a typ danych pieniężnych jest bardziej odpowiedni dla waluty.
Właściwości klucza obcego i nawigacji
Właściwości FK i nawigacji odzwierciedlają następujące relacje:
- Dział może lub nie ma administratora.
- Administrator jest zawsze instruktorem. W związku z
InstructorID
tym właściwość jest dołączana jako klucz FK doInstructor
jednostki.
Właściwość nawigacji ma nazwę Administrator
, ale zawiera Instructor
jednostkę:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Znak zapytania (?) w poprzednim kodzie określa właściwość ma wartość null.
Dział może mieć wiele kursów, więc istnieje właściwość nawigacji Kursy:
public ICollection<Course> Courses { get; set; }
Zgodnie z konwencją umożliwia EF Core usuwanie kaskadowe dla niepustych zestawów FKs i relacji wiele-do-wielu. To domyślne zachowanie może spowodować cykliczne reguły usuwania kaskadowego. Reguły usuwania kaskadowego cykliczne powodują wyjątek podczas dodawania migracji.
Jeśli na przykład Department.InstructorID
właściwość została zdefiniowana jako niepusta, EF Core skonfiguruje regułę usuwania kaskadowego. W takim przypadku dział zostanie usunięty po usunięciu instruktora przypisanego jako jego administrator. W tym scenariuszu reguła ograniczeń będzie bardziej zrozumiała. Następujący płynny interfejs API ustawi regułę ograniczania i wyłączy kaskadowe usuwanie.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Jednostka Rejestracja
Rekord rejestracji dotyczy jednego kursu podjętego przez jednego ucznia.
Zaktualizuj Models/Enrollment.cs
za pomocą następującego kodu:
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 FK i właściwości nawigacji odzwierciedlają następujące relacje:
Rekord rejestracji dotyczy jednego kursu, więc istnieje CourseID
właściwość FK i Course
właściwość nawigacji:
public int CourseID { get; set; }
public Course Course { get; set; }
Rekord rejestracji jest przeznaczony dla jednego ucznia, więc istnieje StudentID
właściwość FK i Student
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
. Jednostka Enrollment
pełni funkcję tabeli sprzężenia wiele-do-wielu z ładunkiem w bazie danych. "Z ładunkiem" oznacza, że Enrollment
tabela zawiera dodatkowe dane oprócz zestawów FKs dla tabel sprzężonych (w tym przypadku PK i Grade
).
Poniższa ilustracja przedstawia wygląd tych relacji na diagramie jednostki. (Ten diagram został wygenerowany przy użyciu polecenia Narzędzia EF Power Tools for EF 6.x. Tworzenie diagramu nie jest częścią samouczka).
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 elementy FKs (CourseID
i StudentID
). Tabela sprzężenia wiele-do-wielu bez ładunku jest czasami nazywana czystą tabelą sprzężenia (PJT).
Jednostki Instructor
i Course
mają relację wiele do wielu przy użyciu czystej tabeli sprzężenia.
Uwaga: program EF 6.x obsługuje niejawne tabele sprzężenia dla relacji wiele-do-wielu, ale EF Core nie. Aby uzyskać więcej informacji, zobacz Relacje wiele-do-wielu w EF Core wersji 2.0.
Jednostka CourseAssignment
Utwórz Models/CourseAssignment.cs
za pomocą następującego kodu:
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; }
}
}
Relacja Instruktor-to-Courses wiele do wielu wymaga tabeli sprzężenia, a jednostka dla tej tabeli sprzężenia to CourseAssignment.
Nazwa jednostki EntityName1EntityName2
sprzężenia jest pospolita. Na przykład tabela dołączania instruktora do kursów używająca tego wzorca to CourseInstructor
. Zalecamy jednak użycie nazwy, która opisuje relację.
Modele danych zaczynają się proste i rosną. Tabele sprzężenia bez ładunku (PJT) często ewoluują w celu uwzględnienia ładunku. Zaczynając od opisowej nazwy jednostki, nazwa nie musi zmieniać się po zmianie tabeli sprzężenia. 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 z jednostką sprzężenia o nazwie Ratings. W przypadku relacji CourseAssignment
Instruktor-to-Courses wiele do wielu preferowane jest ponad CourseInstructor
.
Klucz złożony
Dwa zestawy FKs w CourseAssignment
elemecie (InstructorID
i CourseID
) są unikatowo identyfikowane w każdym wierszu CourseAssignment
tabeli. CourseAssignment
nie wymaga dedykowanego klucza PK. Właściwości InstructorID
i CourseID
działają jako złożony klucz PK. Jedynym sposobem określenia złożonych zestawów PKs EF Core do jest użycie płynnego interfejsu API. W następnej sekcji pokazano, jak skonfigurować złożony klucz PK.
Klucz złożony gwarantuje, że:
- Wiele wierszy jest dozwolonych dla jednego kursu.
- Wiele wierszy jest dozwolonych dla jednego instruktora.
- Wiele wierszy nie jest dozwolonych dla tego samego instruktora i kursu.
Jednostka Enrollment
sprzężenia definiuje własny klucz PK, więc możliwe są duplikaty tego rodzaju. Aby zapobiec takim duplikatom:
- Dodawanie unikatowego indeksu w polach FK lub
- Skonfiguruj
Enrollment
przy użyciu podstawowego klucza złożonego podobnego doCourseAssignment
. Aby uzyskać więcej informacji, zobacz Indeksy.
Aktualizowanie kontekstu bazy danych
Zaktualizuj Data/SchoolContext.cs
za pomocą następującego kodu:
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 });
}
}
}
Powyższy kod dodaje nowe jednostki i konfiguruje CourseAssignment
złożony klucz PK jednostki.
Fluent API alternatywa dla atrybutów
Metoda OnModelCreating
w poprzednim kodzie używa płynnego interfejsu API do konfigurowania EF Core zachowania. Interfejs API jest nazywany "płynnym", ponieważ jest często używany przez ciągowanie serii wywołań metod w jedną instrukcję. Poniższy kod jest przykładem płynnego interfejsu API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
W tym samouczku płynny interfejs API jest używany tylko do mapowania bazy danych, których nie można wykonać za pomocą atrybutów. Jednak płynny interfejs API może określać większość reguł formatowania, walidacji i mapowania, które można wykonać za pomocą atrybutów.
Niektóre atrybuty, takie jak MinimumLength
nie można zastosować za pomocą płynnego interfejsu API. MinimumLength
nie zmienia schematu, ale stosuje tylko regułę weryfikacji minimalnej długości.
Niektórzy deweloperzy wolą używać płynnego interfejsu API wyłącznie tak, aby mogli zachować klasy jednostek "czyste". Atrybuty i płynny interfejs API można mieszać. Istnieją pewne konfiguracje, które można wykonać tylko za pomocą płynnego interfejsu API (określając złożony klucz PK). Istnieją pewne konfiguracje, które można wykonać tylko za pomocą atrybutów (MinimumLength
). Zalecana praktyka korzystania z płynnego interfejsu API lub atrybutów:
- Wybierz jedno z tych dwóch podejść.
- Użyj wybranego podejścia spójnie, jak najwięcej.
Niektóre atrybuty używane w tym samouczku są używane w następujących celach:
- Tylko walidacja (na przykład
MinimumLength
). - EF Core tylko konfiguracja (na przykład
HasKey
). - Walidacja i EF Core konfiguracja (na przykład
[StringLength(50)]
).
Aby uzyskać więcej informacji na temat atrybutów i płynnego interfejsu API, zobacz Metody konfiguracji.
Diagram jednostki
Na poniższej ilustracji przedstawiono diagram utworzony przez narzędzia EF Power Tools dla ukończonego modelu School.
Na powyższym diagramie przedstawiono:
- Kilka wierszy relacji jeden do wielu (od 1 do *).
- Wiersz relacji jeden do zera lub jednego (od 1 do 0,1) między jednostkami
Instructor
iOfficeAssignment
. - Wiersz relacji zero lub jeden do wielu (od 0..1 do *) między jednostkami
Instructor
iDepartment
.
Inicjowanie bazy danych
Zaktualizuj kod w pliku Data/DbInitializer.cs
:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01") }
};
context.Students.AddRange(students);
context.SaveChanges();
var instructors = new Instructor[]
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
context.Instructors.AddRange(instructors);
context.SaveChanges();
var departments = new Department[]
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};
context.Departments.AddRange(departments);
context.SaveChanges();
var courses = new Course[]
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};
context.Courses.AddRange(courses);
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};
context.OfficeAssignments.AddRange(officeAssignments);
context.SaveChanges();
var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};
context.CourseAssignments.AddRange(courseInstructors);
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}
Powyższy kod zawiera dane inicjujące dla nowych jednostek. Większość tego kodu tworzy nowe obiekty jednostek i ładuje przykładowe dane. Przykładowe dane są używane do testowania. Zobacz Enrollments
i CourseAssignments
, aby zapoznać się z przykładami liczby do wielu tabel sprzężenia, które można zainicjować.
Dodawanie migracji
Skompiluj projekt.
W usłudze PMC uruchom następujące polecenie.
Add-Migration ComplexDataModel
Poprzednie polecenie wyświetla 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.
To undo this action, use 'ef migrations remove'
database update
Jeśli polecenie zostanie uruchomione, zostanie wygenerowany następujący błąd:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in
database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.
W następnej sekcji zobaczysz, co zrobić z tym błędem.
Zastosuj migrację lub upuść i utwórz ponownie
Teraz, gdy masz istniejącą bazę danych, musisz zastanowić się, jak zastosować do niej zmiany. W tym samouczku przedstawiono dwie alternatywy:
- Upuść i ponownie utwórz bazę danych. Wybierz tę sekcję, jeśli używasz biblioteki SQLite.
- Zastosuj migrację do istniejącej bazy danych. Instrukcje w tej sekcji działają tylko dla programu SQL Server, a nie dla sqLite.
Wybór działa w przypadku programu SQL Server. Chociaż metoda apply-migration jest bardziej złożona i czasochłonna, jest to preferowane podejście dla rzeczywistych środowisk produkcyjnych.
Usuwanie i ponowne tworzenie bazy danych
Pomiń tę sekcję , jeśli używasz programu SQL Server i chcesz wykonać podejście apply-migration w poniższej sekcji.
Aby wymusić EF Core utworzenie nowej bazy danych, upuść i zaktualizować bazę danych:
W konsoli Menedżer pakietów (PMC) uruchom następujące polecenie:
Drop-Database
Usuń folder Migrations, a następnie uruchom następujące polecenie:
Add-Migration InitialCreate Update-Database
Uruchom aplikację. Uruchomienie aplikacji powoduje uruchomienie DbInitializer.Initialize
metody . Obiekt DbInitializer.Initialize
wypełnia nową bazę danych.
Otwórz bazę danych w programie SSOX:
Jeśli system SSOX został wcześniej otwarty, kliknij przycisk Odśwież .
Rozwiń węzeł Tabele. Zostaną wyświetlone utworzone tabele.
Zapoznaj się z tabelą CourseAssignment :
- Kliknij prawym przyciskiem myszy tabelę CourseAssignment i wybierz pozycję Wyświetl dane.
- Sprawdź, czy tabela CourseAssignment zawiera dane.
Stosowanie migracji
Ta sekcja jest opcjonalna. Te kroki działają tylko dla programu SQL Server LocalDB i tylko wtedy, gdy pominięto poprzednią sekcję Drop i ponownie utwórz bazę danych .
Gdy migracje są uruchamiane z istniejącymi danymi, mogą istnieć ograniczenia szyfrowania FK, które nie są zadowalające z istniejących danych. W przypadku danych produkcyjnych należy wykonać kroki migracji istniejących danych. Ta sekcja zawiera przykład naprawiania naruszeń ograniczeń FK. Nie wprowadzaj tych zmian w kodzie bez tworzenia kopii zapasowej. Nie wprowadzaj tych zmian w kodzie, jeśli ukończono poprzednią sekcję Upuść i ponownie utwórz bazę danych .
Plik {timestamp}_ComplexDataModel.cs
zawiera następujący kod:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Powyższy kod dodaje do Course
tabeli klucz FK bez DepartmentID
wartości null. Baza danych z poprzedniego samouczka zawiera wiersze w Course
pliku , dzięki czemu nie można zaktualizować tabeli przez migracje.
Aby migracja ComplexDataModel
działała z istniejącymi danymi:
- Zmień kod, aby nadać nowej kolumnie (
DepartmentID
) wartość domyślną. - Utwórz fałszywy dział o nazwie "Temp", aby działać jako domyślny dział.
Naprawianie ograniczeń klucza obcego
ComplexDataModel
W klasie migracji zaktualizuj metodę Up
:
- Otwórz plik
{timestamp}_ComplexDataModel.cs
. - Oznacz jako komentarz wiersz kodu, który dodaje kolumnę
DepartmentID
Course
do tabeli.
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. Nowy kod przechodzi po .CreateTable( name: "Department"
bloku:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(type: "int", nullable: true),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);
Po wykonaniu powyższych zmian istniejące Course
wiersze będą powiązane z działem "Temp" po uruchomieniu ComplexDataModel.Up
metody.
Sposób obsługi sytuacji pokazanej tutaj jest uproszczony dla tego samouczka. Aplikacja produkcyjna byłaby:
- Dołącz kod lub skrypty, aby dodać
Department
wiersze i powiązaneCourse
wiersze do nowychDepartment
wierszy. - Nie należy używać działu "Temp" ani wartości domyślnej dla .
Course.DepartmentID
W konsoli Menedżer pakietów (PMC) uruchom następujące polecenie:
Update-Database
DbInitializer.Initialize
Ponieważ metoda jest przeznaczona do pracy tylko z pustą bazą danych, użyj programu SSOX, aby usunąć wszystkie wiersze w tabelach Student i Course. (Usunięcie kaskadowe zajmie się tabelą Enrollment).
Uruchom aplikację. Uruchomienie aplikacji powoduje uruchomienie DbInitializer.Initialize
metody . Obiekt DbInitializer.Initialize
wypełnia nową bazę danych.
Następne kroki
W kolejnych dwóch samouczkach pokazano, jak odczytywać i aktualizować powiązane dane.
Poprzednie samouczki współpracowały z podstawowym modelem danych składającym się z trzech jednostek. W tym samouczku:
- Dodano więcej jednostek i relacji.
- Model danych jest dostosowywany przez określenie reguł formatowania, walidacji i mapowania bazy danych.
Klasy jednostek dla ukończonego modelu danych przedstawiono na poniższej ilustracji:
Jeśli napotkasz problemy, których nie możesz rozwiązać, pobierz ukończoną aplikację.
Dostosowywanie modelu danych za pomocą atrybutów
W tej sekcji model danych jest dostosowywany przy użyciu atrybutów.
Atrybut DataType
Strony uczniów wyświetla obecnie godzinę rejestracji. Zazwyczaj pola daty zawierają tylko datę, a nie godzinę.
Zaktualizuj Models/Student.cs
za pomocą następującego wyróżnionego kodu:
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 określa typ danych, który jest bardziej szczegółowy niż typ wewnętrzny bazy danych. W takim przypadku powinna być wyświetlana tylko data, a nie data i godzina. Wyliczenie DataType zawiera wiele typów danych, takich jak Data, Godzina, Numer telefonu, Waluta, Adres e-mail itp. Atrybut DataType
może również umożliwić aplikacji automatyczne udostępnianie funkcji specyficznych dla typu. Na przykład:
- Link
mailto:
jest tworzony automatycznie dla elementuDataType.EmailAddress
. - Selektor dat jest udostępniany
DataType.Date
w większości przeglądarek.
Atrybut DataType
emituje atrybuty HTML 5 data-
(wymawiane kreska danych), z których korzystają przeglądarki HTML 5. Atrybuty DataType
nie zapewniają walidacji.
DataType.Date
nie określa formatu wyświetlanej daty. Domyślnie pole daty 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 do interfejsu użytkownika edycji. Niektóre pola nie powinny używać elementu ApplyFormatInEditMode
. Na przykład symbol waluty zazwyczaj nie powinien być wyświetlany w polu tekstowym edycji.
Atrybut DisplayFormat
może być używany samodzielnie. Zazwyczaj dobrym pomysłem jest użycie atrybutu DataType
z atrybutem DisplayFormat
. Atrybut DataType
przekazuje semantyka danych, w przeciwieństwie do sposobu renderowania ich na ekranie. Atrybut DataType
zapewnia następujące korzyści, które nie są dostępne w programie DisplayFormat
:
- Przeglądarka może włączyć funkcje HTML5. Na przykład pokaż kontrolkę kalendarza, odpowiedni symbol waluty ustawień regionalnych, linki e-mail, walidację danych wejściowych po stronie klienta itp.
- Domyślnie przeglądarka renderuje 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. Czasy nie są już wyświetlane. Każdy widok korzystający z Student
modelu wyświetla datę bez godziny.
Atrybut StringLength
Reguły walidacji danych i komunikaty o błędach walidacji można określić za pomocą atrybutów. Atrybut StringLength określa minimalną i maksymalną długość znaków dozwolonych w polu danych. Atrybut StringLength
zapewnia również weryfikację po stronie klienta i po stronie serwera. Minimalna wartość nie ma wpływu na schemat bazy danych.
Student
Zaktualizuj model przy użyciu następującego kodu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Powyższy kod ogranicza nazwy do nie więcej niż 50 znaków. Atrybut StringLength
nie uniemożliwia użytkownikowi wprowadzania białych znaków dla nazwy. Atrybut RegularExpression służy do stosowania ograniczeń 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]*$")]
Uruchom aplikację:
- Przejdź do strony Uczniowie.
- Wybierz pozycję Utwórz nowy i wprowadź nazwę dłuższą niż 50 znaków.
- Wybierz pozycję Utwórz, walidacja po stronie klienta wyświetla komunikat o błędzie.
W programie SQL Server Eksplorator obiektów (SSOX) otwórz projektanta tabeli Student, klikając dwukrotnie tabelę Student.
Na powyższym obrazie przedstawiono schemat tabeli Student
. Pola nazw mają typ nvarchar(MAX)
, ponieważ migracje nie zostały uruchomione w bazie danych. Po uruchomieniu migracji w dalszej części tego samouczka pola nazw staną się .nvarchar(50)
Atrybut Kolumna
Atrybuty mogą kontrolować sposób mapowania klas i właściwości na bazę danych. W tej sekcji atrybut Column
jest używany do mapowania nazwy FirstMidName
właściwości na "FirstName" w bazie danych.
Podczas tworzenia bazy danych nazwy właściwości w modelu są używane dla nazw kolumn (z wyjątkiem przypadków użycia atrybutu Column
).
Model Student
używa FirstMidName
pola imię, ponieważ pole może również zawierać nazwę środkową.
Student.cs
Zaktualizuj plik przy użyciu następującego wyróżnionego kodu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Po poprzedniej zmianie Student.FirstMidName
w aplikacji jest mapowanie na kolumnę FirstName
Student
tabeli.
Dodanie atrybutu Column
zmienia model kopii zapasowej SchoolContext
elementu . Model kopii zapasowej SchoolContext
bazy danych nie jest już zgodny z bazą danych. Jeśli aplikacja jest uruchamiana przed zastosowaniem migracji, zostanie wygenerowany następujący wyjątek:
SqlException: Invalid column name 'FirstName'.
Aby zaktualizować bazę danych:
- Skompiluj projekt.
- Otwórz okno polecenia w folderze projektu. Wprowadź następujące polecenia, aby utworzyć nową migrację i zaktualizować bazę danych:
Add-Migration ColumnFirstName
Update-Database
Polecenie migrations add ColumnFirstName
generuje następujący komunikat ostrzegawczy:
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Ostrzeżenie jest generowane, ponieważ pola nazw są teraz ograniczone do 50 znaków. Jeśli nazwa w bazie danych miała więcej niż 50 znaków, utracono od 51 do ostatniego znaku.
- Testowanie aplikacji.
Otwórz tabelę Student w programie SSOX:
Przed zastosowaniem migracji kolumny nazw były typu nvarchar(MAX). Kolumny nazw to teraz nvarchar(50)
. Nazwa kolumny zmieniła się z FirstMidName
na FirstName
.
Uwaga
W poniższej sekcji kompilowanie aplikacji na niektórych etapach generuje błędy kompilatora. Instrukcje określają, kiedy należy skompilować aplikację.
Aktualizacja jednostki ucznia
Zaktualizuj Models/Student.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 Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Wymagany atrybut
Atrybut Required
sprawia, że właściwości nazwy są wymagane pola. Atrybut Required
nie jest wymagany w przypadku typów innych niż null, takich jak typy wartości (DateTime
, int
, double
itp.). Typy, które nie mogą mieć wartości null, są automatycznie traktowane jako wymagane pola.
Atrybut Required
można zastąpić parametrem o minimalnej długości w atrybucie StringLength
:
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
Atrybut Wyświetlania
Atrybut Display
określa, że podpis pól tekstowych powinien mieć wartość "Imię", "Nazwisko", "Imię", "Pełna nazwa" i "Data rejestracji". Domyślne napisy nie miały spacji dzielącej wyrazy, na przykład "Lastname".
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. FullName
Nie można go ustawić, ma tylko metodę pobierania. W bazie danych nie FullName
jest tworzona żadna kolumna.
Tworzenie jednostki instruktora
Utwórz Models/Instructor.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 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; }
}
}
Wiele atrybutów może znajdować się w jednym wierszu. Atrybuty HireDate
można napisać 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 zawiera wiele jednostek:
- Musi to być typ listy, w którym można dodawać, usuwać i aktualizować wpisy.
Typy właściwości nawigacji obejmują:
ICollection<T>
List<T>
HashSet<T>
Jeśli ICollection<T>
zostanie określony, EF Core domyślnie tworzy HashSet<T>
kolekcję.
Jednostka CourseAssignment
jest objaśniona w sekcji dotyczącej relacji wiele-do-wielu.
Reguły biznesowe firmy Contoso University stwierdzają, że instruktor może mieć co najwyżej jedno biuro. Właściwość OfficeAssignment
zawiera jedną OfficeAssignment
jednostkę. OfficeAssignment
parametr ma wartość null, jeśli nie przypisano żadnego pakietu Office.
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
Atrybut [Key]
służy do identyfikowania właściwości jako klucza podstawowego (PK), gdy nazwa właściwości jest czymś innym niż classnameID lub ID.
Istnieje relacja jeden do zera lub jednego między jednostkami Instructor
i OfficeAssignment
. Przypisanie biura istnieje tylko w odniesieniu do instruktora, do niego przypisano. Klucz OfficeAssignment
PK jest również jego kluczem obcym (FK) do Instructor
jednostki. EF Core Nie można automatycznie rozpoznać InstructorID
klucza PK z następujących powodów OfficeAssignment
:
InstructorID
nie jest zgodna z konwencją nazewnictwa id lub classnameID.
W związku z tym Key
atrybut jest używany do identyfikowania InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Domyślnie klucz jest traktowany jako niegenerowany przez bazę danych, EF Core ponieważ kolumna służy do identyfikowania relacji.
Właściwość nawigacji instruktora
Właściwość OfficeAssignment
nawigacji dla Instructor
jednostki jest dopuszczana do wartości null, ponieważ:
- Typy odwołań (takie jak klasy są dopuszczane do wartości null).
- Instruktor może nie mieć przypisania biura.
Jednostka OfficeAssignment
ma właściwość nawigacji bez wartości null Instructor
, ponieważ:
InstructorID
nie jest dopuszczana do wartości null.- Przypisanie biura nie może istnieć bez instruktora.
Instructor
Gdy jednostka ma powiązaną OfficeAssignment
jednostkę, każda jednostka ma odwołanie do drugiej jednostki we właściwości nawigacji.
Atrybut [Required]
można zastosować do Instructor
właściwości nawigacji:
[Required]
public Instructor Instructor { get; set; }
Powyższy kod określa, że musi istnieć powiązany instruktor. Powyższy kod jest niepotrzebny, ponieważ InstructorID
klucz obcy (który jest również kluczem PK) nie może zawierać wartości null.
Modyfikowanie jednostki kursu
Zaktualizuj Models/Course.cs
za pomocą następującego kodu:
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 Course
ma właściwość DepartmentID
klucza obcego (FK). DepartmentID
wskazuje powiązaną Department
jednostkę. Jednostka Course
ma Department
właściwość nawigacji.
EF Core nie wymaga właściwości FK dla modelu danych, gdy model ma właściwość nawigacji dla powiązanej jednostki.
EF Core automatycznie tworzy zestawy FKs w bazie danych wszędzie tam, gdzie są potrzebne. EF Core Tworzy właściwości w tle dla automatycznie utworzonych zestawów FKs. Posiadanie klucza FK w modelu danych może sprawić, że aktualizacje będą prostsze i bardziej wydajne. Rozważmy na przykład model, w którym właściwość DepartmentID
FK nie jest uwzględniona. Po pobraniu jednostki kursu do edycji:
- Jednostka
Department
ma wartość null, jeśli nie jest jawnie załadowana. - Aby zaktualizować jednostkę kursu,
Department
należy najpierw pobrać jednostkę.
Jeśli właściwość DepartmentID
FK jest uwzględniona w modelu danych, nie ma potrzeby pobierania Department
jednostki przed aktualizacją.
Atrybut DatabaseGenerated
Atrybut [DatabaseGenerated(DatabaseGeneratedOption.None)]
określa, że klucz PK jest dostarczany przez aplikację, a nie generowany przez bazę danych.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Domyślnie zakłada się, EF Core że wartości PK są generowane przez bazę danych. Wartości PK wygenerowane przez bazę danych są zazwyczaj najlepszym rozwiązaniem. W przypadku Course
jednostek użytkownik określa klucz PK. Na przykład numer kursu, taki jak seria 1000 dla działu matematyki, seria 2000 dla działu angielskiego.
Atrybut może być również używany do generowania DatabaseGenerated
wartości domyślnych. Na przykład baza danych może automatycznie wygenerować pole daty, aby zarejestrować datę 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 FK i Department
właściwość nawigacji.
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ą:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
jest wyjaśniony później.
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
Column
Wcześniej atrybut był używany do zmiany mapowania nazw kolumn. W kodzie Department
jednostki Column
atrybut jest używany do zmiany mapowania typu danych SQL. Kolumna jest definiowana Budget
przy użyciu typu pieniędzy programu SQL Server w bazie danych:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapowanie kolumn zwykle nie jest wymagane. EF Core zazwyczaj wybiera odpowiedni typ danych programu SQL Server na podstawie typu CLR dla właściwości . Typ CLR decimal
jest mapowy na typ programu SQL Server decimal
. Budget
jest dla waluty, a typ danych pieniężnych jest bardziej odpowiedni dla waluty.
Właściwości klucza obcego i nawigacji
Właściwości FK i nawigacji odzwierciedlają następujące relacje:
- Dział może lub nie ma administratora.
- Administrator jest zawsze instruktorem. W związku z
InstructorID
tym właściwość jest dołączana jako klucz FK doInstructor
jednostki.
Właściwość nawigacji ma nazwę Administrator
, ale zawiera Instructor
jednostkę:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Znak zapytania (?) w poprzednim kodzie określa właściwość ma wartość null.
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ą EF Core umożliwia usuwanie kaskadowe dla niepustych zestawów FKs i relacji wiele-do-wielu. Kaskadowe usuwanie może spowodować cykliczne reguły usuwania kaskadowego. Reguły usuwania kaskadowego powodują wyjątek podczas dodawania migracji.
Jeśli na przykład właściwość została zdefiniowana Department.InstructorID
jako niepusta:
EF Core Konfiguruje regułę usuwania kaskadowego w celu usunięcia działu po usunięciu instruktora.
Usunięcie działu, gdy instruktor zostanie usunięty, nie jest zamierzonym zachowaniem.
Poniższy płynny interfejs API ustawi regułę ograniczenia zamiast kaskady.
modelBuilder.Entity<Department>() .HasOne(d => d.Administrator) .WithMany() .OnDelete(DeleteBehavior.Restrict)
Powyższy kod wyłącza kaskadowe usuwanie relacji z działem instruktora.
Aktualizowanie jednostki Enrollment
Rekord rejestracji dotyczy jednego kursu podjętego przez jednego ucznia.
Zaktualizuj Models/Enrollment.cs
za pomocą następującego kodu:
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 FK i właściwości nawigacji odzwierciedlają następujące relacje:
Rekord rejestracji dotyczy jednego kursu, więc istnieje CourseID
właściwość FK i Course
właściwość nawigacji:
public int CourseID { get; set; }
public Course Course { get; set; }
Rekord rejestracji jest przeznaczony dla jednego ucznia, więc istnieje StudentID
właściwość FK i Student
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
. Jednostka Enrollment
pełni funkcję tabeli sprzężenia wiele-do-wielu z ładunkiem w bazie danych. "Z ładunkiem" oznacza, że Enrollment
tabela zawiera dodatkowe dane oprócz zestawów FKs dla tabel sprzężonych (w tym przypadku PK i Grade
).
Poniższa ilustracja przedstawia wygląd tych relacji na diagramie jednostki. (Ten diagram został wygenerowany przy użyciu polecenia Narzędzia EF Power Tools for EF 6.x. Tworzenie diagramu nie jest częścią samouczka).
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 elementy FKs (CourseID
i StudentID
). Tabela sprzężenia wiele-do-wielu bez ładunku jest czasami nazywana czystą tabelą sprzężenia (PJT).
Jednostki Instructor
i Course
mają relację wiele do wielu przy użyciu czystej tabeli sprzężenia.
Uwaga: program EF 6.x obsługuje niejawne tabele sprzężenia dla relacji wiele-do-wielu, ale EF Core nie. Aby uzyskać więcej informacji, zobacz Relacje wiele-do-wielu w EF Core wersji 2.0.
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; }
}
}
Kursy do instruktora
Relacja Instruktor-to-Courses wiele do wielu:
- Wymaga tabeli sprzężenia, która musi być reprezentowana przez zestaw jednostek.
- To czysta tabela sprzężenia (tabela bez ładunku).
Nazwa jednostki EntityName1EntityName2
sprzężenia jest pospolita. Na przykład tabela Dołączanie instruktora do kursów przy użyciu tego wzorca to CourseInstructor
. Zalecamy jednak użycie nazwy, która opisuje relację.
Modele danych zaczynają się proste i rosną. Sprzężenia no-payload (PJTs) często ewoluują w celu uwzględnienia ładunku. Zaczynając od opisowej nazwy jednostki, nazwa nie musi zmieniać się po zmianie tabeli sprzężenia. 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 z jednostką sprzężenia o nazwie Ratings. W przypadku relacji CourseAssignment
Instruktor-to-Courses wiele do wielu preferowane jest ponad CourseInstructor
.
Klucz złożony
FKs nie są dopuszczane do wartości null. Dwa zestawy FKs w CourseAssignment
elemecie (InstructorID
i CourseID
) są unikatowo identyfikowane w każdym wierszu CourseAssignment
tabeli. CourseAssignment
nie wymaga dedykowanego klucza PK. Właściwości InstructorID
i CourseID
działają jako złożony klucz PK. Jedynym sposobem określenia złożonych zestawów PKs EF Core do jest użycie płynnego interfejsu API. W następnej sekcji pokazano, jak skonfigurować złożony klucz PK.
Klucz złożony zapewnia:
- Wiele wierszy jest dozwolonych dla jednego kursu.
- Wiele wierszy jest dozwolonych dla jednego instruktora.
- Wiele wierszy dla tego samego instruktora i kursu nie jest dozwolone.
Jednostka Enrollment
sprzężenia definiuje własny klucz PK, więc możliwe są duplikaty tego rodzaju. Aby zapobiec takim duplikatom:
- Dodawanie unikatowego indeksu w polach FK lub
- Skonfiguruj
Enrollment
przy użyciu podstawowego klucza złożonego podobnego doCourseAssignment
. Aby uzyskać więcej informacji, zobacz Indeksy.
Aktualizowanie kontekstu bazy danych
Dodaj następujący wyróżniony kod do :Data/SchoolContext.cs
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Models
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollment { get; set; }
public DbSet<Student> Student { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Powyższy kod dodaje nowe jednostki i konfiguruje CourseAssignment
złożony klucz PK jednostki.
Fluent API alternatywa dla atrybutów
Metoda OnModelCreating
w poprzednim kodzie używa płynnego interfejsu API do konfigurowania EF Core zachowania. Interfejs API jest nazywany "płynnym", ponieważ jest często używany przez ciągowanie serii wywołań metod w jedną instrukcję. Poniższy kod jest przykładem płynnego interfejsu API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
W tym samouczku płynny interfejs API jest używany tylko do mapowania bazy danych, których nie można wykonać za pomocą atrybutów. Jednak płynny interfejs API może określać większość reguł formatowania, walidacji i mapowania, które można wykonać za pomocą atrybutów.
Niektóre atrybuty, takie jak MinimumLength
nie można zastosować za pomocą płynnego interfejsu API. MinimumLength
nie zmienia schematu, ale stosuje tylko regułę weryfikacji minimalnej długości.
Niektórzy deweloperzy wolą używać płynnego interfejsu API wyłącznie tak, aby mogli zachować klasy jednostek "czyste". Atrybuty i płynny interfejs API można mieszać. Istnieją pewne konfiguracje, które można wykonać tylko za pomocą płynnego interfejsu API (określając złożony klucz PK). Istnieją pewne konfiguracje, które można wykonać tylko za pomocą atrybutów (MinimumLength
). Zalecana praktyka korzystania z płynnego interfejsu API lub atrybutów:
- Wybierz jedno z tych dwóch podejść.
- Użyj wybranego podejścia spójnie, jak najwięcej.
Niektóre atrybuty używane w tym samouczku są używane w następujących celach:
- Tylko walidacja (na przykład
MinimumLength
). - EF Core tylko konfiguracja (na przykład
HasKey
). - Walidacja i EF Core konfiguracja (na przykład
[StringLength(50)]
).
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 EF Power Tools dla ukończonego modelu School.
Na powyższym diagramie przedstawiono:
- Kilka wierszy relacji jeden do wielu (od 1 do *).
- Wiersz relacji jeden do zera lub jednego (od 1 do 0,1) między jednostkami
Instructor
iOfficeAssignment
. - Wiersz relacji zero lub jeden do wielu (od 0..1 do *) między jednostkami
Instructor
iDepartment
.
Rozmieszczanie bazy danych przy użyciu danych testowych
Zaktualizuj kod w pliku Data/DbInitializer.cs
:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
// Look for any students.
if (context.Student.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};
foreach (Student s in students)
{
context.Student.Add(s);
}
context.SaveChanges();
var instructors = new Instructor[]
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
foreach (Instructor i in instructors)
{
context.Instructors.Add(i);
}
context.SaveChanges();
var departments = new Department[]
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};
foreach (Department d in departments)
{
context.Departments.Add(d);
}
context.SaveChanges();
var courses = new Course[]
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};
foreach (OfficeAssignment o in officeAssignments)
{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();
var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};
foreach (CourseAssignment ci in courseInstructors)
{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollment.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollment.Add(e);
}
}
context.SaveChanges();
}
}
}
Powyższy kod zawiera dane inicjujące dla nowych jednostek. Większość tego kodu tworzy nowe obiekty jednostek i ładuje przykładowe dane. Przykładowe dane są używane do testowania. Zobacz Enrollments
i CourseAssignments
, aby zapoznać się z przykładami liczby do wielu tabel sprzężenia, które można zainicjować.
Dodawanie migracji
Skompiluj projekt.
Add-Migration ComplexDataModel
Poprzednie polecenie wyświetla 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'
database update
Jeśli polecenie zostanie uruchomione, zostanie wygenerowany następujący błąd:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in
database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.
Stosowanie migracji
Teraz, gdy masz istniejącą bazę danych, musisz zastanowić się, jak zastosować do niej przyszłe zmiany. W tym samouczku przedstawiono dwa podejścia:
- Usuwanie i ponowne tworzenie bazy danych
- Zastosuj migrację do istniejącej bazy danych. Chociaż ta metoda jest bardziej złożona i czasochłonna, jest to preferowane podejście dla rzeczywistych środowisk produkcyjnych. Uwaga: jest to opcjonalna sekcja samouczka. Możesz wykonać kroki upuszczania i ponownego tworzenia i pominąć tę sekcję. Jeśli chcesz wykonać kroki opisane w tej sekcji, nie wykonaj czynności upuszczania i ponownego tworzenia.
Usuwanie i ponowne tworzenie bazy danych
Kod w zaktualizowanym DbInitializer
pliku dodaje dane inicjujowe dla nowych jednostek. Aby wymusić EF Core utworzenie nowej bazy danych, upuść i zaktualizować bazę danych:
W konsoli Menedżer pakietów (PMC) uruchom następujące polecenie:
Drop-Database
Update-Database
Uruchom polecenie Get-Help about_EntityFrameworkCore
z pmC, aby uzyskać informacje pomocy.
Uruchom aplikację. Uruchomienie aplikacji powoduje uruchomienie DbInitializer.Initialize
metody . Obiekt DbInitializer.Initialize
wypełnia nową bazę danych.
Otwórz bazę danych w programie SSOX:
- Jeśli system SSOX został wcześniej otwarty, kliknij przycisk Odśwież .
- Rozwiń węzeł Tabele. Zostaną wyświetlone utworzone tabele.
Zapoznaj się z tabelą CourseAssignment :
- Kliknij prawym przyciskiem myszy tabelę CourseAssignment i wybierz pozycję Wyświetl dane.
- Sprawdź, czy tabela CourseAssignment zawiera dane.
Stosowanie migracji do istniejącej bazy danych
Ta sekcja jest opcjonalna. Te kroki działają tylko wtedy, gdy pominięto poprzednią sekcję Drop i ponownie utwórz bazę danych .
Gdy migracje są uruchamiane z istniejącymi danymi, mogą istnieć ograniczenia szyfrowania FK, które nie są zadowalające z istniejących danych. W przypadku danych produkcyjnych należy wykonać kroki migracji istniejących danych. Ta sekcja zawiera przykład naprawiania naruszeń ograniczeń FK. Nie wprowadzaj tych zmian w kodzie bez tworzenia kopii zapasowej. Nie wprowadzaj tych zmian w kodzie, jeśli poprzednia sekcja została ukończona i zaktualizowana baza danych.
Plik {timestamp}_ComplexDataModel.cs
zawiera następujący kod:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Powyższy kod dodaje do Course
tabeli klucz FK bez DepartmentID
wartości null. Baza danych z poprzedniego samouczka zawiera wiersze w Course
pliku , dzięki czemu nie można zaktualizować tabeli przez migracje.
Aby migracja ComplexDataModel
działała z istniejącymi danymi:
- Zmień kod, aby nadać nowej kolumnie (
DepartmentID
) wartość domyślną. - Utwórz fałszywy dział o nazwie "Temp", aby działać jako domyślny dział.
Naprawianie ograniczeń klucza obcego
Zaktualizuj metodę ComplexDataModel
klas Up
:
- Otwórz plik
{timestamp}_ComplexDataModel.cs
. - Oznacz jako komentarz wiersz kodu, który dodaje kolumnę
DepartmentID
Course
do tabeli.
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. Nowy kod przechodzi po .CreateTable( name: "Department"
bloku:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(type: "int", nullable: true),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);
Po wykonaniu powyższych zmian istniejące Course
wiersze będą powiązane z działem "Temp" po uruchomieniu ComplexDataModel
Up
metody.
Aplikacja produkcyjna byłaby:
- Dołącz kod lub skrypty, aby dodać
Department
wiersze i powiązaneCourse
wiersze do nowychDepartment
wierszy. - Nie należy używać działu "Temp" ani wartości domyślnej dla .
Course.DepartmentID
W następnym samouczku omówiono powiązane dane.