Entity Framework 4.1 Code First (4)

Entity Framework Code First (4)
簡單介紹TPH, TPT, TPC

此篇簡單地介紹如何在Code First 的模式中設定3種資料表關連對應 Entity 繼承樣式 (TPH, TPT, TPC)

在此盡量用圖示的方式表現,比較容易理解。若是原來已經在Entity Framework 4.0 用過,相信會非常熟悉

不過老實說,於由個人Entity Framework實戰經驗還不夠多,一開始對於TPH, TPT, TPC 定義老是不清楚或搞混,因此看Code First時,直接用Code First 產生 database schema方式(可參考上一篇的說明)來幫助我理解,而我提供的範例也是用這樣的方式

 

 

1.TPH : Table per Hierarchy

在這模式中,Employee 是一個abstract class,分別有RegularEmployee (正職員工)與ContractEmployee(約聘員工) 來繼承

RegularEmployee 有Level(職等) property 與Title(職稱) property,而ContractEmployee 有StartDate property, EndDate property  (聘用起迄日期)

在Database 中對應一個 Employee Table ,以EmployeeType 這欄位來做區分

 

Entity 關係如下圖所示

 

TPH_C

在程式中的寫法如下

1.1 建立相關的Entity

 


	public abstract class Employee {

		[Key]
		public int EmployeeId { get; set; }

		[Required, MaxLength(20)]
		public String Name { get; set; }
	}

	public class RegularEmployee : Employee{

		[MaxLength(30)]
		public String Title { get; set; }

		[MaxLength(10)]
		public String Level { get; set; }
	}

	public class ContractEmployee : Employee {

		[Required]
		public DateTime? StartDate { get; set; }

		[Required]
		public DateTime? EndDate { get; set; }
	}

 

 

1.2 建立CompanyDbContext,繼承DbContext


	public class CompanyDbContext : DbContext{

		public CompanyDbContext()
			: base("name=CompanyDbContext") {

		}

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

		protected override void OnModelCreating(DbModelBuilder modelBuilder) {
			modelBuilder.Entity<Employee>().ToTable("Employee");
			modelBuilder.Entity<Employee>()
				.Map<RegularEmployee>(m => m.Requires("EmployeeType").HasValue(1))
				.Map<ContractEmployee>(m => m.Requires("EmployeeType").HasValue(2));
		}
	}

在OnModelCreating method 定義了RegualrEmployee及ContractEmployee 篩選的條件

 

1.3 建立了一個CompanyDbInitializer class 來產生資料,如前面所說,協助我理解 TPH


	public class CompanyDbInitializer : DropCreateDatabaseAlways<CompanyDbContext> {

		protected override void Seed(CompanyDbContext context) {
			var regularEmp = new RegularEmployee {
				Name = "正職A",
				Title = "經理",
				Level = "7職等"
			};
			context.Employees.Add(regularEmp);

			var contractor = new ContractEmployee {
				Name = "約聘A",
				StartDate = new DateTime(2011, 1, 1),
				EndDate = new DateTime(2011, 12, 31)
			};
			context.Employees.Add(contractor);


			base.Seed(context);
		}
	}

 

產生Database schema 及Data之後,Table 欄位如下

TPH_D

 

 

 

 

2. TPT :  Table per Type

Entity 關係幾乎與上一次TPH相同

而Database 中,分別有Employee, RegualrEmployee以及ContractEmployee 3個Table

 

Entity 關係如下圖所示

TPT_C

 

對應的Table 關係如下

TPT_D

 

 

 

 

 

 

Entity code


	public class Employee {

		[Key]
		public int EmployeeId { get; set; }

		[Required, MaxLength(20)]
		public String Name { get; set; }
	}

	public class RegularEmployee : Employee {

		[Required, MaxLength(30)]
		public String Title { get; set; }

		[Required, MaxLength(10)]
		public String Level { get; set; }
	}

	public class ContractEmployee : Employee {

		[Required]
		public DateTime? StartDate { get; set; }

		[Required]
		public DateTime? EndDate { get; set; }
	}

CompanyDbContext


	public class CompanyDbContext : DbContext{

		public CompanyDbContext()
			: base("name=CompanyDbContext") {

		}

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

		protected override void OnModelCreating(DbModelBuilder modelBuilder) {
			modelBuilder.Entity<Employee>().ToTable("Employee");
			modelBuilder.Entity<RegularEmployee>().ToTable("RegularEmployee");
			modelBuilder.Entity<ContractEmployee>().ToTable("ContractEmployee");
		}
	}

產生資料的方式我就省略…,可以參考我提供的範例

 

3.TPC  : Table per Concrete class

TPC 較為特別一點,將不同Table但相同欄位的schema,取成一個class,減少重複property 產生

(Entity framework 4 model designer 沒有直接提供此一設定,需直接修改XML file)

 

Entity 關係如下圖所示

TPC_C

 

Table 關係如下

TPC_D

 

Entity code


	public abstract class Employee {

		[Key]
		public int EmployeeId { get; set; }

		[Required, MaxLength(20)]
		public String Name { get; set; }
	}

	public class RegularEmployee : Employee {

		[Required, MaxLength(30)]
		public String Title { get; set; }

		[Required, MaxLength(10)]
		public String Level { get; set; }
	}

	public class ContractEmployee : Employee {

		[Required]
		public DateTime? StartDate { get; set; }

		[Required]
		public DateTime? EndDate { get; set; }
	}

 

CompanyDbContext class


	public class CompanyDbContext : DbContext{

		public CompanyDbContext()
			: base("name=CompanyDbContext") { }

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

		protected override void OnModelCreating(DbModelBuilder modelBuilder) {
			modelBuilder.Entity<RegularEmployee>()
				.Map(m => {
					m.MapInheritedProperties();
					m.ToTable("RegularEmployee");
				});

			modelBuilder.Entity<ContractEmployee>()
				.Map(m => {
					m.MapInheritedProperties();
					m.ToTable("ContractEmployee");
				});
		}
	}

前面有提到TPC 比較特別一點,是因為key 值關係,以這個範例來說,由於這2個Table RegularEmployeeContractEmployee ,都有各自的key值p>

但若是對應到TPC 繼承關係時,若是2個Table有相同的key,會造成錯誤。因此使用TPC時,對於key 會有所約束限制,須確保key值不會相同

(下一版的SQL Server 好像會提供跨Table identity value)

 

CompanyDbInitializer class 產生資料


	public class CompanyDbInitializer : DropCreateDatabaseAlways<CompanyDbContext> {

		protected override void Seed(CompanyDbContext context) {
			var regularEmp = new RegularEmployee {
				EmployeeId = 1,
				Name = "正職A",
				Title = "經理",
				Level = "7職等"
			};
			context.Employees.Add(regularEmp);

			var contractor = new ContractEmployee {
				EmployeeId = 2,
				Name = "約聘A",
				StartDate = new DateTime(2011, 1, 1),
				EndDate = new DateTime(2011, 12, 31)
			};
			context.Employees.Add(contractor);


			base.Seed(context);
		}
	}

 

在此只是列出簡單的作法來說明TPH, TPT, TPC,實際使用上還有很多變化,做主要還是要看Database 的設計。

我自己覺得,這部份東西是Database 設計架構 影響 code 關係,而非單單從code first 角度出發

(自己經驗值仍不足,還得繼續累積實戰經驗,才能更體會這部份設計)

 

到此對於1 Entity 對應多個Table沒有提到,下次有機會再補充

範例下載MyCodeFirstSln.rar