Entity Framework 5.0系列之約定配置


Code First之所以能夠讓開發人員以一種更加高效、靈活的方式進行數據操作有一個重要的原因在於它的約定配置。現在軟件開發越來復雜,大家也都試圖將軟件設計的越來越靈活,很多內容我們都希望是可配置的,但是過多的配置也會帶來很大的工作量,解決這個問題的方法就是約定。對於一些簡單的,不太可能經常變化的內容我們以一種約定的方式進行設計。使用過其他ORM框架的朋友可能知道一般ORM都有對應的映射配置文件(一般是一個Xml文件),但是EF並沒有。在EF中是以一種約定的方式進行表、列同實體類進行映射的,與此同時為了提高最大的靈活性EF中可以通過Fluent API和Data Annotations兩種方式對映射進行靈活配置。

EF默認約定

我們先來看一下EF對於數據類(概念模型,或域模型)的默認約定:

  • 將數據類的類名復數形式作為數據表名稱,並且使用“dbo”作為默認架構。

例如定義一個Person數據類,那么將會自動生成“dbo.People”表。

  • 將數據類中的“ID”屬性或者“<類名>+ID”作為主鍵(不區分大小寫),並且如果該列為數值類型或者GUID列將作為標識列。

例如在Order類中如果有ID或者OrderID屬性將默認作為主鍵,二者均出現優先使用 “ID”屬性。

  • 使用導航屬性約束兩個表之間的關系,在從表數據類中除了導航屬性,推薦定義一個外鍵屬性在從表數據類中(如果不指定將默認生成一個“<主表類名>+<主表類的主鍵名>”的外鍵列;此外在主表中推薦定義一個集合從表屬性用戶導航,當然這個屬性不定義也可以正常生成外鍵關系但是不利於使用),具體規則:“<導航屬性名>+<主表的主鍵屬性名>”或者“<主表類名>+<主鍵屬性名>”又或者“<主表的主鍵屬性名>”,其屬性名不區分大小寫並且如果出現多種匹配按照先后順序匹配;如果不存在外鍵屬性則外鍵關系注冊為可選的,否則注冊為必選項並且此時將設置級聯刪除關系;如果在從表類中有多個導航屬性對應同一個數據類那么需要使用fluent API或者Data Annotations進行手動配置。

例如有一個Order類,主鍵為OrderID,在OrderDetail類中有一個導航屬性Order(Order類型),那么當你定義一個OrderID在OrderDetail中,那么在Order和OrderDetail直接將建立一個級聯刪除關系。

  • 當EF按照上述規則在數據類中沒有找到主鍵屬性時(或者通過fluent API、Data Annotations沒有定義)將認為此類為“復雜類型”(對於不了解復雜類型的朋友請點擊這里What is a Complex Type)。

例如在“Person”數據類中有一個“Name”屬性,但是數據庫中可能將“Name”分為FirstName和LastName存儲,此時就可以定義一個Name類,在此類中不定義主鍵列定義“FirstName”和“LastName”屬性,就會在表“dbo.People”中生成“Name_FirstName”和“Name_LastName”列。

定義約定

EF的默認約定不是一成不變的,我們可以選擇移除和修改它,例如EF默認生成數據表時將數據類名的復數形式作為表名,下面的代碼就可以移除這個規則:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using CodeFirst.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace CodeFirst
{
    public class OrderContext:DbContext
    {
        public OrderContext()
            : base("CodeFirstDb")
        {
            Database.SetInitializer<OrderContext>(
                new DropCreateDatabaseIfModelChanges<OrderContext>()
            );
        }

        public DbSet<Person> Person
        {
            get;
            set;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Conventions.Remove<PluralizingTableNameConvention>();
        }
    }
}

這些規則都在“System.Data.Entity.ModelConfiguration.Conventions”命名空間下,可以根據實際情況進行選擇。

一般情況下我們是不需要移除默認約定的,我們更多的時候是要修改豐富這些約定,達到對生成規則的更多細節控制。在EF提供了兩種方式進行映射配置:Data Annotations和Fluent API。

DataAnnotations

DataAnnotations是ASP.NET WEB中添加的一種驗證方式,但是在EF中它又可以對映射關系進行控制,相比較Fluent API使用起來要簡單一些。下面我們通過一個例子對DataAnnotations進行說明。在此我們假設有一個“Employee”類用於描述員工信息,一個“Customer”類用於描述客戶信息,還有一個“Order”類用於描述訂單信息,此外還有一個“Name”復雜類型表示人員姓名。在Order類中有一個屬性“Customer”用於描述此訂單的客戶,它是“Customer”類型;還有一個“DeliverPerson”屬性用於描述訂單發貨人,一個“CheckPerson”屬性用戶描述訂單揀貨人,它們都是“Employee”類型。下面是具體代碼:

Employee類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    [Table("People",Schema="Person")]
    public class Employee
    {
        [Key]
        public int No
        {
            get;
            set;
        }

        public Name Name
        {
            get;
            set;
        }

        [MinLength(5),MaxLength(30)]
        public string Title
        {
            get;
            set;
        }

        [Required]
        public DateTime BirthDate
        {
            get;
            set;
        }

        [ConcurrencyCheck]
        public string Address
        {
            get;
            set;
        }

        [Column("Notes",TypeName="ntext",Order=5)]
        public string Note
        {
            get;
            set;
        }

        [DatabaseGenerated(System.ComponentModel.DataAnnotations.Schema.DatabaseGeneratedOption.Computed)]
        public DateTime CreateDate
        {
            get;
            set;
        }

        [NotMapped]
        public string PhotoPath
        {
            get;
            set;
        }

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

        [InverseProperty("DeliverPerson")]
        public List<Order> DeliverOrder
        {
            get;
            set;
        }

        [InverseProperty("CheckPerson")]
        public List<Order> CheckOrder
        {
            get;
            set;
        }
    }
}

Customer類:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Customer
    {
        public int CustomerID
        {
            get;
            set;
        }

        public string CompanyName
        {
            get;
            set;
        }
    }
}

Name類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    [ComplexType]//根據前面我們說的默認約定,不標記為ComplexType只有沒有找到ID也會將Name作為一個復雜類型
    public class Name
    {
        public string FirstName
        {
            get;
            set;
        }

        public string LastName
        {
            get;
            set;
        }
    }
}

Order類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Order
    {
        public int OrderID 
        { 
            get; 
            set; 
        }

        public string OrderTitle 
        { 
            get; 
            set; 
        }

        public string CustomerName 
        { 
            get; 
            set; 
        }

        public DateTime TransactionDate 
        { 
            get; 
            set; 
        }

        public int CustomerNo
        {
            get;
            set;
        }

        [ForeignKey("CustomerNo")]
        public Customer Customer
        {
            get;
            set;
        }

        public Employee DeliverPerson
        {
            get;
            set;
        }

        public Employee CheckPerson
        {
            get;
            set;
        }
    }
}

這是通過Data Annotations配置后EF生成的數據庫表結構:

下面解釋每個配置的作用

Table:用於指定生成表的表名、架構信息。

Column:用於指定生成數據表的列信息,如列名、數據類型、順序等。

Key:用於指定任何名稱的屬性作為主鍵列並且默認將此列作為標識列(如果不想默認生成標識可以指定“DatabaseGenerated”屬性的值為“None”),如果不指定此標記屬性,將根據EF默認約定創建主鍵。如上代碼指定“No”為“Employee”的主鍵。

Required:用戶指定非空列,如上面的“BirthDay”創建列之后為“not null”列。

MinLengthMaxLength:指定字段長度(此屬性通常可以用戶客戶端驗證),例如上面“Title”定義成了“nvarchar(30)”。

ComplexType:用於標記復雜類型,對於包含復雜類型數據屬性的類在生成數據表時復雜類型中每個屬性都將作為其中一列。

DatabaseGenerated:用於指定數據庫字段生成列,此類EF將不會直接更新。可以指定為計算列、標識列和非數據庫生成列(例如給主鍵列指定此屬性為“None”則不會生成標識列)。需要注意的是如果使用Code First字段生成數據庫,那么此屬性僅僅可以用於byte、timestamp列上,否則請應用在已經存在數據庫的情況下,因為Code First無法判定生成具體計算列的公式(至少目前Code First還不支持公式配置)。

NotMapped:用戶指定非映射列,標記此屬性的列將不會在數據庫中生成相應的列,例如上面的“PhotoPath ”沒有在數據庫中生成具體列,實際使用中它可能是通過其他具體規則得到的。

ConcurrencyCheck:用於進行並發檢查,當一個用戶A獲得實體后通常會與數據庫斷開,此時如果另一個用戶B也獲得了實體並進行了修改,那么當A再進行更新時如果進行了“ConcurrencyCheck”標記則會進行並發檢查,並根據原始值判斷該實體是否存在,如果不存在則拋出異常。

TimeStamp:用於指定時間戳列,一個實體只能有一個TimeStamp列。在EF中TimeStamp是另一種並發控制方式,當EF遇到TimeStamp列會自動配置 “ConcurrencyCheck”及“DatabaseGenerated.Computed”來控制並發(通常我們推薦使用此方法)。

ForeignKey:用於指定外鍵列,我們知道按照上面提到的默認約定第三條,當我們在“Order”中定義了“Customer”屬性后,如果定義“CustomerID” 屬性(當然還有其他形式,大家可以按照聲明說的默認約定3進行測試),那么EF會在“Order”表中創建一個“CustomerID”列並建立與“Customer”表的外鍵關系。但是如果像上面定義“CustomerNo”屬性並且不指定“ForeignKey”標記的話將達不到我們的預期,EF將默認創建一個“Customer_CustomerID”列並創建與“Customer”的外鍵約束,同時創建一個“CustomerNo”列。當然解決的方式大家已經看到了那就是給導航屬性“Customer”指定“ForegnKey”標記並且指定外鍵列為“CustomerNo”(注意雖然在“Customer”中不定義“Order的導航屬性”生成的表中也並沒用任何問題,但是我們推薦您定義相應的導航屬性)。

InverseProperty:用於定義多重外鍵關系約束。我們在EF中通過導航屬性定義主外鍵關系,但是當主表中有兩個外鍵約束時可能僅僅通過添加相應的導航屬性就無法完成了,例如上面“Order”中“DeliverPerson”和“CheckPerson”均為“Employee”類型,按我們的預期當然是在生成“Order”表時生成兩個外鍵列並創建與“Employee”外鍵約束關系,但是如果沒有在“Employee”類的“DeliverOrder”和“CheckOrder”屬性上標記 “InverseProperty”屬性EF是無法識別這種關系的(具體結果可以看下圖),當然解決方式就是在對應導航屬性中標記“InverseProperty”並指定對於的列名。

clip_image004

注意:DataAnnotations可以同時在同一個類后者屬性上使用多個標記屬性,上面的例子中對於每個類或屬性只使用了一個單獨的標記屬性是為了說明起來更加簡單;另外聲明的例子中同時使用“ConcurrencyCheck”和“TimeStamp”指定了不同的列只是為了演示,一般情況下我們通過其中一種方式即可。

Fluent API

Fluent API一般配置

盡管我們可以通過Data Annotations進行映射關系約定,但是相比較而言Fluent API的功能更加強大,從功能上而言Data Annotations是Fluent API的一個子集, Data Annotations可以實現的功能Fluent API都能實現。下面我們先看一個例子,在這個例子中我們通過Fluent API約定方式實現上面Data Annotations的功能並且包含更多控制:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using CodeFirst.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace CodeFirst
{
    public class OrderContext:DbContext
    {
        public OrderContext()
            : base("CodeFirstDb")
        {
            Database.SetInitializer<OrderContext>(
                new DropCreateDatabaseIfModelChanges<OrderContext>()
            );
        }

        public DbSet<Order> Orders
        {
            get;
            set;
        }

        public DbSet<Employee> Employees
        {
            get;
            set;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Employee>().ToTable("People", "Person");//指定“Employee”對應表名及架構

            modelBuilder.Entity<Employee>().HasKey(emp => emp.No);//定義主鍵為“No”
            //modelBuilder.Entity<Employee>().HasKey(emp => new { emp.No, emp.Title });//指定"No"和“Title”作為復合主鍵,使用Data Annotations無法做到

            modelBuilder.Entity<Employee>().Property(emp => emp.No).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);//去掉標識列,前面我們也提到過通過Data Annotations通用可以去掉主鍵默認標示屬性

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).HasMaxLength(30);//指定“Title”最大長度為30

            modelBuilder.Entity<Employee>().Property(emp => emp.BirthDate).IsRequired();//指定“BirthDate”為不可為空

            modelBuilder.Entity<Employee>().Property(emp => emp.Note).HasColumnName("Notes").HasColumnType("ntext");//指定“Note”對應列名為“Notes”,並指定數據類型

            modelBuilder.Entity<Employee>().Ignore(emp => emp.PhotoPath);//指定“PhotoPath”為非映射列

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).IsUnicode(true);//“Title”列是否支持Unicode編碼

            modelBuilder.ComplexType<Name>().Property(n=>n.FirstName).HasMaxLength(50);//指定Name為復雜數據類型,並指定復雜類型中“FirstName”長度
            //modelBuilder.Entity<Employee>().Property(emp => emp.Name.LastName).HasMaxLength(20);//還可以通過這種方式指定復雜類型“Name”的“LastName”列的長度

            //modelBuilder.Entity<Employee>().Property(emp => emp.Address).IsConcurrencyToken();//指定“Address”進行並發控制,通常這一列我們知道為“TimeStamp”列而不是“Addree”這里只是為了說明可以標記其他列
            modelBuilder.Entity<Employee>().Property(emp => emp.TimeStamp).IsRowVersion();//通過指定“TimeStamp”進行並發版本控制
        }
    }
}

從上面的代碼中可以看到基本上在Data Annotations中實現的功能使用Fluent API都實現了,並且在上面的代碼注釋中我也提到了一些Data Annotations無法實現的功能,具體代碼基本上都已經注釋了在此也不再解釋了。

Fluent API關系配置

下面讓看一下EF中關系配置的實現,看一下Fluent API如何進行實體關系約束,這里假設每個公司員工“Employee”在企業內部都有一個通訊賬戶“MessagingAcount”,這二者之間是一對一的關系;同時添加一個產品類“Product”,它與“Order”的關系是多對多的關系,具體定義如下:

MessageAcount類:

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class MessagingAccount
    {
        [Key()]
        public int EmployeeNo
        {
            get;
            set;
        }

        public Employee Employee
        {
            get;
            set;
        }

        public string UserName
        {
            get;
            set;
        }

        public string Password
        {
            get;
            set;
        }
        
    }
}

Product類:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Product
    {
        public int ProductID
        {
            get;
            set;
        }

        public string ProductName
        {
            get;
            set;
        }

        public double UnitPrice
        {
            get;
            set;
        }

        public int OrderID
        {
            get;
            set;
        }

        public List<Order> Orders
        {
            get;
            set;
        }

    }
}

Employee類(添加了對應的屬性):

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Employee
    {
        public int No
        {
            get;
            set;
        }

        public Name Name
        {
            get;
            set;
        }

        public string Title
        {
            get;
            set;
        }

        public DateTime BirthDate
        {
            get;
            set;
        }

        public string Address
        {
            get;
            set;
        }

        public string Note
        {
            get;
            set;
        }

        public DateTime CreateDate
        {
            get;
            set;
        }

        public string PhotoPath
        {
            get;
            set;
        }

        public byte[] TimeStamp
        {
            get;
            set;
        }

        public List<Order> DeliverOrder
        {
            get;
            set;
        }

        public List<Order> CheckOrder
        {
            get;
            set;
        }

        public MessagingAccount Acount
        {
            get;
            set;
        }
    }
}

Order類(添加了對應的屬性):

using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Order
    {
        public int OrderID 
        { 
            get; 
            set; 
        }

        public string OrderTitle 
        { 
            get; 
            set; 
        }

        public string CustomerName 
        { 
            get; 
            set; 
        }

        public DateTime TransactionDate 
        { 
            get; 
            set; 
        }

        public int CustomerNo
        {
            get;
            set;
        }

        public Customer Customer
        {
            get;
            set;
        }

        public int ProductID
        {
            get;
            set;
        }

        public List<Product> Products
        {
            get;
            set;
        }
             

        public Employee DeliverPerson
        {
            get;
            set;
        }

        public Employee CheckPerson
        {
            get;
            set;
        }
    }
}

OrderContext類,定義了數據類之間的關系,主要是關系配置部分:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using CodeFirst.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace CodeFirst
{
    public class OrderContext:DbContext
    {
        public OrderContext()
            : base("CodeFirstDb")
        {
            Database.SetInitializer<OrderContext>(
                new DropCreateDatabaseIfModelChanges<OrderContext>()
            );
        }

        public DbSet<Order> Orders
        {
            get;
            set;
        }

        public DbSet<Employee> Employees
        {
            get;
            set;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            modelBuilder.Entity<Employee>().ToTable("People", "Person");//指定“Employee”對應表名及架構

            modelBuilder.Entity<Employee>().HasKey(emp => emp.No);//定義主鍵為“No”
            //modelBuilder.Entity<Employee>().HasKey(emp => new { emp.No, emp.Title });//指定"No"和“Title”作為復合主鍵,使用Data Annotations無法做到

            modelBuilder.Entity<Employee>().Property(emp => emp.No).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);//去掉標識列,前面我們也提到過通過Data Annotations通用可以去掉主鍵默認標示屬性

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).HasMaxLength(30);//指定“Title”最大長度為30

            modelBuilder.Entity<Employee>().Property(emp => emp.BirthDate).IsRequired();//指定“BirthDate”為不可為空

            modelBuilder.Entity<Employee>().Property(emp => emp.Note).HasColumnName("Notes").HasColumnType("ntext");//指定“Note”對應列名為“Notes”,並指定數據類型

            modelBuilder.Entity<Employee>().Ignore(emp => emp.PhotoPath);//指定“PhotoPath”為非映射列

            //modelBuilder.Entity<Customer>().HasRequired(c=>c.Orders).WithMany().Map(m=>m.MapKey("CustomerOrder"));

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).IsUnicode(true);//“Title”列是否支持Unicode編碼

            modelBuilder.ComplexType<Name>().Property(n=>n.FirstName).HasMaxLength(50);//指定Name為復雜數據類型,並指定復雜類型中“FirstName”長度
            //modelBuilder.Entity<Employee>().Property(emp => emp.Name.LastName).HasMaxLength(20);//還可以通過這種方式指定復雜類型“Name”的“LastName”列的長度

            //modelBuilder.Entity<Employee>().Property(emp => emp.Address).IsConcurrencyToken();//指定“Address”進行並發控制,通常這一列我們知道為“TimeStamp”列而不是“Addree”這里只是為了說明可以標記其他列
            modelBuilder.Entity<Employee>().Property(emp => emp.TimeStamp).IsRowVersion();//通過指定“TimeStamp”進行並發版本控制

            /*下面代碼演示EF中的關系約束*/
            //modelBuilder.Entity<MessagingAccount>().HasRequired(a => a.Employee).WithOptional(emp => emp.Acount);//配置一對零關系,允許存在一個Employee而不存在MessagingAcount的情況(注意在Employee中添加Acont屬性)
            modelBuilder.Entity<Employee>().HasRequired(emp => emp.Acount).WithRequiredPrincipal(a => a.Employee);//配置一對一關系,和上面的WithOptionnal關系區別是每個Employee必須有一個MessagingAcount而每個MessageAcount也必須有一個Employee;但是Employee是主表,此時允許Employee單獨持久化而不允許MessagingAcount單獨持久化
            //注意配置一對一關系也可以使用WithRequiredDependent,只不過主表發生了變化,上面的語句與下面的語句是等價的
            //modelBuilder.Entity<MessagingAccount>().HasRequired(a => a.Employee).WithRequiredDependent(a => a.Acount);//
            //下面的方法解決了一對一的關系,此時Employee和MessageAcount將必須同時存在
            //modelBuilder.Entity<Employee>().HasRequired(emp => emp.Acount).WithMany().HasForeignKey(emp => emp.MessagingAccountID);
            //modelBuilder.Entity<MessagingAccount>().HasRequired(a => a.Employee).WithMany().HasForeignKey(a => a.EmployeeNo);

            //modelBuilder.Entity<Order>().HasRequired(o=>o.Customer).WithMany();//一對多的關系,一個Customer有多個Order(注意,運行之前先把Order中CustomerNo和Customer中的Orders屬性刪除掉,否則將生成兩個外鍵一個是自動生成的另一個是Fluent API配置生成的,對應這種關系推薦使用默認生成)
            //modelBuilder.Entity<Order>().HasRequired(o => o.Customer).WithMany().WillCascadeOnDelete();//添加添加級聯刪除
            //modelBuilder.Entity<Order>().HasRequired(o => o.Customer).WithMany().Map(m=>m.MapKey("Customer_Order");//外鍵重命名
            //modelBuilder.Entity<Order>().HasRequired(o => o.Customer).WithMany().HasForeignKey(o => new { o.CustomerNo,o.CustomerName});//組合外鍵,注意本例中沒有組合外鍵(CustomerName不是外鍵),這里只是舉例而已
            //modelBuilder.Entity<Order>().HasRequired(o => o.Customer).WithMany(c=>c.Orders).HasForeignKey(o => o.CustomerNo);//指定外鍵(一般用戶外鍵名不符合默認約束命名時)

            modelBuilder.Entity<Order>().HasMany(o => o.Products).WithMany(p => p.Orders).Map(m => {
                m.ToTable("OrderDetails");
                m.MapLeftKey("OrderID");
                m.MapRightKey("ProductID");
            });//配置多對多的關系,並指定了表名、對應的外鍵;注意如果不使用FluentAPI配置,Product和Order配置了相應的導航屬性,EF也會默認生成一張表(表名為“<數據類1>+<數據類2>”)
        }
    }
}

運行生成的數據庫結構如下圖:

在上面的代碼中我們着重看關系配置部分,我們注釋了一部分關系約束配置代碼主要是因為有些關系不能共存大家可以自己去掉執行試試,這部分代碼希望初學者不要略過。關於上面的代碼相信大家看注釋都可以明白,這里我主要強調一點,那就是多重外鍵約束。大家通過上圖已經看到CheckPerson和DeliverPerson的約束像在Data Annotations中提到的一樣並沒有達到我們的預期,其主要原因是因為EF並沒有明白這種約束關系,解決辦法很簡單,只要配置“Employee”和“Order”一對多約束關系即可(注意只有配置一個屬性即可,例如我們配置CheckPerson):

modelBuilder.Entity<Order>().HasRequired(o => o.CheckPerson).WithMany(emp => emp.CheckOrder).WillCascadeOnDelete();

Fluent API繼承實現

下面看一下EF強大的繼承實現,在EF中支持三種類型的繼承實現:

  • Table-Per-Hierarchy(TPH):EF默認支持的類型,無需配置即可實現,整個層次在一張表中,基類中沒有的屬性被標記為可空,通過添加一個額外的“Discniminator”列進行類型區分;
  • Table-Per-Type(TPT:每個類型一張表,在子類中包含自身屬性和一個指向基類的外鍵列;
  • Table-Per-Concrete Calss(TPC):每個類型一張表,但是和TPT不同的是子類中並沒有創建外鍵列而是直接將基類的屬性在子類中展開(子類表包含基類表的所有列);

在演示上面幾種方式之前先讓我們定義兩個類“Worker”表示當前在職員工和“Retired”表示退休員工,它們繼承於“Employee”。

TPH方式

TPH方式是EF默認支持,我們無需更多的配置即可完成。

Worker類:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Worker:Employee
    {
        public decimal AnnualSalary
        {
            get;
            set;
        }
    }
}

Retired類:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CodeFirst.Entities
{
    public class Retired:Employee
    {
        public decimal MonthlyPension
        {
            get;
            set;
        }
    }
}

接下來插入兩條數據進行測試:

using System;
using System.Collections.Generic;
using System.Data.Entity;
using System.Data.SqlClient;
using System.Linq;
using System.Text;
using CodeFirst;
using CodeFirst.Entities;

namespace CodeFirst
{
    class Program
    {
        static void Main(string[] args)
        {
            using (var db = new OrderContext())
            {
                db.Workers.Add(new Worker()
                {
                    No = 1,
                    Name = new Name { FirstName="Stephen",LastName="Chow"},
                    Title = "Software Architect",
                    BirthDate=new DateTime(1976,10,10),
                    Address="Peking",
                    Note="",
                    CreateDate=DateTime.Now,
                    AnnualSalary=999999999
                });

                db.Retireds.Add(new Retired
                {
                    No = 2,
                    Name = new Name { FirstName = "Jeffrey", LastName = "Lee" },
                    Title = "Software Development Engineer",
                    BirthDate = new DateTime(1956, 8, 8),
                    Address = "Hong Kong",
                    Note = "",
                    CreateDate = DateTime.Now,
                    MonthlyPension=9999999
                });
                db.SaveChanges();
            }
        }
    }
}

下面是具體結果:

TPT方式

使用TPT方式其實也十分簡單,只需要配置基類及子類生成的表信息即可:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using CodeFirst.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace CodeFirst
{
    public class OrderContext:DbContext
    {
        public OrderContext()
            : base("CodeFirstDb")
        {
            Database.SetInitializer<OrderContext>(
                new DropCreateDatabaseIfModelChanges<OrderContext>()
            );
        }

        public DbSet<Order> Orders
        {
            get;
            set;
        }

        public DbSet<Employee> Employees
        {
            get;
            set;
        }

        public DbSet<Worker> Workers
        {
            get;
            set;
        }

        public DbSet<Retired> Retireds
        {
            get;
            set;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //modelBuilder.Entity<Employee>().ToTable("People", "Person");//指定“Employee”對應表名及架構

            modelBuilder.Entity<Employee>().HasKey(emp => emp.No);//定義主鍵為“No”
            //modelBuilder.Entity<Employee>().HasKey(emp => new { emp.No, emp.Title });//指定"No"和“Title”作為復合主鍵,使用Data Annotations無法做到

            modelBuilder.Entity<Employee>().Property(emp => emp.No).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);//去掉標識列,前面我們也提到過通過Data Annotations通用可以去掉主鍵默認標示屬性

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).HasMaxLength(30);//指定“Title”最大長度為30

            modelBuilder.Entity<Employee>().Property(emp => emp.BirthDate).IsRequired();//指定“BirthDate”為不可為空

            modelBuilder.Entity<Employee>().Property(emp => emp.Note).HasColumnName("Notes").HasColumnType("ntext");//指定“Note”對應列名為“Notes”,並指定數據類型

            modelBuilder.Entity<Employee>().Ignore(emp => emp.PhotoPath);//指定“PhotoPath”為非映射列

            //modelBuilder.Entity<Customer>().HasRequired(c=>c.Orders).WithMany().Map(m=>m.MapKey("CustomerOrder"));

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).IsUnicode(true);//“Title”列是否支持Unicode編碼

            modelBuilder.ComplexType<Name>().Property(n=>n.FirstName).HasMaxLength(50);//指定Name為復雜數據類型,並指定復雜類型中“FirstName”長度
            //modelBuilder.Entity<Employee>().Property(emp => emp.Name.LastName).HasMaxLength(20);//還可以通過這種方式指定復雜類型“Name”的“LastName”列的長度

            //modelBuilder.Entity<Employee>().Property(emp => emp.Address).IsConcurrencyToken();//指定“Address”進行並發控制,通常這一列我們知道為“TimeStamp”列而不是“Addree”這里只是為了說明可以標記其他列
            modelBuilder.Entity<Employee>().Property(emp => emp.TimeStamp).IsRowVersion();//通過指定“TimeStamp”進行並發版本控制

            /*下面代碼演示EF中的繼承關系實現*/
            //TPH默認支持,無需手動進行配置
            //TPT,只需要指定生成的表即可
            modelBuilder.Entity<Employee>().ToTable("People", "Person");
            modelBuilder.Entity<Worker>().ToTable("Worker", "Person");
            modelBuilder.Entity<Retired>().ToTable("Retired", "Person");
        }
    }
}

生成的表結構如下:

TPC方式

最后看一下TPC方式,TPC方式同TPT一樣同樣是每個類型創建一張表,不同的是TPC每個子類中只有子類特有屬性和外鍵列,子類通過外鍵查找基類屬性;而在TPC方式中每個子類和基類之間並沒有創建約束關系,子類表中擁有自身屬性和基類所有屬性。TPC定義方式也很簡單,只需要在子類中通過“MapInheritedProperties”方法指定集成基類屬性即可:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Data.Entity;
using CodeFirst.Entities;
using System.ComponentModel.DataAnnotations.Schema;
using System.Data.Entity.ModelConfiguration.Conventions;

namespace CodeFirst
{
    public class OrderContext:DbContext
    {
        public OrderContext()
            : base("CodeFirstDb")
        {
            Database.SetInitializer<OrderContext>(
                new DropCreateDatabaseIfModelChanges<OrderContext>()
            );
        }

        public DbSet<Order> Orders
        {
            get;
            set;
        }

        public DbSet<Employee> Employees
        {
            get;
            set;
        }

        public DbSet<Worker> Workers
        {
            get;
            set;
        }

        public DbSet<Retired> Retireds
        {
            get;
            set;
        }

        protected override void OnModelCreating(DbModelBuilder modelBuilder)
        {
            base.OnModelCreating(modelBuilder);
            //modelBuilder.Entity<Employee>().ToTable("People", "Person");//指定“Employee”對應表名及架構

            modelBuilder.Entity<Employee>().HasKey(emp => emp.No);//定義主鍵為“No”
            //modelBuilder.Entity<Employee>().HasKey(emp => new { emp.No, emp.Title });//指定"No"和“Title”作為復合主鍵,使用Data Annotations無法做到

            modelBuilder.Entity<Employee>().Property(emp => emp.No).HasDatabaseGeneratedOption(DatabaseGeneratedOption.None);//去掉標識列,前面我們也提到過通過Data Annotations通用可以去掉主鍵默認標示屬性

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).HasMaxLength(30);//指定“Title”最大長度為30

            modelBuilder.Entity<Employee>().Property(emp => emp.BirthDate).IsRequired();//指定“BirthDate”為不可為空

            modelBuilder.Entity<Employee>().Property(emp => emp.Note).HasColumnName("Notes").HasColumnType("ntext");//指定“Note”對應列名為“Notes”,並指定數據類型

            modelBuilder.Entity<Employee>().Ignore(emp => emp.PhotoPath);//指定“PhotoPath”為非映射列

            //modelBuilder.Entity<Customer>().HasRequired(c=>c.Orders).WithMany().Map(m=>m.MapKey("CustomerOrder"));

            modelBuilder.Entity<Employee>().Property(emp => emp.Title).IsUnicode(true);//“Title”列是否支持Unicode編碼

            modelBuilder.ComplexType<Name>().Property(n=>n.FirstName).HasMaxLength(50);//指定Name為復雜數據類型,並指定復雜類型中“FirstName”長度
            //modelBuilder.Entity<Employee>().Property(emp => emp.Name.LastName).HasMaxLength(20);//還可以通過這種方式指定復雜類型“Name”的“LastName”列的長度

            //modelBuilder.Entity<Employee>().Property(emp => emp.Address).IsConcurrencyToken();//指定“Address”進行並發控制,通常這一列我們知道為“TimeStamp”列而不是“Addree”這里只是為了說明可以標記其他列
            modelBuilder.Entity<Employee>().Property(emp => emp.TimeStamp).IsRowVersion();//通過指定“TimeStamp”進行並發版本控制

            /*下面代碼演示EF中的繼承關系實現*/
            //TPH默認支持,無需手動進行配置
            //TPT,只需要指定生成的表即可
            //modelBuilder.Entity<Employee>().ToTable("People", "Person");
            //modelBuilder.Entity<Worker>().ToTable("Worker", "Person");
            //modelBuilder.Entity<Retired>().ToTable("Retired", "Person");
            //TPC,只要子類中指定映射繼承屬性即可
            modelBuilder.Entity<Employee>().ToTable("People", "Person");
            modelBuilder.Entity<Worker>().Map(m => {
                m.MapInheritedProperties();
                m.ToTable("Worker", "Person");
            });
            modelBuilder.Entity<Retired>().Map(m =>
            {
                m.MapInheritedProperties();
                m.ToTable("Retired", "Person");
            });
        }
    }
}

生成的表結構如下:

clip_image012

注意:盡管Fluent API功能更加強大,對於可以使用Data Annotations實現的功能我們推薦使用Data Annotations方式。

至此關於EF中默認約定及自定義配置的內容已經討論結束了,關於EF中如何進行查詢以及如何優化查詢等更多內容敬請關注后面的文章。

 
 
 
 
 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM