在前面幾篇介紹了Entity Framework 實體框架的形成過程,整體框架主要是基於Database First的方式構建,也就是利用EDMX文件的映射關系,構建表與表之間的關系,這種模式彈性好,也可以利用圖形化的設計器來設計表之間的關系,是開發項目較多采用的模式,不過問題還是這個XML太過復雜,因此有時候也想利用Code First模式構建整個框架。本文主要介紹利用Code First 來構建整個框架的過程以及碰到的問題探討。
1、基於SqlServer的Code First模式
為了快速了解Code First的工作模式,我們先以微軟自身的SQLServer數據庫進行開發測試,我們還是按照常規的模式先構建一個標准關系的數據庫,如下所示。

這個表包含了幾個經典的關系,一個是自引用關系的Role表,一個是User和Role表的多對多關系,一個是User和UserDetail之間的引用關系。
一般情況下,能處理好這幾種關系,基本上就能滿足大多數項目上的要求了。這幾個表的數據庫腳本如下所示。
create table dbo.Role ( ID nvarchar(50) not null, Name nvarchar(50) null, ParentID nvarchar(50) null, constraint PK_ROLE primary key (ID) ) go create table dbo."User" ( ID nvarchar(50) not null, Account nvarchar(50) null, Password nvarchar(50) null, constraint PK_USER primary key (ID) ) go create table dbo.UserDetail ( ID nvarchar(50) not null, User_ID nvarchar(50) null, Name nvarchar(50) null, Sex int null, Birthdate datetime null, Height decimal null, Note ntext null, constraint PK_USERDETAIL primary key (ID) ) go create table dbo.UserRole ( User_ID nvarchar(50) not null, Role_ID nvarchar(50) not null, constraint PK_USERROLE primary key (User_ID, Role_ID) ) go alter table dbo.Role add constraint FK_ROLE_REFERENCE_ROLE foreign key (ParentID) references dbo.Role (ID) go alter table dbo.UserDetail add constraint FK_USERDETA_REFERENCE_USER foreign key (User_ID) references dbo."User" (ID) go alter table dbo.UserRole add constraint FK_USERROLE_REFERENCE_ROLE foreign key (Role_ID) references dbo.Role (ID) go alter table dbo.UserRole add constraint FK_USERROLE_REFERENCE_USER foreign key (User_ID) references dbo."User" (ID) go
我們采用剛才介紹的Code Frist方式來構建實體框架,如下面幾個步驟所示。
1)選擇來自數據庫的Code First方式。

2)選擇指定的數據庫連接,並選擇對應的數據庫表,如下所示(包括中間表UserRole)。

生成項目后,項目工程會增加幾個類,包括Role實體類,User實體類,UserDetail實體類(沒有中間表UserRole的實體類),還有一個是包含這些實體類的數據庫上下文關系,它們的表之間的關系,是通過代碼指定的,沒有了EDMX文件了。
幾個類文件的代碼如下所示,其中實體類在類定義的頭部,增加了[Table("Role")]的說明,表明了這個實體類和數據庫表之間的關系。
[Table("Role")] public partial class Role { public Role() { Children = new HashSet<Role>(); Users = new HashSet<User>(); } [StringLength(50)] public string ID { get; set; } [StringLength(50)] public string Name { get; set; } [StringLength(50)] public string ParentID { get; set; } public virtual ICollection<Role> Children { get; set; } public virtual Role Parent { get; set; } public virtual ICollection<User> Users { get; set; } }
其他類如下所示。
[Table("User")] public partial class User { public User() { UserDetails = new HashSet<UserDetail>(); Roles = new HashSet<Role>(); } [StringLength(50)] public string ID { get; set; } [StringLength(50)] public string Account { get; set; } [StringLength(50)] public string Password { get; set; } public virtual ICollection<UserDetail> UserDetails { get; set; } public virtual ICollection<Role> Roles { get; set; } }
[Table("UserDetail")] public partial class UserDetail { [StringLength(50)] public string ID { get; set; } [StringLength(50)] public string User_ID { get; set; } [StringLength(50)] public string Name { get; set; } public int? Sex { get; set; } public DateTime? Birthdate { get; set; } public decimal? Height { get; set; } [Column(TypeName = "ntext")] public string Note { get; set; } public virtual User User { get; set; } }
還有一個就是生成的數據庫上下文的類。
public partial class DbEntities : DbContext { public DbEntities() : base("name=Model1") { } public virtual DbSet<Role> Roles { get; set; } public virtual DbSet<User> Users { get; set; } public virtual DbSet<UserDetail> UserDetails { get; set; } protected override void OnModelCreating(DbModelBuilder modelBuilder) { modelBuilder.Entity<Role>() .HasMany(e => e.Children) .WithOptional(e => e.Parent) .HasForeignKey(e => e.ParentID); modelBuilder.Entity<Role>() .HasMany(e => e.Users) .WithMany(e => e.Roles) .Map(m => m.ToTable("UserRole")); modelBuilder.Entity<User>() .HasMany(e => e.UserDetails) .WithOptional(e => e.User) .HasForeignKey(e => e.User_ID); modelBuilder.Entity<UserDetail>() .Property(e => e.Height) .HasPrecision(18, 0); } }
上面這個數據庫上下文的操作類,通過在OnModelCreating函數里面使用代碼方式指定了幾個表之間的關系,代替了EDMX文件的描述。
這樣好像看起來比EDMX文件簡單了很多,感覺很開心,一切就那么順利。
如果我們使用這個數據庫上下文進行數據庫的插入,也是很順利的執行,並包含了的多個表之間的關系處理,代碼如下所示。
private void NormalTest() { DbEntities db = new DbEntities(); Role role = new Role() { ID = Guid.NewGuid().ToString(), Name = "test33" }; User user = new User() { ID = Guid.NewGuid().ToString(), Account = "test33", Password = "test33" }; UserDetail detail = new UserDetail() { ID = Guid.NewGuid().ToString(), Name = "userName33", Sex = 1, Note = "測試內容33", Height = 175 }; user.UserDetails.Add(detail); role.Users.Add(user); db.Roles.Add(role); db.SaveChanges(); List<Role> list = db.Roles.ToList(); }
我們發現,通過上面代碼的操作,幾個表都寫入了數據,已經包含了他們之間的引用關系了。
2、基於泛型的倉儲模式實體框架的提煉
為了更好對不同數據庫的封裝,我引入了前面介紹的基於泛型的倉儲模式實體框架的結構,希望后面能夠兼容多種數據庫的支持,最終構建代碼的分層結構如下所示。

使用這種框架的分層,相當於為各個數據庫訪問提供了統一標准的通用接口,為我們利用各種強大的基類快速實現各種功能提供了很好的保障。使用這種分層的框架代碼如下所示。
private void FrameworkTest() { Role role = new Role() { ID = Guid.NewGuid().ToString(), Name = "test33" }; User user = new User() { ID = Guid.NewGuid().ToString(), Account = "test33", Password = "test33" }; UserDetail detail = new UserDetail() { ID = Guid.NewGuid().ToString(), Name = "userName33", Sex = 1, Note = "測試內容33", Height = 175 }; user.UserDetails.Add(detail); role.Users.Add(user); IFactory.Instance<IRoleBLL>().Insert(role); ICollection<Role> list = IFactory.Instance<IRoleBLL>().GetAll(); }
我們發現,這部分代碼執行的效果和純粹使用自動生成的數據庫上下文DbEntities 來操作數據庫一樣,能夠寫入各個表的數據,並添加了相關的應用關系。
滿以為這樣也可以很容易擴展到Oracle數據庫上,但使用SQLServer數據庫生成的實體類,在Oracle數據庫訪問的時候,發現它生成的實體類名稱全部是大寫,一旦修改為Camel駝峰格式的字段,就會出現找不到對應表字段的錯誤。
尋找了很多解決方案,依舊無法有效避免這個問題,因為Oracle本身的表或者字段名稱是大小寫敏感的,關於Oracle這個問題,先關注后續解決吧,不過對於如果不考慮支持多種數據庫的話,基於SQLServer數據庫的Code First構建框架真的還是比較方便,我們不用維護那個比較麻煩的EDMX文件,只需要在代碼函數里面動態添加幾個表之間的關系即可。
這個系列文章索引如下:
Entity Framework 實體框架的形成之旅--基於泛型的倉儲模式的實體框架(1)
Entity Framework 實體框架的形成之旅--利用Unity對象依賴注入優化實體框架(2)
Entity Framework 實體框架的形成之旅--基類接口的統一和異步操作的實現(3)
