Poznámky k datům Code First
Poznámka:
POUZE EF4.1 – funkce, rozhraní API atd. probírané na této stránce byly představeny v Entity Frameworku 4.1. Pokud používáte starší verzi, některé nebo všechny tyto informace se nevztahují.
Obsah na této stránce je upraven z článku původně napsaného Julie Lermanem (<http://thedatafarm.com>).
Entity Framework Code First umožňuje použít vlastní třídy domény k reprezentaci modelu, který EF spoléhá na provádění dotazů, sledování změn a aktualizace funkcí. Code First využívá programovací vzor, který se označuje jako konvence nad konfigurací. Code First předpokládá, že vaše třídy dodržují konvence entity Framework, a v takovém případě automaticky vytvoří, jak provést svou úlohu. Pokud však vaše třídy tyto konvence nedodržují, máte možnost do tříd přidat konfigurace, které poskytují EF s požadovanými informacemi.
Code First nabízí dva způsoby, jak tyto konfigurace přidat do tříd. Jeden používá jednoduché atributy s názvem DataAnnotations a druhý používá rozhraní Fluent API Code First, které poskytuje způsob, jak v kódu imperativní popisovat konfigurace.
Tento článek se zaměří na použití DataAnnotations (v oboru názvů System.ComponentModel.DataAnnotations) ke konfiguraci tříd – zvýraznění nejčastěji potřebných konfigurací. DataAnnotations jsou také srozumitelné pro řadu aplikací .NET, jako je ASP.NET MVC, které těmto aplikacím umožňují využívat stejné poznámky pro ověřování na straně klienta.
Model
Předvedem Code First DataAnnotations s jednoduchým párem tříd: Blog a Příspěvek.
public class Blog
{
public int Id { get; set; }
public string Title { get; set; }
public string BloggerName { get; set;}
public virtual ICollection<Post> Posts { get; set; }
}
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
public ICollection<Comment> Comments { get; set; }
}
Jak jsou, blog a post třídy pohodlně dodržují kód první konvence a nevyžadují žádné úpravy pro povolení kompatibility EF. Poznámky ale můžete také použít k poskytnutí dalších informací ef o třídách a databázi, na kterou se mapují.
Klíč
Entity Framework spoléhá na každou entitu s klíčovou hodnotou, která se používá ke sledování entit. Jednou konvencí Code First je implicitní klíčové vlastnosti; Code First vyhledá vlastnost s názvem Id nebo kombinaci názvu třídy a ID, například BlogId. Tato vlastnost se mapuje na sloupec primárního klíče v databázi.
Třídy Blog a Post se řídí touto konvencí. Co když to neudělali? Co když blog místo toho použil název PrimaryTrackingKey nebo dokonce foo? Pokud kód nejprve nenajde vlastnost, která odpovídá této konvenci, vyvolá výjimku z důvodu požadavku entity Framework, že musíte mít klíčovou vlastnost. Pomocí poznámky ke klíči můžete určit, která vlastnost se má použít jako EntityKey.
public class Blog
{
[Key]
public int PrimaryTrackingKey { get; set; }
public string Title { get; set; }
public string BloggerName { get; set;}
public virtual ICollection<Post> Posts { get; set; }
}
Pokud používáte funkci generování databáze prvního kódu, tabulka blogů bude mít sloupec primárního klíče s názvem PrimaryTrackingKey, který se ve výchozím nastavení definuje jako Identita.
Složené klávesy
Entity Framework podporuje složené klíče – primární klíče, které se skládají z více než jedné vlastnosti. Můžete mít například třídu Passport, jejíž primární klíč je kombinací PassportNumber a IssuingCountry.
public class Passport
{
[Key]
public int PassportNumber { get; set; }
[Key]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Při pokusu o použití výše uvedené třídy v modelu EF by došlo k InvalidOperationException
:
Nelze určit řazení složeného primárního klíče pro typ Passport. K určení pořadí složených primárních klíčů použijte ColumnAttribute nebo HasKey metoda.
Aby bylo možné používat složené klíče, entity Framework vyžaduje, abyste definovali pořadí pro vlastnosti klíče. Můžete to provést pomocí anotace Sloupce k zadání pořadí.
Poznámka:
Hodnota objednávky je relativní (nikoli na základě indexu), takže je možné použít všechny hodnoty. Například 100 a 200 by bylo přijatelné místo 1 a 2.
public class Passport
{
[Key]
[Column(Order=1)]
public int PassportNumber { get; set; }
[Key]
[Column(Order = 2)]
public string IssuingCountry { get; set; }
public DateTime Issued { get; set; }
public DateTime Expires { get; set; }
}
Pokud máte entity se složenými cizími klíči, musíte zadat stejné pořadí sloupců, které jste použili pro odpovídající vlastnosti primárního klíče.
Stejné musí být pouze relativní řazení ve vlastnostech cizího klíče, přesné hodnoty přiřazené objednávce se nemusí shodovat. Například v následující třídě lze místo 1 a 2 použít 3 a 4.
public class PassportStamp
{
[Key]
public int StampId { get; set; }
public DateTime Stamped { get; set; }
public string StampingCountry { get; set; }
[ForeignKey("Passport")]
[Column(Order = 1)]
public int PassportNumber { get; set; }
[ForeignKey("Passport")]
[Column(Order = 2)]
public string IssuingCountry { get; set; }
public Passport Passport { get; set; }
}
Požaduje se
Poznámka Required
říká EF, že je vyžadována určitá vlastnost.
Přidání Povinné do vlastnosti Title vynutí EF (a MVC), aby se zajistilo, že vlastnost obsahuje data.
[Required]
public string Title { get; set; }
Bez dalších změn kódu nebo značek v aplikaci provede aplikace MVC ověření na straně klienta, a to i dynamicky sestavení zprávy pomocí názvů vlastností a poznámek.
Atribut Required bude mít vliv také na vygenerovanou databázi tím, že mapovaná vlastnost není nullable. Všimněte si, že pole Název se změnilo na "not null".
Poznámka:
V některých případech nemusí být možné, aby sloupec v databázi byl nenulový, i když je vlastnost povinná. Například při použití dat strategie dědičnosti TPH pro více typů jsou uložena v jedné tabulce. Pokud odvozený typ obsahuje požadovanou vlastnost, sloupec nelze vytvořit bez hodnoty null, protože ne všechny typy v hierarchii budou mít tuto vlastnost.
MaxLength a MinLength
MinLength
Atributy MaxLength
umožňují zadat další ověření vlastností, stejně jako jste to udělali s Required
.
Tady je BloggerName s požadavky na délku. Příklad také ukazuje, jak kombinovat atributy.
[MaxLength(10),MinLength(5)]
public string BloggerName { get; set; }
Poznámka MaxLength ovlivní databázi nastavením délky vlastnosti na 10.
Poznámky na straně klienta MVC a poznámka na straně serveru EF 4.1 budou respektovat toto ověření, opět dynamicky vytváří chybovou zprávu: "Pole BloggerName musí být řetězec nebo typ pole s maximální délkou 10". Ta zpráva je trochu dlouhá. Mnoho poznámek umožňuje zadat chybovou zprávu s atributem ErrorMessage.
[MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
public string BloggerName { get; set; }
Můžete také zadat ErrorMessage v povinné anotace.
NotMapped
První konvence kódu určuje, že každá vlastnost, která je podporovaným datovým typem, je reprezentována v databázi. Nejedná se ale vždy o případ ve vašich aplikacích. Můžete mít například vlastnost ve třídě blogu, která vytvoří kód založený na polích Title a BloggerName. Tuto vlastnost lze vytvořit dynamicky a není nutné ji ukládat. Všechny vlastnosti, které se nemapují na databázi, můžete označit poznámkou NotMapped, jako je tato vlastnost BlogCode.
[NotMapped]
public string BlogCode
{
get
{
return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
}
}
ComplexType
Není neobvyklé popsat entity vaší domény napříč sadou tříd a pak tyto třídy vrstvit, aby popsaly úplnou entitu. Do modelu můžete například přidat třídu s názvem BlogDetails.
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
Všimněte si, že BlogDetails
nemá žádný typ vlastnosti klíče. V návrhu BlogDetails
řízeném doménou se označuje jako objekt hodnoty. Entity Framework odkazuje na objekty hodnot jako komplexní typy. Složité typy nelze sledovat samostatně.
Jako vlastnost třídy Blog
BlogDetails
se však bude sledovat jako součást objektu Blog
. Aby kód nejprve rozpoznal, musíte třídu označit BlogDetails
jako ComplexType
.
[ComplexType]
public class BlogDetails
{
public DateTime? DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
Teď můžete do třídy přidat vlastnost Blog
, která bude představovat BlogDetails
pro tento blog.
public BlogDetails BlogDetail { get; set; }
V databázi Blog
bude tabulka obsahovat všechny vlastnosti blogu včetně vlastností obsažených v jeho BlogDetail
vlastnosti. Ve výchozím nastavení je každý z nich před názvem komplexního typu "BlogDetail".
ConcurrencyCheck
Poznámka ConcurrencyCheck
umožňuje označit jednu nebo více vlastností, které se mají použít pro kontrolu souběžnosti v databázi, když uživatel upraví nebo odstraní entitu. Pokud pracujete s EF Designerem, je to v souladu s nastavením vlastnosti ConcurrencyMode
na Fixed
hodnotu .
Pojďme se podívat, jak ConcurrencyCheck
funguje, když ji přidáme do BloggerName
vlastnosti.
[ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
public string BloggerName { get; set; }
Je-li SaveChanges
volána, protože ConcurrencyCheck
poznámka k BloggerName
poli, původní hodnota této vlastnosti bude použita v aktualizaci. Příkaz se pokusí vyhledat správný řádek filtrováním nejen podle hodnoty klíče, ale také původní hodnoty BloggerName
. Tady jsou kritické části příkazu UPDATE odeslaného do databáze, kde uvidíte, že příkaz aktualizuje řádek, který má PrimaryTrackingKey
hodnotu 1, a " BloggerName
Julie", což byla původní hodnota při načtení tohoto blogu z databáze.
where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
@4=1,@5=N'Julie'
Pokud někdo změnil název bloggeru pro tento blog mezitím, tato aktualizace selže a dostanete DbUpdateConcurrencyException , kterou budete muset zpracovat.
Časové razítko
Pro kontrolu souběžnosti je častější použití polí rowversion nebo časového razítka. Místo použití poznámky ConcurrencyCheck
ale můžete použít konkrétnější TimeStamp
anotaci, pokud je typ vlastnosti bajtové pole. Nejprve kód bude zacházet s Timestamp
vlastnostmi stejnými jako ConcurrencyCheck
s vlastnostmi, ale také zajistí, že pole databáze, které kód poprvé vygeneruje, není nullable. V dané třídě můžete mít pouze jednu vlastnost časového razítka.
Přidání následující vlastnosti do třídy Blog:
[Timestamp]
public Byte[] TimeStamp { get; set; }
výsledkem je vytvoření sloupce časového razítka bez hodnoty null v tabulce databáze.
Tabulka a sloupec
Pokud necháte Code First vytvořit databázi, můžete změnit název tabulek a sloupců, které vytváří. Můžete také použít Code First s existující databází. Nejedná se ale vždy o případ, že názvy tříd a vlastností ve vaší doméně odpovídají názvům tabulek a sloupců v databázi.
Moje třída je pojmenována Blog
a podle konvence, kód nejprve předpokládá, že se mapuje na tabulku s názvem Blogs
. Pokud tomu tak není, můžete zadat název tabulky pomocí atributu Table
. Tady je například poznámka, která určuje, že název tabulky je InternalBlogs.
[Table("InternalBlogs")]
public class Blog
Anotace Column
je podrobnější určení atributů mapovaného sloupce. Můžete určit název, datový typ nebo dokonce pořadí, ve kterém se sloupec zobrazuje v tabulce. Tady je příklad atributu Column
.
[Column("BlogDescription", TypeName="ntext")]
public String Description {get;set;}
Nezaměňujte atribut sloupce TypeName
s objektem DataType DataAnnotation. Datový typ je poznámka použitá pro uživatelské rozhraní a je ignorována kódem First.
Tady je tabulka po opětovném vygenerování. Název tabulky se změnil na InternalBlogs a Description
sloupec z komplexního typu je nyní BlogDescription
. Protože název byl zadán v poznámce, kód nejprve nepoužije konvenci spuštění názvu sloupce s názvem komplexního typu.
DatabaseGenerated
Důležitými databázovými funkcemi je schopnost mít vypočítané vlastnosti. Pokud mapujete třídy Code First na tabulky, které obsahují počítané sloupce, nechcete, aby se Entity Framework pokusil tyto sloupce aktualizovat. Chcete ale, aby ef tyto hodnoty vrátil z databáze po vložení nebo aktualizaci dat. Poznámku DatabaseGenerated
můžete použít k označení těchto vlastností ve třídě spolu s výčtem Computed
. Další výčty jsou None
a Identity
.
[DatabaseGenerated(DatabaseGeneratedOption.Computed)]
public DateTime DateCreated { get; set; }
Databázi vygenerovanou na bajtech nebo sloupcích časového razítka můžete použít při prvním vygenerování databáze, jinak byste ji měli použít jenom při odkazování na existující databáze, protože kód nejprve nebude moct určit vzorec pro vypočítaný sloupec.
Výše jste si přečetli, že ve výchozím nastavení se klíč, který je celé číslo, stane klíčem identity v databázi. To by bylo stejné jako nastavení DatabaseGenerated
na DatabaseGeneratedOption.Identity
. Pokud nechcete, aby se jedná o klíč identity, můžete hodnotu nastavit na DatabaseGeneratedOption.None
hodnotu .
Index
Poznámka:
POUZE EF6.1 – Atribut Index
byl zaveden v Entity Frameworku 6.1. Pokud používáte starší verzi, informace v této části se nevztahují.
Index můžete vytvořit na jednom nebo více sloupcích pomocí atributu IndexAttribute. Přidání atributu do jedné nebo více vlastností způsobí, že EF vytvoří odpovídající index v databázi, když vytvoří databázi, nebo vygeneruje odpovídající volání CreateIndex, pokud používáte Migrace Code First.
Například následující kód způsobí vytvoření indexu Posts
ve Rating
sloupci tabulky v databázi.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Index]
public int Rating { get; set; }
public int BlogId { get; set; }
}
Ve výchozím nastavení bude index pojmenován IX_<názvový název> (IX_Rating v předchozím příkladu). Můžete ale také zadat název indexu. Následující příklad určuje, že index by měl být pojmenován PostRatingIndex
.
[Index("PostRatingIndex")]
public int Rating { get; set; }
Ve výchozím nastavení jsou indexy ne jedinečné, ale pojmenovaný parametr můžete použít IsUnique
k určení, že index by měl být jedinečný. Následující příklad představuje jedinečný index přihlašovacího User
jména.
public class User
{
public int UserId { get; set; }
[Index(IsUnique = true)]
[StringLength(200)]
public string Username { get; set; }
public string DisplayName { get; set; }
}
Indexy s více sloupci
Indexy, které pokrývají více sloupců, se zadají pomocí stejného názvu ve více poznámkách indexu pro danou tabulku. Při vytváření indexů s více sloupci je nutné zadat pořadí sloupců v indexu. Následující kód například vytvoří index s více sloupci a Rating
zavolá IX_BlogIdAndRating.BlogId
BlogId
je první sloupec v indexu a Rating
je druhý.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public string Content { get; set; }
[Index("IX_BlogIdAndRating", 2)]
public int Rating { get; set; }
[Index("IX_BlogIdAndRating", 1)]
public int BlogId { get; set; }
}
Atributy relace: InverseProperty a ForeignKey
Poznámka:
Tato stránka obsahuje informace o nastavení relací v modelu Code First pomocí datových poznámek. Obecné informace o relacích v ef a o tom, jak přistupovat k datům a manipulovat s nimi pomocí relací, naleznete v tématu Relace a vlastnosti navigace.*
První konvence kódu se postará o nejběžnější relace ve vašem modelu, ale v některých případech potřebuje pomoct.
Změna názvu klíčové vlastnosti ve Blog
třídě vytvořila problém s jeho vztahem k Post
.
Při generování databáze kód nejprve uvidí BlogId
vlastnost ve třídě Post a rozpozná ji podle konvence, která odpovídá názvu třídy plus ID, jako cizí klíč třídy Blog
. Ale ve třídě blogu není žádná BlogId
vlastnost. Řešením je vytvořit navigační vlastnost v objektu Post
DataAnnotation a pomocí ForeignKey
dataAnnotation nejprve pochopit, jak vytvořit relaci mezi dvěma třídami (pomocí Post.BlogId
vlastnosti) a jak určit omezení v databázi.
public class Post
{
public int Id { get; set; }
public string Title { get; set; }
public DateTime DateCreated { get; set; }
public string Content { get; set; }
public int BlogId { get; set; }
[ForeignKey("BlogId")]
public Blog Blog { get; set; }
public ICollection<Comment> Comments { get; set; }
}
Omezení v databázi zobrazuje vztah mezi InternalBlogs.PrimaryTrackingKey
a Posts.BlogId
.
Používá InverseProperty
se, pokud máte více relací mezi třídami.
Post
Ve třídě můžete sledovat, kdo napsal blogový příspěvek a kdo ho upravil. Tady jsou dvě nové navigační vlastnosti třídy Post.
public Person CreatedBy { get; set; }
public Person UpdatedBy { get; set; }
Budete také muset přidat třídu Person
odkazovanou těmito vlastnostmi. Třída Person
má navigační vlastnosti zpět na Post
, jeden pro všechny příspěvky napsané osobou a jeden pro všechny příspěvky aktualizované danou osobou.
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public List<Post> PostsWritten { get; set; }
public List<Post> PostsUpdated { get; set; }
}
Nejprve kód nedokáže shodovat vlastnosti ve dvou třídách samostatně. Tabulka Posts
databáze by měla mít jeden cizí klíč pro osobu a druhý pro CreatedBy
UpdatedBy
osobu, ale kód nejprve vytvoří čtyři vlastnosti cizího klíče: Person_Id, Person_Id1, CreatedBy_Id a UpdatedBy_Id.
Chcete-li tyto problémy vyřešit, můžete pomocí poznámky InverseProperty
určit zarovnání vlastností.
[InverseProperty("CreatedBy")]
public List<Post> PostsWritten { get; set; }
[InverseProperty("UpdatedBy")]
public List<Post> PostsUpdated { get; set; }
Vzhledem k tomu, že PostsWritten
vlastnost v Person ví, že to odkazuje na Post
typ, vytvoří vztah k Post.CreatedBy
. PostsUpdated
Podobně bude připojen k Post.UpdatedBy
. A kód nejprve nevytvoří další cizí klíče.
Shrnutí
DataAnnotations umožňují nejen popsat ověřování na straně klienta a serveru ve vašich prvních třídách kódu, ale také umožňují vylepšit a dokonce opravit předpoklady, že kód nejprve provede vaše třídy na základě jeho konvencí. Pomocí DataAnnotations můžete řídit nejen generování schématu databáze, ale můžete také mapovat své první třídy kódu na předem existující databázi.
I když jsou velmi flexibilní, mějte na paměti, že DataAnnotation poskytují pouze nejčastěji potřebné změny konfigurace, které můžete provést u svých prvních tříd kódu. Pokud chcete nakonfigurovat třídy pro některé z hraničních případů, měli byste se podívat na alternativní konfigurační mechanismus, rozhraní Fluent API služby Code First .