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, třídy blog a post pohodlně dodržují kód první konvence a nevyžadují žádné úpravy, aby bylo možné povolit kompatibilitu 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í.

 

Key

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.

Blog table with primary key

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.

Create page with Title is required error

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.

 

Blogs table

 

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.

Blogs table showing max length on BloggerName column

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.

Create page with custom error message

 

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 BlogBlogDetails 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".

Blog table with complex type

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

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.

Blogs table with time stamp column

 

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.

Blogs table and column renamed

 

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

 

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 Userjmé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

relationship between InternalBlogs.PrimaryTrackingKey and 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 CreatedByUpdatedBy osobu, ale kód nejprve vytvoří čtyři vlastnosti cizího klíče: Person_Id, Person_Id1, CreatedBy_Id a UpdatedBy_Id.

Posts table with extra foreign keys

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.

Posts table without extra foreign keys

 

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 .