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; }
}
Tak jak są, klasy Blog i Post wygodnie przestrzegają pierwszej konwencji kodu i nie wymagają żadnych poprawek, aby umożliwić zgodność z platformą EF. Można jednak również użyć adnotacji, aby uzyskać więcej informacji na temat klas i bazy danych, do których mapują.
Klucz
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.
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.
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ść.
MaxLength i MinLength
Atrybuty MaxLength
i MinLength
umożliwiają określenie dodatkowych weryfikacji właściwości, tak jak w przypadku Required
elementu .
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.
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.
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ę BlogDetails
ComplexType
jako .
[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".
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 projektantem EF, jest to zgodne z ustawieniem właściwości ConcurrencyMode
na Fixed
wartość .
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 BloggerName
wartoś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.
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.
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 User
logowania.
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 Rating
BlogId
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ą Post
z .
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
.
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 CreatedBy
UpdatedBy
osoby, ale kod najpierw utworzy cztery właściwości klucza obcego: Person_Id, Person_Id1, CreatedBy_Id i UpdatedBy_Id.
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.
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.