Část 5, Razor Stránky s EF Core ASP.NET Jádrem – datový model
Tom Dykstra, Jeremy Likness a Jon P Smith
Webová aplikace Contoso University ukazuje, jak vytvářet Razor webové aplikace Pages pomocí EF Core sady Visual Studio. Informace o sérii kurzů najdete v prvním kurzu.
Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte si dokončenou aplikaci a porovnejte tento kód s tím, co jste vytvořili podle kurzu.
Předchozí kurzy pracovaly se základním datovým modelem složeným ze tří entit. V tomto kurzu:
- Přidají se další entity a relace.
- Datový model je přizpůsobený zadáním pravidel formátování, ověřování a mapování databáze.
Dokončený datový model je znázorněn na následujícím obrázku:
Následující diagram databáze byl proveden pomocí dataedo:
Vytvoření databázového diagramu pomocí Dataedo:
- Nasazení aplikace do Azure
- Stáhněte a nainstalujte dataedo do počítače.
- Postupujte podle pokynů vygenerování dokumentace ke službě Azure SQL Database za 5 minut.
V předchozím diagramu CourseInstructor
Dataedo je tabulka spojení vytvořená rozhraním Entity Framework. Další informace najdete v tématu M:N
Entita Student
Nahraďte kód v souboru Models/Student.cs
následujícím kódem:
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; }
}
}
Předchozí kód přidá FullName
vlastnost a přidá do existujících vlastností následující atributy:
Počítaná vlastnost FullName
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dvou dalších vlastností. FullName
nejde nastavit, takže má jenom přístupový objekt get. V databázi se nevytvořil žádný FullName
sloupec.
Atribut DataType
[DataType(DataType.Date)]
U dat zápisu studentů se na všech stránkách aktuálně zobrazuje čas dne spolu s datem, i když je relevantní jenom datum. Pomocíatributůch
Atribut DataType určuje datový typ, který je konkrétnější než vnitřní typ databáze. V tomto případě by se mělo zobrazit jenom datum, nikoli datum a čas. Výčet DataType poskytuje mnoho datových typů, jako je datum, čas, telefonní číslo, měna, EmailAddress atd. Atribut DataType
může také aplikaci povolit, aby automaticky poskytovala funkce specifické pro typ. Příklad:
- Odkaz
mailto:
se automaticky vytvoří proDataType.EmailAddress
. - Ve většině prohlížečů je k dispozici
DataType.Date
selektor data.
Atribut DataType
generuje atributy HTML 5 data-
(vyslovuje se pomlčka dat). Atributy DataType
neposkytují ověření.
Atribut DisplayFormat
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
nezadá formát zobrazeného data. Ve výchozím nastavení se pole data zobrazí podle výchozích formátů založených na souboru CultureInfo serveru.
Atribut DisplayFormat
se používá k explicitní zadání formátu data. Nastavení ApplyFormatInEditMode
určuje, že formátování se má použít také v uživatelském rozhraní pro úpravy. Některá pole by neměla používat ApplyFormatInEditMode
. Symbol měny by se například neměl zobrazovat v textovém poli pro úpravy.
Atribut DisplayFormat
lze použít sám. Obecně je vhodné použít DataType
atribut s atributem DisplayFormat
. Atribut DataType
vyjadřuje sémantiku dat na rozdíl od toho, jak je vykreslit na obrazovce. Atribut DataType
poskytuje následující výhody, které nejsou k dispozici v DisplayFormat
:
- Prohlížeč může povolit funkce HTML5. Můžete například zobrazit ovládací prvek kalendáře, symbol měny odpovídající národnímu prostředí, e-mailové odkazy a ověření vstupu na straně klienta.
- Ve výchozím nastavení prohlížeč vykreslí data pomocí správného formátu na základě národního prostředí.
Další informace najdete v dokumentaci ke vstupním> pomocným <rutině značek.
Atribut StringLength
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Pomocí atributů je možné zadat pravidla ověření dat a chybové zprávy ověřování. Atribut StringLength určuje minimální a maximální délku znaků, které jsou povoleny v datovém poli. Zobrazený kód omezuje názvy maximálně na 50 znaků. Příklad, který nastaví minimální délku řetězce, se zobrazí později.
Atribut StringLength
také poskytuje ověřování na straně klienta a na straně serveru. Minimální hodnota nemá žádný vliv na schéma databáze.
Atribut StringLength
nezabrání uživateli v zadávání prázdných znaků pro jméno. Atribut RegularExpression lze použít k použití omezení pro vstup. Například následující kód vyžaduje, aby první znak byl velkým písmenem a zbývající znaky byly abecední:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
V SQL Serveru Průzkumník objektů (SSOX) otevřete návrháře tabulek Student poklikáním na tabulku Student.
Předchozí obrázek znázorňuje schéma tabulky Student
. Pole názvů mají typ nvarchar(MAX)
. Při vytvoření a použití migrace později v tomto kurzu se pole názvů stanou nvarchar(50)
výsledkem atributů délky řetězce.
Atribut Column
[Column("FirstName")]
public string FirstMidName { get; set; }
Atributy mohou řídit, jak se třídy a vlastnosti mapují na databázi. Student
V modelu Column
se atribut používá k mapování názvu FirstMidName
vlastnosti na "FirstName" v databázi.
Při vytváření databáze se názvy vlastností v modelu používají pro názvy sloupců (s výjimkou použití atributu Column
). Model Student
používá FirstMidName
pro pole s křestní jméno, protože pole může obsahovat také prostřední název.
S atributem [Column]
Student.FirstMidName
se v datovém modelu mapuje na FirstName
sloupec Student
tabulky. Přidání atributu Column
změní model backing the SchoolContext
. Model, který SchoolContext
už databázi nepodporuje, neodpovídá. Tento nesrovnalosti vyřešíte přidáním migrace později v tomto kurzu.
Požadovaný atribut
[Required]
Atribut Required
vytvoří požadované vlastnosti názvu. Atribut Required
není potřeba pro typy bez hodnoty null, jako jsou například typy hodnot (například DateTime
, int
a double
). Typy, které nemohou být null, se automaticky považují za povinná pole.
Atribut Required
musí být použit s vynuceným vynuceným atributem MinimumLength
MinimumLength
.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength
a Required
povolte, aby ověření splňovalo prázdné znaky. RegularExpression
Atribut použijte pro úplnou kontrolu nad řetězcem.
Atribut Display
[Display(Name = "Last Name")]
Atribut Display
určuje, že titulek textových polí by měl být "Jméno", "Příjmení", "Celé jméno" a "Datum registrace". Výchozí titulky neměly žádné mezery, které by dělily slova, například "Příjmení".
Vytvoření migrace
Spusťte aplikaci a přejděte na stránku Studenti. Vyvolá se výjimka. Atribut [Column]
způsobí, že EF očekává nalezení sloupce s názvem FirstName
, ale název sloupce v databázi je stále FirstMidName
.
Chybová zpráva je podobná následujícímu příkladu:
SqlException: Invalid column name 'FirstName'.
There are pending model changes
Pending model changes are detected in the following:
SchoolContext
V PMC zadejte následující příkazy pro vytvoření nové migrace a aktualizaci databáze:
Add-Migration ColumnFirstName Update-Database
První z těchto příkazů vygeneruje následující zprávu upozornění:
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Upozornění se vygeneruje, protože pole názvů jsou teď omezená na 50 znaků. Pokud název v databázi obsahoval více než 50 znaků, ztratí se 51 až poslední znak.
Otevřete tabulku Student v SSOX:
Před použitím migrace byly sloupce názvů typu nvarchar(MAX). Sloupce názvů jsou nyní
nvarchar(50)
. Název sloupce se změnil zFirstMidName
naFirstName
.
- Spusťte aplikaci a přejděte na stránku Studenti.
- Všimněte si, že časy nejsou vstupní ani zobrazené spolu s kalendářními daty.
- Vyberte Vytvořit nový a zkuste zadat název delší než 50 znaků.
Poznámka:
V následujících částech sestavení aplikace v některých fázích generuje chyby kompilátoru. Pokyny určují, kdy se má aplikace sestavit.
Entita instruktora
Vytvořte Models/Instructor.cs
pomocí následujícího kódu:
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; }
}
}
Více atributů může být na jednom řádku. Atributy HireDate
lze zapsat takto:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Vlastnosti navigace
Vlastnosti Courses
a OfficeAssignment
vlastnosti jsou navigační vlastnosti.
Instruktor může učit libovolný počet kurzů, takže Courses
je definován jako kolekce.
public ICollection<Course> Courses { get; set; }
Instruktor může mít maximálně jednu kancelář, takže OfficeAssignment
vlastnost obsahuje jednu OfficeAssignment
entitu. OfficeAssignment
má hodnotu null, pokud není přiřazena žádná kancelář.
public OfficeAssignment OfficeAssignment { get; set; }
Entita OfficeAssignment
Vytvořte Models/OfficeAssignment.cs
pomocí následujícího kódu:
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; }
}
}
Atribut Klíč
Atribut [Key]
slouží k identifikaci vlastnosti jako primárního klíče (PK), pokud je název vlastnosti něco jiného než classnameID
nebo ID
.
Mezi entitami Instructor
OfficeAssignment
existuje vztah 1:1 nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, ke kterému je přiřazený. Pk OfficeAssignment
je také jeho cizí klíč (FK) pro entitu Instructor
. Relace 1:0 nebo 1 nastane v případě, že pk v jedné tabulce je PK i FK v jiné tabulce.
EF Core nemůže automaticky rozpoznat InstructorID
jako PK, OfficeAssignment
protože InstructorID
neodpovídá konvenci pojmenování ID nebo classnameID. Proto se Key
atribut používá k identifikaci InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Ve výchozím nastavení považuje klíč za negenerovaný databází, EF Core protože sloupec slouží k identifikaci relace. Další informace najdete v tématu Klíče EF.
Vlastnost navigace instruktora
Vlastnost Instructor.OfficeAssignment
navigace může mít hodnotu null, protože pro daného instruktora nemusí být OfficeAssignment
řádek. Instruktor nemusí mít zadání kanceláře.
Vlastnost OfficeAssignment.Instructor
navigace bude mít vždy entitu instruktora, protože typ cizího klíče InstructorID
je int
, typ hodnoty, který není null. Zadání kanceláře nemůže existovat bez instruktora.
Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita má ve své navigační vlastnosti odkaz na druhou entitu.
Entita kurzu
Aktualizujte Models/Course.cs
následujícím kódem:
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; }
}
}
Entita Course
má vlastnost DepartmentID
cizího klíče (FK). DepartmentID
odkazuje na související Department
entitu. Entita Course
má Department
navigační vlastnost.
EF Core nevyžaduje vlastnost cizího klíče pro datový model, pokud má model navigační vlastnost pro související entitu. EF Core v databázi automaticky vytváří sady FK, kdekoli jsou potřeba. EF Core vytvoří stínové vlastnosti pro automaticky vytvořené sady FK. Explicitní zahrnutí FK do datového modelu však může zjednodušit a zefektivnit aktualizace. Představte si například model, ve kterém není zahrnuta vlastnost DepartmentID
FK. Když se entita kurzu načte pro úpravy:
- Vlastnost
Department
jenull
, pokud není explicitně načtena. - Chcete-li aktualizovat entitu kurzu, musí být entita
Department
nejprve načtena.
Pokud je vlastnost DepartmentID
FK součástí datového modelu, není nutné před aktualizací načíst entitu Department
.
Atribut DatabaseGenerated
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
určuje, že PK je poskytována aplikací místo generování databáze.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení se předpokládá, EF Core že databáze generuje hodnoty PK. Vygenerovaná databáze je obecně nejlepší přístup. Pro Course
entity určuje uživatel PK. Například číslo kurzu, například 1000 řad pro matematické oddělení, 2000 série pro anglické oddělení.
Atribut DatabaseGenerated
lze také použít k vygenerování výchozích hodnot. Databáze může například automaticky vygenerovat pole kalendářního data pro zaznamenání data vytvoření nebo aktualizace řádku. Další informace naleznete v tématu Vygenerované vlastnosti.
Vlastnosti cizího klíče a navigace
Vlastnosti cizího klíče (FK) a vlastnosti navigace v entitě Course
odrážejí následující relace:
Kurz je přiřazen k jednomu oddělení, takže je k DepartmentID
dispozici FK a Department
navigační vlastnost.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kurz může mít v něm zaregistrovaný libovolný počet studentů, takže Enrollments
navigační vlastnost je kolekce:
public ICollection<Enrollment> Enrollments { get; set; }
Kurz může učit více instruktorů, takže Instructors
navigační vlastnost je kolekce:
public ICollection<Instructor> Instructors { get; set; }
Entita Oddělení
Vytvořte Models/Department.cs
pomocí následujícího kódu:
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; }
}
}
Atribut Column
Dříve byl Column
atribut použit ke změně mapování názvů sloupců. V kódu entity Column
se atribut používá ke změně mapování datového Department
typu SQL. Sloupec Budget
se definuje pomocí typu peněz SQL Serveru v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obecně nevyžaduje. EF Core zvolí odpovídající datový typ SQL Serveru na základě typu CLR pro vlastnost. Typ CLR decimal
se mapuje na typ SQL Serveru decimal
. Budget
je pro měnu a datový typ peněz je vhodnější pro měnu.
Vlastnosti cizího klíče a navigace
Vlastnosti FK a navigace odrážejí následující relace:
- Oddělení může nebo nemusí mít správce.
- Správce je vždy instruktor.
InstructorID
Proto je vlastnost zahrnuta jako FK entityInstructor
.
Navigační vlastnost je pojmenovaná Administrator
, ale obsahuje entitu Instructor
:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
V ?
předchozím kódu určuje vlastnost nullable.
Oddělení může mít mnoho kurzů, takže je zde vlastnost Navigace v kurzech:
public ICollection<Course> Courses { get; set; }
Podle konvence EF Core umožňuje kaskádové odstranění pro nenulové FK a pro relace M:N. Toto výchozí chování může vést k cyklický kaskádový odstranění pravidel. Cyklické kaskádové odstranění pravidel způsobí výjimku při přidání migrace.
Pokud Department.InstructorID
byla například vlastnost definována jako nenulová, EF Core nakonfigurovala by kaskádové pravidlo odstranění. V takovém případě by se oddělení odstranilo, když se odstraní instruktor přiřazený jako správce. V tomto scénáři by pravidlo omezení dávalo větší smysl. Následující fluentové rozhraní API by nastavilo pravidlo omezení a zakázalo kaskádové odstranění.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Vlastnosti cizího klíče zápisu a navigace
Záznam o registraci je určen pro jeden kurz pořízený jedním studentem.
Aktualizujte Models/Enrollment.cs
následujícím kódem:
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; }
}
}
Vlastnosti FK a navigační vlastnosti odrážejí následující relace:
Záznam registrace je pro jeden kurz, takže vlastnost CourseID
FK a Course
navigační vlastnost:
public int CourseID { get; set; }
public Course Course { get; set; }
Záznam registrace je určený pro jednoho studenta, takže vlastnost StudentID
FK a Student
navigační vlastnost:
public int StudentID { get; set; }
public Student Student { get; set; }
Relace M:N
Mezi entitami Student
Course
a entitami existuje vztah M:N. Entita Enrollment
funguje jako tabulka spojení M:N s datovou částí v databázi. Datová část znamená, že Enrollment
tabulka obsahuje další data kromě FK pro spojené tabulky. V entitě Enrollment
jsou další data kromě FK PK a Grade
.
Následující obrázek znázorňuje, jak tyto relace vypadají v diagramu entit. (Tento diagram byl generován pomocí EF Power Tools pro EF 6.x. Vytvoření diagramu není součástí kurzu.)
Každá čára relace má na jednom konci 1 a hvězdičku (*) na druhé, která označuje relaci 1:N.
Enrollment
Pokud tabulka neobsahuje informace o známce, musí obsahovat pouze dvě sady FK CourseID
a StudentID
. Tabulka spojení M:N bez datové části se někdy označuje jako čistá join table (PJT).
Course
Entity Instructor
mají relaci M:N pomocí PJT.
Aktualizace kontextu databáze
Aktualizujte Data/SchoolContext.cs
následujícím kódem:
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));
}
}
}
Předchozí kód přidá nové entity a nakonfiguruje vztah M:N mezi entitami Instructor
a Course
entitami.
Alternativa rozhraní Fluent API k atributům
Metoda OnModelCreating
v předchozím kódu používá rozhraní API fluent ke konfiguraci EF Core chování. Rozhraní API se nazývá "fluent", protože se často používá řetězcem řady volání metod do jednoho příkazu. Následující kód je příkladem rozhraní FLUENT API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
V tomto kurzu se rozhraní API fluent používá pouze pro mapování databáze, které se nedají provádět s atributy. Rozhraní API fluent však může určovat většinu pravidel formátování, ověřování a mapování, která se dají provádět s atributy.
Některé atributy, jako MinimumLength
je například nelze použít s rozhraním FLUENT API. MinimumLength
nezmění schéma, použije pouze ověřovací pravidlo minimální délky.
Někteří vývojáři raději používají rozhraní API fluent výhradně, aby mohli třídy entit udržovat čisté. Atributy a fluentské rozhraní API je možné kombinovat. Existují některé konfigurace, které je možné provádět pouze s rozhraním FLUENT API, například zadáním složené infrastruktury veřejných klíčů. Existují některé konfigurace, které je možné provádět pouze s atributy (MinimumLength
). Doporučený postup pro používání rozhraní API nebo atributů fluent:
- Zvolte jeden z těchto dvou přístupů.
- Zvolený přístup používejte co nejvíce konzistentně.
Některé atributy použité v tomto kurzu se používají pro:
- Pouze ověřování (například
MinimumLength
). - EF Core pouze konfigurace (například
HasKey
). - Ověřování a EF Core konfigurace (například
[StringLength(50)]
).
Další informace o atributech a rozhraní API fluent naleznete v tématu Metody konfigurace.
Počáteční hodnota databáze
Aktualizujte kód v 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();
}
}
}
Předchozí kód poskytuje počáteční data pro nové entity. Většina tohoto kódu vytvoří nové objekty entity a načte ukázková data. Ukázková data se používají k testování.
Použití migrace nebo vyřazení a opětovné vytvoření
U existující databáze existují dva přístupy ke změně databáze:
- Odstraňte databázi a znovu ji vytvořte. Tuto část zvolte při použití SQLite.
- Použijte migraci na existující databázi. Pokyny v této části fungují jenom pro SQL Server, ne pro SQLite.
Obě možnosti fungují pro SQL Server. I když je metoda apply-migration složitější a časově náročná, je to upřednostňovaný přístup pro skutečná produkční prostředí.
Odstranění a opětovné vytvoření databáze
Pokud chcete vynutit EF Core vytvoření nové databáze, odstraňte a aktualizujte databázi:
- Odstraňte složku Migrace.
- V konzole Správce balíčků (PMC) spusťte následující příkazy:
Drop-Database
Add-Migration InitialCreate
Update-Database
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
Otevřete databázi v SSOX:
- Pokud jste SSOX otevřeli dříve, klikněte na tlačítko Aktualizovat .
- Rozbalte uzel Tabulky. Zobrazí se vytvořené tabulky.
Další kroky
V dalších dvou kurzech se dozvíte, jak číst a aktualizovat související data.
Předchozí kurzy pracovaly se základním datovým modelem složeným ze tří entit. V tomto kurzu:
- Přidají se další entity a relace.
- Datový model je přizpůsobený zadáním pravidel formátování, ověřování a mapování databáze.
Dokončený datový model je znázorněn na následujícím obrázku:
Entita Student
Nahraďte kód v souboru Models/Student.cs
následujícím kódem:
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; }
}
}
Předchozí kód přidá FullName
vlastnost a přidá do existujících vlastností následující atributy:
[DataType]
[DisplayFormat]
[StringLength]
[Column]
[Required]
[Display]
Počítaná vlastnost FullName
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dvou dalších vlastností. FullName
nejde nastavit, takže má jenom přístupový objekt get. V databázi se nevytvořil žádný FullName
sloupec.
Atribut DataType
[DataType(DataType.Date)]
U dat zápisu studentů se na všech stránkách aktuálně zobrazuje čas dne spolu s datem, i když je relevantní jenom datum. Pomocíatributůch
Atribut DataType určuje datový typ, který je konkrétnější než vnitřní typ databáze. V tomto případě by se mělo zobrazit jenom datum, nikoli datum a čas. Výčet DataType poskytuje mnoho datových typů, jako je datum, čas, telefonní číslo, měna, EmailAddress atd. Atribut DataType
může také aplikaci povolit, aby automaticky poskytovala funkce specifické pro typ. Příklad:
- Odkaz
mailto:
se automaticky vytvoří proDataType.EmailAddress
. - Ve většině prohlížečů je k dispozici
DataType.Date
selektor data.
Atribut DataType
generuje atributy HTML 5 data-
(vyslovuje se pomlčka dat). Atributy DataType
neposkytují ověření.
Atribut DisplayFormat
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
nezadá formát zobrazeného data. Ve výchozím nastavení se pole data zobrazí podle výchozích formátů založených na souboru CultureInfo serveru.
Atribut DisplayFormat
se používá k explicitní zadání formátu data. Nastavení ApplyFormatInEditMode
určuje, že formátování se má použít také v uživatelském rozhraní pro úpravy. Některá pole by neměla používat ApplyFormatInEditMode
. Symbol měny by se například neměl zobrazovat v textovém poli pro úpravy.
Atribut DisplayFormat
lze použít sám. Obecně je vhodné použít DataType
atribut s atributem DisplayFormat
. Atribut DataType
vyjadřuje sémantiku dat na rozdíl od toho, jak je vykreslit na obrazovce. Atribut DataType
poskytuje následující výhody, které nejsou k dispozici v DisplayFormat
:
- Prohlížeč může povolit funkce HTML5. Můžete například zobrazit ovládací prvek kalendáře, symbol měny odpovídající národnímu prostředí, e-mailové odkazy a ověření vstupu na straně klienta.
- Ve výchozím nastavení prohlížeč vykreslí data pomocí správného formátu na základě národního prostředí.
Další informace najdete v dokumentaci ke vstupním> pomocným <rutině značek.
Atribut StringLength
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Pomocí atributů je možné zadat pravidla ověření dat a chybové zprávy ověřování. Atribut StringLength určuje minimální a maximální délku znaků, které jsou povoleny v datovém poli. Zobrazený kód omezuje názvy maximálně na 50 znaků. Příklad, který nastaví minimální délku řetězce, se zobrazí později.
Atribut StringLength
také poskytuje ověřování na straně klienta a na straně serveru. Minimální hodnota nemá žádný vliv na schéma databáze.
Atribut StringLength
nezabrání uživateli v zadávání prázdných znaků pro jméno. Atribut RegularExpression lze použít k použití omezení pro vstup. Například následující kód vyžaduje, aby první znak byl velkým písmenem a zbývající znaky byly abecední:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
V SQL Serveru Průzkumník objektů (SSOX) otevřete návrháře tabulek Student poklikáním na tabulku Student.
Předchozí obrázek znázorňuje schéma tabulky Student
. Pole názvů mají typ nvarchar(MAX)
. Při vytvoření a použití migrace později v tomto kurzu se pole názvů stanou nvarchar(50)
výsledkem atributů délky řetězce.
Atribut Column
[Column("FirstName")]
public string FirstMidName { get; set; }
Atributy mohou řídit, jak se třídy a vlastnosti mapují na databázi. Student
V modelu Column
se atribut používá k mapování názvu FirstMidName
vlastnosti na "FirstName" v databázi.
Při vytváření databáze se názvy vlastností v modelu používají pro názvy sloupců (s výjimkou použití atributu Column
). Model Student
používá FirstMidName
pro pole s křestní jméno, protože pole může obsahovat také prostřední název.
S atributem [Column]
Student.FirstMidName
se v datovém modelu mapuje na FirstName
sloupec Student
tabulky. Přidání atributu Column
změní model backing the SchoolContext
. Model, který SchoolContext
už databázi nepodporuje, neodpovídá. Tento nesrovnalosti vyřešíte přidáním migrace později v tomto kurzu.
Požadovaný atribut
[Required]
Atribut Required
vytvoří požadované vlastnosti názvu. Atribut Required
není potřeba pro typy bez hodnoty null, jako jsou například typy hodnot (například DateTime
, int
a double
). Typy, které nemohou být null, se automaticky považují za povinná pole.
Atribut Required
musí být použit s vynuceným vynuceným atributem MinimumLength
MinimumLength
.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength
a Required
povolte, aby ověření splňovalo prázdné znaky. RegularExpression
Atribut použijte pro úplnou kontrolu nad řetězcem.
Atribut Display
[Display(Name = "Last Name")]
Atribut Display
určuje, že titulek textových polí by měl být "Jméno", "Příjmení", "Celé jméno" a "Datum registrace". Výchozí titulky neměly žádné mezery, které by dělily slova, například "Příjmení".
Vytvoření migrace
Spusťte aplikaci a přejděte na stránku Studenti. Vyvolá se výjimka. Atribut [Column]
způsobí, že EF očekává nalezení sloupce s názvem FirstName
, ale název sloupce v databázi je stále FirstMidName
.
Chybová zpráva je podobná následujícímu příkladu:
SqlException: Invalid column name 'FirstName'.
V PMC zadejte následující příkazy pro vytvoření nové migrace a aktualizaci databáze:
Add-Migration ColumnFirstName Update-Database
První z těchto příkazů vygeneruje následující zprávu upozornění:
An operation was scaffolded that may result in the loss of data. Please review the migration for accuracy.
Upozornění se vygeneruje, protože pole názvů jsou teď omezená na 50 znaků. Pokud název v databázi obsahoval více než 50 znaků, ztratí se 51 až poslední znak.
Otevřete tabulku Student v SSOX:
Před použitím migrace byly sloupce názvů typu nvarchar(MAX). Sloupce názvů jsou nyní
nvarchar(50)
. Název sloupce se změnil zFirstMidName
naFirstName
.
- Spusťte aplikaci a přejděte na stránku Studenti.
- Všimněte si, že časy nejsou vstupní ani zobrazené spolu s kalendářními daty.
- Vyberte Vytvořit nový a zkuste zadat název delší než 50 znaků.
Poznámka:
V následujících částech sestavení aplikace v některých fázích generuje chyby kompilátoru. Pokyny určují, kdy se má aplikace sestavit.
Entita instruktora
Vytvořte Models/Instructor.cs
pomocí následujícího kódu:
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; }
}
}
Více atributů může být na jednom řádku. Atributy HireDate
lze zapsat takto:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Vlastnosti navigace
Vlastnosti CourseAssignments
a OfficeAssignment
vlastnosti jsou navigační vlastnosti.
Instruktor může učit libovolný počet kurzů, takže CourseAssignments
je definován jako kolekce.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Instruktor může mít maximálně jednu kancelář, takže OfficeAssignment
vlastnost obsahuje jednu OfficeAssignment
entitu. OfficeAssignment
má hodnotu null, pokud není přiřazena žádná kancelář.
public OfficeAssignment OfficeAssignment { get; set; }
Entita OfficeAssignment
Vytvořte Models/OfficeAssignment.cs
pomocí následujícího kódu:
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; }
}
}
Atribut Klíč
Atribut [Key]
slouží k identifikaci vlastnosti jako primárního klíče (PK), pokud název vlastnosti je něco jiného než classnameID nebo ID.
Mezi entitami Instructor
OfficeAssignment
existuje vztah 1:1 nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, ke kterému je přiřazený. Pk OfficeAssignment
je také jeho cizí klíč (FK) pro entitu Instructor
.
EF Core nemůže automaticky rozpoznat InstructorID
jako PK, OfficeAssignment
protože InstructorID
neodpovídá konvenci pojmenování ID nebo classnameID. Proto se Key
atribut používá k identifikaci InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Ve výchozím nastavení považuje klíč za negenerovaný databází, EF Core protože sloupec slouží k identifikaci relace.
Vlastnost navigace instruktora
Vlastnost Instructor.OfficeAssignment
navigace může mít hodnotu null, protože pro daného instruktora nemusí být OfficeAssignment
řádek. Instruktor nemusí mít zadání kanceláře.
Vlastnost OfficeAssignment.Instructor
navigace bude mít vždy entitu instruktora, protože typ cizího klíče InstructorID
je int
, typ hodnoty, který není null. Zadání kanceláře nemůže existovat bez instruktora.
Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita má ve své navigační vlastnosti odkaz na druhou entitu.
Entita kurzu
Aktualizujte Models/Course.cs
následujícím kódem:
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; }
}
}
Entita Course
má vlastnost DepartmentID
cizího klíče (FK). DepartmentID
odkazuje na související Department
entitu. Entita Course
má Department
navigační vlastnost.
EF Core nevyžaduje vlastnost cizího klíče pro datový model, pokud má model navigační vlastnost pro související entitu. EF Core v databázi automaticky vytváří sady FK, kdekoli jsou potřeba. EF Core vytvoří stínové vlastnosti pro automaticky vytvořené sady FK. Explicitní zahrnutí FK do datového modelu však může zjednodušit a zefektivnit aktualizace. Představte si například model, ve kterém není zahrnuta vlastnost DepartmentID
FK. Když se entita kurzu načte pro úpravy:
- Vlastnost
Department
má hodnotu null, pokud není explicitně načtena. - Chcete-li aktualizovat entitu kurzu, musí být entita
Department
nejprve načtena.
Pokud je vlastnost DepartmentID
FK součástí datového modelu, není nutné před aktualizací načíst entitu Department
.
Atribut DatabaseGenerated
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
určuje, že PK je poskytována aplikací místo generování databáze.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení se předpokládá, EF Core že databáze generuje hodnoty PK. Vygenerovaná databáze je obecně nejlepší přístup. Pro Course
entity určuje uživatel PK. Například číslo kurzu, například 1000 řad pro matematické oddělení, 2000 série pro anglické oddělení.
Atribut DatabaseGenerated
lze také použít k vygenerování výchozích hodnot. Databáze může například automaticky vygenerovat pole kalendářního data pro zaznamenání data vytvoření nebo aktualizace řádku. Další informace naleznete v tématu Vygenerované vlastnosti.
Vlastnosti cizího klíče a navigace
Vlastnosti cizího klíče (FK) a vlastnosti navigace v entitě Course
odrážejí následující relace:
Kurz je přiřazen k jednomu oddělení, takže je k DepartmentID
dispozici FK a Department
navigační vlastnost.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kurz může mít v něm zaregistrovaný libovolný počet studentů, takže Enrollments
navigační vlastnost je kolekce:
public ICollection<Enrollment> Enrollments { get; set; }
Kurz může učit více instruktorů, takže CourseAssignments
navigační vlastnost je kolekce:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
je vysvětleno později.
Entita Oddělení
Vytvořte Models/Department.cs
pomocí následujícího kódu:
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; }
}
}
Atribut Column
Dříve byl Column
atribut použit ke změně mapování názvů sloupců. V kódu entity Column
se atribut používá ke změně mapování datového Department
typu SQL. Sloupec Budget
se definuje pomocí typu peněz SQL Serveru v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obecně nevyžaduje. EF Core zvolí odpovídající datový typ SQL Serveru na základě typu CLR pro vlastnost. Typ CLR decimal
se mapuje na typ SQL Serveru decimal
. Budget
je pro měnu a datový typ peněz je vhodnější pro měnu.
Vlastnosti cizího klíče a navigace
Vlastnosti FK a navigace odrážejí následující relace:
- Oddělení může nebo nemusí mít správce.
- Správce je vždy instruktor.
InstructorID
Proto je vlastnost zahrnuta jako FK entityInstructor
.
Navigační vlastnost je pojmenovaná Administrator
, ale obsahuje entitu Instructor
:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Otazník (?) v předchozím kódu určuje vlastnost null.
Oddělení může mít mnoho kurzů, takže je zde vlastnost Navigace v kurzech:
public ICollection<Course> Courses { get; set; }
Podle konvence EF Core umožňuje kaskádové odstranění pro nenulové FK a pro relace M:N. Toto výchozí chování může vést k cyklický kaskádový odstranění pravidel. Cyklické kaskádové odstranění pravidel způsobí výjimku při přidání migrace.
Pokud Department.InstructorID
byla například vlastnost definována jako nenulová, EF Core nakonfigurovala by kaskádové pravidlo odstranění. V takovém případě by se oddělení odstranilo, když se odstraní instruktor přiřazený jako správce. V tomto scénáři by pravidlo omezení dávalo větší smysl. Následující fluentové rozhraní API by nastavilo pravidlo omezení a zakázalo kaskádové odstranění.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Entita Registrace
Záznam o registraci je určen pro jeden kurz pořízený jedním studentem.
Aktualizujte Models/Enrollment.cs
následujícím kódem:
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; }
}
}
Vlastnosti cizího klíče a navigace
Vlastnosti FK a navigační vlastnosti odrážejí následující relace:
Záznam registrace je pro jeden kurz, takže vlastnost CourseID
FK a Course
navigační vlastnost:
public int CourseID { get; set; }
public Course Course { get; set; }
Záznam registrace je určený pro jednoho studenta, takže vlastnost StudentID
FK a Student
navigační vlastnost:
public int StudentID { get; set; }
public Student Student { get; set; }
Relace M:N
Mezi entitami Student
Course
a entitami existuje vztah M:N. Entita Enrollment
funguje jako tabulka spojení M:N s datovou částí v databázi. "S datovou částí" znamená, že Enrollment
tabulka obsahuje další data kromě FK pro spojené tabulky (v tomto případě PK a Grade
).
Následující obrázek znázorňuje, jak tyto relace vypadají v diagramu entit. (Tento diagram byl generován pomocí EF Power Tools pro EF 6.x. Vytvoření diagramu není součástí kurzu.)
Každá čára relace má na jednom konci 1 a hvězdičku (*) na druhé, která označuje relaci 1:N.
Enrollment
Pokud tabulka neobsahuje informace o známce, musí obsahovat pouze dvě sady FK (CourseID
aStudentID
). Tabulka spojení M:N bez datové části se někdy označuje jako čistá join table (PJT).
Course
Entity Instructor
mají relaci M:N pomocí tabulky s čistým spojením.
Poznámka: EF 6.x podporuje implicitní spojení tabulek pro relace M:N, ale EF Core ne. Další informace naleznete v tématu Relace M:N ve EF Core verzi 2.0.
Entita CourseAssignment
Vytvořte Models/CourseAssignment.cs
pomocí následujícího kódu:
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; }
}
}
Relace M:N pro instruktora vyžaduje tabulku spojení a entita pro tuto tabulku spojení je CourseAssignment.
Je běžné pojmenovat entitu EntityName1EntityName2
spojení . Například tabulka spojení Instruktor-to-Courses pomocí tohoto vzoru by byla CourseInstructor
. Doporučujeme ale použít název, který popisuje relaci.
Datové modely začínají jednoduchým a růstem. Spojení tabulek bez datové části (PJT) se často vyvíjí tak, aby zahrnovaly datovou část. Když začnete s popisným názvem entity, název se při změně tabulky spojení nemusí měnit. V ideálním případě by entita spojení měla v obchodní doméně svůj vlastní přirozený (případně jednoslovný) název. Knihy a zákazníci můžou být například propojeny s entitou spojení s názvem Hodnocení. Pro vztah CourseAssignment
typu Instruktor-to-Courses M:N je preferován před CourseInstructor
.
Složený klíč
Oba sady FK v CourseAssignment
tabulce (InstructorID
a CourseID
) společně jednoznačně identifikují každý řádek CourseAssignment
tabulky. CourseAssignment
nevyžaduje vyhrazenou pk. Funkce InstructorID
a CourseID
vlastnosti slouží jako složená infrastruktura veřejných klíčů. Jediným způsobem, jak zadat složené sady PK, je EF Core rozhraní FLUENT API. V další části se dozvíte, jak nakonfigurovat složenou pk.
Složený klíč zajistí, že:
- Pro jeden kurz je povoleno více řádků.
- Pro jednoho instruktora je povoleno více řádků.
- Pro stejného instruktora a kurz není povoleno více řádků.
Entita Enrollment
join definuje vlastní PK, takže duplicitní položky tohoto typu jsou možné. Chcete-li těmto duplicitám zabránit:
- Přidání jedinečného indexu do polí FK nebo
- Nakonfigurujte
Enrollment
primární složený klíč podobnýCourseAssignment
. Další informace najdete v tématu Indexy.
Aktualizace kontextu databáze
Aktualizujte Data/SchoolContext.cs
následujícím kódem:
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 });
}
}
}
Předchozí kód přidá nové entity a nakonfiguruje CourseAssignment
složenou PK entity.
Alternativa rozhraní Fluent API k atributům
Metoda OnModelCreating
v předchozím kódu používá rozhraní API fluent ke konfiguraci EF Core chování. Rozhraní API se nazývá "fluent", protože se často používá řetězcem řady volání metod do jednoho příkazu. Následující kód je příkladem rozhraní FLUENT API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
V tomto kurzu se rozhraní API fluent používá pouze pro mapování databáze, které se nedají provádět s atributy. Rozhraní API fluent však může určovat většinu pravidel formátování, ověřování a mapování, která se dají provádět s atributy.
Některé atributy, jako MinimumLength
je například nelze použít s rozhraním FLUENT API. MinimumLength
nezmění schéma, použije pouze ověřovací pravidlo minimální délky.
Někteří vývojáři raději používají výhradně rozhraní API fluent, aby mohli třídy entit udržovat "čisté". Atributy a fluentské rozhraní API je možné kombinovat. Existují některé konfigurace, které je možné provádět pouze s rozhraním FLUENT API (určením složené pk). Existují některé konfigurace, které je možné provádět pouze s atributy (MinimumLength
). Doporučený postup pro používání rozhraní API nebo atributů fluent:
- Zvolte jeden z těchto dvou přístupů.
- Zvolený přístup používejte co nejvíce konzistentně.
Některé atributy použité v tomto kurzu se používají pro:
- Pouze ověřování (například
MinimumLength
). - EF Core pouze konfigurace (například
HasKey
). - Ověřování a EF Core konfigurace (například
[StringLength(50)]
).
Další informace o atributech a rozhraní API fluent naleznete v tématu Metody konfigurace.
Diagram entit
Následující obrázek znázorňuje diagram, který nástroje EF Power Tools vytvoří pro dokončený školní model.
Předchozí diagram znázorňuje:
- Několik čar relací 1:N (1 až *).
- Čára relace 1:nula nebo 1 (1 až 0.1) mezi entitami
Instructor
aOfficeAssignment
entitami. - Čára relace nula nebo 1:N (0..1 až *) mezi entitami
Instructor
aDepartment
entitami.
Počáteční hodnota databáze
Aktualizujte kód v 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();
}
}
}
Předchozí kód poskytuje počáteční data pro nové entity. Většina tohoto kódu vytvoří nové objekty entity a načte ukázková data. Ukázková data se používají k testování. Podívejte Enrollments
se na CourseAssignments
příklady toho, jak se dají spojit tabulky M:N.
Přidání migrace
Sestavte projekt.
V PMC spusťte následující příkaz.
Add-Migration ComplexDataModel
Předchozí příkaz zobrazí upozornění na možnou ztrátu dat.
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
Pokud je příkaz spuštěn, vytvoří se následující chyba:
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'.
V další části se dozvíte, co dělat s touto chybou.
Použití migrace nebo vyřazení a opětovné vytvoření
Teď, když máte existující databázi, je potřeba se zamyslet nad tím, jak na ni použít změny. Tento kurz ukazuje dvě alternativy:
- Odstraňte databázi a znovu ji vytvořte. Tuto část vyberte, pokud používáte SQLite.
- Použijte migraci na existující databázi. Pokyny v této části fungují jenom pro SQL Server, ne pro SQLite.
Obě možnosti fungují pro SQL Server. I když je metoda apply-migration složitější a časově náročná, je to upřednostňovaný přístup pro skutečná produkční prostředí.
Odstranění a opětovné vytvoření databáze
Tuto část přeskočte, pokud používáte SQL Server a chcete použít migraci v následující části.
Pokud chcete vynutit EF Core vytvoření nové databáze, odstraňte a aktualizujte databázi:
V konzole Správce balíčků (PMC) spusťte následující příkaz:
Drop-Database
Odstraňte složku Migrations a spusťte následující příkaz:
Add-Migration InitialCreate Update-Database
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
Otevřete databázi v SSOX:
Pokud jste SSOX otevřeli dříve, klikněte na tlačítko Aktualizovat .
Rozbalte uzel Tabulky. Zobrazí se vytvořené tabulky.
Prozkoumejte tabulku CourseAssignment:
- Klikněte pravým tlačítkem myši na tabulku CourseAssignment a vyberte Zobrazit data.
- Ověřte, že tabulka CourseAssignment obsahuje data.
Použití migrace
Tato část je nepovinná. Tyto kroky fungují jenom pro SQL Server LocalDB a pouze v případě, že jste přeskočili předchozí drop a znovu vytvořili oddíl databáze .
Při spuštění migrace s existujícími daty můžou existovat omezení FK, která nejsou s existujícími daty spokojená. U produkčních dat je potřeba provést kroky pro migraci existujících dat. Tato část obsahuje příklad opravy porušení omezení FK. Tyto změny kódu neprodávejte bez zálohy. Tyto změny kódu neprovádějte, pokud jste dokončili předchozí drop a znovu vytvořili oddíl databáze .
Soubor {timestamp}_ComplexDataModel.cs
obsahuje následující kód:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Předchozí kód přidá do Course
tabulky nenulový DepartmentID
FK. Databáze z předchozího kurzu obsahuje řádky v Course
tabulce, aby je migrace neaktualizovala.
Aby migrace fungovala ComplexDataModel
s existujícími daty:
- Změňte kód tak, aby nový sloupec (
DepartmentID
) dal výchozí hodnotu. - Vytvořte falešné oddělení s názvem "Temp", které bude fungovat jako výchozí oddělení.
Oprava omezení cizího klíče
ComplexDataModel
Ve třídě migrace aktualizujte metoduUp
:
- Otevřete soubor
{timestamp}_ComplexDataModel.cs
. - Zakomentujte řádek kódu, který přidá
DepartmentID
sloupec doCourse
tabulky.
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);
Přidejte následující zvýrazněný kód. Nový kód následuje za blokem .CreateTable( name: "Department"
:
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);
Při předchozích změnách budou existující Course
řádky po spuštění metody souviset s oddělením ComplexDataModel.Up
Temp.
Způsob zpracování zde uvedené situace je pro účely tohoto kurzu zjednodušený. Produkční aplikace:
- Přidejte kód nebo skripty pro přidání
Department
řádků a souvisejícíchCourse
řádků do novýchDepartment
řádků. - Nepoužívejte oddělení Temp nebo výchozí hodnotu pro
Course.DepartmentID
.
V konzole Správce balíčků (PMC) spusťte následující příkaz:
Update-Database
Vzhledem k tomu, že DbInitializer.Initialize
metoda je navržená tak, aby fungovala pouze s prázdnou databází, odstraňte všechny řádky v tabulkách Student a Course pomocí SSOX. (Kaskádové odstranění se postará o tabulku Registrace.)
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
Další kroky
V dalších dvou kurzech se dozvíte, jak číst a aktualizovat související data.
Předchozí kurzy pracovaly se základním datovým modelem složeným ze tří entit. V tomto kurzu:
- Přidají se další entity a relace.
- Datový model je přizpůsobený zadáním pravidel formátování, ověřování a mapování databáze.
Třídy entit dokončeného datového modelu jsou znázorněny na následujícím obrázku:
Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte si dokončenou aplikaci.
Přizpůsobení datového modelu pomocí atributů
V této části je datový model přizpůsobený pomocí atributů.
Atribut DataType
Stránky studentů aktuálně zobrazují čas data registrace. Pole kalendářních dat obvykle zobrazují jenom datum, nikoli čas.
Aktualizujte Models/Student.cs
následujícím zvýrazněným kódem:
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; }
}
}
Atribut DataType určuje datový typ, který je konkrétnější než vnitřní typ databáze. V tomto případě by se mělo zobrazit jenom datum, nikoli datum a čas. Výčet DataType poskytuje mnoho datových typů, jako je datum, čas, telefonní číslo, měna, EmailAddress atd. Atribut DataType
může také aplikaci povolit, aby automaticky poskytovala funkce specifické pro typ. Příklad:
- Odkaz
mailto:
se automaticky vytvoří proDataType.EmailAddress
. - Ve většině prohlížečů je k dispozici
DataType.Date
selektor data.
Atribut DataType
generuje atributy HTML 5 data-
(vyslovující datový spojovník), které prohlížeče HTML 5 spotřebovávají. Atributy DataType
neposkytují ověření.
DataType.Date
nezadá formát zobrazeného data. Ve výchozím nastavení se pole data zobrazí podle výchozích formátů založených na souboru CultureInfo serveru.
Atribut DisplayFormat
se používá k explicitní zadání formátu data:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Nastavení ApplyFormatInEditMode
určuje, že formátování se má použít také v uživatelském rozhraní pro úpravy. Některá pole by neměla používat ApplyFormatInEditMode
. Symbol měny by se například neměl zobrazovat v textovém poli pro úpravy.
Atribut DisplayFormat
lze použít sám. Obecně je vhodné použít DataType
atribut s atributem DisplayFormat
. Atribut DataType
vyjadřuje sémantiku dat na rozdíl od toho, jak je vykreslit na obrazovce. Atribut DataType
poskytuje následující výhody, které nejsou k dispozici v DisplayFormat
:
- Prohlížeč může povolit funkce HTML5. Můžete například zobrazit ovládací prvek kalendáře, symbol měny odpovídající národnímu prostředí, e-mailové odkazy, ověření vstupu na straně klienta atd.
- Ve výchozím nastavení prohlížeč vykreslí data pomocí správného formátu na základě národního prostředí.
Další informace najdete v dokumentaci ke vstupním> pomocným <rutině značek.
Spustit aplikaci. Přejděte na stránku Index studentů. Časy se už nezobrazují. Každé zobrazení, které používá Student
model, zobrazuje datum bez času.
Atribut StringLength
Pomocí atributů je možné zadat pravidla ověření dat a chybové zprávy ověřování. Atribut StringLength určuje minimální a maximální délku znaků, které jsou povoleny v datovém poli. Atribut StringLength
také poskytuje ověřování na straně klienta a na straně serveru. Minimální hodnota nemá žádný vliv na schéma databáze.
Student
Aktualizujte model následujícím kódem:
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; }
}
}
Předchozí kód omezuje názvy maximálně na 50 znaků. Atribut StringLength
nezabrání uživateli v zadávání prázdných znaků pro jméno. Atribut RegularExpression se používá k použití omezení pro vstup. Například následující kód vyžaduje, aby první znak byl velkým písmenem a zbývající znaky byly abecední:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Spuštění aplikace:
- Přejděte na stránku Studenti.
- Vyberte Vytvořit nový a zadejte název delší než 50 znaků.
- Vyberte Vytvořit, ověření na straně klienta zobrazí chybovou zprávu.
V SQL Serveru Průzkumník objektů (SSOX) otevřete návrháře tabulek Student poklikáním na tabulku Student.
Předchozí obrázek znázorňuje schéma tabulky Student
. Pole názvů mají typ nvarchar(MAX)
, protože migrace nebyla v databázi spuštěna. Když se migrace spustí později v tomto kurzu, stanou se nvarchar(50)
pole názvů .
Atribut Column
Atributy mohou řídit, jak se třídy a vlastnosti mapují na databázi. V této části Column
se atribut používá k mapování názvu FirstMidName
vlastnosti na "FirstName" v databázi.
Při vytváření databáze se názvy vlastností v modelu používají pro názvy sloupců (s výjimkou použití atributu Column
).
Model Student
používá FirstMidName
pro pole s křestní jméno, protože pole může obsahovat také prostřední název.
Student.cs
Aktualizujte soubor následujícím zvýrazněným kódem:
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; }
}
}
Při předchozí změně Student.FirstMidName
se v aplikaci mapuje na FirstName
sloupec Student
tabulky.
Přidání atributu Column
změní model backing the SchoolContext
. Model, který SchoolContext
už databázi nepodporuje, neodpovídá. Pokud se aplikace spustí před použitím migrací, vygeneruje se následující výjimka:
SqlException: Invalid column name 'FirstName'.
Aktualizace databáze:
- Sestavte projekt.
- Otevřete příkazové okno ve složce projektu. Zadáním následujících příkazů vytvořte novou migraci a aktualizujte databázi:
Add-Migration ColumnFirstName
Update-Database
Příkaz migrations add ColumnFirstName
vygeneruje následující zprávu upozornění:
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Upozornění se vygeneruje, protože pole názvů jsou teď omezená na 50 znaků. Pokud název v databázi obsahoval více než 50 znaků, ztratí se 51 až poslední znak.
- Otestujete aplikaci.
Otevřete tabulku Student v SSOX:
Před použitím migrace byly sloupce názvů typu nvarchar(MAX). Sloupce názvů jsou nyní nvarchar(50)
. Název sloupce se změnil z FirstMidName
na FirstName
.
Poznámka:
V následující části sestavení aplikace v některých fázích generuje chyby kompilátoru. Pokyny určují, kdy se má aplikace sestavit.
Aktualizace studentských entit
Aktualizujte Models/Student.cs
následujícím kódem:
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; }
}
}
Požadovaný atribut
Atribut Required
vytvoří požadované vlastnosti názvu. Atribut Required
není potřeba pro typy bez hodnoty null, jako jsou typy hodnot (DateTime
, int
, double
atd.). Typy, které nemohou být null, se automaticky považují za povinná pole.
Atribut Required
může být nahrazen parametrem minimální délky v atributu StringLength
:
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
Atribut Display
Atribut Display
určuje, že titulek textových polí by měl být "Jméno", "Příjmení", "Celé jméno" a "Datum registrace". Výchozí titulky neměly žádné mezery, které by dělily slova, například "Příjmení".
Počítaná vlastnost FullName
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dvou dalších vlastností. FullName
nelze nastavit, má pouze přístupové objekty get. V databázi se nevytvořil žádný FullName
sloupec.
Vytvoření entity instruktora
Vytvořte Models/Instructor.cs
pomocí následujícího kódu:
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; }
}
}
Více atributů může být na jednom řádku. Atributy HireDate
lze zapsat takto:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Vlastnosti navigace CourseAssignments a OfficeAssignment
Vlastnosti CourseAssignments
a OfficeAssignment
vlastnosti jsou navigační vlastnosti.
Instruktor může učit libovolný počet kurzů, takže CourseAssignments
je definován jako kolekce.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Pokud navigační vlastnost obsahuje více entit:
- Musí se jednat o typ seznamu, do kterého lze položky přidat, odstranit a aktualizovat.
Mezi typy navigačních vlastností patří:
ICollection<T>
List<T>
HashSet<T>
Pokud ICollection<T>
je zadáno, EF Core vytvoří ve výchozím nastavení kolekci HashSet<T>
.
Entita CourseAssignment
je vysvětlená v části o relacích M:N.
Obchodní pravidla společnosti Contoso pro vysokoškoláky uvádějí, že instruktor může mít maximálně jednu kancelář. Vlastnost OfficeAssignment
obsahuje jednu OfficeAssignment
entitu. OfficeAssignment
má hodnotu null, pokud není přiřazena žádná kancelář.
public OfficeAssignment OfficeAssignment { get; set; }
Vytvoření entity OfficeAssignment
Vytvořte Models/OfficeAssignment.cs
pomocí následujícího kódu:
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; }
}
}
Atribut Klíč
Atribut [Key]
slouží k identifikaci vlastnosti jako primárního klíče (PK), pokud název vlastnosti je něco jiného než classnameID nebo ID.
Mezi entitami Instructor
OfficeAssignment
existuje vztah 1:1 nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, ke kterému je přiřazený. Pk OfficeAssignment
je také jeho cizí klíč (FK) pro entitu Instructor
. EF Core nemůže automaticky rozpoznat InstructorID
jako PK OfficeAssignment
z následujících důvodů:
InstructorID
neodpovídá konvenci pojmenování ID nebo classnameID.
Proto se Key
atribut používá k identifikaci InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Ve výchozím nastavení považuje klíč za negenerovaný databází, EF Core protože sloupec slouží k identifikaci relace.
Vlastnost navigace instruktora
Vlastnost OfficeAssignment
navigace pro entitu Instructor
je nullable, protože:
- Odkazové typy (například třídy mají možnou hodnotu null).
- Instruktor nemusí mít zadání kanceláře.
Entita OfficeAssignment
má nenulovou Instructor
navigační vlastnost, protože:
InstructorID
je nenulová.- Zadání kanceláře nemůže existovat bez instruktora.
Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita má ve své navigační vlastnosti odkaz na druhou entitu.
Atribut [Required]
lze použít pro Instructor
navigační vlastnost:
[Required]
public Instructor Instructor { get; set; }
Předchozí kód určuje, že musí existovat související instruktor. Předchozí kód není nutný, protože InstructorID
cizí klíč (což je také PK) není nullable.
Úprava entity kurzu
Aktualizujte Models/Course.cs
následujícím kódem:
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; }
}
}
Entita Course
má vlastnost DepartmentID
cizího klíče (FK). DepartmentID
odkazuje na související Department
entitu. Entita Course
má Department
navigační vlastnost.
EF Core nevyžaduje vlastnost FK pro datový model, pokud má model navigační vlastnost pro související entitu.
EF Core v databázi automaticky vytváří sady FK, kdekoli jsou potřeba. EF Core vytvoří stínové vlastnosti pro automaticky vytvořené sady FK. Použití FK v datovém modelu může zjednodušit a zefektivnit aktualizace. Představte si například model, ve kterém není zahrnuta vlastnost DepartmentID
FK. Když se entita kurzu načte pro úpravy:
- Entita
Department
má hodnotu null, pokud není explicitně načtena. - Chcete-li aktualizovat entitu kurzu, musí být entita
Department
nejprve načtena.
Pokud je vlastnost DepartmentID
FK součástí datového modelu, není nutné před aktualizací načíst entitu Department
.
Atribut DatabaseGenerated
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
určuje, že PK je poskytována aplikací místo generování databáze.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení se předpokládá, EF Core že databáze generuje hodnoty PK. Nejlepší přístup je obecně přístup generovaný databází PK. Pro Course
entity určuje uživatel PK. Například číslo kurzu, například 1000 řad pro matematické oddělení, 2000 série pro anglické oddělení.
Atribut DatabaseGenerated
lze také použít k vygenerování výchozích hodnot. Databáze může například automaticky vygenerovat pole kalendářního data pro zaznamenání data vytvoření nebo aktualizace řádku. Další informace naleznete v tématu Vygenerované vlastnosti.
Vlastnosti cizího klíče a navigace
Vlastnosti cizího klíče (FK) a vlastnosti navigace v entitě Course
odrážejí následující relace:
Kurz je přiřazen k jednomu oddělení, takže je k DepartmentID
dispozici FK a Department
navigační vlastnost.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kurz může mít v něm zaregistrovaný libovolný počet studentů, takže Enrollments
navigační vlastnost je kolekce:
public ICollection<Enrollment> Enrollments { get; set; }
Kurz může učit více instruktorů, takže CourseAssignments
navigační vlastnost je kolekce:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
je vysvětleno později.
Vytvoření entity Oddělení
Vytvořte Models/Department.cs
pomocí následujícího kódu:
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; }
}
}
Atribut Column
Dříve byl Column
atribut použit ke změně mapování názvů sloupců. V kódu entity Column
se atribut používá ke změně mapování datového Department
typu SQL. Sloupec Budget
se definuje pomocí typu peněz SQL Serveru v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obecně nevyžaduje. EF Core obecně zvolí příslušný datový typ SQL Serveru na základě typu CLR pro vlastnost. Typ CLR decimal
se mapuje na typ SQL Serveru decimal
. Budget
je pro měnu a datový typ peněz je vhodnější pro měnu.
Vlastnosti cizího klíče a navigace
Vlastnosti FK a navigace odrážejí následující relace:
- Oddělení může nebo nemusí mít správce.
- Správce je vždy instruktor.
InstructorID
Proto je vlastnost zahrnuta jako FK entityInstructor
.
Navigační vlastnost je pojmenovaná Administrator
, ale obsahuje entitu Instructor
:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Otazník (?) v předchozím kódu určuje vlastnost null.
Oddělení může mít mnoho kurzů, takže je zde vlastnost Navigace v kurzech:
public ICollection<Course> Courses { get; set; }
Poznámka: Podle konvence EF Core umožňuje kaskádové odstranění pro nenulové FK a pro relace M:N. Kaskádové odstranění může vést k cyklický kaskádový odstranění pravidel. Pravidla cyklických kaskádových odstranění způsobí výjimku při přidání migrace.
Pokud Department.InstructorID
byla například vlastnost definována jako nenulová:
EF Core nakonfiguruje kaskádové pravidlo odstranění, které odstraní oddělení při odstranění instruktora.
Odstranění oddělení při odstranění instruktora není zamýšlené chování.
Následující fluentové rozhraní API by nastavilo pravidlo omezení místo kaskádové.
modelBuilder.Entity<Department>() .HasOne(d => d.Administrator) .WithMany() .OnDelete(DeleteBehavior.Restrict)
Předchozí kód zakáže kaskádové odstranění vztahu instruktora oddělení.
Aktualizace entity registrace
Záznam o registraci je určen pro jeden kurz pořízený jedním studentem.
Aktualizujte Models/Enrollment.cs
následujícím kódem:
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; }
}
}
Vlastnosti cizího klíče a navigace
Vlastnosti FK a navigační vlastnosti odrážejí následující relace:
Záznam registrace je pro jeden kurz, takže vlastnost CourseID
FK a Course
navigační vlastnost:
public int CourseID { get; set; }
public Course Course { get; set; }
Záznam registrace je určený pro jednoho studenta, takže vlastnost StudentID
FK a Student
navigační vlastnost:
public int StudentID { get; set; }
public Student Student { get; set; }
Relace M:N
Mezi entitami Student
Course
a entitami existuje vztah M:N. Entita Enrollment
funguje jako tabulka spojení M:N s datovou částí v databázi. "S datovou částí" znamená, že Enrollment
tabulka obsahuje další data kromě FK pro spojené tabulky (v tomto případě PK a Grade
).
Následující obrázek znázorňuje, jak tyto relace vypadají v diagramu entit. (Tento diagram byl generován pomocí EF Power Tools pro EF 6.x. Vytvoření diagramu není součástí kurzu.)
Každá čára relace má na jednom konci 1 a hvězdičku (*) na druhé, která označuje relaci 1:N.
Enrollment
Pokud tabulka neobsahuje informace o známce, musí obsahovat pouze dvě sady FK (CourseID
aStudentID
). Tabulka spojení M:N bez datové části se někdy označuje jako čistá join table (PJT).
Course
Entity Instructor
mají relaci M:N pomocí tabulky s čistým spojením.
Poznámka: EF 6.x podporuje implicitní spojení tabulek pro relace M:N, ale EF Core ne. Další informace naleznete v tématu Relace M:N ve EF Core verzi 2.0.
Entita CourseAssignment
Vytvořte Models/CourseAssignment.cs
pomocí následujícího kódu:
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; }
}
}
Kurzy instruktora
Vztah M:N instruktora:
- Vyžaduje tabulku spojení, která musí být reprezentována sadou entit.
- Je tabulka s čistým spojením (tabulka bez datové části).
Je běžné pojmenovat entitu EntityName1EntityName2
spojení . Například tabulka spojení Instruktor-to-Courses pomocí tohoto vzoru je CourseInstructor
. Doporučujeme ale použít název, který popisuje relaci.
Datové modely začínají jednoduchým a růstem. Spojení bez datové části (PJT) se často vyvíjejí, aby zahrnovala datovou část. Když začnete s popisným názvem entity, název se při změně tabulky spojení nemusí měnit. V ideálním případě by entita spojení měla v obchodní doméně svůj vlastní přirozený (případně jednoslovný) název. Knihy a zákazníci můžou být například propojeny s entitou spojení s názvem Hodnocení. Pro vztah CourseAssignment
typu Instruktor-to-Courses M:N je preferován před CourseInstructor
.
Složený klíč
Sady FK nejsou s možnou hodnotou null. Oba sady FK v CourseAssignment
tabulce (InstructorID
a CourseID
) společně jednoznačně identifikují každý řádek CourseAssignment
tabulky. CourseAssignment
nevyžaduje vyhrazenou pk. Funkce InstructorID
a CourseID
vlastnosti slouží jako složená infrastruktura veřejných klíčů. Jediným způsobem, jak zadat složené sady PK, je EF Core rozhraní FLUENT API. V další části se dozvíte, jak nakonfigurovat složenou pk.
Složený klíč zajišťuje:
- Pro jeden kurz je povoleno více řádků.
- Pro jednoho instruktora je povoleno více řádků.
- Více řádků pro stejného instruktora a kurz není povoleno.
Entita Enrollment
join definuje vlastní PK, takže duplicitní položky tohoto typu jsou možné. Chcete-li těmto duplicitám zabránit:
- Přidání jedinečného indexu do polí FK nebo
- Nakonfigurujte
Enrollment
primární složený klíč podobnýCourseAssignment
. Další informace najdete v tématu Indexy.
Aktualizace kontextu databáze
Přidejte následující zvýrazněný kód 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 });
}
}
}
Předchozí kód přidá nové entity a nakonfiguruje CourseAssignment
složenou PK entity.
Alternativa rozhraní Fluent API k atributům
Metoda OnModelCreating
v předchozím kódu používá rozhraní API fluent ke konfiguraci EF Core chování. Rozhraní API se nazývá "fluent", protože se často používá řetězcem řady volání metod do jednoho příkazu. Následující kód je příkladem rozhraní FLUENT API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
V tomto kurzu se rozhraní API fluent používá pouze pro mapování databáze, které nelze provádět s atributy. Rozhraní API fluent však může určovat většinu pravidel formátování, ověřování a mapování, která se dají provádět s atributy.
Některé atributy, jako MinimumLength
je například nelze použít s rozhraním FLUENT API. MinimumLength
nezmění schéma, použije pouze ověřovací pravidlo minimální délky.
Někteří vývojáři raději používají výhradně rozhraní API fluent, aby mohli třídy entit udržovat "čisté". Atributy a fluentské rozhraní API je možné kombinovat. Existují některé konfigurace, které je možné provádět pouze s rozhraním FLUENT API (určením složené pk). Existují některé konfigurace, které je možné provádět pouze s atributy (MinimumLength
). Doporučený postup pro používání rozhraní API nebo atributů fluent:
- Zvolte jeden z těchto dvou přístupů.
- Zvolený přístup používejte co nejvíce konzistentně.
Některé atributy použité v tomto kurzu se používají pro:
- Pouze ověřování (například
MinimumLength
). - EF Core pouze konfigurace (například
HasKey
). - Ověřování a EF Core konfigurace (například
[StringLength(50)]
).
Další informace o atributech a rozhraní API fluent naleznete v tématu Metody konfigurace.
Diagram entit znázorňující relace
Následující obrázek znázorňuje diagram, který nástroje EF Power Tools vytvoří pro dokončený školní model.
Předchozí diagram znázorňuje:
- Několik čar relací 1:N (1 až *).
- Čára relace 1:nula nebo 1 (1 až 0.1) mezi entitami
Instructor
aOfficeAssignment
entitami. - Čára relace nula nebo 1:N (0..1 až *) mezi entitami
Instructor
aDepartment
entitami.
Počáteční data databáze s testovacími daty
Aktualizujte kód v 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();
}
}
}
Předchozí kód poskytuje počáteční data pro nové entity. Většina tohoto kódu vytvoří nové objekty entity a načte ukázková data. Ukázková data se používají k testování. Podívejte Enrollments
se na CourseAssignments
příklady toho, jak se dají spojit tabulky M:N.
Přidání migrace
Sestavte projekt.
Add-Migration ComplexDataModel
Předchozí příkaz zobrazí upozornění na možnou ztrátu dat.
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
Pokud je příkaz spuštěn, vytvoří se následující chyba:
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'.
Použití migrace
Teď, když máte existující databázi, musíte přemýšlet o tom, jak na ni použít budoucí změny. Tento kurz ukazuje dva přístupy:
- Odstranění a opětovné vytvoření databáze
- Použijte migraci na existující databázi. I když je tato metoda složitější a časově náročná, je to upřednostňovaný přístup pro skutečná produkční prostředí. Poznámka: Toto je volitelná část kurzu. Můžete provést přetažení a znovu vytvořit kroky a přeskočit tuto část. Pokud chcete postupovat podle kroků v této části, neprovádějte kroky přetažení a znovu vytvořte kroky.
Odstranění a opětovné vytvoření databáze
Kód v aktualizovaném DbInitializer
kódu přidá počáteční data pro nové entity. Pokud chcete vynutit EF Core vytvoření nové databáze, odstraňte a aktualizujte databázi:
V konzole Správce balíčků (PMC) spusťte následující příkaz:
Drop-Database
Update-Database
Spuštěním Get-Help about_EntityFrameworkCore
z PMC získejte informace o nápovědě.
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
Otevřete databázi v SSOX:
- Pokud jste SSOX otevřeli dříve, klikněte na tlačítko Aktualizovat .
- Rozbalte uzel Tabulky. Zobrazí se vytvořené tabulky.
Prozkoumejte tabulku CourseAssignment:
- Klikněte pravým tlačítkem myši na tabulku CourseAssignment a vyberte Zobrazit data.
- Ověřte, že tabulka CourseAssignment obsahuje data.
Použití migrace na existující databázi
Tato část je nepovinná. Tyto kroky fungují jenom v případě, že jste přeskočili předchozí drop a znovu vytvořili oddíl databáze .
Při spuštění migrace s existujícími daty můžou existovat omezení FK, která nejsou s existujícími daty spokojená. U produkčních dat je potřeba provést kroky pro migraci existujících dat. Tato část obsahuje příklad opravy porušení omezení FK. Tyto změny kódu neprodávejte bez zálohy. Pokud jste dokončili předchozí část a aktualizovali databázi, tyto změny kódu neprodávejte.
Soubor {timestamp}_ComplexDataModel.cs
obsahuje následující kód:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Předchozí kód přidá do Course
tabulky nenulový DepartmentID
FK. Databáze z předchozího kurzu obsahuje řádky, Course
aby je migrace nemohla aktualizovat.
Aby migrace fungovala ComplexDataModel
s existujícími daty:
- Změňte kód tak, aby nový sloupec (
DepartmentID
) dal výchozí hodnotu. - Vytvořte falešné oddělení s názvem "Temp", které bude fungovat jako výchozí oddělení.
Oprava omezení cizího klíče
Aktualizujte metodu ComplexDataModel
tříd Up
:
- Otevřete soubor
{timestamp}_ComplexDataModel.cs
. - Zakomentujte řádek kódu, který přidá
DepartmentID
sloupec doCourse
tabulky.
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);
Přidejte následující zvýrazněný kód. Nový kód následuje za blokem .CreateTable( name: "Department"
:
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);
Při předchozích změnách budou existující Course
řádky po spuštění metody souviset s oddělením ComplexDataModel
Up
Temp.
Produkční aplikace:
- Přidejte kód nebo skripty pro přidání
Department
řádků a souvisejícíchCourse
řádků do novýchDepartment
řádků. - Nepoužívejte oddělení Temp nebo výchozí hodnotu pro
Course.DepartmentID
.
Další kurz se zabývá souvisejícími daty.