Události
Mistrovství Světa v Power BI DataViz
14. 2. 16 - 31. 3. 16
Se 4 šance na vstup, můžete vyhrát konferenční balíček a udělat to na LIVE Grand Finale v Las Vegas
Další informaceTento prohlížeč se už nepodporuje.
Upgradujte na Microsoft Edge, abyste mohli využívat nejnovější funkce, aktualizace zabezpečení a technickou podporu.
Tom Dykstra, Jeremy Likness a Jon P Smith
Webová aplikace Contoso University ukazuje, jak vytvářet Razor webové aplikace Pages pomocí EF Core sady Visual Studio. Informace o sérii kurzů najdete v prvním kurzu.
Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte si dokončenou aplikaci a porovnejte tento kód s tím, co jste vytvořili podle kurzu.
Předchozí kurzy pracovaly se základním datovým modelem složeným ze tří entit. V tomto kurzu:
Dokončený datový model je znázorněn na následujícím obrázku:
Následující diagram databáze byl proveden pomocí dataedo:
Vytvoření databázového diagramu pomocí Dataedo:
V předchozím diagramu CourseInstructor
Dataedo je tabulka spojení vytvořená rozhraním Entity Framework. Další informace najdete v tématu M:N
Nahraďte kód v souboru Models/Student.cs
následujícím kódem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Předchozí kód přidá FullName
vlastnost a přidá do existujících vlastností následující atributy:
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dvou dalších vlastností. FullName
nejde nastavit, takže má jenom přístupový objekt get. V databázi se nevytvořil žádný FullName
sloupec.
[DataType(DataType.Date)]
U dat zápisu studentů se na všech stránkách aktuálně zobrazuje čas dne spolu s datem, i když je relevantní jenom datum. Pomocíatributůch
Atribut DataType určuje datový typ, který je konkrétnější než vnitřní typ databáze. V tomto případě by se mělo zobrazit jenom datum, nikoli datum a čas. Výčet DataType poskytuje mnoho datových typů, jako je datum, čas, telefonní číslo, měna, EmailAddress atd. Atribut DataType
může také aplikaci povolit, aby automaticky poskytovala funkce specifické pro typ. Příklad:
mailto:
se automaticky vytvoří pro DataType.EmailAddress
.DataType.Date
selektor data.Atribut DataType
generuje atributy HTML 5 data-
(vyslovuje se pomlčka dat). Atributy DataType
neposkytují ověření.
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
nezadá formát zobrazeného data. Ve výchozím nastavení se pole data zobrazí podle výchozích formátů založených na souboru CultureInfo serveru.
Atribut DisplayFormat
se používá k explicitní zadání formátu data. Nastavení ApplyFormatInEditMode
určuje, že formátování se má použít také v uživatelském rozhraní pro úpravy. Některá pole by neměla používat ApplyFormatInEditMode
. Symbol měny by se například neměl zobrazovat v textovém poli pro úpravy.
Atribut DisplayFormat
lze použít sám. Obecně je vhodné použít DataType
atribut s atributem DisplayFormat
. Atribut DataType
vyjadřuje sémantiku dat na rozdíl od toho, jak je vykreslit na obrazovce. Atribut DataType
poskytuje následující výhody, které nejsou k dispozici v DisplayFormat
:
Další informace najdete v dokumentaci ke vstupním> pomocným <rutině značek.
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Pomocí atributů je možné zadat pravidla ověření dat a chybové zprávy ověřování. Atribut StringLength určuje minimální a maximální délku znaků, které jsou povoleny v datovém poli. Zobrazený kód omezuje názvy maximálně na 50 znaků. Příklad, který nastaví minimální délku řetězce, se zobrazí později.
Atribut StringLength
také poskytuje ověřování na straně klienta a na straně serveru. Minimální hodnota nemá žádný vliv na schéma databáze.
Atribut StringLength
nezabrání uživateli v zadávání prázdných znaků pro jméno. Atribut RegularExpression lze použít k použití omezení pro vstup. Například následující kód vyžaduje, aby první znak byl velkým písmenem a zbývající znaky byly abecední:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
V SQL Serveru Průzkumník objektů (SSOX) otevřete návrháře tabulek Student poklikáním na tabulku Student.
Předchozí obrázek znázorňuje schéma tabulky Student
. Pole názvů mají typ nvarchar(MAX)
. Při vytvoření a použití migrace později v tomto kurzu se pole názvů stanou nvarchar(50)
výsledkem atributů délky řetězce.
[Column("FirstName")]
public string FirstMidName { get; set; }
Atributy mohou řídit, jak se třídy a vlastnosti mapují na databázi. Student
V modelu Column
se atribut používá k mapování názvu FirstMidName
vlastnosti na "FirstName" v databázi.
Při vytváření databáze se názvy vlastností v modelu používají pro názvy sloupců (s výjimkou použití atributu Column
). Model Student
používá FirstMidName
pro pole s křestní jméno, protože pole může obsahovat také prostřední název.
S atributem [Column]
Student.FirstMidName
se v datovém modelu mapuje na FirstName
sloupec Student
tabulky. Přidání atributu Column
změní model backing the SchoolContext
. Model, který SchoolContext
už databázi nepodporuje, neodpovídá. Tento nesrovnalosti vyřešíte přidáním migrace později v tomto kurzu.
[Required]
Atribut Required
vytvoří požadované vlastnosti názvu. Atribut Required
není potřeba pro typy bez hodnoty null, jako jsou například typy hodnot (například DateTime
, int
a double
). Typy, které nemohou být null, se automaticky považují za povinná pole.
Atribut Required
musí být použit s vynuceným vynuceným atributem MinimumLength
MinimumLength
.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength
a Required
povolte, aby ověření splňovalo prázdné znaky. RegularExpression
Atribut použijte pro úplnou kontrolu nad řetězcem.
[Display(Name = "Last Name")]
Atribut Display
určuje, že titulek textových polí by měl být "Jméno", "Příjmení", "Celé jméno" a "Datum registrace". Výchozí titulky neměly žádné mezery, které by dělily slova, například "Příjmení".
Spusťte aplikaci a přejděte na stránku Studenti. Vyvolá se výjimka. Atribut [Column]
způsobí, že EF očekává nalezení sloupce s názvem FirstName
, ale název sloupce v databázi je stále FirstMidName
.
Chybová zpráva je podobná následujícímu příkladu:
SqlException: Invalid column name 'FirstName'.
There are pending model changes
Pending model changes are detected in the following:
SchoolContext
V PMC zadejte následující příkazy pro vytvoření nové migrace a aktualizaci databáze:
Add-Migration ColumnFirstName
Update-Database
První z těchto příkazů vygeneruje následující zprávu upozornění:
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Upozornění se vygeneruje, protože pole názvů jsou teď omezená na 50 znaků. Pokud název v databázi obsahoval více než 50 znaků, ztratí se 51 až poslední znak.
Otevřete tabulku Student v SSOX:
Před použitím migrace byly sloupce názvů typu nvarchar(MAX). Sloupce názvů jsou nyní nvarchar(50)
. Název sloupce se změnil z FirstMidName
na FirstName
.
Poznámka
V následujících částech sestavení aplikace v některých fázích generuje chyby kompilátoru. Pokyny určují, kdy se má aplikace sestavit.
Vytvořte Models/Instructor.cs
pomocí následujícího kódu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<Course> Courses { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
Více atributů může být na jednom řádku. Atributy HireDate
lze zapsat takto:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Vlastnosti Courses
a OfficeAssignment
vlastnosti jsou navigační vlastnosti.
Instruktor může učit libovolný počet kurzů, takže Courses
je definován jako kolekce.
public ICollection<Course> Courses { get; set; }
Instruktor může mít maximálně jednu kancelář, takže OfficeAssignment
vlastnost obsahuje jednu OfficeAssignment
entitu. OfficeAssignment
má hodnotu null, pokud není přiřazena žádná kancelář.
public OfficeAssignment OfficeAssignment { get; set; }
Vytvořte Models/OfficeAssignment.cs
pomocí následujícího kódu:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor Instructor { get; set; }
}
}
Atribut [Key]
slouží k identifikaci vlastnosti jako primárního klíče (PK), pokud je název vlastnosti něco jiného než classnameID
nebo ID
.
Mezi entitami Instructor
OfficeAssignment
existuje vztah 1:1 nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, ke kterému je přiřazený. Pk OfficeAssignment
je také jeho cizí klíč (FK) pro entitu Instructor
. Relace 1:0 nebo 1 nastane v případě, že pk v jedné tabulce je PK i FK v jiné tabulce.
EF Core nemůže automaticky rozpoznat InstructorID
jako PK, OfficeAssignment
protože InstructorID
neodpovídá konvenci pojmenování ID nebo classnameID. Proto se Key
atribut používá k identifikaci InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Ve výchozím nastavení považuje klíč za negenerovaný databází, EF Core protože sloupec slouží k identifikaci relace. Další informace najdete v tématu Klíče EF.
Vlastnost Instructor.OfficeAssignment
navigace může mít hodnotu null, protože pro daného instruktora nemusí být OfficeAssignment
řádek. Instruktor nemusí mít zadání kanceláře.
Vlastnost OfficeAssignment.Instructor
navigace bude mít vždy entitu instruktora, protože typ cizího klíče InstructorID
je int
, typ hodnoty, který není null. Zadání kanceláře nemůže existovat bez instruktora.
Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita má ve své navigační vlastnosti odkaz na druhou entitu.
Aktualizujte Models/Course.cs
následujícím kódem:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<Instructor> Instructors { get; set; }
}
}
Entita Course
má vlastnost DepartmentID
cizího klíče (FK). DepartmentID
odkazuje na související Department
entitu. Entita Course
má Department
navigační vlastnost.
EF Core nevyžaduje vlastnost cizího klíče pro datový model, pokud má model navigační vlastnost pro související entitu. EF Core v databázi automaticky vytváří sady FK, kdekoli jsou potřeba. EF Core vytvoří stínové vlastnosti pro automaticky vytvořené sady FK. Explicitní zahrnutí FK do datového modelu však může zjednodušit a zefektivnit aktualizace. Představte si například model, ve kterém není zahrnuta vlastnost DepartmentID
FK. Když se entita kurzu načte pro úpravy:
Department
je null
, pokud není explicitně načtena.Department
nejprve načtena.Pokud je vlastnost DepartmentID
FK součástí datového modelu, není nutné před aktualizací načíst entitu Department
.
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
určuje, že PK je poskytována aplikací místo generování databáze.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení se předpokládá, EF Core že databáze generuje hodnoty PK. Vygenerovaná databáze je obecně nejlepší přístup. Pro Course
entity určuje uživatel PK. Například číslo kurzu, například 1000 řad pro matematické oddělení, 2000 série pro anglické oddělení.
Atribut DatabaseGenerated
lze také použít k vygenerování výchozích hodnot. Databáze může například automaticky vygenerovat pole kalendářního data pro zaznamenání data vytvoření nebo aktualizace řádku. Další informace naleznete v tématu Vygenerované vlastnosti.
Vlastnosti cizího klíče (FK) a vlastnosti navigace v entitě Course
odrážejí následující relace:
Kurz je přiřazen k jednomu oddělení, takže je k DepartmentID
dispozici FK a Department
navigační vlastnost.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kurz může mít v něm zaregistrovaný libovolný počet studentů, takže Enrollments
navigační vlastnost je kolekce:
public ICollection<Enrollment> Enrollments { get; set; }
Kurz může učit více instruktorů, takže Instructors
navigační vlastnost je kolekce:
public ICollection<Instructor> Instructors { get; set; }
Vytvořte Models/Department.cs
pomocí následujícího kódu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}",
ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Dříve byl Column
atribut použit ke změně mapování názvů sloupců. V kódu entity Column
se atribut používá ke změně mapování datového Department
typu SQL. Sloupec Budget
se definuje pomocí typu peněz SQL Serveru v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obecně nevyžaduje. EF Core zvolí odpovídající datový typ SQL Serveru na základě typu CLR pro vlastnost. Typ CLR decimal
se mapuje na typ SQL Serveru decimal
. Budget
je pro měnu a datový typ peněz je vhodnější pro měnu.
Vlastnosti FK a navigace odrážejí následující relace:
InstructorID
Proto je vlastnost zahrnuta jako FK entityInstructor
.Navigační vlastnost je pojmenovaná Administrator
, ale obsahuje entitu Instructor
:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
V ?
předchozím kódu určuje vlastnost nullable.
Oddělení může mít mnoho kurzů, takže je zde vlastnost Navigace v kurzech:
public ICollection<Course> Courses { get; set; }
Podle konvence EF Core umožňuje kaskádové odstranění pro nenulové FK a pro relace M:N. Toto výchozí chování může vést k cyklický kaskádový odstranění pravidel. Cyklické kaskádové odstranění pravidel způsobí výjimku při přidání migrace.
Pokud Department.InstructorID
byla například vlastnost definována jako nenulová, EF Core nakonfigurovala by kaskádové pravidlo odstranění. V takovém případě by se oddělení odstranilo, když se odstraní instruktor přiřazený jako správce. V tomto scénáři by pravidlo omezení dávalo větší smysl. Následující fluentové rozhraní API by nastavilo pravidlo omezení a zakázalo kaskádové odstranění.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Záznam o registraci je určen pro jeden kurz pořízený jedním studentem.
Aktualizujte Models/Enrollment.cs
následujícím kódem:
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Vlastnosti FK a navigační vlastnosti odrážejí následující relace:
Záznam registrace je pro jeden kurz, takže vlastnost CourseID
FK a Course
navigační vlastnost:
public int CourseID { get; set; }
public Course Course { get; set; }
Záznam registrace je určený pro jednoho studenta, takže vlastnost StudentID
FK a Student
navigační vlastnost:
public int StudentID { get; set; }
public Student Student { get; set; }
Mezi entitami Student
Course
a entitami existuje vztah M:N. Entita Enrollment
funguje jako tabulka spojení M:N s datovou částí v databázi. Datová část znamená, že Enrollment
tabulka obsahuje další data kromě FK pro spojené tabulky. V entitě Enrollment
jsou další data kromě FK PK a Grade
.
Následující obrázek znázorňuje, jak tyto relace vypadají v diagramu entit. (Tento diagram byl generován pomocí EF Power Tools pro EF 6.x. Vytvoření diagramu není součástí kurzu.)
Každá čára relace má na jednom konci 1 a hvězdičku (*) na druhé, která označuje relaci 1:N.
Enrollment
Pokud tabulka neobsahuje informace o známce, musí obsahovat pouze dvě sady FK CourseID
a StudentID
. Tabulka spojení M:N bez datové části se někdy označuje jako čistá join table (PJT).
Course
Entity Instructor
mají relaci M:N pomocí PJT.
Aktualizujte Data/SchoolContext.cs
následujícím kódem:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable(nameof(Course))
.HasMany(c => c.Instructors)
.WithMany(i => i.Courses);
modelBuilder.Entity<Student>().ToTable(nameof(Student));
modelBuilder.Entity<Instructor>().ToTable(nameof(Instructor));
}
}
}
Předchozí kód přidá nové entity a nakonfiguruje vztah M:N mezi entitami Instructor
a Course
entitami.
Metoda OnModelCreating
v předchozím kódu používá rozhraní API fluent ke konfiguraci EF Core chování. Rozhraní API se nazývá "fluent", protože se často používá řetězcem řady volání metod do jednoho příkazu. Následující kód je příkladem rozhraní FLUENT API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
V tomto kurzu se rozhraní API fluent používá pouze pro mapování databáze, které se nedají provádět s atributy. Rozhraní API fluent však může určovat většinu pravidel formátování, ověřování a mapování, která se dají provádět s atributy.
Některé atributy, jako MinimumLength
je například nelze použít s rozhraním FLUENT API. MinimumLength
nezmění schéma, použije pouze ověřovací pravidlo minimální délky.
Někteří vývojáři raději používají rozhraní API fluent výhradně, aby mohli třídy entit udržovat čisté. Atributy a fluentské rozhraní API je možné kombinovat. Existují některé konfigurace, které je možné provádět pouze s rozhraním FLUENT API, například zadáním složené infrastruktury veřejných klíčů. Existují některé konfigurace, které je možné provádět pouze s atributy (MinimumLength
). Doporučený postup pro používání rozhraní API nebo atributů fluent:
Některé atributy použité v tomto kurzu se používají pro:
MinimumLength
).HasKey
).[StringLength(50)]
).Další informace o atributech a rozhraní API fluent naleznete v tématu Metody konfigurace.
Aktualizujte kód v Data/DbInitializer.cs
:
using ContosoUniversity.Models;
using System;
using System.Collections.Generic;
using System.Linq;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var alexander = new Student
{
FirstMidName = "Carson",
LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01")
};
var alonso = new Student
{
FirstMidName = "Meredith",
LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var anand = new Student
{
FirstMidName = "Arturo",
LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var barzdukas = new Student
{
FirstMidName = "Gytis",
LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var li = new Student
{
FirstMidName = "Yan",
LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01")
};
var justice = new Student
{
FirstMidName = "Peggy",
LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01")
};
var norman = new Student
{
FirstMidName = "Laura",
LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01")
};
var olivetto = new Student
{
FirstMidName = "Nino",
LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01")
};
var students = new Student[]
{
alexander,
alonso,
anand,
barzdukas,
li,
justice,
norman,
olivetto
};
context.AddRange(students);
var abercrombie = new Instructor
{
FirstMidName = "Kim",
LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11")
};
var fakhouri = new Instructor
{
FirstMidName = "Fadi",
LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06")
};
var harui = new Instructor
{
FirstMidName = "Roger",
LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01")
};
var kapoor = new Instructor
{
FirstMidName = "Candace",
LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15")
};
var zheng = new Instructor
{
FirstMidName = "Roger",
LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12")
};
var instructors = new Instructor[]
{
abercrombie,
fakhouri,
harui,
kapoor,
zheng
};
context.AddRange(instructors);
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
Instructor = fakhouri,
Location = "Smith 17" },
new OfficeAssignment {
Instructor = harui,
Location = "Gowan 27" },
new OfficeAssignment {
Instructor = kapoor,
Location = "Thompson 304" }
};
context.AddRange(officeAssignments);
var english = new Department
{
Name = "English",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = abercrombie
};
var mathematics = new Department
{
Name = "Mathematics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = fakhouri
};
var engineering = new Department
{
Name = "Engineering",
Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = harui
};
var economics = new Department
{
Name = "Economics",
Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
Administrator = kapoor
};
var departments = new Department[]
{
english,
mathematics,
engineering,
economics
};
context.AddRange(departments);
var chemistry = new Course
{
CourseID = 1050,
Title = "Chemistry",
Credits = 3,
Department = engineering,
Instructors = new List<Instructor> { kapoor, harui }
};
var microeconomics = new Course
{
CourseID = 4022,
Title = "Microeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var macroeconmics = new Course
{
CourseID = 4041,
Title = "Macroeconomics",
Credits = 3,
Department = economics,
Instructors = new List<Instructor> { zheng }
};
var calculus = new Course
{
CourseID = 1045,
Title = "Calculus",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { fakhouri }
};
var trigonometry = new Course
{
CourseID = 3141,
Title = "Trigonometry",
Credits = 4,
Department = mathematics,
Instructors = new List<Instructor> { harui }
};
var composition = new Course
{
CourseID = 2021,
Title = "Composition",
Credits = 3,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var literature = new Course
{
CourseID = 2042,
Title = "Literature",
Credits = 4,
Department = english,
Instructors = new List<Instructor> { abercrombie }
};
var courses = new Course[]
{
chemistry,
microeconomics,
macroeconmics,
calculus,
trigonometry,
composition,
literature
};
context.AddRange(courses);
var enrollments = new Enrollment[]
{
new Enrollment {
Student = alexander,
Course = chemistry,
Grade = Grade.A
},
new Enrollment {
Student = alexander,
Course = microeconomics,
Grade = Grade.C
},
new Enrollment {
Student = alexander,
Course = macroeconmics,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = calculus,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = trigonometry,
Grade = Grade.B
},
new Enrollment {
Student = alonso,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = anand,
Course = chemistry
},
new Enrollment {
Student = anand,
Course = microeconomics,
Grade = Grade.B
},
new Enrollment {
Student = barzdukas,
Course = chemistry,
Grade = Grade.B
},
new Enrollment {
Student = li,
Course = composition,
Grade = Grade.B
},
new Enrollment {
Student = justice,
Course = literature,
Grade = Grade.B
}
};
context.AddRange(enrollments);
context.SaveChanges();
}
}
}
Předchozí kód poskytuje počáteční data pro nové entity. Většina tohoto kódu vytvoří nové objekty entity a načte ukázková data. Ukázková data se používají k testování.
U existující databáze existují dva přístupy ke změně databáze:
Obě možnosti fungují pro SQL Server. I když je metoda apply-migration složitější a časově náročná, je to upřednostňovaný přístup pro skutečná produkční prostředí.
Pokud chcete vynutit EF Core vytvoření nové databáze, odstraňte a aktualizujte databázi:
Drop-Database
Add-Migration InitialCreate
Update-Database
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
Otevřete databázi v SSOX:
V dalších dvou kurzech se dozvíte, jak číst a aktualizovat související data.
Předchozí kurzy pracovaly se základním datovým modelem složeným ze tří entit. V tomto kurzu:
Dokončený datový model je znázorněn na následujícím obrázku:
Nahraďte kód v souboru Models/Student.cs
následujícím kódem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Předchozí kód přidá FullName
vlastnost a přidá do existujících vlastností následující atributy:
[DataType]
[DisplayFormat]
[StringLength]
[Column]
[Required]
[Display]
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dvou dalších vlastností. FullName
nejde nastavit, takže má jenom přístupový objekt get. V databázi se nevytvořil žádný FullName
sloupec.
[DataType(DataType.Date)]
U dat zápisu studentů se na všech stránkách aktuálně zobrazuje čas dne spolu s datem, i když je relevantní jenom datum. Pomocíatributůch
Atribut DataType určuje datový typ, který je konkrétnější než vnitřní typ databáze. V tomto případě by se mělo zobrazit jenom datum, nikoli datum a čas. Výčet DataType poskytuje mnoho datových typů, jako je datum, čas, telefonní číslo, měna, EmailAddress atd. Atribut DataType
může také aplikaci povolit, aby automaticky poskytovala funkce specifické pro typ. Příklad:
mailto:
se automaticky vytvoří pro DataType.EmailAddress
.DataType.Date
selektor data.Atribut DataType
generuje atributy HTML 5 data-
(vyslovuje se pomlčka dat). Atributy DataType
neposkytují ověření.
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
DataType.Date
nezadá formát zobrazeného data. Ve výchozím nastavení se pole data zobrazí podle výchozích formátů založených na souboru CultureInfo serveru.
Atribut DisplayFormat
se používá k explicitní zadání formátu data. Nastavení ApplyFormatInEditMode
určuje, že formátování se má použít také v uživatelském rozhraní pro úpravy. Některá pole by neměla používat ApplyFormatInEditMode
. Symbol měny by se například neměl zobrazovat v textovém poli pro úpravy.
Atribut DisplayFormat
lze použít sám. Obecně je vhodné použít DataType
atribut s atributem DisplayFormat
. Atribut DataType
vyjadřuje sémantiku dat na rozdíl od toho, jak je vykreslit na obrazovce. Atribut DataType
poskytuje následující výhody, které nejsou k dispozici v DisplayFormat
:
Další informace najdete v dokumentaci ke vstupním> pomocným <rutině značek.
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
Pomocí atributů je možné zadat pravidla ověření dat a chybové zprávy ověřování. Atribut StringLength určuje minimální a maximální délku znaků, které jsou povoleny v datovém poli. Zobrazený kód omezuje názvy maximálně na 50 znaků. Příklad, který nastaví minimální délku řetězce, se zobrazí později.
Atribut StringLength
také poskytuje ověřování na straně klienta a na straně serveru. Minimální hodnota nemá žádný vliv na schéma databáze.
Atribut StringLength
nezabrání uživateli v zadávání prázdných znaků pro jméno. Atribut RegularExpression lze použít k použití omezení pro vstup. Například následující kód vyžaduje, aby první znak byl velkým písmenem a zbývající znaky byly abecední:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
V SQL Serveru Průzkumník objektů (SSOX) otevřete návrháře tabulek Student poklikáním na tabulku Student.
Předchozí obrázek znázorňuje schéma tabulky Student
. Pole názvů mají typ nvarchar(MAX)
. Při vytvoření a použití migrace později v tomto kurzu se pole názvů stanou nvarchar(50)
výsledkem atributů délky řetězce.
[Column("FirstName")]
public string FirstMidName { get; set; }
Atributy mohou řídit, jak se třídy a vlastnosti mapují na databázi. Student
V modelu Column
se atribut používá k mapování názvu FirstMidName
vlastnosti na "FirstName" v databázi.
Při vytváření databáze se názvy vlastností v modelu používají pro názvy sloupců (s výjimkou použití atributu Column
). Model Student
používá FirstMidName
pro pole s křestní jméno, protože pole může obsahovat také prostřední název.
S atributem [Column]
Student.FirstMidName
se v datovém modelu mapuje na FirstName
sloupec Student
tabulky. Přidání atributu Column
změní model backing the SchoolContext
. Model, který SchoolContext
už databázi nepodporuje, neodpovídá. Tento nesrovnalosti vyřešíte přidáním migrace později v tomto kurzu.
[Required]
Atribut Required
vytvoří požadované vlastnosti názvu. Atribut Required
není potřeba pro typy bez hodnoty null, jako jsou například typy hodnot (například DateTime
, int
a double
). Typy, které nemohou být null, se automaticky považují za povinná pole.
Atribut Required
musí být použit s vynuceným vynuceným atributem MinimumLength
MinimumLength
.
[Display(Name = "Last Name")]
[Required]
[StringLength(50, MinimumLength=2)]
public string LastName { get; set; }
MinimumLength
a Required
povolte, aby ověření splňovalo prázdné znaky. RegularExpression
Atribut použijte pro úplnou kontrolu nad řetězcem.
[Display(Name = "Last Name")]
Atribut Display
určuje, že titulek textových polí by měl být "Jméno", "Příjmení", "Celé jméno" a "Datum registrace". Výchozí titulky neměly žádné mezery, které by dělily slova, například "Příjmení".
Spusťte aplikaci a přejděte na stránku Studenti. Vyvolá se výjimka. Atribut [Column]
způsobí, že EF očekává nalezení sloupce s názvem FirstName
, ale název sloupce v databázi je stále FirstMidName
.
Chybová zpráva je podobná následujícímu příkladu:
SqlException: Invalid column name 'FirstName'.
V PMC zadejte následující příkazy pro vytvoření nové migrace a aktualizaci databáze:
Add-Migration ColumnFirstName
Update-Database
První z těchto příkazů vygeneruje následující zprávu upozornění:
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Upozornění se vygeneruje, protože pole názvů jsou teď omezená na 50 znaků. Pokud název v databázi obsahoval více než 50 znaků, ztratí se 51 až poslední znak.
Otevřete tabulku Student v SSOX:
Před použitím migrace byly sloupce názvů typu nvarchar(MAX). Sloupce názvů jsou nyní nvarchar(50)
. Název sloupce se změnil z FirstMidName
na FirstName
.
Poznámka
V následujících částech sestavení aplikace v některých fázích generuje chyby kompilátoru. Pokyny určují, kdy se má aplikace sestavit.
Vytvořte Models/Instructor.cs
pomocí následujícího kódu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<CourseAssignment> CourseAssignments { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
Více atributů může být na jednom řádku. Atributy HireDate
lze zapsat takto:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Vlastnosti CourseAssignments
a OfficeAssignment
vlastnosti jsou navigační vlastnosti.
Instruktor může učit libovolný počet kurzů, takže CourseAssignments
je definován jako kolekce.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Instruktor může mít maximálně jednu kancelář, takže OfficeAssignment
vlastnost obsahuje jednu OfficeAssignment
entitu. OfficeAssignment
má hodnotu null, pokud není přiřazena žádná kancelář.
public OfficeAssignment OfficeAssignment { get; set; }
Vytvořte Models/OfficeAssignment.cs
pomocí následujícího kódu:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor Instructor { get; set; }
}
}
Atribut [Key]
slouží k identifikaci vlastnosti jako primárního klíče (PK), pokud název vlastnosti je něco jiného než classnameID nebo ID.
Mezi entitami Instructor
OfficeAssignment
existuje vztah 1:1 nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, ke kterému je přiřazený. Pk OfficeAssignment
je také jeho cizí klíč (FK) pro entitu Instructor
.
EF Core nemůže automaticky rozpoznat InstructorID
jako PK, OfficeAssignment
protože InstructorID
neodpovídá konvenci pojmenování ID nebo classnameID. Proto se Key
atribut používá k identifikaci InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Ve výchozím nastavení považuje klíč za negenerovaný databází, EF Core protože sloupec slouží k identifikaci relace.
Vlastnost Instructor.OfficeAssignment
navigace může mít hodnotu null, protože pro daného instruktora nemusí být OfficeAssignment
řádek. Instruktor nemusí mít zadání kanceláře.
Vlastnost OfficeAssignment.Instructor
navigace bude mít vždy entitu instruktora, protože typ cizího klíče InstructorID
je int
, typ hodnoty, který není null. Zadání kanceláře nemůže existovat bez instruktora.
Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita má ve své navigační vlastnosti odkaz na druhou entitu.
Aktualizujte Models/Course.cs
následujícím kódem:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}
Entita Course
má vlastnost DepartmentID
cizího klíče (FK). DepartmentID
odkazuje na související Department
entitu. Entita Course
má Department
navigační vlastnost.
EF Core nevyžaduje vlastnost cizího klíče pro datový model, pokud má model navigační vlastnost pro související entitu. EF Core v databázi automaticky vytváří sady FK, kdekoli jsou potřeba. EF Core vytvoří stínové vlastnosti pro automaticky vytvořené sady FK. Explicitní zahrnutí FK do datového modelu však může zjednodušit a zefektivnit aktualizace. Představte si například model, ve kterém není zahrnuta vlastnost DepartmentID
FK. Když se entita kurzu načte pro úpravy:
Department
má hodnotu null, pokud není explicitně načtena.Department
nejprve načtena.Pokud je vlastnost DepartmentID
FK součástí datového modelu, není nutné před aktualizací načíst entitu Department
.
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
určuje, že PK je poskytována aplikací místo generování databáze.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení se předpokládá, EF Core že databáze generuje hodnoty PK. Vygenerovaná databáze je obecně nejlepší přístup. Pro Course
entity určuje uživatel PK. Například číslo kurzu, například 1000 řad pro matematické oddělení, 2000 série pro anglické oddělení.
Atribut DatabaseGenerated
lze také použít k vygenerování výchozích hodnot. Databáze může například automaticky vygenerovat pole kalendářního data pro zaznamenání data vytvoření nebo aktualizace řádku. Další informace naleznete v tématu Vygenerované vlastnosti.
Vlastnosti cizího klíče (FK) a vlastnosti navigace v entitě Course
odrážejí následující relace:
Kurz je přiřazen k jednomu oddělení, takže je k DepartmentID
dispozici FK a Department
navigační vlastnost.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kurz může mít v něm zaregistrovaný libovolný počet studentů, takže Enrollments
navigační vlastnost je kolekce:
public ICollection<Enrollment> Enrollments { get; set; }
Kurz může učit více instruktorů, takže CourseAssignments
navigační vlastnost je kolekce:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
je vysvětleno později.
Vytvořte Models/Department.cs
pomocí následujícího kódu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Dříve byl Column
atribut použit ke změně mapování názvů sloupců. V kódu entity Column
se atribut používá ke změně mapování datového Department
typu SQL. Sloupec Budget
se definuje pomocí typu peněz SQL Serveru v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obecně nevyžaduje. EF Core zvolí odpovídající datový typ SQL Serveru na základě typu CLR pro vlastnost. Typ CLR decimal
se mapuje na typ SQL Serveru decimal
. Budget
je pro měnu a datový typ peněz je vhodnější pro měnu.
Vlastnosti FK a navigace odrážejí následující relace:
InstructorID
Proto je vlastnost zahrnuta jako FK entityInstructor
.Navigační vlastnost je pojmenovaná Administrator
, ale obsahuje entitu Instructor
:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Otazník (?) v předchozím kódu určuje vlastnost null.
Oddělení může mít mnoho kurzů, takže je zde vlastnost Navigace v kurzech:
public ICollection<Course> Courses { get; set; }
Podle konvence EF Core umožňuje kaskádové odstranění pro nenulové FK a pro relace M:N. Toto výchozí chování může vést k cyklický kaskádový odstranění pravidel. Cyklické kaskádové odstranění pravidel způsobí výjimku při přidání migrace.
Pokud Department.InstructorID
byla například vlastnost definována jako nenulová, EF Core nakonfigurovala by kaskádové pravidlo odstranění. V takovém případě by se oddělení odstranilo, když se odstraní instruktor přiřazený jako správce. V tomto scénáři by pravidlo omezení dávalo větší smysl. Následující fluentové rozhraní API by nastavilo pravidlo omezení a zakázalo kaskádové odstranění.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Záznam o registraci je určen pro jeden kurz pořízený jedním studentem.
Aktualizujte Models/Enrollment.cs
následujícím kódem:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Vlastnosti FK a navigační vlastnosti odrážejí následující relace:
Záznam registrace je pro jeden kurz, takže vlastnost CourseID
FK a Course
navigační vlastnost:
public int CourseID { get; set; }
public Course Course { get; set; }
Záznam registrace je určený pro jednoho studenta, takže vlastnost StudentID
FK a Student
navigační vlastnost:
public int StudentID { get; set; }
public Student Student { get; set; }
Mezi entitami Student
Course
a entitami existuje vztah M:N. Entita Enrollment
funguje jako tabulka spojení M:N s datovou částí v databázi. "S datovou částí" znamená, že Enrollment
tabulka obsahuje další data kromě FK pro spojené tabulky (v tomto případě PK a Grade
).
Následující obrázek znázorňuje, jak tyto relace vypadají v diagramu entit. (Tento diagram byl generován pomocí EF Power Tools pro EF 6.x. Vytvoření diagramu není součástí kurzu.)
Každá čára relace má na jednom konci 1 a hvězdičku (*) na druhé, která označuje relaci 1:N.
Enrollment
Pokud tabulka neobsahuje informace o známce, musí obsahovat pouze dvě sady FK (CourseID
aStudentID
). Tabulka spojení M:N bez datové části se někdy označuje jako čistá join table (PJT).
Course
Entity Instructor
mají relaci M:N pomocí tabulky s čistým spojením.
Poznámka: EF 6.x podporuje implicitní spojení tabulek pro relace M:N, ale EF Core ne. Další informace naleznete v tématu Relace M:N ve EF Core verzi 2.0.
Vytvořte Models/CourseAssignment.cs
pomocí následujícího kódu:
namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}
Relace M:N pro instruktora vyžaduje tabulku spojení a entita pro tuto tabulku spojení je CourseAssignment.
Je běžné pojmenovat entitu EntityName1EntityName2
spojení . Například tabulka spojení Instruktor-to-Courses pomocí tohoto vzoru by byla CourseInstructor
. Doporučujeme ale použít název, který popisuje relaci.
Datové modely začínají jednoduchým a růstem. Spojení tabulek bez datové části (PJT) se často vyvíjí tak, aby zahrnovaly datovou část. Když začnete s popisným názvem entity, název se při změně tabulky spojení nemusí měnit. V ideálním případě by entita spojení měla v obchodní doméně svůj vlastní přirozený (případně jednoslovný) název. Knihy a zákazníci můžou být například propojeny s entitou spojení s názvem Hodnocení. Pro vztah CourseAssignment
typu Instruktor-to-Courses M:N je preferován před CourseInstructor
.
Oba sady FK v CourseAssignment
tabulce (InstructorID
a CourseID
) společně jednoznačně identifikují každý řádek CourseAssignment
tabulky. CourseAssignment
nevyžaduje vyhrazenou pk. Funkce InstructorID
a CourseID
vlastnosti slouží jako složená infrastruktura veřejných klíčů. Jediným způsobem, jak zadat složené sady PK, je EF Core rozhraní FLUENT API. V další části se dozvíte, jak nakonfigurovat složenou pk.
Složený klíč zajistí, že:
Entita Enrollment
join definuje vlastní PK, takže duplicitní položky tohoto typu jsou možné. Chcete-li těmto duplicitám zabránit:
Enrollment
primární složený klíč podobný CourseAssignment
. Další informace najdete v tématu Indexy.Aktualizujte Data/SchoolContext.cs
následujícím kódem:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Data
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollments { get; set; }
public DbSet<Student> Students { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Předchozí kód přidá nové entity a nakonfiguruje CourseAssignment
složenou PK entity.
Metoda OnModelCreating
v předchozím kódu používá rozhraní API fluent ke konfiguraci EF Core chování. Rozhraní API se nazývá "fluent", protože se často používá řetězcem řady volání metod do jednoho příkazu. Následující kód je příkladem rozhraní FLUENT API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
V tomto kurzu se rozhraní API fluent používá pouze pro mapování databáze, které se nedají provádět s atributy. Rozhraní API fluent však může určovat většinu pravidel formátování, ověřování a mapování, která se dají provádět s atributy.
Některé atributy, jako MinimumLength
je například nelze použít s rozhraním FLUENT API. MinimumLength
nezmění schéma, použije pouze ověřovací pravidlo minimální délky.
Někteří vývojáři raději používají výhradně rozhraní API fluent, aby mohli třídy entit udržovat "čisté". Atributy a fluentské rozhraní API je možné kombinovat. Existují některé konfigurace, které je možné provádět pouze s rozhraním FLUENT API (určením složené pk). Existují některé konfigurace, které je možné provádět pouze s atributy (MinimumLength
). Doporučený postup pro používání rozhraní API nebo atributů fluent:
Některé atributy použité v tomto kurzu se používají pro:
MinimumLength
).HasKey
).[StringLength(50)]
).Další informace o atributech a rozhraní API fluent naleznete v tématu Metody konfigurace.
Následující obrázek znázorňuje diagram, který nástroje EF Power Tools vytvoří pro dokončený školní model.
Předchozí diagram znázorňuje:
Instructor
a OfficeAssignment
entitami.Instructor
a Department
entitami.Aktualizujte kód v Data/DbInitializer.cs
:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
// Look for any students.
if (context.Students.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2016-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2019-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2018-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2017-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2019-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2011-09-01") }
};
context.Students.AddRange(students);
context.SaveChanges();
var instructors = new Instructor[]
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
context.Instructors.AddRange(instructors);
context.SaveChanges();
var departments = new Department[]
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};
context.Departments.AddRange(departments);
context.SaveChanges();
var courses = new Course[]
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};
context.Courses.AddRange(courses);
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};
context.OfficeAssignments.AddRange(officeAssignments);
context.SaveChanges();
var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};
context.CourseAssignments.AddRange(courseInstructors);
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollments.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollments.Add(e);
}
}
context.SaveChanges();
}
}
}
Předchozí kód poskytuje počáteční data pro nové entity. Většina tohoto kódu vytvoří nové objekty entity a načte ukázková data. Ukázková data se používají k testování. Podívejte Enrollments
se na CourseAssignments
příklady toho, jak se dají spojit tabulky M:N.
Sestavte projekt.
V PMC spusťte následující příkaz.
Add-Migration ComplexDataModel
Předchozí příkaz zobrazí upozornění na možnou ztrátu dat.
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
To undo this action, use 'ef migrations remove'
database update
Pokud je příkaz spuštěn, vytvoří se následující chyba:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in
database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.
V další části se dozvíte, co dělat s touto chybou.
Teď, když máte existující databázi, je potřeba se zamyslet nad tím, jak na ni použít změny. Tento kurz ukazuje dvě alternativy:
Obě možnosti fungují pro SQL Server. I když je metoda apply-migration složitější a časově náročná, je to upřednostňovaný přístup pro skutečná produkční prostředí.
Tuto část přeskočte, pokud používáte SQL Server a chcete použít migraci v následující části.
Pokud chcete vynutit EF Core vytvoření nové databáze, odstraňte a aktualizujte databázi:
V konzole Správce balíčků (PMC) spusťte následující příkaz:
Drop-Database
Odstraňte složku Migrations a spusťte následující příkaz:
Add-Migration InitialCreate
Update-Database
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
Otevřete databázi v SSOX:
Pokud jste SSOX otevřeli dříve, klikněte na tlačítko Aktualizovat .
Rozbalte uzel Tabulky. Zobrazí se vytvořené tabulky.
Prozkoumejte tabulku CourseAssignment:
Tato část je nepovinná. Tyto kroky fungují jenom pro SQL Server LocalDB a pouze v případě, že jste přeskočili předchozí drop a znovu vytvořili oddíl databáze .
Při spuštění migrace s existujícími daty můžou existovat omezení FK, která nejsou s existujícími daty spokojená. U produkčních dat je potřeba provést kroky pro migraci existujících dat. Tato část obsahuje příklad opravy porušení omezení FK. Tyto změny kódu neprodávejte bez zálohy. Tyto změny kódu neprovádějte, pokud jste dokončili předchozí drop a znovu vytvořili oddíl databáze .
Soubor {timestamp}_ComplexDataModel.cs
obsahuje následující kód:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Předchozí kód přidá do Course
tabulky nenulový DepartmentID
FK. Databáze z předchozího kurzu obsahuje řádky v Course
tabulce, aby je migrace neaktualizovala.
Aby migrace fungovala ComplexDataModel
s existujícími daty:
DepartmentID
) dal výchozí hodnotu.ComplexDataModel
Ve třídě migrace aktualizujte metoduUp
:
{timestamp}_ComplexDataModel.cs
.DepartmentID
sloupec do Course
tabulky.migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Course",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldNullable: true);
//migrationBuilder.AddColumn<int>(
// name: "DepartmentID",
// table: "Course",
// nullable: false,
// defaultValue: 0);
Přidejte následující zvýrazněný kód. Nový kód následuje za blokem .CreateTable( name: "Department"
:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(type: "int", nullable: true),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);
Při předchozích změnách budou existující Course
řádky po spuštění metody souviset s oddělením ComplexDataModel.Up
Temp.
Způsob zpracování zde uvedené situace je pro účely tohoto kurzu zjednodušený. Produkční aplikace:
Department
řádků a souvisejících Course
řádků do nových Department
řádků.Course.DepartmentID
.V konzole Správce balíčků (PMC) spusťte následující příkaz:
Update-Database
Vzhledem k tomu, že DbInitializer.Initialize
metoda je navržená tak, aby fungovala pouze s prázdnou databází, odstraňte všechny řádky v tabulkách Student a Course pomocí SSOX. (Kaskádové odstranění se postará o tabulku Registrace.)
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
V dalších dvou kurzech se dozvíte, jak číst a aktualizovat související data.
Předchozí kurzy pracovaly se základním datovým modelem složeným ze tří entit. V tomto kurzu:
Třídy entit dokončeného datového modelu jsou znázorněny na následujícím obrázku:
Pokud narazíte na problémy, které nemůžete vyřešit, stáhněte si dokončenou aplikaci.
V této části je datový model přizpůsobený pomocí atributů.
Stránky studentů aktuálně zobrazují čas data registrace. Pole kalendářních dat obvykle zobrazují jenom datum, nikoli čas.
Aktualizujte Models/Student.cs
následujícím zvýrazněným kódem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
public string LastName { get; set; }
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Atribut DataType určuje datový typ, který je konkrétnější než vnitřní typ databáze. V tomto případě by se mělo zobrazit jenom datum, nikoli datum a čas. Výčet DataType poskytuje mnoho datových typů, jako je datum, čas, telefonní číslo, měna, EmailAddress atd. Atribut DataType
může také aplikaci povolit, aby automaticky poskytovala funkce specifické pro typ. Příklad:
mailto:
se automaticky vytvoří pro DataType.EmailAddress
.DataType.Date
selektor data.Atribut DataType
generuje atributy HTML 5 data-
(vyslovující datový spojovník), které prohlížeče HTML 5 spotřebovávají. Atributy DataType
neposkytují ověření.
DataType.Date
nezadá formát zobrazeného data. Ve výchozím nastavení se pole data zobrazí podle výchozích formátů založených na souboru CultureInfo serveru.
Atribut DisplayFormat
se používá k explicitní zadání formátu data:
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Nastavení ApplyFormatInEditMode
určuje, že formátování se má použít také v uživatelském rozhraní pro úpravy. Některá pole by neměla používat ApplyFormatInEditMode
. Symbol měny by se například neměl zobrazovat v textovém poli pro úpravy.
Atribut DisplayFormat
lze použít sám. Obecně je vhodné použít DataType
atribut s atributem DisplayFormat
. Atribut DataType
vyjadřuje sémantiku dat na rozdíl od toho, jak je vykreslit na obrazovce. Atribut DataType
poskytuje následující výhody, které nejsou k dispozici v DisplayFormat
:
Další informace najdete v dokumentaci ke vstupním> pomocným <rutině značek.
Spustit aplikaci. Přejděte na stránku Index studentů. Časy se už nezobrazují. Každé zobrazení, které používá Student
model, zobrazuje datum bez času.
Pomocí atributů je možné zadat pravidla ověření dat a chybové zprávy ověřování. Atribut StringLength určuje minimální a maximální délku znaků, které jsou povoleny v datovém poli. Atribut StringLength
také poskytuje ověřování na straně klienta a na straně serveru. Minimální hodnota nemá žádný vliv na schéma databáze.
Student
Aktualizujte model následujícím kódem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Předchozí kód omezuje názvy maximálně na 50 znaků. Atribut StringLength
nezabrání uživateli v zadávání prázdných znaků pro jméno. Atribut RegularExpression se používá k použití omezení pro vstup. Například následující kód vyžaduje, aby první znak byl velkým písmenem a zbývající znaky byly abecední:
[RegularExpression(@"^[A-Z]+[a-zA-Z]*$")]
Spuštění aplikace:
V SQL Serveru Průzkumník objektů (SSOX) otevřete návrháře tabulek Student poklikáním na tabulku Student.
Předchozí obrázek znázorňuje schéma tabulky Student
. Pole názvů mají typ nvarchar(MAX)
, protože migrace nebyla v databázi spuštěna. Když se migrace spustí později v tomto kurzu, stanou se nvarchar(50)
pole názvů .
Atributy mohou řídit, jak se třídy a vlastnosti mapují na databázi. V této části Column
se atribut používá k mapování názvu FirstMidName
vlastnosti na "FirstName" v databázi.
Při vytváření databáze se názvy vlastností v modelu používají pro názvy sloupců (s výjimkou použití atributu Column
).
Model Student
používá FirstMidName
pro pole s křestní jméno, protože pole může obsahovat také prostřední název.
Student.cs
Aktualizujte soubor následujícím zvýrazněným kódem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[StringLength(50)]
public string LastName { get; set; }
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
public DateTime EnrollmentDate { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Při předchozí změně Student.FirstMidName
se v aplikaci mapuje na FirstName
sloupec Student
tabulky.
Přidání atributu Column
změní model backing the SchoolContext
. Model, který SchoolContext
už databázi nepodporuje, neodpovídá. Pokud se aplikace spustí před použitím migrací, vygeneruje se následující výjimka:
SqlException: Invalid column name 'FirstName'.
Aktualizace databáze:
Add-Migration ColumnFirstName
Update-Database
Příkaz migrations add ColumnFirstName
vygeneruje následující zprávu upozornění:
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Upozornění se vygeneruje, protože pole názvů jsou teď omezená na 50 znaků. Pokud název v databázi obsahoval více než 50 znaků, ztratí se 51 až poslední znak.
Otevřete tabulku Student v SSOX:
Před použitím migrace byly sloupce názvů typu nvarchar(MAX). Sloupce názvů jsou nyní nvarchar(50)
. Název sloupce se změnil z FirstMidName
na FirstName
.
Poznámka
V následující části sestavení aplikace v některých fázích generuje chyby kompilátoru. Pokyny určují, kdy se má aplikace sestavit.
Aktualizujte Models/Student.cs
následujícím kódem:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Student
{
public int ID { get; set; }
[Required]
[StringLength(50)]
[Display(Name = "Last Name")]
public string LastName { get; set; }
[Required]
[StringLength(50, ErrorMessage = "First name cannot be longer than 50 characters.")]
[Column("FirstName")]
[Display(Name = "First Name")]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Enrollment Date")]
public DateTime EnrollmentDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get
{
return LastName + ", " + FirstMidName;
}
}
public ICollection<Enrollment> Enrollments { get; set; }
}
}
Atribut Required
vytvoří požadované vlastnosti názvu. Atribut Required
není potřeba pro typy bez hodnoty null, jako jsou typy hodnot (DateTime
, int
, double
atd.). Typy, které nemohou být null, se automaticky považují za povinná pole.
Atribut Required
může být nahrazen parametrem minimální délky v atributu StringLength
:
[Display(Name = "Last Name")]
[StringLength(50, MinimumLength=1)]
public string LastName { get; set; }
Atribut Display
určuje, že titulek textových polí by měl být "Jméno", "Příjmení", "Celé jméno" a "Datum registrace". Výchozí titulky neměly žádné mezery, které by dělily slova, například "Příjmení".
FullName
je počítaná vlastnost, která vrací hodnotu vytvořenou zřetězením dvou dalších vlastností. FullName
nelze nastavit, má pouze přístupové objekty get. V databázi se nevytvořil žádný FullName
sloupec.
Vytvořte Models/Instructor.cs
pomocí následujícího kódu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Instructor
{
public int ID { get; set; }
[Required]
[Display(Name = "Last Name")]
[StringLength(50)]
public string LastName { get; set; }
[Required]
[Column("FirstName")]
[Display(Name = "First Name")]
[StringLength(50)]
public string FirstMidName { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Hire Date")]
public DateTime HireDate { get; set; }
[Display(Name = "Full Name")]
public string FullName
{
get { return LastName + ", " + FirstMidName; }
}
public ICollection<CourseAssignment> CourseAssignments { get; set; }
public OfficeAssignment OfficeAssignment { get; set; }
}
}
Více atributů může být na jednom řádku. Atributy HireDate
lze zapsat takto:
[DataType(DataType.Date),Display(Name = "Hire Date"),DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
Vlastnosti CourseAssignments
a OfficeAssignment
vlastnosti jsou navigační vlastnosti.
Instruktor může učit libovolný počet kurzů, takže CourseAssignments
je definován jako kolekce.
public ICollection<CourseAssignment> CourseAssignments { get; set; }
Pokud navigační vlastnost obsahuje více entit:
Mezi typy navigačních vlastností patří:
ICollection<T>
List<T>
HashSet<T>
Pokud ICollection<T>
je zadáno, EF Core vytvoří ve výchozím nastavení kolekci HashSet<T>
.
Entita CourseAssignment
je vysvětlená v části o relacích M:N.
Obchodní pravidla společnosti Contoso pro vysokoškoláky uvádějí, že instruktor může mít maximálně jednu kancelář. Vlastnost OfficeAssignment
obsahuje jednu OfficeAssignment
entitu. OfficeAssignment
má hodnotu null, pokud není přiřazena žádná kancelář.
public OfficeAssignment OfficeAssignment { get; set; }
Vytvořte Models/OfficeAssignment.cs
pomocí následujícího kódu:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class OfficeAssignment
{
[Key]
public int InstructorID { get; set; }
[StringLength(50)]
[Display(Name = "Office Location")]
public string Location { get; set; }
public Instructor Instructor { get; set; }
}
}
Atribut [Key]
slouží k identifikaci vlastnosti jako primárního klíče (PK), pokud název vlastnosti je něco jiného než classnameID nebo ID.
Mezi entitami Instructor
OfficeAssignment
existuje vztah 1:1 nebo 1. Přiřazení kanceláře existuje pouze ve vztahu k instruktorovi, ke kterému je přiřazený. Pk OfficeAssignment
je také jeho cizí klíč (FK) pro entitu Instructor
. EF Core nemůže automaticky rozpoznat InstructorID
jako PK OfficeAssignment
z následujících důvodů:
InstructorID
neodpovídá konvenci pojmenování ID nebo classnameID.Proto se Key
atribut používá k identifikaci InstructorID
jako PK:
[Key]
public int InstructorID { get; set; }
Ve výchozím nastavení považuje klíč za negenerovaný databází, EF Core protože sloupec slouží k identifikaci relace.
Vlastnost OfficeAssignment
navigace pro entitu Instructor
je nullable, protože:
Entita OfficeAssignment
má nenulovou Instructor
navigační vlastnost, protože:
InstructorID
je nenulová.Pokud má entita Instructor
související OfficeAssignment
entitu, každá entita má ve své navigační vlastnosti odkaz na druhou entitu.
Atribut [Required]
lze použít pro Instructor
navigační vlastnost:
[Required]
public Instructor Instructor { get; set; }
Předchozí kód určuje, že musí existovat související instruktor. Předchozí kód není nutný, protože InstructorID
cizí klíč (což je také PK) není nullable.
Aktualizujte Models/Course.cs
následujícím kódem:
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Course
{
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Title { get; set; }
[Range(0, 5)]
public int Credits { get; set; }
public int DepartmentID { get; set; }
public Department Department { get; set; }
public ICollection<Enrollment> Enrollments { get; set; }
public ICollection<CourseAssignment> CourseAssignments { get; set; }
}
}
Entita Course
má vlastnost DepartmentID
cizího klíče (FK). DepartmentID
odkazuje na související Department
entitu. Entita Course
má Department
navigační vlastnost.
EF Core nevyžaduje vlastnost FK pro datový model, pokud má model navigační vlastnost pro související entitu.
EF Core v databázi automaticky vytváří sady FK, kdekoli jsou potřeba. EF Core vytvoří stínové vlastnosti pro automaticky vytvořené sady FK. Použití FK v datovém modelu může zjednodušit a zefektivnit aktualizace. Představte si například model, ve kterém není zahrnuta vlastnost DepartmentID
FK. Když se entita kurzu načte pro úpravy:
Department
má hodnotu null, pokud není explicitně načtena.Department
nejprve načtena.Pokud je vlastnost DepartmentID
FK součástí datového modelu, není nutné před aktualizací načíst entitu Department
.
Atribut [DatabaseGenerated(DatabaseGeneratedOption.None)]
určuje, že PK je poskytována aplikací místo generování databáze.
[DatabaseGenerated(DatabaseGeneratedOption.None)]
[Display(Name = "Number")]
public int CourseID { get; set; }
Ve výchozím nastavení se předpokládá, EF Core že databáze generuje hodnoty PK. Nejlepší přístup je obecně přístup generovaný databází PK. Pro Course
entity určuje uživatel PK. Například číslo kurzu, například 1000 řad pro matematické oddělení, 2000 série pro anglické oddělení.
Atribut DatabaseGenerated
lze také použít k vygenerování výchozích hodnot. Databáze může například automaticky vygenerovat pole kalendářního data pro zaznamenání data vytvoření nebo aktualizace řádku. Další informace naleznete v tématu Vygenerované vlastnosti.
Vlastnosti cizího klíče (FK) a vlastnosti navigace v entitě Course
odrážejí následující relace:
Kurz je přiřazen k jednomu oddělení, takže je k DepartmentID
dispozici FK a Department
navigační vlastnost.
public int DepartmentID { get; set; }
public Department Department { get; set; }
Kurz může mít v něm zaregistrovaný libovolný počet studentů, takže Enrollments
navigační vlastnost je kolekce:
public ICollection<Enrollment> Enrollments { get; set; }
Kurz může učit více instruktorů, takže CourseAssignments
navigační vlastnost je kolekce:
public ICollection<CourseAssignment> CourseAssignments { get; set; }
CourseAssignment
je vysvětleno později.
Vytvořte Models/Department.cs
pomocí následujícího kódu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class Department
{
public int DepartmentID { get; set; }
[StringLength(50, MinimumLength = 3)]
public string Name { get; set; }
[DataType(DataType.Currency)]
[Column(TypeName = "money")]
public decimal Budget { get; set; }
[DataType(DataType.Date)]
[DisplayFormat(DataFormatString = "{0:yyyy-MM-dd}", ApplyFormatInEditMode = true)]
[Display(Name = "Start Date")]
public DateTime StartDate { get; set; }
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
public ICollection<Course> Courses { get; set; }
}
}
Dříve byl Column
atribut použit ke změně mapování názvů sloupců. V kódu entity Column
se atribut používá ke změně mapování datového Department
typu SQL. Sloupec Budget
se definuje pomocí typu peněz SQL Serveru v databázi:
[Column(TypeName="money")]
public decimal Budget { get; set; }
Mapování sloupců se obecně nevyžaduje. EF Core obecně zvolí příslušný datový typ SQL Serveru na základě typu CLR pro vlastnost. Typ CLR decimal
se mapuje na typ SQL Serveru decimal
. Budget
je pro měnu a datový typ peněz je vhodnější pro měnu.
Vlastnosti FK a navigace odrážejí následující relace:
InstructorID
Proto je vlastnost zahrnuta jako FK entityInstructor
.Navigační vlastnost je pojmenovaná Administrator
, ale obsahuje entitu Instructor
:
public int? InstructorID { get; set; }
public Instructor Administrator { get; set; }
Otazník (?) v předchozím kódu určuje vlastnost null.
Oddělení může mít mnoho kurzů, takže je zde vlastnost Navigace v kurzech:
public ICollection<Course> Courses { get; set; }
Poznámka: Podle konvence EF Core umožňuje kaskádové odstranění pro nenulové FK a pro relace M:N. Kaskádové odstranění může vést k cyklický kaskádový odstranění pravidel. Pravidla cyklických kaskádových odstranění způsobí výjimku při přidání migrace.
Pokud Department.InstructorID
byla například vlastnost definována jako nenulová:
EF Core nakonfiguruje kaskádové pravidlo odstranění, které odstraní oddělení při odstranění instruktora.
Odstranění oddělení při odstranění instruktora není zamýšlené chování.
Následující fluentové rozhraní API by nastavilo pravidlo omezení místo kaskádové.
modelBuilder.Entity<Department>()
.HasOne(d => d.Administrator)
.WithMany()
.OnDelete(DeleteBehavior.Restrict)
Předchozí kód zakáže kaskádové odstranění vztahu instruktora oddělení.
Záznam o registraci je určen pro jeden kurz pořízený jedním studentem.
Aktualizujte Models/Enrollment.cs
následujícím kódem:
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public enum Grade
{
A, B, C, D, F
}
public class Enrollment
{
public int EnrollmentID { get; set; }
public int CourseID { get; set; }
public int StudentID { get; set; }
[DisplayFormat(NullDisplayText = "No grade")]
public Grade? Grade { get; set; }
public Course Course { get; set; }
public Student Student { get; set; }
}
}
Vlastnosti FK a navigační vlastnosti odrážejí následující relace:
Záznam registrace je pro jeden kurz, takže vlastnost CourseID
FK a Course
navigační vlastnost:
public int CourseID { get; set; }
public Course Course { get; set; }
Záznam registrace je určený pro jednoho studenta, takže vlastnost StudentID
FK a Student
navigační vlastnost:
public int StudentID { get; set; }
public Student Student { get; set; }
Mezi entitami Student
Course
a entitami existuje vztah M:N. Entita Enrollment
funguje jako tabulka spojení M:N s datovou částí v databázi. "S datovou částí" znamená, že Enrollment
tabulka obsahuje další data kromě FK pro spojené tabulky (v tomto případě PK a Grade
).
Následující obrázek znázorňuje, jak tyto relace vypadají v diagramu entit. (Tento diagram byl generován pomocí EF Power Tools pro EF 6.x. Vytvoření diagramu není součástí kurzu.)
Každá čára relace má na jednom konci 1 a hvězdičku (*) na druhé, která označuje relaci 1:N.
Enrollment
Pokud tabulka neobsahuje informace o známce, musí obsahovat pouze dvě sady FK (CourseID
aStudentID
). Tabulka spojení M:N bez datové části se někdy označuje jako čistá join table (PJT).
Course
Entity Instructor
mají relaci M:N pomocí tabulky s čistým spojením.
Poznámka: EF 6.x podporuje implicitní spojení tabulek pro relace M:N, ale EF Core ne. Další informace naleznete v tématu Relace M:N ve EF Core verzi 2.0.
Vytvořte Models/CourseAssignment.cs
pomocí následujícího kódu:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace ContosoUniversity.Models
{
public class CourseAssignment
{
public int InstructorID { get; set; }
public int CourseID { get; set; }
public Instructor Instructor { get; set; }
public Course Course { get; set; }
}
}
Vztah M:N instruktora:
Je běžné pojmenovat entitu EntityName1EntityName2
spojení . Například tabulka spojení Instruktor-to-Courses pomocí tohoto vzoru je CourseInstructor
. Doporučujeme ale použít název, který popisuje relaci.
Datové modely začínají jednoduchým a růstem. Spojení bez datové části (PJT) se často vyvíjejí, aby zahrnovala datovou část. Když začnete s popisným názvem entity, název se při změně tabulky spojení nemusí měnit. V ideálním případě by entita spojení měla v obchodní doméně svůj vlastní přirozený (případně jednoslovný) název. Knihy a zákazníci můžou být například propojeny s entitou spojení s názvem Hodnocení. Pro vztah CourseAssignment
typu Instruktor-to-Courses M:N je preferován před CourseInstructor
.
Sady FK nejsou s možnou hodnotou null. Oba sady FK v CourseAssignment
tabulce (InstructorID
a CourseID
) společně jednoznačně identifikují každý řádek CourseAssignment
tabulky. CourseAssignment
nevyžaduje vyhrazenou pk. Funkce InstructorID
a CourseID
vlastnosti slouží jako složená infrastruktura veřejných klíčů. Jediným způsobem, jak zadat složené sady PK, je EF Core rozhraní FLUENT API. V další části se dozvíte, jak nakonfigurovat složenou pk.
Složený klíč zajišťuje:
Entita Enrollment
join definuje vlastní PK, takže duplicitní položky tohoto typu jsou možné. Chcete-li těmto duplicitám zabránit:
Enrollment
primární složený klíč podobný CourseAssignment
. Další informace najdete v tématu Indexy.Přidejte následující zvýrazněný kód do Data/SchoolContext.cs
:
using ContosoUniversity.Models;
using Microsoft.EntityFrameworkCore;
namespace ContosoUniversity.Models
{
public class SchoolContext : DbContext
{
public SchoolContext(DbContextOptions<SchoolContext> options) : base(options)
{
}
public DbSet<Course> Courses { get; set; }
public DbSet<Enrollment> Enrollment { get; set; }
public DbSet<Student> Student { get; set; }
public DbSet<Department> Departments { get; set; }
public DbSet<Instructor> Instructors { get; set; }
public DbSet<OfficeAssignment> OfficeAssignments { get; set; }
public DbSet<CourseAssignment> CourseAssignments { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Course>().ToTable("Course");
modelBuilder.Entity<Enrollment>().ToTable("Enrollment");
modelBuilder.Entity<Student>().ToTable("Student");
modelBuilder.Entity<Department>().ToTable("Department");
modelBuilder.Entity<Instructor>().ToTable("Instructor");
modelBuilder.Entity<OfficeAssignment>().ToTable("OfficeAssignment");
modelBuilder.Entity<CourseAssignment>().ToTable("CourseAssignment");
modelBuilder.Entity<CourseAssignment>()
.HasKey(c => new { c.CourseID, c.InstructorID });
}
}
}
Předchozí kód přidá nové entity a nakonfiguruje CourseAssignment
složenou PK entity.
Metoda OnModelCreating
v předchozím kódu používá rozhraní API fluent ke konfiguraci EF Core chování. Rozhraní API se nazývá "fluent", protože se často používá řetězcem řady volání metod do jednoho příkazu. Následující kód je příkladem rozhraní FLUENT API:
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
modelBuilder.Entity<Blog>()
.Property(b => b.Url)
.IsRequired();
}
V tomto kurzu se rozhraní API fluent používá pouze pro mapování databáze, které nelze provádět s atributy. Rozhraní API fluent však může určovat většinu pravidel formátování, ověřování a mapování, která se dají provádět s atributy.
Některé atributy, jako MinimumLength
je například nelze použít s rozhraním FLUENT API. MinimumLength
nezmění schéma, použije pouze ověřovací pravidlo minimální délky.
Někteří vývojáři raději používají výhradně rozhraní API fluent, aby mohli třídy entit udržovat "čisté". Atributy a fluentské rozhraní API je možné kombinovat. Existují některé konfigurace, které je možné provádět pouze s rozhraním FLUENT API (určením složené pk). Existují některé konfigurace, které je možné provádět pouze s atributy (MinimumLength
). Doporučený postup pro používání rozhraní API nebo atributů fluent:
Některé atributy použité v tomto kurzu se používají pro:
MinimumLength
).HasKey
).[StringLength(50)]
).Další informace o atributech a rozhraní API fluent naleznete v tématu Metody konfigurace.
Následující obrázek znázorňuje diagram, který nástroje EF Power Tools vytvoří pro dokončený školní model.
Předchozí diagram znázorňuje:
Instructor
a OfficeAssignment
entitami.Instructor
a Department
entitami.Aktualizujte kód v Data/DbInitializer.cs
:
using System;
using System.Linq;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using ContosoUniversity.Models;
namespace ContosoUniversity.Data
{
public static class DbInitializer
{
public static void Initialize(SchoolContext context)
{
//context.Database.EnsureCreated();
// Look for any students.
if (context.Student.Any())
{
return; // DB has been seeded
}
var students = new Student[]
{
new Student { FirstMidName = "Carson", LastName = "Alexander",
EnrollmentDate = DateTime.Parse("2010-09-01") },
new Student { FirstMidName = "Meredith", LastName = "Alonso",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Arturo", LastName = "Anand",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Gytis", LastName = "Barzdukas",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Yan", LastName = "Li",
EnrollmentDate = DateTime.Parse("2012-09-01") },
new Student { FirstMidName = "Peggy", LastName = "Justice",
EnrollmentDate = DateTime.Parse("2011-09-01") },
new Student { FirstMidName = "Laura", LastName = "Norman",
EnrollmentDate = DateTime.Parse("2013-09-01") },
new Student { FirstMidName = "Nino", LastName = "Olivetto",
EnrollmentDate = DateTime.Parse("2005-09-01") }
};
foreach (Student s in students)
{
context.Student.Add(s);
}
context.SaveChanges();
var instructors = new Instructor[]
{
new Instructor { FirstMidName = "Kim", LastName = "Abercrombie",
HireDate = DateTime.Parse("1995-03-11") },
new Instructor { FirstMidName = "Fadi", LastName = "Fakhouri",
HireDate = DateTime.Parse("2002-07-06") },
new Instructor { FirstMidName = "Roger", LastName = "Harui",
HireDate = DateTime.Parse("1998-07-01") },
new Instructor { FirstMidName = "Candace", LastName = "Kapoor",
HireDate = DateTime.Parse("2001-01-15") },
new Instructor { FirstMidName = "Roger", LastName = "Zheng",
HireDate = DateTime.Parse("2004-02-12") }
};
foreach (Instructor i in instructors)
{
context.Instructors.Add(i);
}
context.SaveChanges();
var departments = new Department[]
{
new Department { Name = "English", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Abercrombie").ID },
new Department { Name = "Mathematics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID },
new Department { Name = "Engineering", Budget = 350000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Harui").ID },
new Department { Name = "Economics", Budget = 100000,
StartDate = DateTime.Parse("2007-09-01"),
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID }
};
foreach (Department d in departments)
{
context.Departments.Add(d);
}
context.SaveChanges();
var courses = new Course[]
{
new Course {CourseID = 1050, Title = "Chemistry", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Engineering").DepartmentID
},
new Course {CourseID = 4022, Title = "Microeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 4041, Title = "Macroeconomics", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "Economics").DepartmentID
},
new Course {CourseID = 1045, Title = "Calculus", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 3141, Title = "Trigonometry", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "Mathematics").DepartmentID
},
new Course {CourseID = 2021, Title = "Composition", Credits = 3,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
new Course {CourseID = 2042, Title = "Literature", Credits = 4,
DepartmentID = departments.Single( s => s.Name == "English").DepartmentID
},
};
foreach (Course c in courses)
{
context.Courses.Add(c);
}
context.SaveChanges();
var officeAssignments = new OfficeAssignment[]
{
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Fakhouri").ID,
Location = "Smith 17" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Harui").ID,
Location = "Gowan 27" },
new OfficeAssignment {
InstructorID = instructors.Single( i => i.LastName == "Kapoor").ID,
Location = "Thompson 304" },
};
foreach (OfficeAssignment o in officeAssignments)
{
context.OfficeAssignments.Add(o);
}
context.SaveChanges();
var courseInstructors = new CourseAssignment[]
{
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Kapoor").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Zheng").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Fakhouri").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Harui").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
new CourseAssignment {
CourseID = courses.Single(c => c.Title == "Literature" ).CourseID,
InstructorID = instructors.Single(i => i.LastName == "Abercrombie").ID
},
};
foreach (CourseAssignment ci in courseInstructors)
{
context.CourseAssignments.Add(ci);
}
context.SaveChanges();
var enrollments = new Enrollment[]
{
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID,
Grade = Grade.A
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics" ).CourseID,
Grade = Grade.C
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alexander").ID,
CourseID = courses.Single(c => c.Title == "Macroeconomics" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Calculus" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Trigonometry" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Alonso").ID,
CourseID = courses.Single(c => c.Title == "Composition" ).CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Chemistry" ).CourseID
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Anand").ID,
CourseID = courses.Single(c => c.Title == "Microeconomics").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Barzdukas").ID,
CourseID = courses.Single(c => c.Title == "Chemistry").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Li").ID,
CourseID = courses.Single(c => c.Title == "Composition").CourseID,
Grade = Grade.B
},
new Enrollment {
StudentID = students.Single(s => s.LastName == "Justice").ID,
CourseID = courses.Single(c => c.Title == "Literature").CourseID,
Grade = Grade.B
}
};
foreach (Enrollment e in enrollments)
{
var enrollmentInDataBase = context.Enrollment.Where(
s =>
s.Student.ID == e.StudentID &&
s.Course.CourseID == e.CourseID).SingleOrDefault();
if (enrollmentInDataBase == null)
{
context.Enrollment.Add(e);
}
}
context.SaveChanges();
}
}
}
Předchozí kód poskytuje počáteční data pro nové entity. Většina tohoto kódu vytvoří nové objekty entity a načte ukázková data. Ukázková data se používají k testování. Podívejte Enrollments
se na CourseAssignments
příklady toho, jak se dají spojit tabulky M:N.
Sestavte projekt.
Add-Migration ComplexDataModel
Předchozí příkaz zobrazí upozornění na možnou ztrátu dat.
An operation was scaffolded that may result in the loss of data.
Please review the migration for accuracy.
Done. To undo this action, use 'ef migrations remove'
database update
Pokud je příkaz spuštěn, vytvoří se následující chyba:
The ALTER TABLE statement conflicted with the FOREIGN KEY constraint "FK_dbo.Course_dbo.Department_DepartmentID". The conflict occurred in
database "ContosoUniversity", table "dbo.Department", column 'DepartmentID'.
Teď, když máte existující databázi, musíte přemýšlet o tom, jak na ni použít budoucí změny. Tento kurz ukazuje dva přístupy:
Kód v aktualizovaném DbInitializer
kódu přidá počáteční data pro nové entity. Pokud chcete vynutit EF Core vytvoření nové databáze, odstraňte a aktualizujte databázi:
V konzole Správce balíčků (PMC) spusťte následující příkaz:
Drop-Database
Update-Database
Spuštěním Get-Help about_EntityFrameworkCore
z PMC získejte informace o nápovědě.
Spustit aplikaci. Spuštění aplikace spustí metodu DbInitializer.Initialize
. Naplní DbInitializer.Initialize
novou databázi.
Otevřete databázi v SSOX:
Prozkoumejte tabulku CourseAssignment:
Tato část je nepovinná. Tyto kroky fungují jenom v případě, že jste přeskočili předchozí drop a znovu vytvořili oddíl databáze .
Při spuštění migrace s existujícími daty můžou existovat omezení FK, která nejsou s existujícími daty spokojená. U produkčních dat je potřeba provést kroky pro migraci existujících dat. Tato část obsahuje příklad opravy porušení omezení FK. Tyto změny kódu neprodávejte bez zálohy. Pokud jste dokončili předchozí část a aktualizovali databázi, tyto změny kódu neprodávejte.
Soubor {timestamp}_ComplexDataModel.cs
obsahuje následující kód:
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
type: "int",
nullable: false,
defaultValue: 0);
Předchozí kód přidá do Course
tabulky nenulový DepartmentID
FK. Databáze z předchozího kurzu obsahuje řádky, Course
aby je migrace nemohla aktualizovat.
Aby migrace fungovala ComplexDataModel
s existujícími daty:
DepartmentID
) dal výchozí hodnotu.Aktualizujte metodu ComplexDataModel
tříd Up
:
{timestamp}_ComplexDataModel.cs
.DepartmentID
sloupec do Course
tabulky.migrationBuilder.AlterColumn<string>(
name: "Title",
table: "Course",
maxLength: 50,
nullable: true,
oldClrType: typeof(string),
oldNullable: true);
//migrationBuilder.AddColumn<int>(
// name: "DepartmentID",
// table: "Course",
// nullable: false,
// defaultValue: 0);
Přidejte následující zvýrazněný kód. Nový kód následuje za blokem .CreateTable( name: "Department"
:
migrationBuilder.CreateTable(
name: "Department",
columns: table => new
{
DepartmentID = table.Column<int>(type: "int", nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
Budget = table.Column<decimal>(type: "money", nullable: false),
InstructorID = table.Column<int>(type: "int", nullable: true),
Name = table.Column<string>(type: "nvarchar(50)", maxLength: 50, nullable: true),
StartDate = table.Column<DateTime>(type: "datetime2", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Department", x => x.DepartmentID);
table.ForeignKey(
name: "FK_Department_Instructor_InstructorID",
column: x => x.InstructorID,
principalTable: "Instructor",
principalColumn: "ID",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.Sql("INSERT INTO dbo.Department (Name, Budget, StartDate) VALUES ('Temp', 0.00, GETDATE())");
// Default value for FK points to department created above, with
// defaultValue changed to 1 in following AddColumn statement.
migrationBuilder.AddColumn<int>(
name: "DepartmentID",
table: "Course",
nullable: false,
defaultValue: 1);
Při předchozích změnách budou existující Course
řádky po spuštění metody souviset s oddělením ComplexDataModel
Up
Temp.
Produkční aplikace:
Department
řádků a souvisejících Course
řádků do nových Department
řádků.Course.DepartmentID
.Další kurz se zabývá souvisejícími daty.
Zpětná vazba k produktu ASP.NET Core
ASP.NET Core je open source projekt. Vyberte odkaz pro poskytnutí zpětné vazby:
Události
Mistrovství Světa v Power BI DataViz
14. 2. 16 - 31. 3. 16
Se 4 šance na vstup, můžete vyhrát konferenční balíček a udělat to na LIVE Grand Finale v Las Vegas
Další informace