Poznámka
Přístup k této stránce vyžaduje autorizaci. Můžete se zkusit přihlásit nebo změnit adresáře.
Přístup k této stránce vyžaduje autorizaci. Můžete zkusit změnit adresáře.
V předchozích kurzech jste pracovali s jednoduchým datovým modelem složeným ze tří entit. V tomto kurzu přidáte další entity a relace a upravíte datový model zadáním formátování, ověření a pravidel mapování databáze. 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.
Po dokončení tvoří třídy entit dokončený datový model, který je zobrazený na následujícím obrázku:
V tomto kurzu se naučíte:
- Přizpůsobení datového modelu
- Aktualizace entity 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í formátování, ověřování a pravidla mapování databáze. Potom v několika následujících částech vytvoříte kompletní School
datový model přidáním atributů do tříd, které jste už vytvořili, a vytvořením nových tříd pro zbývající typy entit v modelu.
Atribut DataType
U dat zápisu studentů se na všech webových stránkách aktuálně zobrazuje čas spolu s datem, i když všechno, co vás zajímá o toto pole, 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 toho, jak to udělat, přidáte do vlastnosti ve Student
třídě atributEnrollmentDate
.
V models\Student.cs přidejte using
příkaz pro System.ComponentModel.DataAnnotations
obor názvů a přidejte DataType
a DisplayFormat
atributy do EnrollmentDate
vlastnosti, 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, nikoli datum a čas. Výčet DataType poskytuje mnoho datových typů, jako je datum, čas, telefonní číslo, měna, EmailAddress a další. Atribut DataType
může také aplikaci povolit, aby automaticky poskytovala funkce specifické pro typ. Odkaz lze například mailto:
vytvořit pro DataType.EmailAddress a pro DataType.Date lze v prohlížečích podporujících HTML5 zadat selektor data. Atributy DataType generují atributy HTML 5 data- (vyslovuje se pomlčka), které můžou prohlížeče HTML 5 pochopit. Atributy DataType neposkytují žádné ověření.
DataType.Date
nezadá formát zobrazeného data. Ve výchozím nastavení se datové pole 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 zadané formátování se má použít také při zobrazení hodnoty v textovém poli pro úpravy. (U některých polí to možná nechcete – například pro hodnoty měny, nemusíte 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, se DisplayFormat
kterými se nedostanete:
- Prohlížeč může povolit funkce HTML5 (například zobrazení ovládacího prvku 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č 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 Brad Wilson's ASP.NET MVC 2 Templates. (I když je napsaný pro MVC 2, tento článek se stále vztahuje na aktuální verzi ASP.NET MVC.)
Pokud atribut používáte DataType
s polem kalendářního data, musíte atribut zadat DisplayFormat
také, aby se pole vykreslovaly správně v prohlížečích Chrome. Další informace najdete v tomto vlákně StackOverflow.
Další informace o tom, jak zpracovat jiné formáty kalendářních dat v MVC, přejděte do MVC 5 Úvod: Zkoumání metod úprav a zobrazení pro úpravy a hledání na stránce "internationalization".
Znovu spusťte stránku Index studenta a všimněte si, že časy se už nezobrazují pro data registrace. Totéž platí pro každé zobrazení, které model používá Student
.
The StringLengthAttribute
Pomocí atributů můžete také zadat pravidla ověření dat a chybové zprávy ověření. Atribut StringLength nastaví maximální délku v databázi a poskytuje ověřování na straně klienta a na straně serveru pro ASP.NET MVC. Můžete také zadat minimální délku řetězce v tomto atributu, ale minimální hodnota nemá žádný vliv na schéma databáze.
Předpokládejme, že chcete zajistit, aby uživatelé nezadáli více než 50 znaků pro jméno. Chcete-li přidat toto omezení, přidejte stringLength atributy do LastName
a FirstMidName
vlastnosti, 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ávání prázdných znaků pro jméno. Atribut RegularExpression můžete 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""'\s-]*$")]
Atribut MaxLength poskytuje podobné funkce jako StringLength atribut, ale neposkytuje ověřování 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).
Databázový model se změnil způsobem, který vyžaduje změnu ve schématu databáze, a Entity Framework to zjistil. Migrace použijete k aktualizaci schématu bez ztráty dat přidaných do databáze pomocí uživatelského rozhraní. Pokud jste změnili data vytvořená Seed
metodou, která se změní zpět do původního stavu kvůli metodě AddOrUpdate , kterou v Seed
metodě používáte. (AddOrUpdate je ekvivalentní operaci 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.
Rozhraní Entity Framework používá časové razítko před názvem souboru migrace k řazení migrací. Před spuštěním update-database
příkazu můžete vytvořit několik migrací a potom se všechny migrace použijí v pořadí, v jakém byly vytvořeny.
Spusťte stránku Vytvořit a zadejte název delší než 50 znaků. Když kliknete na Vytvořit, zobrazí se na straně klienta chybová zpráva: Pole LastName musí být řetězec s maximální délkou 50.
Atribut sloupce
Pomocí atributů můžete také řídit, jak se třídy a vlastnosti mapují na databázi. Předpokládejme, že jste použili název FirstMidName
pole pro jméno, protože pole může obsahovat také prostřední název. Ale chcete, aby byl databázový sloupec pojmenován FirstName
, protože uživatelé, kteří budou psát ad hoc dotazy na databázi, jsou zvyklí na tento název. K vytvoření tohoto mapování můžete použít Column
atribut.
Atribut Column
určuje, že při vytvoření databáze bude sloupec Student
tabulky, která se mapuje na FirstMidName
vlastnost, pojmenována FirstName
. Jinými slovy, když váš kód odkazuje Student.FirstMidName
, data pocházejí nebo se aktualizují ve FirstName
sloupci Student
tabulky. Pokud nezadáte názvy sloupců, mají stejný název jako název vlastnosti.
V souboru Student.cs přidejte using
příkaz pro System.ComponentModel.DataAnnotations.Schema a přidejte atribut názvu sloupce do FirstMidName
vlastnosti, 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 se neshoduje s databází. 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 návrháře tabulky Student poklikáním na tabulku Student.
Následující obrázek znázorňuje původní název sloupce, který byl před prvními dvěma migracemi. Kromě názvu sloupce, ze FirstMidName
který se mění na FirstName
, se dva sloupce názvů změnily z MAX
délky na 50 znaků.
Změny mapování databáze můžete provádět 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.
Aktualizace entity Student
V models\Student.cs nahraďte kód, který jste přidali dříve, 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 vytvoří požadované vlastnosti názvu pole. Pro Required attribute
typy hodnot, jako jsou DateTime, int, double a float, není potřeba. Typy 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 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
Použijte atribut pro úplnou kontrolu nad řetězcem.
Atribut zobrazení
Atribut Display
určuje, že titulek textových polí by měl být "Jméno", "Příjmení", "Příjmení", "Celé jméno" a "Datum registrace" místo názvu vlastnosti v každé instanci (který neobsahuje mezeru rozdělující slova).
Počítaná vlastnost FullName
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dvou dalších vlastností. Proto má pouze přístupové get
objekty a v databázi se nevygeneruje žádný FullName
sloupec.
Vytvoření entity Instruktor
Vytvořte modely\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í je v entitách Student
Instructor
stejné. V kurzu Implementace dědičnosti dále v této sérii refaktorujete tento kód, 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; }
}
Vlastnosti navigace Courses a OfficeAssignment
Vlastnosti Courses
a OfficeAssignment
vlastnosti jsou navigační vlastnosti. Jak bylo vysvětleno dříve, jsou obvykle 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, jeho typ musí implementovat ICollection<T> Rozhraní. Například IList<T> kvalifikuje, ale ne IEnumerable<T>, protože IEnumerable<T>
neimplementuje Add.
Instruktor může učit 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 v null
případě, že není přiřazena žádná kancelář).
public virtual OfficeAssignment OfficeAssignment { get; set; }
Vytvoření entity OfficeAssignment
Vytvořte modely\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é kopírování a vkládání chyb, které kompilátor dokáže zachytit.
Klíčový atribut
Mezi entitami OfficeAssignment
existuje relace Instructor
1:0 nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, kterému je přiřazen, a proto je jeho primárním klíčem i jeho cizí klíč k entitě Instructor
. Entity Framework ale nedokáže automaticky rozpoznat InstructorID
jako primární klíč této entity, protože jeho název neodpovídá ID
konvenci pojmenování názvů nebo názvů třídID
. Key
Atribut se proto používá k identifikaci jako klíče:
[Key]
[ForeignKey("Instructor")]
public int InstructorID { get; set; }
Atribut můžete použít Key
také v případě, že entita má svůj vlastní primární klíč, ale chcete pojmenovat vlastnost něco jiného než classnameID
nebo ID
. Ef ve výchozím nastavení považuje klíč za negenerovaný databází, protože sloupec slouží k identifikaci relace.
Atribut ForeignKey
Pokud je mezi dvěma entitami relace 1:1 nebo 1:1 relace mezi dvěma entitami (například mezi OfficeAssignment
a Instructor
), ef nemůže zjistit, který konec relace je objekt zabezpečení a který konec je závislý. Relace 1:1 mají v každé třídě odkazovou navigační vlastnost do druhé třídy. Atribut ForeignKey lze použít u závislé třídy k navázání 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 pro fluent relace nebo datových poznámek.
Později v tomto kurzu se dozvíte, jak nakonfigurovat tento vztah s rozhraním FLUENT API.
Vlastnost navigace instruktora
Entita Instructor
má vlastnost navigace s možnou OfficeAssignment
hodnotou null (protože instruktor nemusí mít přiřazení kanceláře) a OfficeAssignment
entita má nenulovou navigační vlastnost (protože přiřazení kanceláře nemůže existovat bez instruktora – InstructorID
je nenulovéInstructor
). Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita bude mít ve své navigační vlastnosti odkaz na druhou entitu.
Do vlastnosti navigace instruktora můžete zadat [Required]
atribut, který určuje, že musí existovat související instruktor, ale nemusíte to udělat, protože cizí klíč InstructorID (což je také klíč k této tabulce) není nullable.
Úprava entity Course
V části 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á navigační vlastnost. Entity Framework nevyžaduje, abyste do datového modelu přidali vlastnost cizího klíče, pokud máte navigační vlastnost pro související entitu. SYSTÉM EF automaticky vytvoří cizí klíče v databázi všude, kde jsou potřeba. Cizí klíč v datovém modelu ale může zjednodušit a zefektivnit aktualizace. Pokud například načtete entitu kurzu, která se má upravit, má entita hodnotu null, Department
pokud ji nenačtete, takže když entitu kurzu aktualizujete, musíte nejprve entitu Department
načíst. Pokud je vlastnost DepartmentID
cizího klíče součástí datového modelu, nemusíte před aktualizací načíst entitu Department
.
Atribut DatabaseGenerated
Atribut DatabaseGenerated s parametrem None ve CourseID
vlastnosti určuje, že hodnoty primárního klíče jsou poskytovány uživatelem místo generování databáze.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení entity Framework předpokládá, že databáze generuje hodnoty primárního klíče. To je to, co chcete ve většině scénářů. U Course
entit ale použijete uživatelem zadané číslo kurzu, například 1000 řad pro jedno oddělení, 2000 řad 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í relace:
Kurz je přiřazen k jednomu oddělení, takže z výše uvedených důvodů existuje
DepartmentID
cizí klíč aDepartment
vlastnost navigace.public int DepartmentID { get; set; } public virtual 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 virtual ICollection<Enrollment> Enrollments { get; set; }
Kurz může učit 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 modely\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 sloupce
Dříve jste použili atribut Sloupec 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 tak, aby se sloupec definoval pomocí typu peněz SQL Serveru v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obvykle nevyžaduje, protože Entity Framework obvykle vybírá příslušný datový typ SQL Serveru na základě typu CLR, který definujete pro vlastnost. Typ CLR decimal
se mapuje na typ SQL Serveru decimal
. V tomto případě ale víte, že sloupec bude obsahovat částky měny a datový typ peněz je vhodnější pro tento typ. Další informace o datových typech CLR a o tom, jak odpovídají datovým typům SQL Serveru, 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 instruktorem.
InstructorID
Proto je vlastnost zahrnuta jako cizí klíč entityInstructor
a za označení typu se přidáint
otazník, který vlastnost označí jako nullable. Navigační vlastnost je pojmenovanáAdministrator
, ale obsahuje entituInstructor
:public int? InstructorID { get; set; } public virtual Instructor Administrator { get; set; }
Oddělení může mít mnoho kurzů, takže je
Courses
k dispozici 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 bez hodnoty null a pro relace M:N. Výsledkem může být cyklický kaskádový odstranění pravidel, která při pokusu o přidání migrace způsobí výjimku. Pokud jste například vlastnost nedefinovali jako možnou
Department.InstructorID
hodnotu null, zobrazila by se následující zpráva o výjimce: "Odkazovací relace bude mít za následek cyklický odkaz, který není povolený." Pokud vaše obchodní pravidla vyžadujíInstructorID
, aby vlastnost neměla hodnotu null, museli byste k zakázání kaskádového odstranění v relaci použít následující příkaz rozhraní API fluent:
modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);
Úprava entity registrace
V models\Enrollment.cs nahraďte kód, který jste přidali dříve, 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 vlastnosti navigace odrážejí následující relace:
Záznam registrace je určený pro jeden kurz, takže vlastnost cizího
CourseID
klíče aCourse
navigační vlastnost:public int CourseID { get; set; } public virtual Course Course { get; set; }
Záznam registrace je určený pro jednoho studenta, takže vlastnost cizího
StudentID
klíče aStudent
navigační vlastnost:public int StudentID { get; set; } public virtual Student Student { get; set; }
Relace M:N
Mezi entitami a entitami existuje relace Student
Course
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í Entity Framework Power Tools; vytvoření diagramu není součástí kurzu, jenom se tady používá jako obrázek.)
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, bude muset obsahovat pouze dva cizí klíče CourseID
a StudentID
. V takovém případě by to odpovídalo tabulce spojení M:N bez datové části (nebo tabulky čistého spojení) v databázi a nemuseli byste pro ni vůbec vytvářet třídu modelu. Course
Entity Instructor
mají takový typ relace M:N a jak vidíte, mezi nimi neexistuje žádná třída entit:
V databázi se vyžaduje 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í a Course.Instructors
navigace.
Diagram vztahu mezi entitami
Následující obrázek znázorňuje diagram, který nástroje Entity Framework Power Tools vytvářejí pro dokončený školní model.
Kromě čar relací M:N (* k *) a čar relací 1:N (1 až *) se zde můžete podívat na čáru relace 1:0 nebo 1 (1 až 0..1) mezi Instructor
OfficeAssignment
entitami relace 1:N a 1:N (0...1 až *) mezi entitami instruktora a oddělení.
Přidání kódu do kontextu databáze
Dále do třídy přidáte nové entity SchoolContext
a přizpůsobíte některé mapování pomocí volání rozhraní API fluent. Rozhraní API je "fluent", protože je často používáno řetězcem řady volání metod do jednoho příkazu, jak je znázorněno 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 formátování, ověřování a mapování pravidel, která můžete provádět pomocí atributů, ale můžete také použít rozhraní API fluent. Některé atributy, jako MinimumLength
je například nelze použít s rozhraním FLUENT API. Jak už jsme zmínili dříve, MinimumLength
nezmění schéma, použije pouze ověřovací pravidlo na straně klienta a serveru.
Někteří vývojáři raději používají výhradně rozhraní API fluent, aby mohli třídy entit udržovat "čisté". Pokud chcete, můžete kombinovat atributy a rozhraní API fluent a existuje několik přizpůsobení, která se dají provést pouze pomocí rozhraní FLUENT API, ale obecně se doporučuje zvolit jeden z těchto dvou přístupů a použít ho co nejvíce konzistentně.
Pokud chcete do datového modelu přidat nové entity a provést mapování databáze, které jste neudělali pomocí atributů, nahraďte kód v 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"));
}
}
}
Nový příkaz v metodě OnModelCreating konfiguruje tabulku spojení M:N:
Pro relaci M:N mezi
Instructor
entitami aCourse
entitami kód určuje názvy tabulek a sloupců pro tabulku spojení. Code First může nakonfigurovat relaci M:N pro vás bez tohoto kódu, ale pokud ji nezavoláte, zobrazí se výchozí názvy, jakoInstructorInstructorID
je sloupecInstructorID
.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 místo atributů použít rozhraní FLUENT API k určení vztahu mezi entitami Instructor
OfficeAssignment
:
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, abyste mohli poskytnout 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 entity a načte ukázková data do vlastností podle potřeby pro testování. Všimněte si ale, jak se entita Course
, která má vztah M:N s entitou Instructor
, zpracovává:
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
navigační vlastnost jako prázdnou kolekci pomocí kódu Instructors = new List<Instructor>()
. Díky tomu můžete pomocí metody přidat Instructor
entity, které s tím Course
Instructors.Add
souvisejí. Pokud jste nevytvořili prázdný seznam, nebudete moct tyto relace přidat, protože Instructors
vlastnost by měla hodnotu null a neměla by metodu Add
. Můžete také přidat inicializaci seznamu do konstruktoru.
Přidání migrace
V PMC zadejte add-migration
příkaz (zatím tento příkaz neudělávejte update-database
):
add-Migration ComplexDataModel
Pokud jste se v tomto okamžiku update-database
pokusili spustit příkaz (zatím ho neudělávejte), zobrazí se následující chyba:
Příkaz ALTER TABLE byl v konfliktu s omezením CIZÍ KLÍČ "FK_dbo. Course_dbo. Department_DepartmentID". Ke konfliktu došlo v databázi ContosoUniversity, tabulce dbo. Department", column 'DepartmentID'.
Někdy při provádění migrací s existujícími daty musíte do databáze vložit data zástupných procedur, aby vyhovovala omezením cizího klíče, a to je to, co teď musíte udělat. Vygenerovaný kód v ComplexDataModel Up
metoda přidá do Course
tabulky non-nullable DepartmentID
cizí klíč. Vzhledem k tomu, že při spuštění kódu už jsou v Course
tabulce řádky, operace selže, AddColumn
protože SQL Server neví, jakou hodnotu má vložit do sloupce, který nemůže mít hodnotu null. Proto musíte změnit kód tak, aby nový sloupec měl 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 důsledku toho budou existující Course
řádky po spuštění metody souviset s oddělením Up
Temp. Můžete je spojit se správnými odděleními v Seed
metodě.
<Upravte časové razítko>_ComplexDataModel.cs soubor, zakomentujte řádek kódu, který přidá sloupec DepartmentID do tabulky Course, a přidejte následující zvýrazněný kód (zvýrazněný je také řádek s komentáři):
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));
Když se Seed
metoda spustí, vloží řádky do Department
tabulky a bude s těmito novými Department
řádky souvisetCourse
. Pokud jste do uživatelského rozhraní nepřidali žádné kurzy, už nebudete potřebovat oddělení Temp nebo výchozí hodnotu ve sloupci Course.DepartmentID
. Pokud chcete umožnit, aby 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é dříve spuštěním 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 časového razítka<>_ComplexDataModel.cs souboru zadejte update-database
příkaz v PMC a spusťte migraci.
update-database
Poznámka:
Při migraci dat a provádění změn schématu je možné získat 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í řetězec nebo databázi odstranit. Nejjednodušším přístupem je přejmenování databáze v souboru Web.config . Následující příklad ukazuje název, který se změnil na CU_Test:
<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;Integrated Security=SSPI;"
providerName="System.Data.SqlClient" />
U nové databáze se nemigrují žádná data a update-database
příkaz se s mnohem větší pravděpodobností dokončí bez chyb. Pokyny k odstranění databáze najdete v tématu Jak odstranit databázi ze sady Visual Studio 2012.
Pokud to selže, můžete zkusit znovu inicializovat databázi zadáním následujícího příkazu do PMC:
update-database -TargetMigration:0
Otevřete databázi v Průzkumníku serveru, jak jste to udělali dříve, a rozbalte uzel Tabulky , abyste viděli, že všechny tabulky byly vytvořeny. (Pokud stále máte Průzkumník serveru se otevře dříve, 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 entitami Instructor
a Course
entitami.
Klikněte pravým tlačítkem myši na CourseInstructor
tabulku a vyberte Zobrazit data tabulky a ověřte, že obsahuje data v tabulce v důsledku Instructor
entit, které jste přidali do Course.Instructors
navigační vlastnosti.
Získání kódu
Další materiály
Odkazy na další prostředky Entity Framework najdete v ASP.NET přístupu k datům – doporučené zdroje informací.
Další kroky
V tomto kurzu se naučíte:
- Přizpůsobení datového modelu
- Aktualizovaná entita Student
- Vytvořená entita Instruktor
- Vytvoření entity OfficeAssignment
- Změna entity Course
- Vytvoření entity Oddělení
- Změna 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
V dalším článku se dozvíte, jak číst a zobrazovat související data, která Entity Framework načítá do navigačních vlastností.