Kurz: Vytvoření složitějšího datového modelu pro aplikaci ASP.NET MVC
V předchozích kurzech jste pracovali s jednoduchým datovým modelem, který se skládal ze tří entit. V tomto kurzu přidáte další entity a relace a přizpůsobíte datový model zadáním pravidel formátování, ověřování a mapování databází. Tento článek ukazuje dva způsoby přizpůsobení datového modelu: přidáním atributů do tříd entit a přidáním kódu do třídy kontextu databáze.
Jakmile budete hotovi, třídy entit vytvoří dokončený datový model, který je znázorněný na následujícím obrázku:
V tomto kurzu jste:
- Přizpůsobení datového modelu
- Aktualizovat entitu Student
- Vytvoření entity Instruktor
- Vytvoření entity OfficeAssignment
- Úprava entity Course
- Vytvoření entity Oddělení
- Úprava entity registrace
- Přidání kódu do kontextu databáze
- Počáteční databáze s testovacími daty
- Přidání migrace
- Aktualizace databáze
Požadavky
Přizpůsobení datového modelu
V této části se dozvíte, jak přizpůsobit datový model pomocí atributů, které určují pravidla formátování, ověřování a mapování databáze. Potom v několika následujících částech vytvoříte úplný School
datový model přidáním atributů do již vytvořených tříd a vytvořením nových tříd pro zbývající typy entit v modelu.
Atribut Datového typu
U dat registrace studentů se na všech webových stránkách aktuálně zobrazuje čas spolu s datem, ale jediné, co vás zajímá, je datum. Pomocí atributů datových poznámek můžete provést jednu změnu kódu, která opraví formát zobrazení v každém zobrazení, které zobrazuje data. Pokud chcete vidět příklad, jak to udělat, přidáte atribut do EnrollmentDate
vlastnosti ve Student
třídě .
V souboru Models\Student.cs přidejte using
příkaz pro System.ComponentModel.DataAnnotations
obor názvů a do EnrollmentDate
vlastnosti přidejte DataType
atributy a DisplayFormat
, jak je znázorněno v následujícím příkladu:
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 virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Atribut DataType slouží k určení datového typu, který je konkrétnější než vnitřní typ databáze. V tomto případě chceme sledovat pouze datum, ne datum a čas. Výčet DataType poskytuje mnoho datových typů, například Datum, Čas, Telefonní číslo, Měna, EmailAddress a další. Atribut DataType
může také aplikaci umožnit, aby automaticky poskytovala funkce specifické pro typ. mailto:
Například odkaz může být vytvořen pro DataType.EmailAddress a v prohlížečích, které podporují HTML5, lze pro DataType.Date zadat výběr data. Atributy DataType vygenerují atributy HTML 5 ( vyslovuje se datová pomlčka), kterým prohlížeče HTML 5 rozumí. Atributy DataType neposkytují žádné ověření.
DataType.Date
neurčoval formát data, které je zobrazeno. Ve výchozím nastavení je datové pole zobrazeno podle výchozích formátů založených na CultureInfo serveru.
Atribut DisplayFormat
slouží k explicitní specifikaci formátu data:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Nastavení ApplyFormatInEditMode
určuje, že se má zadané formátování použít také při zobrazení hodnoty v textovém poli pro úpravy. (U některých polí to možná nebudete chtít – například pro hodnoty měny nebudete chtít symbol měny v textovém poli pro úpravy.)
Atribut DisplayFormat můžete použít samostatně, ale obecně je vhodné použít také atribut DataType . Atribut DataType
vyjadřuje sémantiku dat na rozdíl od toho, jak je vykreslit na obrazovce, a poskytuje následující výhody, které nemáte s DisplayFormat
:
- Prohlížeč může povolit funkce HTML5 (například k zobrazení ovládacího prvku kalendáře, symbolu měny odpovídajícího národnímu prostředí, e-mailových odkazů, ověření některých vstupů na straně klienta atd.).
- Ve výchozím nastavení prohlížeč vykresluje data pomocí správného formátu na základě vašeho národního prostředí.
- Atribut DataType umožňuje MVC zvolit správnou šablonu pole pro vykreslení dat ( DisplayFormat používá šablonu řetězce). Další informace najdete v tématu Šablony ASP.NET MVC 2 od Brada Wilsona. (I když je tento článek napsaný pro MVC 2, stále platí pro aktuální verzi ASP.NET MVC.)
Pokud použijete DataType
atribut s polem data, musíte atribut zadat DisplayFormat
také, aby se zajistilo, že se pole v prohlížečích Chrome správně vykresluje. Další informace najdete v tomto vlákně StackOverflow.
Další informace o zpracování jiných formátů kalendářních dat v MVC najdete v tématu MVC 5 Úvod: Zkoumání metod úprav a zobrazení úprav a vyhledejte na stránce "internacionalizace".
Znovu spusťte stránku Index studenta a všimněte si, že se pro data registrace už nezobrazují časy. Totéž bude platit pro všechna zobrazení, která model používají Student
.
Atribut StringLengthAttribute
Pomocí atributů můžete také zadat pravidla ověření dat a chybové zprávy ověření. Atribut StringLength nastavuje maximální délku v databázi a poskytuje ověření na straně klienta a serveru pro ASP.NET MVC. V tomto atributu můžete také zadat minimální délku řetězce, ale minimální hodnota nemá žádný vliv na schéma databáze.
Předpokládejme, že chcete zajistit, aby uživatelé nezadáli do názvu více než 50 znaků. Chcete-li přidat toto omezení, přidejte do LastName
vlastností a FirstMidName
atributy StringLength, jak je znázorněno v následujícím příkladu:
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 virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Atribut StringLength nezabrání uživateli v zadání prázdných znaků pro jméno. Pomocí atributu RegularExpression můžete použít omezení vstupu. Například následující kód vyžaduje, aby první znak byl velkými písmeny a zbývající znaky abecedně:
[RegularExpression(@"^[A-Z]+[a-zA-Z""'\s-]*$")]
Atribut MaxLength poskytuje podobné funkce jako atribut StringLength , ale neposkytuje ověření na straně klienta.
Spusťte aplikaci a klikněte na kartu Studenti . Zobrazí se následující chyba:
Model, který zálohuje kontext SchoolContext, se od vytvoření databáze změnil. Zvažte použití Migrace Code First k aktualizaci databáze (https://go.microsoft.com/fwlink/?LinkId=238269).
Model databáze se změnil způsobem, který vyžaduje změnu schématu databáze, a Entity Framework to zjistila. Pomocí migrací aktualizujete schéma bez ztráty dat, která jste přidali do databáze pomocí uživatelského rozhraní. Pokud jste změnili data vytvořená metodou Seed
, změní se zpět do původního stavu kvůli metodě AddOrUpdate , kterou v Seed
metodě používáte. (AddOrUpdate je ekvivalentem operace upsert z terminologie databáze.)
V konzole Správce balíčků (PMC) zadejte následující příkazy:
add-migration MaxLengthOnNames
update-database
Příkaz add-migration
vytvoří soubor s názvem <timeStamp>_MaxLengthOnNames.cs. Tento soubor obsahuje kód v Up
metodě, která aktualizuje databázi tak, aby odpovídala aktuálnímu datovému modelu. Příkaz update-database
spustil tento kód.
Časové razítko před název souboru migrace se používá v Entity Frameworku k řazení migrací. Před spuštěním update-database
příkazu můžete vytvořit více migrací a pak se všechny migrace použijí v pořadí, ve kterém byly vytvořeny.
Spusťte stránku Vytvořit a zadejte název delší než 50 znaků. Když kliknete na Vytvořit, ověření na straně klienta zobrazí chybovou zprávu: Pole Příjmení musí být řetězec o maximální délce 50.
Atribut column
Atributy můžete také použít k řízení způsobu mapování tříd a vlastností na databázi. Předpokládejme, že jste použili název FirstMidName
pro pole se jménem, protože pole může obsahovat také druhé jméno. Chcete ale, aby se sloupec databáze jmenoval FirstName
, protože uživatelé, kteří budou psát dotazy ad hoc na databázi, jsou na tento název zvyklí. K tomuto mapování můžete použít atribut .Column
Atribut Column
určuje, že při vytvoření databáze bude mít sloupec Student
tabulky mapovaná na FirstMidName
vlastnost název FirstName
. Jinými slovy, když váš kód odkazuje na Student.FirstMidName
, data budou pocházet ze sloupce tabulky nebo se aktualizují ve FirstName
sloupci Student
tabulky. Pokud nezadáte názvy sloupců, dostanou stejný název jako název vlastnosti.
V souboru Student.cs přidejte using
příkaz pro System.ComponentModel.DataAnnotations.Schema a do FirstMidName
vlastnosti přidejte atribut název sloupce, jak je znázorněno v následujícím zvýrazněném kódu:
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 virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Přidání atributu Column změní model backing schoolContext, takže nebude odpovídat databázi. Zadáním následujících příkazů v PMC vytvořte další migraci:
add-migration ColumnFirstName
update-database
V Průzkumníku serveru otevřete poklikáním na tabulku Student návrháře tabulkyStudent .
Následující obrázek ukazuje původní název sloupce, který byl před prvními dvěma migracemi. Kromě toho, že se název sloupce změnil z FirstMidName
na FirstName
, se změnily dva sloupce názvů z MAX
délky na 50 znaků.
Změny mapování databází můžete provést také pomocí rozhraní Fluent API, jak uvidíte později v tomto kurzu.
Poznámka
Pokud se pokusíte zkompilovat před dokončením vytváření všech tříd entit v následujících částech, může dojít k chybám kompilátoru.
Aktualizovat entitu Student
V souboru Models\Student.cs nahraďte dříve přidaný kód následujícím kódem. Změny jsou zvýrazněné.
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 virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Požadovaný atribut
Atribut Required nastaví vlastnosti názvu jako povinná pole. Není Required attribute
potřeba pro typy hodnot, jako jsou DateTime, int, double a float. Typům hodnot nelze přiřadit hodnotu null, takže jsou ze své podstaty považovány za povinná pole.
Atribut Required
musí být použit s atributem , MinimumLength
aby MinimumLength
se vynutil .
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength
a Required
povolit prázdné znaky, aby se ověření splnilo. RegularExpression
Použijte atribut pro úplné řízení nad řetězcem.
Atribut Zobrazení
Atribut Display
určuje, že popis pro textová pole by měly být "Jméno", "Příjmení", "Celé jméno" a "Datum registrace" místo názvu vlastnosti v každé instanci (který neobsahuje mezeru rozdělující slova).
Vypočtená vlastnost FullName
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dalších dvou vlastností. Proto má pouze přístup a get
v databázi se nevygeneruje žádný FullName
sloupec.
Vytvoření entity Instruktor
Vytvořte soubor Models\Instructor.cs a nahraďte kód šablony 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 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 virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
}
Všimněte si, že několik vlastností v entitách a Instructor
je stejnýchStudent
. V kurzu Implementace dědičnosti dále v této sérii tento kód refaktorujete, abyste eliminovali redundanci.
Na jeden řádek můžete umístit více atributů, takže můžete také napsat třídu instruktora následujícím způsobem:
public class Instructor
{
public int ID { get; set; }
[Display(Name = "Last Name"),StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
[Column("FirstName"),Display(Name = "First Name"),StringLength(50, MinimumLength=1)]
public string FirstMidName { get; set; }
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
The Courses and OfficeAssignment Navigation Properties
Vlastnosti Courses
a OfficeAssignment
jsou navigační vlastnosti. Jak bylo vysvětleno dříve, obvykle jsou definovány jako virtuální , aby mohly využívat funkci Entity Framework označovanou jako opožděné načítání. Kromě toho, pokud navigační vlastnost může obsahovat více entit, musí její typ implementovat ICollection<T> Rozhraní. Například IList<T> kvalifikuje, ale ne IEnumerable<T> , protože IEnumerable<T>
neimplementuje Add.
Instruktor může vyučovat libovolný počet kurzů, takže Courses
je definován jako kolekce Course
entit.
public virtual ICollection<Course> Courses { get; set; }
Naše obchodní pravidla uvádějí, že instruktor může mít maximálně jednu kancelář, takže OfficeAssignment
je definována jako jedna OfficeAssignment
entita (což může být null
, pokud není přiřazena žádná kancelář).
public virtual OfficeAssignment OfficeAssignment { get; set; }
Vytvoření entity OfficeAssignment
Vytvořte Soubor Models\OfficeAssignment.cs s následujícím kódem:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public virtual Instructor Instructor { get; set; }
}
}
Sestavte projekt, který uloží změny a ověří, že jste neprovedli žádné chyby kopírování a vkládání, které kompilátor dokáže zachytit.
Klíčový atribut
Mezi entitami a OfficeAssignment
existuje vztah Instructor
1:nula nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, kterému je přiřazeno, a proto je jeho primární klíč také cizím klíčem entity Instructor
. Entity Framework ale nedokáže automaticky rozpoznat InstructorID
jako primární klíč této entity, protože její název se nedodržuje ID
konvence pojmenování názvů classnameID
nebo . Atribut se Key
proto používá k jeho identifikaci jako klíče:
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
Atribut můžete použít také v Key
případě, že entita má vlastní primární klíč, ale chcete vlastnost pojmenovat jinak než classnameID
nebo ID
. EF ve výchozím nastavení považuje klíč za negenerovaný databází, protože sloupec je určený pro identifikační relaci.
Atribut ForeignKey
Pokud existuje vztah 1:nula nebo 1 nebo vztah 1:1 mezi dvěma entitami (například mezi OfficeAssignment
entitami a Instructor
), EF nemůže zjistit, který konec relace je objektem zabezpečení a který konec je závislý. Relace 1:1 mají navigační vlastnost odkazu v každé třídě na druhou třídu. Atribut ForeignKey lze použít na závislé třídy k vytvoření relace. Pokud vynecháte atribut ForeignKey, při pokusu o vytvoření migrace se zobrazí následující chyba:
Nelze určit hlavní konec přidružení mezi typy ContosoUniversity.Models.OfficeAssignment a ContosoUniversity.Models.Instructor. Hlavní konec tohoto přidružení musí být explicitně nakonfigurovaný pomocí rozhraní API fluent relace nebo datových poznámek.
Později v tomto kurzu se dozvíte, jak tuto relaci nakonfigurovat pomocí rozhraní Fluent API.
Vlastnost Navigace instruktora
Entita Instructor
má navigační vlastnost s možnou OfficeAssignment
hodnotou null (protože instruktor nemusí mít přiřazení kanceláře) a OfficeAssignment
entita má navigační vlastnost, která nemůže mít hodnotu null Instructor
(protože přiřazení kanceláře nemůže existovat bez instruktora – InstructorID
nemůže mít hodnotu null). Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita bude mít ve své navigační vlastnosti odkaz na druhou entitu.
Do navigační vlastnosti instruktora můžete zadat [Required]
atribut, který určí, že musí existovat související instruktor, ale nemusíte to udělat, protože cizí klíč InstructorID (který je také klíčem k této tabulce) nemůže mít hodnotu null.
Úprava entity Course
V souboru Models\Course.cs nahraďte dříve přidaný kód 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 virtual Department Department { get; set; }
public virtual ICollection<Enrollment> Enrollments { get; set; }
public virtual ICollection<Instructor> Instructors { get; set; }
}
}
Entita kurzu má vlastnost DepartmentID
cizího klíče, která odkazuje na související Department
entitu Department
a má vlastnost navigace. Entity Framework nevyžaduje přidání vlastnosti cizího klíče do datového modelu, pokud máte vlastnost navigace pro související entitu. EF automaticky vytvoří cizí klíče v databázi bez ohledu na to, kde jsou potřeba. Cizí klíč v datovém modelu ale může zjednodušit a zefektivnit aktualizace. Když například načtete entitu kurzu, kterou chcete upravit, entita bude mít hodnotu null, Department
pokud ji nenačtete, takže při aktualizaci entity kurzu byste ji museli nejprve načíst Department
. Pokud je vlastnost DepartmentID
cizího klíče součástí datového modelu, nemusíte entitu Department
před aktualizací načítat.
Atribut DatabaseGenerated
Atribut DatabaseGenerated s parametrem None u CourseID
vlastnosti určuje, že hodnoty primárního klíče jsou poskytovány uživatelem, nikoli generovány databází.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení Entity Framework předpokládá, že hodnoty primárního klíče jsou generovány databází. To je to, co chcete ve většině scénářů. U Course
entit ale použijete uživatelem zadané číslo kurzu, například řadu 1000 pro jedno oddělení, řadu 2000 pro jiné oddělení atd.
Vlastnosti cizího klíče a navigace
Vlastnosti cizího klíče a vlastnosti navigace v entitě Course
odrážejí následující vztahy:
Kurz je přiřazen k jednomu oddělení, takže z výše uvedených důvodů existuje
DepartmentID
cizí klíč aDepartment
navigační vlastnost.public int DepartmentID { get; set; } public virtual Department Department { get; set; }
Kurz může mít zaregistrovaný libovolný počet studentů, takže
Enrollments
navigační vlastností je kolekce:public virtual ICollection<Enrollment> Enrollments { get; set; }
Kurz může vyučovat více instruktorů, takže
Instructors
navigační vlastností je kolekce:public virtual ICollection<Instructor> Instructors { get; set; }
Vytvoření entity Oddělení
Vytvořte Soubor Models\Department.cs s 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 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 virtual Instructor Administrator { get; set; }
public virtual ICollection<Course> Courses { get; set; }
}
}
Atribut column
Dříve jste ke změně mapování názvů sloupců použili atribut Column . V kódu entity Column
se atribut používá ke změně mapování datového Department
typu SQL tak, aby se sloupec definoval pomocí SQL Server typu peněz v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obecně nevyžaduje, protože Entity Framework obvykle vybírá vhodný datový typ SQL Server na základě typu CLR, který pro vlastnost definujete. Typ CLR decimal
se mapuje na typ SQL Serverdecimal
. V tomto případě ale víte, že sloupec bude obsahovat peněžní částky a datový typ peníze je pro to vhodnější. Další informace o datových typech CLR a o tom, jak se shodují s SQL Server datovými typy, najdete v tématu SqlClient pro Entity FrameworkTypes.
Vlastnosti cizího klíče a navigace
Vlastnosti cizího klíče a navigace odrážejí následující relace:
Oddělení může nebo nemusí mít správce a správce je vždy instruktor.
InstructorID
Vlastnost je proto zahrnuta jako cizí klíčInstructor
entity a zaint
označení typu se přidá otazník, který tuto vlastnost označí jako s možnou hodnotou null. Vlastnost navigace má názevAdministrator
, ale obsahuje entituInstructor
:public int? InstructorID { get; set; } public virtual Instructor Administrator { get; set; }
Oddělení může mít mnoho kurzů, takže existuje
Courses
navigační vlastnost:public virtual ICollection<Course> Courses { get; set; }
Poznámka
Podle konvence Umožňuje Entity Framework kaskádové odstranění pro cizí klíče, které nemají hodnotu null, a pro relace M:N. To může vést k cyklické kaskádové odstranění pravidel, která způsobí výjimku při pokusu o přidání migrace. Pokud byste například nedefinovali vlastnost s možnou
Department.InstructorID
hodnotou null, zobrazila by se následující zpráva o výjimce: "Referenční vztah bude mít za následek cyklický odkaz, který není povolený." Pokud vaše obchodní pravidla vyžadovalaInstructorID
, aby vlastnost neměla hodnotu null, museli byste k zakázání kaskádového odstranění relace použít následující příkaz rozhraní API fluent:
modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);
Úprava entity registrace
V souboru Models\Enrollment.cs nahraďte dříve přidaný kód 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 virtual Course Course { get; set; }
public virtual Student Student { get; set; }
}
}
Vlastnosti cizího klíče a navigace
Vlastnosti cizího klíče a navigační vlastnosti odrážejí následující relace:
Záznam registrace je pro jeden kurz, takže existuje vlastnost cizího
CourseID
klíče aCourse
vlastnost navigace:public int CourseID { get; set; } public virtual Course Course { get; set; }
Záznam registrace je pro jednoho studenta, takže existuje vlastnost cizího
StudentID
klíče aStudent
vlastnost navigace:public int StudentID { get; set; } public virtual Student Student { get; set; }
Relace M:N
Mezi entitami a Course
existuje relace Student
M:N a Enrollment
entita funguje jako tabulka spojení M:N s datovou částí v databázi. To znamená, že Enrollment
tabulka obsahuje další data kromě cizích klíčů pro spojené tabulky (v tomto případě primární klíč a Grade
vlastnost).
Následující obrázek znázorňuje, jak tyto relace vypadají v diagramu entit. (Tento diagram byl vygenerován pomocí Nástrojů Power Tools pro Entity Framework; vytvoření diagramu není součástí kurzu, používá se jenom jako ilustrace.)
Každá čára relace má na jednom konci 1 a na druhém hvězdičku (*), což označuje relaci 1:N.
Enrollment
Pokud by tabulka neobsahovala informace o známce, stačí, když bude obsahovat dva cizí klíče CourseID
a StudentID
. V takovém případě by to odpovídalo spojité tabulce M:N bez datové části (nebo tabulky čistého spojení) v databázi a vůbec byste pro ni nemuseli vytvářet třídu modelu. Entity Instructor
a Course
mají takový druh relace M:N, a jak vidíte, není mezi nimi žádná třída entit:
V databázi je však vyžadována tabulka spojení, jak je znázorněno v následujícím diagramu databáze:
Entity Framework automaticky vytvoří CourseInstructor
tabulku a vy ji čtete a aktualizujete nepřímo čtením a aktualizací Instructor.Courses
vlastností navigace a Course.Instructors
.
Diagram vztahů entit
Následující obrázek znázorňuje diagram, který nástroje Entity Framework Power Tools vytvoří pro dokončený školní model.
Kromě čar relací M:N (* až *) a čar relací 1:N (1 až *) můžete vidět čáru relace 1:0 nebo 1 (1 až 0..1) mezi Instructor
entitami a OfficeAssignment
a čárou relace nula nebo 1:N (0...1 až *) mezi entitami Instruktor a Department.
Přidání kódu do kontextu databáze
Dále přidáte nové entity do SchoolContext
třídy a přizpůsobíte některé mapování pomocí fluent api volání. Rozhraní API je "fluent", protože se často používá řetězení řady volání metod do jednoho příkazu, jako v následujícím příkladu:
modelBuilder.Entity<Course>()
.HasMany(c => c.Instructors).WithMany(i => i.Courses)
.Map(t => t.MapLeftKey("CourseID")
.MapRightKey("InstructorID")
.ToTable("CourseInstructor"));
V tomto kurzu použijete rozhraní Fluent API pouze pro mapování databáze, které nemůžete provádět s atributy. K určení většiny pravidel formátování, ověřování a mapování, která můžete provést pomocí atributů, můžete použít také rozhraní FLUENT API. Některé atributy, jako MinimumLength
je například, se nedají použít s rozhraním Fluent API. Jak už bylo zmíněno dříve, MinimumLength
nezmění schéma, použije se pouze ověřovací pravidlo na straně klienta a serveru.
Někteří vývojáři raději používají rozhraní API fluent výhradně, aby mohli udržovat třídy entit "čisté". Pokud chcete, můžete kombinovat atributy a rozhraní FLUENT API a existuje několik přizpůsobení, která se dají provést pouze pomocí rozhraní Fluent API, obecně ale doporučeným postupem je zvolit jeden z těchto dvou přístupů a používat ho co nejvíce konzistentně.
Pokud chcete přidat nové entity do datového modelu a provést mapování databáze, které jste neudělali pomocí atributů, nahraďte kód v souboru DAL\SchoolContext.cs následujícím kódem:
using ContosoUniversity.Models;
using System.Data.Entity;
using System.Data.Entity.ModelConfiguration.Conventions;
namespace ContosoUniversity.DAL
{
public class SchoolContext : DbContext
{
public DbSet<Course> Courses { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
protected override void OnModelCreating(DbModelBuilder modelBuilder)
{
modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
modelBuilder.Entity<Course>()
.HasMany(c => c.Instructors).WithMany(i => i.Courses)
.Map(t => t.MapLeftKey("CourseID")
.MapRightKey("InstructorID")
.ToTable("CourseInstructor"));
}
}
}
Příkaz new v metodě OnModelCreating konfiguruje tabulku spojení M:N:
Pro relaci M:N mezi
Instructor
entitami aCourse
určuje kód názvy tabulek a sloupců pro tabulku spojení. Code First za vás může nakonfigurovat relaci M:N bez tohoto kódu, ale pokud ji nezavoláte, dostanete výchozí názvy, napříkladInstructorInstructorID
proInstructorID
sloupec.modelBuilder.Entity<Course>() .HasMany(c => c.Instructors).WithMany(i => i.Courses) .Map(t => t.MapLeftKey("CourseID") .MapRightKey("InstructorID") .ToTable("CourseInstructor"));
Následující kód poskytuje příklad, jak byste mohli k určení vztahu mezi Instructor
entitami a OfficeAssignment
použít fluent API místo atributů:
modelBuilder.Entity<Instructor>()
.HasOptional(p => p.OfficeAssignment).WithRequired(p => p.Instructor);
Informace o tom, co příkazy fluent API dělají na pozadí, najdete v blogovém příspěvku rozhraní Fluent API .
Počáteční databáze s testovacími daty
Nahraďte kód v souboru Migrations\Configuration.cs následujícím kódem, který poskytne počáteční data pro nové entity, které jste vytvořili.
namespace ContosoUniversity.Migrations
{
using ContosoUniversity.Models;
using ContosoUniversity.DAL;
using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
internal sealed class Configuration : DbMigrationsConfiguration<SchoolContext>
{
public Configuration()
{
AutomaticMigrationsEnabled = false;
}
protected override void Seed(SchoolContext context)
{
var students = new List<Student>
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};
students.ForEach(s => context.Students.AddOrUpdate(p => p.LastName, s));
context.SaveChanges();
var instructors = new List<Instructor>
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
instructors.ForEach(s => context.Instructors.AddOrUpdate(p => p.LastName, s));
context.SaveChanges();
var departments = new List<Department>
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").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 }
};
departments.ForEach(s => context.Departments.AddOrUpdate(p => p.Name, s));
context.SaveChanges();
var courses = new List<Course>
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
Instructors = new List<Instructor>()
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID,
Instructors = new List<Instructor>()
},
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();
var officeAssignments = new List<OfficeAssignment>
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").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" },
};
officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.InstructorID, s));
context.SaveChanges();
AddOrUpdateInstructor(context, "Chemistry", "Kapoor");
AddOrUpdateInstructor(context, "Chemistry", "Harui");
AddOrUpdateInstructor(context, "Microeconomics", "Zheng");
AddOrUpdateInstructor(context, "Macroeconomics", "Zheng");
AddOrUpdateInstructor(context, "Calculus", "Fakhouri");
AddOrUpdateInstructor(context, "Trigonometry", "Harui");
AddOrUpdateInstructor(context, "Composition", "Abercrombie");
AddOrUpdateInstructor(context, "Literature", "Abercrombie");
context.SaveChanges();
var enrollments = new List<Enrollment>
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").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();
}
void AddOrUpdateInstructor(SchoolContext context, string courseTitle, string instructorName)
{
var crs = context.Courses.SingleOrDefault(c => c.Title == courseTitle);
var inst = crs.Instructors.SingleOrDefault(i => i.LastName == instructorName);
if (inst == null)
crs.Instructors.Add(context.Instructors.Single(i => i.LastName == instructorName));
}
}
}
Jak jste viděli v prvním kurzu, většina tohoto kódu jednoduše aktualizuje nebo vytvoří nové objekty entit a načte ukázková data do vlastností podle požadavků pro testování. Všimněte si ale, jak se Course
zpracovává entita, která má s entitou Instructor
vztah M:N:
var courses = new List<Course>
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID,
Instructors = new List<Instructor>()
},
...
};
courses.ForEach(s => context.Courses.AddOrUpdate(p => p.CourseID, s));
context.SaveChanges();
Při vytváření objektu Course
inicializujete Instructors
vlastnost navigace jako prázdnou kolekci pomocí kódu Instructors = new List<Instructor>()
. To umožňuje přidat Instructor
entity, které s tím Course
souvisejí, pomocí metody .Instructors.Add
Pokud byste nevytvořili prázdný seznam, nemohli byste tyto relace přidat, protože Instructors
vlastnost by byla null a neměla by metodu Add
. Do konstruktoru můžete také přidat inicializaci seznamu.
Přidání migrace
V PMC zadejte add-migration
příkaz (příkaz ještě neudělujte update-database
):
add-Migration ComplexDataModel
Pokud byste se v tomto okamžiku update-database
pokusili spustit příkaz (ještě to neuděláte), zobrazila by se následující chyba:
Příkaz ALTER TABLE byl v konfliktu s omezením cizího klíče "FK_dbo. Course_dbo. Department_DepartmentID". Ke konfliktu došlo v databázi ContosoUniversity, tabulce dbo. Department", sloupec 'DepartmentID'.
Někdy při provádění migrací s existujícími daty potřebujete do databáze vložit data zástupných procedur, aby se splnila omezení cizího klíče, a to je to, co teď musíte udělat. Vygenerovaný kód v ComplexDataModel Up
metoda přidá do tabulky cizí klíč s možnou DepartmentID
Course
hodnotou null. Vzhledem k tomu, že při spuštění kódu už v Course
tabulce existují řádky, operace selže, AddColumn
protože SQL Server neví, jakou hodnotu vložit do sloupce, která nemůže mít hodnotu null. Proto je nutné změnit kód tak, aby nový sloupec získal výchozí hodnotu, a vytvořit oddělení zástupných procedur s názvem "Temp", které bude fungovat jako výchozí oddělení. Výsledkem je, že všechny existující Course
řádky budou po spuštění metody souviset s dočasným oddělením Up
. V metodě je můžete propojit se správnými odděleními Seed
.
< Upravte soubor timestamp>_ComplexDataModel.cs, zakomentujte řádek kódu, který do tabulky Course přidává sloupec DepartmentID, a přidejte následující zvýrazněný kód (zvýrazněný je také řádek s komentářem):
CreateTable(
"dbo.CourseInstructor",
c => new
{
CourseID = c.Int(nullable: false),
InstructorID = c.Int(nullable: false),
})
.PrimaryKey(t => new { t.CourseID, t.InstructorID })
.ForeignKey("dbo.Course", t => t.CourseID, cascadeDelete: true)
.ForeignKey("dbo.Instructor", t => t.InstructorID, cascadeDelete: true)
.Index(t => t.CourseID)
.Index(t => t.InstructorID);
// Create a department for course to point to.
Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// default value for FK points to department created above.
AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false, defaultValue: 1));
//AddColumn("dbo.Course", "DepartmentID", c => c.Int(nullable: false));
AlterColumn("dbo.Course", "Title", c => c.String(maxLength: 50));
Seed
Při spuštění metody se vloží řádky do Department
tabulky a stávající řádky budou propojeny s Course
těmito novými Department
řádky. Pokud jste do uživatelského rozhraní nepřidali žádné kurzy, už nebudete potřebovat dočasné oddělení ani výchozí hodnotu ve sloupci Course.DepartmentID
. Abyste umožnili možnost, že někdo přidal kurzy pomocí aplikace, měli byste také aktualizovat Seed
kód metody, aby se zajistilo, že všechny Course
řádky (nejen řádky vložené při dřívějších spuštěních Seed
metody) mají platné DepartmentID
hodnoty, než odeberete výchozí hodnotu ze sloupce a odstraníte oddělení Temp.
Aktualizace databáze
Po dokončení úprav < souboru timestamp>_ComplexDataModel.cs zadejte update-database
v PMC příkaz pro spuštění migrace.
update-database
Poznámka
Při migraci dat a provádění změn schématu se můžou zobrazit další chyby. Pokud dojde k chybám migrace, které nemůžete vyřešit, můžete změnit název databáze v připojovacím řetězci nebo databázi odstranit. Nejjednodušším řešením je přejmenovat databázi v souboruWeb.config . Následující příklad ukazuje název změněný na CU_Test:
<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />
V případě nové databáze se žádná data nemigrují a update-database
příkaz se s větší pravděpodobností dokončí bez chyb. Pokyny k odstranění databáze najdete v tématu Odstranění databáze ze sady Visual Studio 2012.
Pokud se to nezdaří, můžete zkusit databázi znovu inicializovat zadáním následujícího příkazu v PMC:
update-database -TargetMigration:0
Otevřete databázi v Průzkumníku serveru , jako jste to udělali dříve, a rozbalte uzel Tabulky , abyste viděli, že všechny tabulky byly vytvořeny. (Pokud je Průzkumník serveru stále otevřený z dřívější doby, klikněte na tlačítko Aktualizovat .)
Nevytvořili jste pro CourseInstructor
tabulku třídu modelu. Jak bylo vysvětleno dříve, jedná se o tabulku spojení pro relaci M:N mezi Instructor
entitami a Course
.
Klikněte pravým tlačítkem myši na CourseInstructor
tabulku a vyberte Zobrazit data tabulky a ověřte, že obsahuje data v důsledku Instructor
entit, které jste přidali do Course.Instructors
vlastnosti navigace.
Získání kódu
Další materiály
Odkazy na další prostředky Entity Framework najdete v tématu ASP.NET Data Access – Doporučené zdroje informací.
Další kroky
V tomto kurzu jste:
- Přizpůsobení datového modelu
- Aktualizovaná entita Student
- Vytvořená entita Instruktor
- Vytvoření entity OfficeAssignment
- Úprava entity Course
- Vytvoření entity Oddělení
- Úprava entity registrace
- Přidání kódu do kontextu databáze
- Nasazená databáze s testovacími daty
- Přidání migrace
- Databáze se aktualizovala.
V dalším článku se dozvíte, jak číst a zobrazovat související data, která Entity Framework načte do navigačních vlastností.
Váš názor
https://aka.ms/ContentUserFeedback.
Připravujeme: V průběhu roku 2024 budeme postupně vyřazovat problémy z GitHub coby mechanismus zpětné vazby pro obsah a nahrazovat ho novým systémem zpětné vazby. Další informace naleznete v tématu:Odeslat a zobrazit názory pro