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”列。
MinLength、MaxLength:指定字段长度(此属性通常可以用户客户端验证),例如上面“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”并指定对于的列名。
![]() | 注意: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");
- });
- }
- }
- }
生成的表结构如下:
![]() | 注意:尽管Fluent API功能更加强大,对于可以使用Data Annotations实现的功能我们推荐使用Data Annotations方式。 |
至此关于EF中默认约定及自定义配置的内容已经讨论结束了,关于EF中如何进行查询以及如何优化查询等更多内容敬请关注后面的文章。