實體框架 (EF) 入門 => 三、CodeFirst 支持的完整特性列表
KeyAttribute設置主鍵。如果為int類型,將自動設置為自增長列。
系統默認以Id或類名+Id作為主鍵。
StringLengthAttribute可設置最大最小長度以及驗證提示信息等。最大長度會映射到數據庫。
MaxLengthAttribute最大長度。會映射的數據庫。
ConcurrencyCheckAttribute修改或刪除時,將帶此屬性的列的原有值與主鍵一起傳送到數據庫,如果傳遞的值與數據庫中不一致,則修改或刪除失敗。用於並發檢查。
RequiredAttribute必填字段。將映射到數據庫,使字段屬性為 not null。客戶段作必填項驗證。
TimestampAttribute用於並發檢查,一個實體類中只能有一個標記為此屬性。
[Timestamp] public Byte[] TimeStamp { get; set; }
這樣,Code First 將在數據庫表中創建一個不可為空的 Timestamp 列。
ComplexTypeAttribute
ColumnAttribute
指定列名
[Column(“BlogDescription", TypeName="ntext")]
public String Description {get;set;}
TableAttribute指定表名
[Table("InternalBlogs")]
public class Blog
InversePropertyAttribute兩個類之間存在多個關系時,將使用 InverseProperty。
ForeignKeyAttribute用於外鍵名稱與主鍵不一致的情況。
DatabaseGeneratedAttributeIdentity 自增長列,一般主鍵會自動設置此屬性,非主鍵才需要設置。
None 用於不將主鍵設置為自增長列。
Computed 不讓實體框架嘗試更新這些列,一般用於使用默認值。
[DatabaseGenerated(DatabaseGenerationOption.Computed)]
不能添加[Required],驗證通不過。也不必添加[Required],數據庫中自動設置為 not null。
bool => 0 (false)
int => 0
float => 0
decimal => 0
datetime => 1900-01-01 00:00:00
byte => 0 在數據庫中為 tinyint (取值範圍0-255)
short=> 0 在數據庫中為 smallyint
如果設置了
[DatabaseGenerated(DatabaseGenerationOption.Computed)] ,再取消,數據庫中也不會修改
除了string 外,基本都允許有默認值
數值類型也沒有必要在數據庫中添加默認值,值類型在初始化時,自動會賦予默認值並保存到數據庫中。
datetime 或 string 如果想使用默認值,可以在構造函數中進行初始化。
NotMappedAttribute不映射到數據庫,一般用於將映射到數據庫的字段通過計算獲取只讀值。
通過實體框架 Code First,可以使用您自己的域類表示 EF 執行查詢、更改跟蹤和更新函數所依賴的模型。Code First 利用稱為“約定先於配置”的編程模式。這就是說,Code First 將假定您的類遵從 EF 所使用的約定。在這種情況下,EF 將能夠找出自己工作所需的詳細信息。但是,如果您的類不遵守這些約定,則可以向類中添加配置,以向 EF 提供它需要的信息。
Code First 為您提供了兩種方法來向類中添加這些配置。一種方法是使用名為 DataAnnotations 的簡單特性,另一種方法是使用 Code First 的 Fluent API,該 API 向您提供了在代碼中以命令方式描述配置的方法。
本文重點介紹如何使用 DataAnnotations(在 System.ComponentModel.DataAnnotations 命名空間中)對類進行配置,著重講述常用的配置。很多 .NET 應用程序(如 ASP.NET MVC)都能夠理解 DataAnnotations,它允許這些應用程序對客戶端驗證使用相同的註釋。
我將通過 Blog 和 Post 這兩個簡單的類來說明 Code First DataAnnotations。
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; }
}
Blog 和 Post 類本身就遵守 Code First 約定,無需調整即可讓 EF 與之共同使用。但您也可以使用註釋向 EF 提供有關類以及類所映射到的數據庫的更多信息。
鍵
實體框架依賴於每個具有鍵值的實體,它使用鍵值來跟蹤實體。Code First 依賴的一個約定是它在每一個 Code First 類中以何種方式表示哪一個屬性是鍵。該約定是查找名為“Id”或類名與“Id”組合在一起(如“BlogId”)的屬性。該屬性將映射到數據庫中的主鍵列。
Blog 和 Post 類都遵守此約定。但如果它們不遵守呢?如果 Blog 使用名稱 PrimaryTrackingKey,甚至使用 foo 呢?如果 Code First 找不到符合此約定的屬性,它將引發異常,因為實體框架要求必須要有一個鍵屬性。您可以使用鍵註釋來指定要將哪一個屬性用作 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; }
}
如果您在使用 Code First 的數據庫生成功能,則 Blog 表將具有名為 PrimaryTrackingKey 的主鍵列,該列默認情況下還定義為 Identity。
必需
Required 註釋告訴 EF 某一個特定屬性是必需的。
在 Title 屬性中添加 Required 將強制 EF(和 MVC)確保該屬性中包含數據。
[Required]
public string Title { get; set; }
MVC 應用程序無需添加其他代碼或更改標記,就能執行客戶端驗證,甚至還能使用屬性和註釋名稱動態生成消息。
Required 特性還將使被映射的屬性不可為空來影響生成的數據庫。請註意,Title 字段已經更改為“not null”。
MaxLength 和 MinLength
使用 MaxLength 和 MinLength 特性,您可以就像對 Required 那樣指定其他屬性驗證。
下面是具有長度要求的 BloggerName。該示例也說明如何組合特性。
[MaxLength(10),MinLength(5)]
public string BloggerName { get; set; }
MaxLength 註釋將通過把屬性長度設置為 10 來影響數據庫。
MVC 客戶端註釋和 EF 4.1 服務器端註釋都要執行此驗證,也會動態生成錯誤消息:“字段 BloggerName 必須是最大長度為 10 的字符串或數組類型。”該消息有一點長。很多註釋都允許您使用 ErrorMessage 特性來指定錯誤消息。
[MaxLength(10, ErrorMessage="BloggerName 必須在 10 個字符以下"),MinLength(5)]
public string BloggerName { get; set; }
您也可以在 Required 註釋中指定 ErrorMessage。
NotMapped
Code First 約定指示具有受支持數據類型的每個屬性都要在數據庫中有表示。但在您的應用程序中並不總是如此。例如,您可以在 Blog 類中使用一個屬性來基於 Title 和 BloggerName 字段創建代碼。該屬性可以動態創建,無需存儲。您可以使用 NotMapped 註釋來標記不映射到數據庫的所有屬性,如下面的 BlogCode 屬性。
[NotMapped]
public string BlogCode
{
get
{
return Title.Substring(0, 1) + ":" + BloggerName.Substring(0, 1);
}
}
ComplexType
跨一組類描述域實體,然後將這些類分層以描述一個完整實體的情況並不少見。例如,您可以向模型中添加一個名為 BlogDetails 的類。
public class BlogDetails
{
public DateTime?DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
請註意,BlogDetails 沒有任何鍵屬性類型。在域驅動的設計中,BlogDetails 稱為值對象。實體框架將值對象稱為復雜類型。復雜類型不能自行跟蹤。
但是 BlogDetails 作為 Blog 類中的一個屬性,將作為 Blog 對象的一部分被跟蹤。為了讓 Code First 認識到這一點,您必須將 BlogDetails 類標記為 ComplexType。
[ComplexType]
public class BlogDetails
{
public DateTime?DateCreated { get; set; }
[MaxLength(250)]
public string Description { get; set; }
}
現在,您可以在 Blog 類中添加一個屬性來表示該博客的 BlogDetails。
public BlogDetails BlogDetail { get; set; }
在數據庫中,Blog 表將包含該博客的所有屬性,包括在其 BlogDetail 屬性中所含的屬性。默認情況下,每個屬性都將添加復雜類型名稱前綴 BlogDetail。
另外,有趣的是,雖然 DateCreated 屬性在類中定義為不可為空的 DateTime,但相關數據庫字段是可為空的。如果想影響數據庫架構,則必須使用 Required 註釋。
ConcurrencyCheck
ConcurrencyCheck 註釋可用於標記要在用戶編輯或刪除實體時用於在數據庫中進行並發檢查的一個或多個屬性。如果之前使用 EF 設計器,則這等同於將屬性的 ConcurrencyMode 設置為 Fixed。
現在讓我們將 ConcurrencyCheck 添加到 BloggerName 屬性,看看它如何工作。
[ConcurrencyCheck, MaxLength(10, ErrorMessage="BloggerName must be 10 characters or less"),MinLength(5)]
public string BloggerName { get; set; }
調用 SaveChanges 時,因為 BloggerName 字段上具有 ConcurrencyCheck 註釋,所以在更新中將使用該屬性的初始值。該命令將嘗試通過同時依據鍵值和 BloggerName 的初始值進行篩選來查找正確的行。下面是發送到數據庫的 UPDATE 命令的關鍵部分,在其中您可以看到該命令將更新 PrimaryTrackingKey 為 1 且 BloggerName 為“Julie”(這是從數據庫中檢索到該博客時的初始值)的行。
where (([PrimaryTrackingKey] = @4) and ([BloggerName] = @5))
@4=1,@5=N‘Julie‘
如果在此期間有人更改了該博客的博主姓名,則此更新將失敗,並引發 DbUpdateConcurrencyException 並且需要處理該異常。
TimeStamp
使用 rowversion 或 timestamp 字段來進行並發檢查更為常見。但是比起使用 ConcurrencyCheck 註釋,只要屬性類型為字節數組,則不如使用更為具體的 TimeStamp 註釋。Code First 將 Timestamp 屬性與 ConcurrencyCheck 屬性同等對待,但它還將確保 Code First 生成的數據庫字段是不可為空的。在一個指定類中,只能有一個 timestamp 屬性。
將以下屬性添加到 Blog 類:
[Timestamp]
public Byte[] TimeStamp { get; set; }
這樣,Code First 將在數據庫表中創建一個不可為空的 Timestamp 列。
表和列
如果您讓 Code First 創建數據庫,則可能希望更改它創建的表和列的名稱。也可以將 Code First 用於現有數據庫。但是域中的類和屬性的名稱並不總是與數據庫中表和列的名稱相匹配。
我的類名為 Blog,按照約定,Code First 將假定此類映射到名為 Blogs 的表。如果不是這樣,您可以用 Table 特性指定該表的名稱。舉例來說,下面的註釋指定表名稱為 InternalBlogs。
[Table("InternalBlogs")]
public class Blog
Column 註釋更適於用來指定被映射列的特性。您可以規定名稱、數據類型甚至列出現在表中的順序。下面是 Column 特性的示例。
[Column(“BlogDescription", TypeName="ntext")]
public String Description {get;set;}
不要將列的 TypeName 特性與 DataType DataAnnotation 相混淆。DataType 是一個用於 UI 的註釋,Code First 會將它忽略。
下面是重新生成後的表。表名稱已更改為 InternalBlogs,復雜類型的 Description 列現在是 BlogDescription。因為該名稱在註釋中指定,Code First 不會使用以復雜類型名稱作為列名開頭的約定。
DatabaseGenerated
一個重要的數據庫功能是可以使用計算屬性。如果您將 Code First 類映射到包含計算列的表,則您可能不想讓實體框架嘗試更新這些列。但是在插入或更新數據後,您的確需要 EF 從數據庫中返回這些值。您可以使用 DatabaseGenerated 註釋與 Computed 枚舉一起在您的類中標註這些屬性。其他枚舉為 None 和 Identity。
[DatabaseGenerated(DatabaseGenerationOption.Computed)]
public DateTime DateCreated { get; set; }
當 Code First 生成數據庫時,您可以對 byte 或 timestamp 列使用生成的數據庫,否則您只應該在指向現有數據庫時使用,因為 Code First 將不能確定計算列的公式。
您閱讀過以上內容,知道默認情況下,整數鍵屬性將成為數據庫中的標識鍵。這與將 DatabaseGenerated 設置為 DatabaseGenerationOption.Identity 是一樣的。如果不希望它成為標識鍵,則可以將該值設置為 DatabaseGenerationOption.None。
[DatabaseGenerated(DatabaseGenerationOption.Computed)]
不能添加[Required],驗證通不過。也不必添加[Required],數據庫中自動設置為 not null。
bool => 0 (false)
int => 0
float => 0
decimal => 0
datetime => 1900-01-01 00:00:00
byte => 0 在數據庫中為 tinyint (取值範圍0-255)
short=> 0 在數據庫中為 smallyint
如果設置了 [DatabaseGenerated(DatabaseGenerationOption.Computed)] ,再取消,數據庫中也不會修改
除了string 外,基本都允許有默認值
關系特性:InverseProperty 和 ForeignKey
註意:此頁面提供有關使用數據註釋在 Code First 模型中設置關系的信息。有關 EF 中的關系的一般信息和如何使用關系來訪問和操作數據,請參閱 關系和導航屬性。
Code First 約定將在您的模型中處理最常用的關系,但是在某些情況下它需要幫助。
在 Blog 類中更改鍵屬性的名稱造成它與 Post 的關系出現問題。
生成數據庫時,Code First 會在 Post 類中看到 BlogId 屬性並識別出該屬性,按照約定,它與類名加“Id”匹配,並作為 Blog 類的外鍵。但是在此 Blog 類中沒有 BlogId 屬性。解決方法是,在 Post 中創建一個導航屬性,並使用 Foreign DataAnnotation 來幫助 Code First 了解如何在兩個類之間創建關系(那就是使用 Post.BlogId 屬性)以及如何在數據庫中指定約束。
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; }
}
數據庫中的約束顯示 InternalBlogs.PrimaryTrackingKey 與 Posts.BlogId 之間的關系。
類之間存在多個關系時,將使用 InverseProperty。
在 Post 類中,您可能需要跟蹤是誰撰寫了博客文章以及誰編輯了它。下面是 Post 類的兩個新的導航屬性。
public Person CreatedBy { get; set; }
public Person UpdatedBy { get; set; }
您還需要在這些屬性引用的 Person 類中添加內容。Person 類具有返回到 Post 的導航屬性,一個屬性指向該用戶撰寫的所有文章,一個屬性指向該用戶更新的所有文章。
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; }
}
Code First 不能自行使這兩個類中的屬性匹配。Posts 的數據庫表應該有一個表示 CreatedBy 人員的外鍵,有一個表示 UpdatedBy 人員的外鍵,但是 Code First 將創建四個外鍵屬性:Person_Id、Person_Id1、CreatedBy_Id 和 UpdatedBy_Id。
要解決這些問題,您可以使用 InverseProperty 註釋來指定這些屬性的匹配。
[InverseProperty("CreatedBy")]
public List<Post> PostsWritten { get; set; }
[InverseProperty("UpdatedBy")]
public List<Post> PostsUpdated { get; set; }
因為 Person 中的 PostsWritten 屬性知道這指的是 Post 類型,所以它將與 Post.CreatedBy 建立關系。同樣,PostsUpdated 也將與 Post.UpdatedBy 建立關系。Code First 不會創建額外的外鍵。
表內主外鍵
實體框架 (EF) 入門 => 三、CodeFirst 支持的完整特性列表