Adnotacje code first data

Uwaga

Tylko program EF4.1 — funkcje, interfejsy API itp. omówione na tej stronie zostały wprowadzone w programie Entity Framework 4.1. Jeśli używasz starszej wersji, niektóre lub wszystkie te informacje nie mają zastosowania.

Zawartość na tej stronie została zaadaptowana z artykułu pierwotnie napisanego przez Julie Lerman (<http://thedatafarm.com>).

Program Entity Framework Code First umożliwia używanie własnych klas domen do reprezentowania modelu, na którym polega program EF do wykonywania zapytań, śledzenia zmian i aktualizowania funkcji. Code First wykorzystuje wzorzec programowania określany jako "konwencja konfiguracji". Code First zakłada, że klasy są zgodne z konwencjami programu Entity Framework, a w takim przypadku automatycznie dopracuje sposób wykonywania zadania. Jeśli jednak klasy nie są zgodne z tymi konwencjami, masz możliwość dodawania konfiguracji do klas w celu udostępnienia efowi wymaganych informacji.

Code First udostępnia dwa sposoby dodawania tych konfiguracji do klas. Jeden z nich używa prostych atrybutów o nazwie DataAnnotations, a drugi używa interfejsu API Fluent Code First, który zapewnia sposób opisywania konfiguracji imperatywnie w kodzie.

W tym artykule skoncentrujemy się na używaniu funkcji DataAnnotations (w przestrzeni nazw System.ComponentModel.DataAnnotations) w celu skonfigurowania klas — z wyróżnieniem najczęściej potrzebnych konfiguracji. Adnotacje danych są również zrozumiałe dla wielu aplikacji platformy .NET, takich jak ASP.NET MVC, co umożliwia tym aplikacjom korzystanie z tych samych adnotacji na potrzeby weryfikacji po stronie klienta.

Model

Przedstawię funkcje Code First DataAnnotations z prostą parą klas: Blog i Post.

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

W miarę ich występowania klasy Blog i Post wygodnie przestrzegają pierwszej konwencji kodu i nie wymagają żadnych poprawek, aby umożliwić komponowanie platformy EF. Można jednak również użyć adnotacji, aby uzyskać więcej informacji na temat klas i bazy danych, do których mapują.

 

Key

Program Entity Framework opiera się na każdej jednostce mającej wartość klucza używaną do śledzenia jednostek. Jedną z konwencji Code First są niejawne właściwości klucza; Code First będzie szukać właściwości o nazwie "Id" lub kombinacji nazwy klasy i "Id", takiej jak "BlogId". Ta właściwość będzie mapować na kolumnę klucza podstawowego w bazie danych.

Klasy Blog i Post są zgodne z tą konwencją. Co zrobić, jeśli nie? Co zrobić, jeśli w blogu użyto nazwy PrimaryTrackingKey , a nawet foo? Jeśli kod najpierw nie znajdzie właściwości zgodnej z tą konwencją, zgłosi wyjątek z powodu wymagania platformy Entity Framework, że musisz mieć właściwość klucza. Możesz użyć adnotacji klucza, aby określić, która właściwość ma być używana 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; }
    }

Jeśli używasz funkcji generowania bazy danych pierwszego kodu, tabela Blog będzie mieć kolumnę klucza podstawowego o nazwie PrimaryTrackingKey, która jest również zdefiniowana jako tożsamość domyślnie.

Blog table with primary key

Klucze złożone

Platforma Entity Framework obsługuje klucze złożone — klucze podstawowe składające się z więcej niż jednej właściwości. Na przykład można mieć klasę Passport, której klucz podstawowy jest kombinacją kolumn PassportNumber i 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; }
    }

Próba użycia powyższej klasy w modelu EF spowoduje, że element :InvalidOperationException

Nie można określić złożonej kolejności klucza podstawowego dla typu "Passport". Użyj atrybutu ColumnAttribute lub metody HasKey, aby określić kolejność złożonych kluczy podstawowych.

Aby można było używać kluczy złożonych, program Entity Framework wymaga zdefiniowania kolejności dla właściwości klucza. Możesz to zrobić, używając adnotacji Kolumna, aby określić kolejność.

Uwaga

Wartość zamówienia jest względna (a nie oparta na indeksie), więc można użyć dowolnych wartości. Na przykład 100 i 200 byłoby dopuszczalne zamiast wartości 1 i 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; }
    }

Jeśli masz jednostki z złożonymi kluczami obcymi, musisz określić tę samą kolejność kolumn, która była używana dla odpowiednich właściwości klucza podstawowego.

Tylko względne porządkowanie we właściwościach klucza obcego musi być takie same, a dokładne wartości przypisane do zamówienia nie muszą być zgodne. Na przykład w poniższej klasie można użyć wartości 3 i 4 zamiast 1 i 2.

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

Wymagania

Adnotacja Required informuje ef, że wymagana jest określona właściwość.

Dodanie właściwości Wymagane do właściwości Title wymusi wymusi użycie platformy EF (i MVC), aby upewnić się, że właściwość zawiera w niej dane.

    [Required]
    public string Title { get; set; }

Bez dodatkowych zmian kodu lub znaczników w aplikacji aplikacja MVC przeprowadzi walidację po stronie klienta, nawet dynamicznie tworząc komunikat przy użyciu właściwości i nazw adnotacji.

Create page with Title is required error

Wymagany atrybut będzie również mieć wpływ na wygenerowaną bazę danych przez utworzenie właściwości mapowanej bez wartości null. Zwróć uwagę, że pole Tytuł zostało zmienione na "not null".

Uwaga

W niektórych przypadkach może nie być możliwe, aby kolumna w bazie danych była niepusta, mimo że właściwość jest wymagana. Na przykład w przypadku używania danych strategii dziedziczenia TPH dla wielu typów są przechowywane w jednej tabeli. Jeśli typ pochodny zawiera wymaganą właściwość, nie można utworzyć kolumny bez wartości null, ponieważ nie wszystkie typy w hierarchii będą miały tę właściwość.

 

Blogs table

 

MaxLength i MinLength

Atrybuty MaxLength i MinLength umożliwiają określenie dodatkowych weryfikacji właściwości, tak jak w przypadku Requiredelementu .

Oto nazwa BloggerName z wymaganiami dotyczącymi długości. W przykładzie pokazano również, jak połączyć atrybuty.

    [MaxLength(10),MinLength(5)]
    public string BloggerName { get; set; }

Adnotacja MaxLength wpłynie na bazę danych, ustawiając długość właściwości na 10.

Blogs table showing max length on BloggerName column

Adnotacja po stronie klienta MVC i adnotacja po stronie serwera EF 4.1 będą honorować tę walidację, ponownie dynamicznie tworząc komunikat o błędzie: "Pole BloggerName musi być ciągiem lub typem tablicy o maksymalnej długości "10". Wiadomość ta jest trochę długa. Wiele adnotacji umożliwia określenie komunikatu o błędzie z atrybutem ErrorMessage.

    [MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

Komunikat ErrorMessage można również określić w adnotacji Wymagane.

Create page with custom error message

 

NotMapped

Konwencja code first określa, że każda właściwość, która jest obsługiwanym typem danych, jest reprezentowana w bazie danych. Ale nie zawsze jest to przypadek w aplikacjach. Na przykład możesz mieć właściwość w klasie Blog, która tworzy kod na podstawie pól Title i BloggerName. Ta właściwość może być tworzona dynamicznie i nie musi być przechowywana. Możesz oznaczyć wszystkie właściwości, które nie są mapowane na bazę danych z adnotacją NotMapped, taką jak ta właściwość BlogCode.

    [NotMapped]
    public string BlogCode
    {
        get
        {
            return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
        }
    }

 

ComplexType

Nie rzadko opisano jednostki domeny w zestawie klas, a następnie warstwy tych klas w celu opisania pełnej jednostki. Możesz na przykład dodać klasę o nazwie BlogDetails do modelu.

    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

Zwróć uwagę, że BlogDetails nie ma żadnej właściwości klucza. W projekcie BlogDetails opartym na domenie jest określany jako obiekt wartości. Platforma Entity Framework odwołuje się do obiektów wartości jako typów złożonych.  Nie można śledzić własnych typów złożonych.

Jednak jako właściwość w Blog klasie BlogDetails będzie śledzona jako część Blog obiektu. Aby kod był najpierw rozpoznawany, należy oznaczyć klasę BlogDetailsComplexTypejako .

    [ComplexType]
    public class BlogDetails
    {
        public DateTime? DateCreated { get; set; }

        [MaxLength(250)]
        public string Description { get; set; }
    }

Teraz możesz dodać właściwość w Blog klasie, aby reprezentować dla tego bloga BlogDetails .

        public BlogDetails BlogDetail { get; set; }

W bazie danych tabela będzie zawierać wszystkie właściwości blogu, Blog w tym właściwości zawarte we właściwości BlogDetail . Domyślnie każdy z nich jest poprzedzony nazwą typu złożonego "BlogDetail".

Blog table with complex type

Sprawdzanie współbieżności

Adnotacja ConcurrencyCheck umożliwia flagowanie co najmniej jednej właściwości, która ma być używana do sprawdzania współbieżności w bazie danych, gdy użytkownik edytuje lub usuwa jednostkę. Jeśli pracujesz z Projektant EF, jest to zgodne z ustawieniem właściwości ConcurrencyMode na Fixedwartość .

Zobaczmy, jak ConcurrencyCheck działa dodanie jej do BloggerName właściwości.

    [ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
    public string BloggerName { get; set; }

Gdy SaveChanges jest wywoływana, ze względu ConcurrencyCheck na adnotację w BloggerName polu, oryginalna wartość tej właściwości zostanie użyta w aktualizacji. Polecenie podejmie próbę zlokalizowania poprawnego wiersza, filtrując nie tylko wartość klucza, ale także dla oryginalnej BloggerNamewartości .  Poniżej przedstawiono krytyczne części polecenia UPDATE wysyłanego do bazy danych, gdzie można zobaczyć, że polecenie zaktualizuje wiersz o PrimaryTrackingKey wartości 1 i wartość BloggerName "Julie", która była oryginalną wartością po pobraniu tego bloga z bazy danych.

    where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
    @4=1,@5=N'Julie'

Jeśli ktoś zmienił nazwę bloga dla tego bloga w międzyczasie, ta aktualizacja zakończy się niepowodzeniem i otrzymasz dbUpdateConcurrencyException, który będzie potrzebny do obsługi.

 

Znacznik czasu

Częściej używa się pól rowversion lub timestamp na potrzeby sprawdzania współbieżności. Jednak zamiast używać ConcurrencyCheck adnotacji, można użyć bardziej szczegółowej TimeStamp adnotacji, o ile typ właściwości jest tablicą bajtów. Najpierw kod będzie traktować Timestamp właściwości takie same jak ConcurrencyCheck właściwości, ale zapewni również, że pole bazy danych, które najpierw generuje kod, jest niepuste. W danej klasie można mieć tylko jedną właściwość znacznika czasu.

Dodanie następującej właściwości do klasy Blog:

    [Timestamp]
    public Byte[] TimeStamp { get; set; }

kod najpierw tworzy kolumnę sygnatury czasowej innej niż null w tabeli bazy danych.

Blogs table with time stamp column

 

Tabela i kolumna

Jeśli zezwalasz aplikacji Code First na utworzenie bazy danych, możesz zmienić nazwę tabel i kolumn, które tworzysz. Możesz również użyć funkcji Code First z istniejącą bazą danych. Nie zawsze jednak nazwy klas i właściwości w domenie są zgodne z nazwami tabel i kolumn w bazie danych.

Moja klasa nosi nazwę Blog i zgodnie z konwencją kod najpierw zakłada, że będzie to mapowane na tabelę o nazwie Blogs. Jeśli tak nie jest, możesz określić nazwę tabeli za pomocą atrybutu Table . Na przykład adnotacja określa, że nazwa tabeli to InternalBlogs.

    [Table("InternalBlogs")]
    public class Blog

Adnotacja Column jest bardziej biegła w określaniu atrybutów zamapowanej kolumny. Można określić nazwę, typ danych, a nawet kolejność wyświetlania kolumny w tabeli. Oto przykład atrybutu Column .

    [Column("BlogDescription", TypeName="ntext")]
    public String Description {get;set;}

Nie należy wprowadzać w błąd atrybutu Kolumna TypeName z atrybutem DataType DataAnnotation. DataType to adnotacja używana dla interfejsu użytkownika i jest ignorowana przez funkcję Code First.

Poniżej przedstawiono tabelę po jej ponownej wygenerowaniu. Nazwa tabeli została zmieniona na InternalBlogs i Description kolumna z typu złożonego to teraz BlogDescription. Ponieważ nazwa została określona w adnotacji, kod najpierw nie będzie używać konwencji uruchamiania nazwy kolumny o nazwie typu złożonego.

Blogs table and column renamed

 

DatabaseGenerated

Ważnymi funkcjami bazy danych jest możliwość posiadania właściwości obliczeniowych. Jeśli mapujesz klasy Code First na tabele zawierające obliczone kolumny, nie chcesz, aby program Entity Framework próbował zaktualizować te kolumny. Jednak program EF ma zwracać te wartości z bazy danych po wstawieniu lub zaktualizowaniu danych. Możesz użyć DatabaseGenerated adnotacji, aby oznaczyć te właściwości w klasie wraz z wyliczeniem Computed . Inne wyliczenia to None i Identity.

    [DatabaseGenerated(DatabaseGeneratedOption.Computed)]
    public DateTime DateCreated { get; set; }

Bazę danych wygenerowaną na kolumnach bajtów lub sygnatur czasowych można użyć, gdy kod najpierw generuje bazę danych. W przeciwnym razie należy użyć tej opcji tylko podczas wskazywania istniejących baz danych, ponieważ kod pierwszy nie będzie mógł określić formuły dla obliczonej kolumny.

Przeczytasz powyżej, że domyślnie właściwość klucza, która jest liczbą całkowitą, stanie się kluczem tożsamości w bazie danych. Byłoby to takie samo, jak ustawienie DatabaseGenerated wartości DatabaseGeneratedOption.Identity. Jeśli nie chcesz, aby był to klucz tożsamości, możesz ustawić wartość na DatabaseGeneratedOption.None.

 

Indeks

Uwaga

Tylko program EF6.1 — Index atrybut został wprowadzony w programie Entity Framework 6.1. Jeśli używasz starszej wersji, informacje przedstawione w tej sekcji nie mają zastosowania.

Indeks można utworzyć w co najmniej jednej kolumnie przy użyciu atrybutu IndexAttribute. Dodanie atrybutu do co najmniej jednej właściwości spowoduje utworzenie odpowiedniego indeksu w bazie danych podczas tworzenia bazy danych lub utworzenie szkieletu odpowiednich wywołań CreateIndex, jeśli używasz Migracje Code First.

Na przykład poniższy kod spowoduje utworzenie indeksu w Rating kolumnie Posts tabeli w bazie danych.

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

Domyślnie indeks będzie miał nazwę IX_<właściwości> (IX_Rating w powyższym przykładzie). Można również określić nazwę indeksu. W poniższym przykładzie określono, że indeks powinien mieć nazwę PostRatingIndex.

    [Index("PostRatingIndex")]
    public int Rating { get; set; }

Domyślnie indeksy nie są unikatowe, ale można użyć nazwanego parametru IsUnique , aby określić, że indeks powinien być unikatowy. W poniższym przykładzie wprowadzono unikatowy indeks nazwy Userlogowania.

    public class User
    {
        public int UserId { get; set; }

        [Index(IsUnique = true)]
        [StringLength(200)]
        public string Username { get; set; }

        public string DisplayName { get; set; }
    }

Indeksy wielokolumne

Indeksy obejmujące wiele kolumn są określane przy użyciu tej samej nazwy w wielu adnotacjach indeksu dla danej tabeli. Podczas tworzenia indeksów wielokolumnach należy określić kolejność kolumn w indeksie. Na przykład poniższy kod tworzy indeks wielokolumny i RatingBlogId wywoływany IX_BlogIdAndRating. BlogId jest pierwszą kolumną w indeksie i Rating drugą.

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

 

Atrybuty relacji: InverseProperty i ForeignKey

Uwaga

Ta strona zawiera informacje o konfigurowaniu relacji w modelu Code First przy użyciu adnotacji danych. Aby uzyskać ogólne informacje o relacjach w programie EF oraz sposobie uzyskiwania dostępu do danych i manipulowania nimi przy użyciu relacji, zobacz Relacje i właściwości nawigacji.*

Pierwsza konwencja kodu zajmie się najbardziej typowymi relacjami w modelu, ale istnieje kilka przypadków, w których potrzebuje pomocy.

Zmiana nazwy właściwości klucza w Blog klasie spowodowała problem z relacją Postz . 

Podczas generowania bazy danych kod najpierw widzi BlogId właściwość w klasie Post i rozpoznaje ją zgodnie z konwencją zgodną z nazwą klasy plus identyfikatorem, jako kluczem obcym do Blog klasy. Ale w klasie bloga nie BlogId ma właściwości. Rozwiązaniem tego problemu jest utworzenie właściwości nawigacji w Post obiekcie i użycie ForeignKey funkcji DataAnnotation, aby ułatwić kodowi najpierw zrozumienie sposobu tworzenia relacji między dwiema klasami (przy użyciu Post.BlogId właściwości), a także sposobem określania ograniczeń w bazie danych.

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

Ograniczenie w bazie danych pokazuje relację między InternalBlogs.PrimaryTrackingKey i Posts.BlogId

relationship between InternalBlogs.PrimaryTrackingKey and Posts.BlogId

Element InverseProperty jest używany, gdy istnieje wiele relacji między klasami.

Post W klasie możesz śledzić, kto napisał wpis w blogu, a także kto go edytował. Poniżej przedstawiono dwie nowe właściwości nawigacji dla klasy Post.

    public Person CreatedBy { get; set; }
    public Person UpdatedBy { get; set; }

Należy również dodać klasę Person , do której odwołuje się te właściwości. Klasa Person ma właściwości nawigacji z powrotem do Post, jeden dla wszystkich wpisów napisanych przez osobę i jeden dla wszystkich wpisów zaktualizowanych przez daną osobę.

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

Najpierw kod nie jest w stanie dopasować właściwości w dwóch klasach samodzielnie. Tabela bazy danych dla Posts elementu powinna mieć jeden klucz obcy dla osoby i jeden dla CreatedByUpdatedBy osoby, ale kod najpierw utworzy cztery właściwości klucza obcego: Person_Id, Person_Id1, CreatedBy_Id i UpdatedBy_Id.

Posts table with extra foreign keys

Aby rozwiązać te problemy, możesz użyć InverseProperty adnotacji, aby określić wyrównanie właściwości.

    [InverseProperty("CreatedBy")]
    public List<Post> PostsWritten { get; set; }

    [InverseProperty("UpdatedBy")]
    public List<Post> PostsUpdated { get; set; }

PostsWritten Ponieważ właściwość w osobie wie, że odnosi się to do Post typu, skompiluje relację z Post.CreatedBy. PostsUpdated Podobnie zostanie nawiązane połączenie z usługą Post.UpdatedBy. Kod najpierw nie utworzy dodatkowych kluczy obcych.

Posts table without extra foreign keys

 

Podsumowanie

Funkcje DataAnnotations nie tylko umożliwiają opisywanie weryfikacji po stronie klienta i serwera w klasach pierwszej klasy kodu, ale umożliwiają również ulepszanie, a nawet poprawianie założeń, które kod najpierw wprowadzi w Twoich klasach na podstawie konwencji. Za pomocą funkcji DataAnnotations można nie tylko napędzać generowanie schematu bazy danych, ale także mapować pierwsze klasy kodu na wcześniej istniejącą bazę danych.

Chociaż są one bardzo elastyczne, należy pamiętać, że funkcje DataAnnotations zapewniają tylko najczęściej potrzebne zmiany konfiguracji, które można wprowadzić w pierwszych klasach kodu. Aby skonfigurować klasy dla niektórych przypadków brzegowych, należy zapoznać się z alternatywnym mechanizmem konfiguracji, interfejsem API Fluent Api aplikacji Code First.