Sdílet prostřednictvím


Poznámky k datům Code First

Poznámka:

EF4.1 a novější – funkce, rozhraní API atd. diskutované na této stránce byly představeny ve verzi Entity Framework 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é poskytnou EF požadované informace.

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ředvedu 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; }
    }

V jejich současné podobě třídy Blog a Post pohodlně dodržují konvence code first a nevyžadují žádné úpravy pro kompatibilitu s EF. Poznámky ale můžete také použít k poskytnutí dalších informací o EF, 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 bude mapovat 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 Code First, tabulka Blog bude mít sloupec primárního klíče s názvem PrimaryTrackingKey, který je ve výchozím nastavení definován jako Identity.

Tabulka blogu s primárním klíčem

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í třídy uvedené výše 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í Required k vlastnosti Title zajistí, že EF (a MVC) zajistí, aby vlastnost obsahovala 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.

Chyba: Zadaní názvu je povinné při vytváření stránky

Atribut Required bude mít také vliv na vygenerovanou databázi tím, že mapovaná vlastnost bude nenulová. 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.

 

Tabulka blogů

 

MaxLength a MinLength

MaxLength a MinLength atributy umožňují zadat další ověření vlastností, stejně jako jste to udělali s Required.

Tady je BloggerName s požadavky na délku jména. 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.

Tabulka blogů zobrazující maximální délku sloupce BloggerName

Poznámky na straně klienta MVC a poznámky na straně serveru EF 4.1 opět respektují toto ověřování, přičemž dynamicky vytvářejí chybovou zprávu: "Pole BloggerName musí být typu řetězec nebo pole s maximální délkou '10'." Tato 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.

Vytvoření stránky s vlastní chybovou zprávou

 

Nezmapováno

První konvence kódu určuje, že každá vlastnost, která je podporovaným datovým typem, je reprezentována v databázi. To ale není vždy případ vašich aplikací. 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 mohl rozpoznat tuto skutečnost na první pohled, 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".

Blogová tabulka se složitým typem

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, odpovídá to nastavení 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, vzhledem k anotaci ConcurrencyCheck na poli BloggerName, původní hodnota této vlastnosti bude použita při 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žívat pole rowversion nebo časové razítko. Místo použití poznámky ConcurrencyCheck ale můžete použít konkrétnější TimeStamp anotaci, pokud je typ vlastnosti bajtové pole. "Code first bude zacházet s Timestamp vlastnostmi stejně jako s ConcurrencyCheck vlastnostmi, ale také zajistí, že pole databáze, které vygeneruje, nebude nullovatelné." 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, že kód nejprve vytvoří sloupec časového razítka v tabulce databáze, který neumožňuje hodnotu null.

Tabulka blogů se sloupcem s časovým razítkem

 

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 se nazývá Blog a podle konvence kód předpokládá jako první, ž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 vhodnější pro specifikaci 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. DataType je anotace použitá pro uživatelské rozhraní a je ignorována Code 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 byl název zadán v poznámce, kód nejprve nepoužije konvenci začínání názvu sloupce názvem komplexního typu.

Přejmenování tabulky a sloupce blogů

 

GenerovánoDatabází

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. K označení těchto vlastností ve třídě můžete použít anotaci DatabaseGenerated 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 .

 

Rejstřík

Poznámka:

Pouze od 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 Rating ve Posts 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ázev vlastnosti> (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 na Rating a BlogId nazvaný IX_BlogIdAndRating. 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.*

Konvence Code First zajistí nejběžnější relace ve vašem modelu, ale v některých případech potřebuje pomoc.

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 v třídě blogu není žádná BlogId vlastnost. Řešením je vytvořit navigační vlastnost v Post a použít ForeignKey DataAnnotation, aby kód nejprve pochopil, jak vytvořit relaci mezi dvěma třídami (pomocí Post.BlogId vlastnosti) a jak specifikovat 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

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, na kterou odkazují tyto vlastnosti. 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.

Tabulka příspěvků s dalšími cizími klíči

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 vlastnost PostsWritten v Personu chápe, že odkazuje na typ Post, vytvoří vztah k Post.CreatedBy. PostsUpdated Podobně bude připojen k Post.UpdatedBy. A kód nejprve nevytvoří další cizí klíče.

Tabulka příspěvků bez dalších cizích klíčů

 

Shrnutí

DataAnnotations umožňují nejen popsat ověřování na klientské a serverové straně ve vašich Code First třídách, ale také umožňují vylepšit a dokonce opravit předpoklady, které Code First vyvodí o vašich třídách na základě svých 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 .