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.
Tom Dykstra
Ukázková webová aplikace Contoso University ukazuje, jak vytvářet aplikace ASP.NET MVC 4 pomocí entity Framework 5 Code First a sady Visual Studio 2012. Informace o sérii kurzů najdete v prvním kurzu série.
Poznámka:
Pokud narazíte na problém, který nemůžete vyřešit, stáhněte si dokončenou kapitolu a zkuste problém reprodukovat. Obecně můžete najít řešení problému porovnáním kódu s dokončeným kódem. Informace o některých běžných chybách a jejich řešení najdete v tématu Chyby a alternativní řešení.
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 datový model přizpůsobíte zadáním formátování, ověření a pravidel mapování databáze. Uvidíte 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:

Přizpůsobení datového modelu pomocí atributů
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 StudentID { 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)]
public DateTime EnrollmentDate { get; set; }
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 DisplayFormatkterý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 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 , pokud používá sám sebe, 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.
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 ověřovací pravidla a zprávy dat. 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 StudentID { 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 MaxLengthOnNames vytvoří soubor s názvem <timeStamp>_MaxLengthOnNames.cs. Tento soubor obsahuje kód, který aktualizuje databázi tak, aby odpovídala aktuálnímu datovému modelu. Rozhraní Entity Framework používá časové razítko před názvem souboru migrace k řazení migrací. Po vytvoření více migrací se při vyřazení databáze nebo při nasazení projektu pomocí migrace použijí všechny migrace v pořadí, v jakém byly vytvořeny.
Spusťte stránku Vytvořit a zadejte název delší než 50 znaků. Jakmile překročíte 50 znaků, ověření na straně klienta okamžitě zobrazí chybovou zprávu.

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.
Přidejte příkaz using pro System.ComponentModel.DataAnnotations.Schema a 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 StudentID { 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 (Pokud používáte Express pro web) poklikejte 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ěchto tříd entit, může dojít k chybám kompilátoru.
Vytvoření entity instruktora

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 InstructorID { 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; }
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 použití dědičnosti k odstranění této redundance.
Požadované a zobrazované atributy
Atributy vlastnosti LastName určují, že se jedná o povinné pole, že titulek textového pole by měl být "Příjmení" (místo názvu vlastnosti, což by bylo "Příjmení" bez mezery) a že hodnota nemůže být delší než 50 znaků.
[Required]
[Display(Name="Last Name")]
[StringLength(50)]
public string LastName { get; set; }
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. Atribut Required není nutný pro typy hodnot, jako jsou DateTime, int, double a float. Typy hodnot nelze přiřadit hodnotu null, takže jsou ze své podstaty povinné. Atribut Required můžete odebrat a nahradit ho parametrem minimální délky atributuStringLength:
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
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 InstructorID { 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; }
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public virtual ICollection<Course> Courses { get; set; }
public virtual OfficeAssignment OfficeAssignment { get; set; }
}
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.
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
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í. (Pří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. 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 ICollection<Course> Courses { get; set; }
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 si ukážeme, jak tento vztah nakonfigurovat 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 kurzu

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; }
[Display(Name = "Department")]
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
DepartmentIDcizí klíč aDepartmentvlastnost 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
Enrollmentsnavigační vlastnost je kolekce:public virtual ICollection<Enrollment> Enrollments { get; set; }Kurz může učit více instruktorů, takže
Instructorsnavigač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)]
public DateTime StartDate { get; set; }
[Display(Name = "Administrator")]
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.
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.
InstructorIDProto je vlastnost zahrnuta jako cizí klíč entityInstructora za označení typu se přidáintotazní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
Coursesk 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á způsobí výjimku při spuštění kódu inicializátoru. Pokud jste například vlastnost nedefinovali jako hodnotu null, při spuštění inicializátoru
Department.InstructorIDby se zobrazila následující zpráva o výjimce: "Referenční relace způsobí, že cyklický odkaz, který není povolený." Pokud by vaše obchodní pravidla vyžadovalaInstructorIDvlastnost, která není null, museli byste k zakázání kaskádového odstranění v relaci použít následující rozhraní FLUENT API:
modelBuilder.Entity().HasRequired(d => d.Administrator).WithMany().WillCascadeOnDelete(false);
Úprava entity studenta

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 StudentID { get; set; }
[StringLength(50, MinimumLength = 1)]
public string LastName { get; set; }
[StringLength(50, MinimumLength = 1, 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)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public virtual ICollection<Enrollment> Enrollments { get; set; }
}
}
Entita 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
CourseIDklíče aCoursenavigač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
StudentIDklíče aStudentnavigač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 entit znázorňující relace
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řizpůsobení datového modelu přidáním 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.)
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
Instructorentitami aCourseentitami 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, jakoInstructorInstructorIDje 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í data 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 System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.Entity.Migrations;
using System.Linq;
using ContosoUniversity.Models;
using ContosoUniversity.DAL;
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").InstructorID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").InstructorID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID }
};
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").InstructorID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").InstructorID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").InstructorID,
Location = "Thompson 304" },
};
officeAssignments.ForEach(s => context.OfficeAssignments.AddOrUpdate(p => p.Location, 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").StudentID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").StudentID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").StudentID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").StudentID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").StudentID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").StudentID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").StudentID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").StudentID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.StudentID == 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,
Department = departments.Single( s => s.Name == "Engineering"),
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 a aktualizace databáze
Z PMC zadejte add-migration příkaz:
PM> add-Migration Chap4
Pokud se v tuto chvíli pokusíte databázi aktualizovat, 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'.
<Upravte časové razítko>_Chap4.cs soubor a proveďte následující změny kódu (přidáte příkaz SQL a upravíte AddColumn příkaz):
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));
AddForeignKey("dbo.Course", "DepartmentID", "dbo.Department", "DepartmentID", cascadeDelete: true);
CreateIndex("dbo.Course", "DepartmentID");
}
public override void Down()
{
(Při přidání nového řádku nezapomeňte existující řádek okomentovat nebo odstranit AddColumn , jinak se při zadání update-database příkazu zobrazí chyba.)
Někdy když provádíte migrace 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ď děláte. Vygenerovaný kód přidá do Course tabulky nenulový DepartmentID cizí klíč. Pokud už jsou v Course tabulce řádky, když se kód spustí, operace selže, AddColumn protože SQL Server neví, jakou hodnotu má vložit do sloupce, který nemůže mít hodnotu null. Proto jste kód změnili tak, aby nový sloupec měl výchozí hodnotu, a vytvořili jste oddělení zástupných procedur s názvem "Temp", které bude fungovat jako výchozí oddělení. V důsledku toho platí, že pokud při spuštění tohoto kódu existují řádky Course , budou všechny související s oddělením Temp.
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.
Po dokončení úprav časového razítka<>_Chap4.cs souboru zadejte update-database příkaz v PMC a spusťte migraci.
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 připojovací řetězec v souboru Web.config nebo odstranit databázi. Nejjednodušším přístupem je přejmenování databáze v souboru Web.config . Změňte například název databáze na CU_test, jak je znázorněno v následujícím příkladu:
<add name="SchoolContext" connectionString="Data Source=(LocalDb)\v11.0;Initial Catalog=CU_Test;
Integrated Security=SSPI;AttachDBFilename=|DataDirectory|\CU_Test.mdf"
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.
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.

Shrnutí
Teď máte složitější datový model a odpovídající databázi. V následujícím kurzu se dozvíte více o různých způsobech přístupu k souvisejícím datům.
Odkazy na další prostředky Entity Framework najdete v mapě obsahu ASP.NET Data Accessu.