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:

School_class_diagram

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 .

Students_index_page_with_formatted_date

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ů.

Dva snímky obrazovky znázorňují rozdíly v názvu a datovém typu dvou tabulek Student.

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íč a Department 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 za int 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žadovala InstructorID , 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 a Course 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 a Student 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.)

Course_many many_relationship ze studentů do many_relationship

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:

Instruktor Course_many k many_relationship

V databázi je však vyžadována tabulka spojení, jak je znázorněno v následujícím diagramu databáze:

Instruktor Course_many k many_relationship_tables

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.

School_data_model_diagram

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 a Course 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říklad InstructorInstructorID pro InstructorID 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 DepartmentIDCourse 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 .)

Snímek obrazovky s oknem Průzkumníka serveru Složka Tabulky v kontextu školy je otevřená.

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.

Table_data_in_CourseInstructor_table

Získání kódu

Stažení dokončeného projektu

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í.