1. 程式人生 > >Entity Framework 5.0 Code First全面學習

Entity Framework 5.0 Code First全面學習

不貼圖片了,太累。

Code First 約定

藉助 CodeFirst,可通過使用 C# 或Visual Basic .NET 類來描述模型。模型的基本形狀可通過約定來檢測。約定是規則集,用於在使用 Code First 時基於類定義自動配置概念模型。約定是在 System.Data.Entity.ModelConfiguration.Conventions 名稱空間中定義的。

可通過使用資料註釋或Fluent API 進一步配置模型。優先順序是通過 Fluent API 進行配置,然後是通過資料註釋,再次是通過約定。

API 文件中提供了 Code First 約定的詳細列表。本主題概述 Code First 使用的約定。

型別發現

當使用 CodeFirst 開發時,通常是從編寫用來定義概念(域)模型的 .NET類開始。除了定義類之外,還需要讓 DbContext 知道模型中要包含哪些類。為此,需要定義一個上下文類,此類派生自 DbContext 並公開需要成為模型一部分的型別的 DbSet 屬性。Code First 將包含這些型別,還將包含任何引用型別,即使這些引用型別是在不同的程式集中定義的也是如此。

如果型別存在於繼承層次結構中,則為基類定義 DbSet 屬性就足夠了,如果派生型別位於與基類相同的程式集中,則自動包含這些派生型別。

在下面的示例中,僅對SchoolEntities 類定義一個DbSet 屬性 (Departments)。CodeFirst 使用此屬性來發現幷包含任何引用型別。

publicclass SchoolEntities: DbContext

{

  public DbSet<Department>Departments { get; set;}

}

publicclass Department

{

  // Primary key

  public int DepartmentID { get;set; }

  public string Name { get; set; }

  // Navigationproperty

  public virtual ICollection<Course> Courses { get;set

; }

}

publicclass Course

{

  // Primary key

  public int CourseID { get; set; }

  public string Title { get; set; }

  public int Credits { get; set; }

  // Foreign key

  public int DepartmentID { get;set; }

  // Navigationproperties

  public virtual DepartmentDepartment { get; set;}

}

publicpartial class OnlineCourse : Course

{

  public string URL { get; set; }

}

publicpartial class OnsiteCourse : Course

{

  public string Location { get;set; }

  public string Days { get; set; }

  publicSystem.DateTime Time { get; set; }

}

如果要從模型排除型別,請使用 NotMapped 特性或DbModelBuilder.Ignore

主鍵約定

如果類的屬性名為“ID”(不區分大小寫)或類名的後面跟有“ID”,則 Code First 會推斷該屬性是主鍵。如果主鍵屬性的型別為數值或 GUID,則將其配置為標識列。

publicclass Department

{

  // Primary key

  public int DepartmentID { get;set; }

}

關係約定

實體框架中的導航屬性提供了一種在兩個實體型別之間導航關係的方法。針對物件參與到其中的每個關係,各物件均可以具有導航屬性。使用導航屬性,可以在兩個方向上導航和管理關係,返回引用物件(如果多重性為一或者零或一)或集合(如果多重性為多)。Code First 根據針對型別定義的導航屬性來推斷關係。

除導航屬性外,建議還要包括表示依賴物件的型別的外來鍵屬性。任何資料型別與主體主鍵屬性相同、遵循以下一種格式的屬性都表示關係的外來鍵:“<導航屬性名稱><主體主鍵屬性>”、“<主體類名><主鍵屬性名稱>”或“<主體主鍵屬性名稱>”。如果找到多個匹配項,則優先順序符合上面列出的順序。外來鍵檢測不區分大小寫。在檢測外來鍵屬性時,Code First 基於外來鍵的可空性推斷關係的多重性。如果屬性可以為 Null,則將關係註冊為可選關係;否則,將關係註冊為必需關係。

如果依賴實體上的外來鍵不能為 Null,則 CodeFirst 對關係設定級聯刪除。如果依賴實體上的外來鍵可以為 Null,則Code First 不對關係設定級聯刪除,並且在刪除主體時,會將該外來鍵設定為 Null。通過使用 Fluent API,可以覆蓋由約定檢測的多重性和級聯刪除行為。

publicclass Department

{

  // Primary key

  public int DepartmentID { get;set; }

  public string Name { get; set; }

  // Navigationproperty

  public virtual ICollection<Course> Courses { get;set; }

}

publicclass Course

{

  // Primary key

  public int CourseID { get; set; }

  public string Title { get; set; }

  public int Credits { get; set; }

  // Foreign key

  public int DepartmentID { get;set; }

  // Navigationproperties

  public virtual DepartmentDepartment { get; set;}

}

在下面的示例中,導航屬性和外來鍵用於定義 Department 類與Course 類之間的關係。

注意:如果相同型別間有多個關係(例如,假設定義 Person 和Book 類,其中,Person 包含ReviewedBooks 和AuthoredBooks 導航屬性,而Book 類包含 Author 和Reviewer 導航屬性),則需要使用資料註釋或 Fluent API 手動配置關係。

複雜型別約定

當 CodeFirst 發現無法推斷主鍵以及未通過資料註釋或 Fluent API 註冊主鍵的類時,型別會自動註冊為複雜型別。複雜型別檢測還要求型別不具有引用實體型別的屬性,並且未被其他型別的集合屬性引用。對於以下類定義,Code First 推斷Details 是複雜型別,因為它沒有主鍵。

publicpartial class OnsiteCourse : Course

{

  publicOnsiteCourse()

  {

    Details = newDetails();

  }

  public Details Details { get;set; }

}

publicclass Details

{

  publicSystem.DateTime Time { get; set; }

  public string Location { get;set; }

  public string Days { get; set; }

}

連線字串約定

預設配置

如果您還沒有在應用程式中進行任何其他配置,則對 DbContext 呼叫無引數建構函式將會導致 DbContext 使用按約定建立的資料庫連線在 Code First 模式下執行。例如:

namespaceDemo.EF

{

  public class BloggingContext: DbContext

  {

    publicBloggingContext()

    // C# will callbase class parameterless constructor by default

    {

    }

  }

}

在此示例中,DbContext使用派生上下文類 Demo.EF.BloggingContext 的名稱空間限定名稱作為資料庫名稱,並使用 SQL Express 或 LocalDb 為此資料庫建立連線字串。如果同時安裝了這兩個資料庫,將使用 SQL Express。

預設情況下,VisualStudio 2010 包含 SQLExpress,VisualStudio 2012 包含LocalDb。安裝期間,EntityFrameworkNuGet 包會檢查哪個資料庫伺服器可用。隨後 NuGet 包將設定按約定建立連線時 Code First 所使用的預設資料庫伺服器,以此更新配置檔案。如果 SQL Express 正在執行,將使用它。如果 SQL Express 不可用,則 LocalDb 將註冊為預設資料庫。如果配置檔案已包含預設連線工廠設定,則不會更改該檔案。

指定資料庫名稱

如果您尚未在應用程式中進行任何其他配置,在通過要使用的資料庫名稱對 DbContext 呼叫字串建構函式時,將會導致 DbContext 使用按約定建立的與該名稱資料庫的連線在 Code First 模式下執行。例如:

namespaceDemo.EF

{

  public class BloggingContext: DbContext

  {

    public BloggingContext()

      : base("BloggingDatabase")

    {

    }

  }

}

在此示例中,DbContext使用“BloggingDatabase”作為資料庫名稱,並使用 SQL Express(隨Visual Studio 2010 安裝)或LocalDb(隨Visual Studio 2012 安裝)為此資料庫建立連線字串。如果同時安裝了這兩個資料庫,將使用 SQL Express。

指定連線字串

可以選擇將連線字串放入 app.config 或web.config 檔案中。例如:

<configuration>

  <connectionStrings>

    <addname="BloggingCompactDatabase"

providerName="System.Data.SqlServerCe.4.0"

connectionString="Data Source=Blogging.sdf"/>

  </connectionStrings>

</configuration>

這是一種指示 DbContext 使用資料庫伺服器而非 SQL Express 或LocalDb 的簡單方法 — 上例指定了 SQL Server Compact Edition 資料庫。

如果連線字串的名稱與上下文的名稱(帶或不帶名稱空間限定)相同,則使用無引數建構函式時 DbContext 會找到該連線字串。如果連線字串名稱與上下文名稱不同,則可通過將連線字串名稱傳遞給 DbContext 建構函式,指示 DbContext 在 CodeFirst 模式下使用此連線。例如:

publicclass BloggingContext: DbContext

{

  publicBloggingContext()

    : base("BloggingCompactDatabase")

  {

  }

}

或者,也可以對傳遞給DbContext 建構函式的字串使用 “name=<連線字串名稱>”格式。例如:

publicclass BloggingContext: DbContext

{

  publicBloggingContext()

    : base("name=BloggingCompactDatabase")

  {

  }

}

使用此形式可以明確要求在配置檔案中查詢連線字串。如果未找到具有給定名稱的連線字串,則將引發異常。

資料庫初始化策略:

資料庫建立是由策略來控制的,有如下四種策略:

1.       CreateDatabaseIfNotExists:這是預設的策略。如果資料庫不存在,那麼就建立資料庫。但是如果資料庫存在了,而且實體發生了變化,就會出現異常。

2.       DropCreateDatabaseIfModelChanges:此策略表明,如果模型變化了,資料庫就會被重新建立,原來的資料庫被刪除掉了。

3.       DropCreateDatabaseAlways:此策略表示,每次執行程式都會重新建立資料庫,這在開發和除錯的時候非常有用。

4.       自定製資料庫策略:可以自己實現IDatabaseInitializer來建立自己的策略。或者從已有的實現了IDatabaseInitializer介面的類派生。

如下示例顯示瞭如何應用資料庫建立策略:

public class UserManContext : DbContext

{

    public UserManContext()

        : base("USMDBConnectionString")

    {           

        Database.SetInitializer<UserManContext>(new CreateDatabaseIfNotExists<UserManContext>());

    }   

}

下面的程式碼建立了一個自定義策略,什麼也沒有做,但是我們可以在Seed方法裡新增我們的種子資料。

public class USMDBInitializer : DropCreateDatabaseAlways<UserManContext>

{

    protected override void Seed(UserManContext context)

    {

        base.Seed(context);

    }

}

雖然EF提供了在配置檔案中配置策略的方法,如下所示:

<appSettings>

    <addkey="DatabaseInitializerForType EFCodeFirstSample.UserManContext, EFCodeFirstSample"

value="System.Data.Entity.DropCreateDatabaseAlways`1[[EFCodeFirstSample.UserManContext,EFCodeFirstSample]], EntityFramework" />

  </appSettings>

Key必須以DatabaseInitializerForType開始,後邊加空格,之後是context類的全名稱,包括帶名稱空間的類名和所在的程式集名。Value是策略的全名稱。可以看見key和value都非常難讀,還不如自己寫配置來的好。

如果不想使用策略,就可以關閉策略,特別是預設策略。關閉策略的程式碼如下:

public class UserManContext : DbContext

{

    public UserManContext()

        : base("USMDBConnectionString")

    {           

        Database.SetInitializer<UserManContext>(null);

    }

}

還可以在配置檔案中關閉策略,如下:

<addkey="DatabaseInitializerForTypeEFCodeFirstSample.UserManContext, EFCodeFirstSample"

value="Disabled" />

為資料庫新增種子資料

上面提高可以在自定義資料庫初始化策略中新增種子資料,下面的示例說明如何新增種子資料:

public class USMDBInitializer : DropCreateDatabaseAlways<UserManContext>

{

    protected override void Seed(UserManContext context)

    {

        User admin = new User();

        admin.Name = "admin";

        admin.DisplayName = "Administrator";

        admin.Status = 1;

        admin.LastModDate= DateTime.Now;

        context.Users.Add(admin);

        base.Seed(context);

    }

}

需要注意的是日期欄位,資料庫中的日期範圍小於.NET中的日期範圍,所以必須給一個合適的值,像DateTime.MinValue這樣的值無法儲存到資料庫中。可以參考SqlDateTime型別來確定Sql資料庫支援的時間範圍。

移除約定

可以移除在System.Data.Entity.ModelConfiguration.Conventions 名稱空間中定義的任何約定。下面的示例移除 PluralizingTableNameConvention。

publicclass SchoolEntities: DbContext

{

  protected override voidOnModelCreating(DbModelBuilder modelBuilder)

  {

    // Configure CodeFirst to ignore PluralizingTableName convention

    // If you keepthis convention, the generated tables

    // will havepluralized names.

    modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();

  }

}

可插入約定

可插入(自定義)約定目前不受支援,正在針對 EF6 進行開發。

Code First 資料註釋

通過實體框架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。

publicclass Blog

{

  public int Id { get; set; }

  public string Title { get; set; }

  public string BloggerName { get;set; }

  public virtual ICollection<Post> Posts { get;set; }

}

publicclass 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。

publicclass 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]

publicstring Title { get;set; }

Required 特性將使被對映的屬性不可為空來影響生成的資料庫。請注意,Title 欄位已經更改為“not null”。

MaxLength 和MinLength

使用MaxLength 和MinLength 特性,您可以就像對Required 那樣指定其他屬性驗證。

下面是具有長度要求的BloggerName。該示例也說明如何組合特性。

[MaxLength(10), MinLength(5)]

publicstring BloggerName { get;set; }

MaxLength 註釋將通過把屬性長度設定為 10 來影響資料庫。MinLength屬性不會對資料庫產生影響。

NotMapped

Code First 約定指示具有受支援資料型別的每個屬性都要在資料庫中有表示。但在您的應用程式中並不總是如此。例如,您可以在 Blog 類中使用一個屬性來基於 Title 和BloggerName 欄位建立程式碼。該屬性可以動態建立,無需儲存。您可以使用 NotMapped 註釋來標記不對映到資料庫的所有屬性,如下面的 BlogCode 屬性。

[NotMapped]

publicstring BlogCode

{

  get

  {

    returnTitle.Substring(0, 1) + ":" +BloggerName.Substring(0, 1);

  }

}

ComplexType

跨一組類描述域實體,然後將這些類分層以描述一個完整實體的情況並不少見。例如,您可以向模型中新增一個名為 BlogDetails 的類。

publicclass BlogDetails

{

  public DateTime? DateCreated { get;set; }

  [MaxLength(250)]

  public string Description { get;set; }

}

請注意,BlogDetails 沒有任何鍵屬性型別。在域驅動的設計中,BlogDetails 稱為值物件。實體框架將值物件稱為複雜型別。複雜型別不能自行跟蹤。

但是 BlogDetails 作為 Blog 類中的一個屬性,將作為 Blog 物件的一部分被跟蹤。為了讓 Code First 認識到這一點,您必須將 BlogDetails 類標記為 ComplexType。

[ComplexType]

publicclass 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),MinLength(5)]   

publicstring 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]

publicByte[] TimeStamp { get;set; }

這樣,CodeFirst 將在資料庫表中建立一個不可為空的 Timestamp 列。

表和列

如果您讓Code First 建立資料庫,則可能希望更改它建立的表和列的名稱。也可以將 Code First 用於現有資料庫。但是域中的類和屬性的名稱並不總是與資料庫中表和列的名稱相匹配。

我的類名為Blog,按照約定,Code First 將假定此類對映到名為 Blogs 的表。如果不是這樣,您可以用 Table 特性指定該表的名稱。舉例來說,下面的註釋指定表名稱為 InternalBlogs,同時指定了schema,預設的schema就是dbo。

[Table("InternalBlogs",Schema="dbo")]

publicclass Blog

Column 註釋更適於用來指定被對映列的特性。您可以規定名稱、資料型別甚至列出現在表中的順序。下面是 Column 特性的示例。

[Column("BlogDescription",TypeName = "ntext")]

publicString Description { get; set; }

下面是重新生成後的表。表名稱已更改為 InternalBlogs,複雜型別的 Description 列現在是BlogDescription。因為該名稱在註釋中指定,Code First 不會使用以複雜型別名稱作為列名開頭的約定。

DatabaseGenerated

一個重要的資料庫功能是可以使用計算屬性。如果您將 Code First 類對映到包含計算列的表,則您可能不想讓實體框架嘗試更新這些列。但是在插入或更新資料後,您的確需要 EF 從資料庫中返回這些值。您可以使用 DatabaseGenerated 註釋與 Computed 列舉一起在您的類中標註這些屬性。其他列舉為 None 和Identity。

[DatabaseGenerated(DatabaseGeneratedOption.Computed)]

publicDateTime DateCreated { get; set; }

當 Code First生成資料庫時,您可以對 byte 或timestamp 列使用此標記,否則您只應該在指向現有資料庫時使用,因為 Code First 將不能確定計算列的公式。

您閱讀過以上內容,知道預設情況下,整數鍵屬性將成為資料庫中的標識鍵。這與將 DatabaseGenerated 設定為 DatabaseGenerationOption.Identity 是一樣的。如果不希望它成為標識鍵,則可以將該值設定為 DatabaseGenerationOption.None。

關係特性:InverseProperty和ForeignKey

Code First 約定將在您的模型中處理最常用的關係,但是在某些情況下它需要幫助。

在 Blog 類中更改鍵屬性的名稱造成它與 Post 的關係出現問題。

生成資料庫時,CodeFirst 會在 Post 類中看到 BlogId 屬性並識別出該屬性,按照約定,它與類名加“Id”匹配,並作為 Blog 類的外來鍵。但是在此Blog 類中沒有 BlogId 屬性。解決方法是,在 Post 中建立一個導航屬性,並使用 Foreign DataAnnotation 來幫助 CodeFirst 瞭解如何在兩個類之間建立關係(那就是使用 Post.BlogId 屬性)以及如何在資料庫中指定約束。

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

}

資料庫中的約束顯示InternalBlogs.PrimaryTrackingKey 與Posts.BlogId 之間的關係。

類之間存在多個關係時,將使用 InverseProperty。

在 Post 類中,您可能需要跟蹤是誰撰寫了部落格文章以及誰編輯了它。下面是 Post 類的兩個新的導航屬性。

publicPerson CreatedBy { get;set; }

publicPerson UpdatedBy { get;set; }

您還需要在這些屬性引用的 Person 類中新增內容。Person類具有返回到 Post 的導航屬性,一個屬性指向該使用者撰寫的所有文章,一個屬性指向該使用者更新的所有文章。

publicclass 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")]

publicList<Post>PostsWritten { get; set;}

[InverseProperty("UpdatedBy")]

publicList<Post>PostsUpdated { get; set;}

因為Person 中的PostsWritten 屬性知道這指的是Post 型別,所以它將與 Post.CreatedBy 建立關係。同樣,PostsUpdated 也將與 Post.UpdatedBy 建立關係。Code First 不會建立額外的外來鍵。

總結

DataAnnotations 不僅可用於在 Code First 類中描述客戶端和伺服器端驗證,還讓您能夠加強甚至更正 Code First 將基於其約定對您的類所作的假設。使用 DataAnnotations,您不僅能夠推動資料庫架構生成,還能將 Code First 類對映到預先存在的資料庫。

雖然它們都非常靈活,但請記住,DataAnnotations 只提供您經常需要對 Code First 類進行的配置更改。要為一些邊緣情況配置類,則應該採用另一種替代配置機制,那就是 Code First 的Fluent API。

使用Fluent API 配置/對映屬性和型別

簡介

通常通過重寫派生DbContext 上的OnModelCreating 方法來訪問Code First Fluent API。以下示例旨在顯示如何使用 Fluent API 執行各種任務,您可以將程式碼複製出來並進行自定義,使之適用於您的模型。

屬性對映

Property 方法用於為每個屬於實體或複雜型別的屬性配置特性。Property 方法用於獲取給定屬性的配置物件。配置物件上的選項特定於要配置的型別;例如,IsUnicode 只能用於字串屬性。

配置主鍵

要顯式將某個屬性設定為主鍵,可使用 HasKey 方法。在以下示例中,使用了 HasKey 方法對 OfficeAssignment 型別配置 InstructorID 主鍵。

modelBuilder.Entity<OfficeAssignment>().HasKey(t =>t.InstructorID);

配置組合主鍵

以下示例配置要作為Department 型別的組合主鍵的DepartmentID 和 Name 屬性。

modelBuilder.Entity<Department>().HasKey(t => new { t.DepartmentID, t.Name });

關閉數值主鍵的標識

以下示例將DepartmentID 屬性設定為System.ComponentModel.DataAnnotations.DatabaseGeneratedOption.None,以指示該值不由資料庫生成。

modelBuilder.Entity<Department>().Property(t =>t.DepartmentID).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

指定屬性的最大長度

在以下示例中,Name屬性不應超過 50 個字元。如果其值超過 50 個字元,則出現 DbEntityValidationException 異常。如果 Code First 基於此模型建立資料庫,它還會將 Name 列的最大長度設定為50 個字元。

modelBuilder.Entity<Department>().Property(t =>t.Name).HasMaxLength(50);

將屬性配置為必需

在下面的示例中,Name屬性是必需的。如果不指定 Name,則出現 DbEntityValidationException 異常。如果 Code First 基於此模型建立資料庫,則用於儲存此屬性的列將不可為空。

modelBuilder.Entity<Department>().Property(t =>t.Name).IsRequired();

指定不將CLR 屬性對映到資料庫中的列

以下示例顯示如何指定CLR 型別的屬性不對映到資料庫中的列。

modelBuilder.Entity<Department>().Ignore(t => t.Budget);

將CLR 屬性對映到資料庫中的特定列

以下示例將Name CLR 屬性對映到DepartmentName 資料庫列。

modelBuilder.Entity<Department>().Property(t =>t.Name).HasColumnName("DepartmentName");

重新命名模型中未定義的外來鍵

如果您選擇不對CLR 型別定義外來鍵,但希望指定它在資料庫中應使用的名稱,請編碼如下:

modelBuilder.Entity<Course>()

        .HasRequired(c => c.Department)

        .WithMany(t => t.Courses)

        .Map(m => m.MapKey("ChangedDepartmentID"));

配置字串屬性是否支援Unicode 內容

預設情況下,字串為Unicode(SQLServer 中的nvarchar)。您可以使用IsUnicode 方法指定字串應為varchar 型別。

modelBuilder.Entity<Department>()

        .Property(t => t.Name)

        .IsUnicode(false);

配置資料庫列的資料型別

HasColumnType 方法支援對映到相同基本型別的不同表示。使用此方法並不支援在執行時執行任何資料轉換。請注意,IsUnicode 是將列設定為 varchar 的首選方法,因為它與資料庫無關。

modelBuilder.Entity<Department>()

        .Property(p => p.Name)

        .HasColumnType("varchar");

配置複雜型別的屬性

對複雜型別配置標量屬性有兩種方法。

可以對ComplexTypeConfiguration 呼叫Property。

modelBuilder.ComplexType<Details>()

        .Property(t => t.Location)

        .HasMaxLength(20);

也可以使用點表示法訪問複雜型別的屬性。

modelBuilder.Entity<OnsiteCourse>()

        .Property(t => t.Details.Location)

        .HasMaxLength(20);

將屬性配置為用作樂觀併發令牌

要指定實體中的某個屬性表示併發令牌,可使用 ConcurrencyCheck 特性或 IsConcurrencyToken 方法。

modelBuilder.Entity<OfficeAssignment>()

        .Property(t => t.Timestamp)

        .IsConcurrencyToken();

也可以使用IsRowVersion 方法將屬性配置為資料庫中的行版本。將屬性設定為行版本會自動將它配置為樂觀併發令牌。

modelBuilder.Entity<OfficeAssignment>()

        .Property(t => t.Timestamp)

        .IsRowVersion();

型別對映

將類指定為複雜型別

按約定,沒有指定主鍵的型別將被視為複雜型別。在一些情況下,Code First 不會檢測複雜型別(例如,如果您有名為“ID”的屬性,但不想將它用作主鍵)。在此類情況下,您將使用 Fluent API 顯式指定某型別是複雜型別。

modelBuilder.ComplexType<Details>();

指定不將CLR 實體型別對映到資料庫中的表

以下示例顯示如何排除一個 CLR 型別,使之不對映到資料庫中的表。

modelBuilder.Ignore<OnlineCourse>();

將CLR 實體型別對映到資料庫中的特定表

Department 的所有屬性都將對映到名為 t_ Department 的表中的列。

modelBuilder.Entity<Department>().ToTable("t_Department");

您也可以這樣指定架構名稱:

modelBuilder.Entity<Department>().ToTable("t_Department", "school");

對映“每個層次結構一張表(TPH)”繼承

在 TPH 對映情形下,繼承層次結構中的所有型別都將對映到同一個表。鑑別器列用於標識每行的型別。使用 Code First 建立模型時,TPH 參與繼承層次結構的型別所用的預設策略。預設情況下,鑑別器列將新增到名為“Discriminator”的表,且層次結構中每個型別的 CLR 型別名稱都將用作鑑別器值。可以使用 Fluent API 修改預設行為。

modelBuilder.Entity<Course>()

        .Map<Course>(m=> m.Requires("Type").HasValue("Course"))

        .Map<OnsiteCourse>(m=> m.Requires("Type").HasValue("OnsiteCourse"));

對映“每個型別一張表(TPT)”繼承

在 TPT 對映情形下,所有型別分別對映到不同的表。僅屬於某個基型別或派生型別的屬性儲存在對映到該型別的一個表中。對映到派生型別的表還會儲存一個將派生表與基表聯接的外來鍵。

modelBuilder.Entity<Course>().ToTable("Course");

modelBuilder.Entity<OnsiteCourse>().ToTable("OnsiteCourse");

對映“每個具體類一張表(TPC)”繼承

在 TPC 對映情形下,層次結構中的所有非抽象型別分別對映到不同的表。對映到派生類的表與對映到資料庫中基類的表並無關係。類的所有屬性(包括繼承屬性)都將對映到相應表的列。

呼叫MapInheritedProperties 方法來配置每個派生型別。MapInheritedProperties 將繼承自基類的所有屬性重新對映到派生類的表中的新列。

注意:因為屬於TPC 繼承層次結構的表並不使用同一個主鍵,因此,如果您讓資料庫生成的值具有相同標識種子,則在對映到子類的表中執行插入操作時,會產生重複的實體鍵。要解決此問題,可以為每個表指定不同的初始種子值,或關閉主鍵屬性的標識。當使用 Code First 時,標識就是整數鍵屬性的預設值。

modelBuilder.Entity<Course>()

  .Property(c => c.CourseID)

  .HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);

modelBuilder.Entity<OnsiteCourse>().Map(m =>

{

  m.MapInheritedProperties();

  m.ToTable("OnsiteCourse");

});

modelBuilder.Entity<OnlineCourse>().Map(m =>

{

  m.MapInheritedProperties();

  m.ToTable("OnlineCourse");

});

將實體型別的CLR 屬性對映到資料庫中的多個表(實體拆分)

實體拆分允許一個實體型別的屬性分散在多個表中。在以下示例中,Department 實體拆分到兩個表中:Department 和DepartmentDetails。實體拆分通過多次呼叫 Map 方法將一部分屬性對映到特定表。

modelBuilder.Entity<Department>()

.Map(m=>

{

  m.Properties(t => new{ t.DepartmentID, t.Name });

  m.ToTable("Department");

})

.Map(m=>

{

  m.Properties(t=> new { t.DepartmentID, t.Administrator,t.StartDate, t.Budget });

  m.ToTable("DepartmentDetails");

 });

將多個實體型別對映到資料庫中的一個表(表拆分)

以下示例將使用同一個主鍵的兩個實體型別對映到同一個表。

modelBuilder.Entity<OfficeAssignment>()

  .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()

    .HasRequired(t => t.OfficeAssignment)

    .WithRequiredPrincipal(t =>t.Instructor);

modelBuilder.Entity<Instructor>().ToTable("Instructor");

modelBuilder.Entity<OfficeAssignment>().ToTable("Instructor");

使用FluentAPI配置關係

簡介

使用FluentAPI配置關係的時候,首先要獲得一個EntityTypeConfiguration例項,然後使用其上的HasRequired, HasOptional或者 HasMany方法來指定當前實體參與的關係型別。HasRequired 和HasOptional方法需要一個lambda表示式來指定一個導航屬性,HasMany方法需要一個lambda表示式指定一個集合導航屬性。然後可以使用WithRequired, WithOptional和WithMany方法來指定反向導航屬性,這些方法有不帶引數的過載用來指定單向導航。

之後還可以使用HasForeignKey方法來指定外來鍵屬性。

配置【必須-可選】關係(1-0..1)

OfficeAssignment的鍵屬性不符合命名約定,所以需要我們顯式指定。下面的關係表明,OfficeAssignment的Instructor必須存在,但是Instructor的OfficeAssignment不是必須存在的。

modelBuilder.Entity<OfficeAssignment>()

  .HasKey(t => t.InstructorID);

// Map one-to-zero or one relationship

modelBuilder.Entity<OfficeAssignment>()

  .HasRequired(t => t.Instructor)

  .WithOptional(t => t.OfficeAssignment);

配置兩端都是必須的關係(1-1)

大多數情況下,EF都能推斷哪一個型別是依賴項或者是主體項。然而當關系的兩端都是必須的或者都是可選的,那麼EF就不能識別依賴項或者是主體項。如果關係兩端都是必須的,那麼在HasRequired方法後使用WithRequiredPrincipal或者WithRequiredDependent來確定主體。如果關係兩端都是可選的,那麼在HasRequired方法後使用WithOptionalPrincipal和WithOptionalDependent。

modelBuilder.Entity<OfficeAssignment>()

  .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()

  .HasRequired(t => t.OfficeAssignment)

  .WithRequiredPrincipal(t => t.Instructor);

配置多對多關係

下面的程式碼配置了一個多對多關係,CodeFirst會使用命名約定來建立連線表,命名約定會使用Course_CourseID 和 Instructor_InstructorID作為連線表的列。

modelBuilder.Entity<Course>()

        .HasMany(t => t.Instructors)

        .WithMany(t => t.Courses);

如果想指定連線表的表名和列名,需要使用Map方法,如下:

modelBuilder.Entity<Course>()

        .HasMany(t => t.Instructors)

        .WithMany(t => t.Courses)

        .Map(m =>

        {

          m.ToTable("CourseInstructor");

          m.MapLeftKey("CourseID");

          m.MapRightKey("InstructorID");

        });

配置單向導航屬

所謂單向導航屬性指的是隻在關係的一端定義了導航屬性。按照約定,CodeFirst將單向導航理解為一對多關係,如果需要一對一的單向導航屬性,需要使用如下方法:

modelBuilder.Entity<OfficeAssignment>()

  .HasKey(t => t.InstructorID);

modelBuilder.Entity<Instructor>()

  .HasRequired(t => t.OfficeAssignment)

  .WithRequiredPrincipal();

啟用級聯刪除

使用WillCascadeOnDelete方法來配置關係是否允許級聯刪除。如果外來鍵是不可空的,CodeFirst預設會設定級聯刪除;否則,不會設定級聯刪除,當主體被刪除後,外來鍵將會被置空。

可以使用如下程式碼移除此約定:

modelBuilder.Conventions.Remove<OneToManyCascadeDeleteConvention>();

modelBuilder.Conventions.Remove<ManyToManyCascadeDeleteConvention>();

下面的程式碼片段配置為外來鍵不能為空,而且禁用了級聯刪除。

modelBuilder.Entity<Course>()

  .HasRequired(t => t.Department)

  .WithMany(t => t.Courses)

  .HasForeignKey(d => d.DepartmentID)

  .WillCascadeOnDelete(false);

配置組合外來鍵

下面的程式碼配置了組合外來鍵

modelBuilder.Entity<Department>()

  .HasKey(d => new{ d.DepartmentID, d.Name });

// Composite foreign key

modelBuilder.Entity<Course>()

    .HasRequired(c => c.Department)

    .WithMany(d => d.Courses)

    .HasForeignKey(d => new { d.DepartmentID, d.DepartmentName });

配置不符合命名約定的外來鍵屬性

SomeDepartmentID屬性不符合外來鍵命名約定,需要使用如下方法將其設定為外來鍵屬性:

modelBuilder.Entity<Course>()

         .HasRequired(c => c.Department)

         .WithMany(d => d.Courses)

         .HasForeignKey(c =>c.SomeDepartmentID);

定義DbSet

DbContext 使用DbSet 屬性

Code First 示例中顯示的常見情況是讓 DbContext 為模型實體型別使用公共自動 DbSet 屬性。例如:

publicclass BloggingContext: DbContext

{

  public